Merge branch 'oscVoiceMute' into develop

# Conflicts:
#	model.py
This commit is contained in:
misyaguziya
2024-05-08 09:51:06 +09:00
9 changed files with 182 additions and 47 deletions

View File

@@ -892,6 +892,17 @@ class Config:
self._ENABLE_LOGGER = value
saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value)
@property
@json_serializable('ENABLE_VRC_MIC_MUTE_SYNC')
def ENABLE_VRC_MIC_MUTE_SYNC(self):
return self._ENABLE_VRC_MIC_MUTE_SYNC
@ENABLE_VRC_MIC_MUTE_SYNC.setter
def ENABLE_VRC_MIC_MUTE_SYNC(self, value):
if isinstance(value, bool):
self._ENABLE_VRC_MIC_MUTE_SYNC = value
saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value)
@property
@json_serializable('IS_CONFIG_WINDOW_COMPACT_MODE')
def IS_CONFIG_WINDOW_COMPACT_MODE(self):
@@ -1071,6 +1082,7 @@ class Config:
self._ENABLE_SEND_RECEIVED_MESSAGE_TO_VRC = False # Speaker2Chatbox
self._ENABLE_SPEAKER2CHATBOX_PASS = "000000000"
self._ENABLE_LOGGER = False
self._ENABLE_VRC_MIC_MUTE_SYNC = False
self._IS_CONFIG_WINDOW_COMPACT_MODE = False
def load_config(self):

View File

@@ -924,6 +924,15 @@ def callbackSetEnableAutoExportMessageLogs(value):
else:
model.stopLogger()
def callbackSetEnableVrcMicMuteSync(value):
print("callbackSetEnableVrcMicMuteSync", value)
config.ENABLE_VRC_MIC_MUTE_SYNC = value
if config.ENABLE_VRC_MIC_MUTE_SYNC is True:
model.startCheckMuteSelfStatus()
else:
model.stopCheckMuteSelfStatus()
def callbackSetEnableSendMessageToVrc(value):
print("callbackSetEnableSendMessageToVrc", value)
config.ENABLE_SEND_MESSAGE_TO_VRC = value
@@ -978,7 +987,6 @@ def callbackSetEnableSendReceivedMessageToVrc(value):
config.ENABLE_SEND_RECEIVED_MESSAGE_TO_VRC = value
# ---------------------Speaker2Chatbox---------------------
# Advanced Settings Tab
def callbackSetOscIpAddress(value):
if value == "":
@@ -1044,6 +1052,11 @@ def createMainWindow(splash):
if config.ENABLE_LOGGER is True:
model.startLogger()
# init OSC receive
model.startReceiveOSC()
if config.ENABLE_VRC_MIC_MUTE_SYNC is True:
model.startCheckMuteSelfStatus()
splash.toProgress(3) # Last one.
# set UI and callback
@@ -1148,6 +1161,7 @@ def createMainWindow(splash):
"callback_set_send_message_button_type": callbackSetSendMessageButtonType,
"callback_set_enable_notice_xsoverlay": callbackSetEnableNoticeXsoverlay,
"callback_set_enable_auto_export_message_logs": callbackSetEnableAutoExportMessageLogs,
"callback_set_enable_vrc_mic_mute_sync": callbackSetEnableVrcMicMuteSync,
"callback_set_enable_send_message_to_vrc": callbackSetEnableSendMessageToVrc,
# Others(Message Formats(Send)
"callback_set_send_message_format": callbackSetSendMessageFormat,

View File

@@ -243,6 +243,10 @@ config_window:
label: Auto Export Message Logs
desc: Automatically export the conversation messages as a text file.
vrc_mic_mute_sync:
label: VRChat Mic Mute Sync
desc: VRCT will not send the message to VRChat while VRChat's mic is muted.
send_message_to_vrc:
label: Send Message To VRChat
desc: There is a way to use it without sending messages to VRChat, but it is not supported. Enable this feature when you intend to send a message to VRChat.

View File

@@ -242,6 +242,11 @@ config_window:
label: 会話ログを自動的に保存する
desc: テキストファイルとしてログがlogsフォルダ内に保存されます。
vrc_mic_mute_sync:
label: VRChatマイクミュート同期機能
desc: VRChatのマイクがミュートされている間は、メッセージをVRChatに送信しません。
send_message_to_vrc:
label: VRChatにメッセージを送信する
desc: "サポート対象外ですが、VRChatにメッセージを送信せずに使う方法があります。送信したい場合、この機能を有効にする事を忘れないでください。"

137
model.py
View File

@@ -17,7 +17,7 @@ from typing import Callable
from flashtext import KeywordProcessor
from models.translation.translation_translator import Translator
from models.transcription.transcription_utils import getInputDevices, getOutputDevices
from models.osc.osc_tools import sendTyping, sendMessage, sendTestAction, receiveOscParameters
from models.osc.osc_tools import sendTyping, sendMessage, receiveOscParameters, getOSCParameterValue
from models.transcription.transcription_recorder import SelectedMicEnergyAndAudioRecorder, SelectedSpeakerEnergyAndAudioRecorder
from models.transcription.transcription_recorder import SelectedMicEnergyRecorder, SelectedSpeakerEnergyRecorder
from models.transcription.transcription_transcriber import AudioTranscriber
@@ -37,16 +37,27 @@ class threadFnc(Thread):
self.fnc = fnc
self.end_fnc = end_fnc
self.loop = True
self._pause = False
def stop(self):
self.loop = False
def pause(self):
self._pause = True
def resume(self):
self._pause = False
def run(self):
while self.loop:
self.fnc(*self._args, **self._kwargs)
while self._pause:
sleep(0.1)
if callable(self.end_fnc):
self.end_fnc()
return
class Model:
_instance = None
@@ -82,6 +93,9 @@ class Model:
# self.overlay_image = OverlayImage()
# self.pre_overlay_message = None
# self.th_overlay = None
self.mic_audio_queue = None
self.mic_mute_status = None
self.mic_mute_status_check = None
def checkCTranslatorCTranslate2ModelWeight(self):
return checkCTranslate2Weight(config.PATH_LOCAL, config.CTRANSLATE2_WEIGHT_TYPE)
@@ -238,39 +252,51 @@ class Model:
def oscSendMessage(message):
sendMessage(message, config.OSC_IP_ADDRESS, config.OSC_PORT)
def checkOSCStarted(self, fnc):
self.is_valid_osc = False
def checkOscReceive(address, osc_arguments):
if self.is_valid_osc is False:
self.is_valid_osc = True
@staticmethod
def getMuteSelfStatus():
return getOSCParameterValue(address="/avatar/parameters/MuteSelf")
self.listening_server = receiveOscParameters(checkOscReceive)
def oscListener():
self.listening_server.serve_forever()
def startCheckMuteSelfStatus(self):
def checkMuteSelfStatus():
if self.mic_mute_status is not None:
self.changeMicTranscriptStatus()
self.stopCheckMuteSelfStatus()
def sendTestActionLoop():
for _ in range(10):
sendTestAction()
if self.is_valid_osc is True:
break
sleep(0.1)
self.listening_server.shutdown()
status = self.getMuteSelfStatus()
if status is not None:
self.mic_mute_status = status
self.changeMicTranscriptStatus()
self.stopCheckMuteSelfStatus()
# start receive osc
th_receive_osc_parameters = Thread(target=oscListener)
th_receive_osc_parameters.daemon = True
th_receive_osc_parameters.start()
if not isinstance(self.mic_mute_status_check, threadFnc):
self.mic_mute_status_check = threadFnc(checkMuteSelfStatus)
self.mic_mute_status_check.daemon = True
self.mic_mute_status_check.start()
# check osc started
th_send_osc_test_action = Thread(target=sendTestActionLoop)
th_send_osc_test_action.daemon = True
th_send_osc_test_action.start()
def stopCheckMuteSelfStatus(self):
if isinstance(self.mic_mute_status_check, threadFnc):
self.mic_mute_status_check.stop()
self.mic_mute_status_check = None
th_receive_osc_parameters.join()
th_send_osc_test_action.join()
def startReceiveOSC(self):
osc_parameter_prefix = "/avatar/parameters/"
param_MuteSelf = "MuteSelf"
if self.is_valid_osc is False:
fnc()
def change_handler_mute(address, osc_arguments):
if osc_arguments is True and self.mic_mute_status is False:
self.mic_mute_status = osc_arguments
self.changeMicTranscriptStatus()
elif osc_arguments is False and self.mic_mute_status is True:
self.mic_mute_status = osc_arguments
self.changeMicTranscriptStatus()
dict_filter_and_target = {
osc_parameter_prefix + param_MuteSelf: change_handler_mute,
}
th_osc_server = Thread(target=receiveOscParameters, args=(dict_filter_and_target,))
th_osc_server.daemon = True
th_osc_server.start()
@staticmethod
def checkSoftwareUpdated():
@@ -363,8 +389,9 @@ class Model:
pass
return
mic_audio_queue = Queue()
# mic_energy_queue = Queue()
self.mic_audio_queue = Queue()
# self.mic_energy_queue = Queue()
mic_device = choice_mic_device[0]
record_timeout = config.INPUT_MIC_RECORD_TIMEOUT
phase_timeout = config.INPUT_MIC_PHRASE_TIMEOUT
@@ -377,8 +404,8 @@ class Model:
dynamic_energy_threshold=config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD,
record_timeout=record_timeout,
)
# self.mic_audio_recorder.recordIntoQueue(mic_audio_queue, mic_energy_queue)
self.mic_audio_recorder.recordIntoQueue(mic_audio_queue, None)
# self.mic_audio_recorder.recordIntoQueue(self.mic_audio_queue, mic_energy_queue)
self.mic_audio_recorder.recordIntoQueue(self.mic_audio_queue, None)
self.mic_transcriber = AudioTranscriber(
speaker=False,
source=self.mic_audio_recorder.source,
@@ -390,7 +417,7 @@ class Model:
)
def sendMicTranscript():
try:
res = self.mic_transcriber.transcribeAudioQueue(mic_audio_queue, config.SOURCE_LANGUAGE, config.SOURCE_COUNTRY)
res = self.mic_transcriber.transcribeAudioQueue(self.mic_audio_queue, config.SOURCE_LANGUAGE, config.SOURCE_COUNTRY)
if res:
message = self.mic_transcriber.getTranscript()
fnc(message)
@@ -398,8 +425,10 @@ class Model:
pass
def endMicTranscript():
mic_audio_queue.queue.clear()
# mic_energy_queue.queue.clear()
while not self.mic_audio_queue.empty():
self.mic_audio_queue.get()
# while not self.mic_energy_queue.empty():
# self.mic_energy_queue.get()
del self.mic_transcriber
gc.collect()
@@ -421,6 +450,42 @@ class Model:
# self.mic_get_energy.daemon = True
# self.mic_get_energy.start()
self.changeMicTranscriptStatus()
def resumeMicTranscript(self):
# キューをクリア
if isinstance(self.mic_audio_queue, Queue):
while not self.mic_audio_queue.empty():
self.mic_audio_queue.get()
# 文字起こしを再開
# if isinstance(self.mic_print_transcript, threadFnc):
# self.mic_print_transcript.resume()
# 音声のレコードを再開
if isinstance(self.mic_audio_recorder, SelectedMicEnergyAndAudioRecorder):
self.mic_audio_recorder.resume()
def pauseMicTranscript(self):
# 文字起こしを一時停止
# if isinstance(self.mic_print_transcript, threadFnc):
# self.mic_print_transcript.pause()
# 音声のレコードを一時停止
if isinstance(self.mic_audio_recorder, SelectedMicEnergyAndAudioRecorder):
self.mic_audio_recorder.pause()
def changeMicTranscriptStatus(self):
if config.ENABLE_VRC_MIC_MUTE_SYNC is True:
if self.mic_mute_status is True:
self.pauseMicTranscript()
elif self.mic_mute_status is False:
self.resumeMicTranscript()
else:
pass
else:
self.resumeMicTranscript()
def stopMicTranscript(self):
if isinstance(self.mic_print_transcript, threadFnc):
self.mic_print_transcript.stop()
@@ -493,7 +558,7 @@ class Model:
record_timeout=record_timeout,
)
# self.speaker_audio_recorder.recordIntoQueue(speaker_audio_queue, speaker_energy_queue)
self.speaker_audio_recorder.recordIntoQueue(speaker_audio_queue ,None)
self.speaker_audio_recorder.recordIntoQueue(speaker_audio_queue, None)
self.speaker_transcriber = AudioTranscriber(
speaker=True,
source=self.speaker_audio_recorder.source,

View File

@@ -1,10 +1,10 @@
from time import sleep
import threading
from pythonosc import osc_message_builder
from pythonosc import udp_client
from pythonosc import dispatcher
from pythonosc import osc_server
from tinyoscquery.queryservice import OSCQueryService
from tinyoscquery.query import OSCQueryBrowser, OSCQueryClient
from tinyoscquery.utility import get_open_udp_port, get_open_tcp_port
# send OSC message typing
@@ -48,6 +48,19 @@ def sendChangeVoice(ip_address="127.0.0.1", port=9000):
sendInputVoice(flag=0, ip_address=ip_address, port=port)
sleep(0.05)
def getOSCParameterValue(address, server_name="VRChat-Client"):
value = None
try:
browser = OSCQueryBrowser()
sleep(1)
service = browser.find_service_by_name(server_name)
if service is not None:
oscq = OSCQueryClient(service)
mute_self_node = oscq.query_node(address)
value = mute_self_node.value[0]
except Exception:
pass
return value
def receiveOscParameters(dict_filter_and_target, ip_address="127.0.0.1", title="VRCT"):
osc_port = get_open_udp_port()
@@ -55,14 +68,14 @@ def receiveOscParameters(dict_filter_and_target, ip_address="127.0.0.1", title="
osc_dispatcher = dispatcher.Dispatcher()
for filter, target in dict_filter_and_target.items():
osc_dispatcher.map(filter, target)
osc_udp_server = osc_server.ThreadingOSCUDPServer((ip_address, osc_port), osc_dispatcher)
threading.Thread(target=osc_udp_server.serve_forever, daemon = True).start()
osc_client = OSCQueryService(title, http_port, osc_port)
for filter, target in dict_filter_and_target.items():
osc_client.advertise_endpoint(filter)
osc_udp_server.serve_forever()
if __name__ == "__main__":
osc_parameter_prefix = "/avatar/parameters/"
osc_avatar_change_path = "/avatar/change"

View File

@@ -24,7 +24,7 @@ class BaseRecorder:
def record_callback(_, audio):
audio_queue.put((audio.get_raw_data(), datetime.now()))
self.stop = self.recorder.listen_in_background(self.source, record_callback, phrase_time_limit=self.record_timeout)
self.stop, self.pause, self.resume = self.recorder.listen_in_background(self.source, record_callback, phrase_time_limit=self.record_timeout)
class SelectedMicRecorder(BaseRecorder):
def __init__(self, device, energy_threshold, dynamic_energy_threshold, record_timeout):
@@ -68,7 +68,7 @@ class BaseEnergyRecorder:
def recordCallback(_, energy):
energy_queue.put(energy)
self.stop = self.recorder.listen_energy_in_background(self.source, recordCallback)
self.stop, self.pause, self.resume = self.recorder.listen_energy_in_background(self.source, recordCallback)
class SelectedMicEnergyRecorder(BaseEnergyRecorder):
def __init__(self, device):
@@ -107,17 +107,18 @@ class BaseEnergyAndAudioRecorder:
with self.source:
self.recorder.adjust_for_ambient_noise(self.source)
def recordIntoQueue(self, audio_queue, energy_queue):
def recordIntoQueue(self, audio_queue, energy_queue=None):
def audioRecordCallback(_, audio):
audio_queue.put((audio.get_raw_data(), datetime.now()))
def energyRecordCallback(energy):
energy_queue.put(energy)
if isinstance(energy_queue, Queue):
self.stop = self.recorder.listen_energy_and_audio_in_background(self.source, audioRecordCallback, phrase_time_limit=self.record_timeout, callback_energy=energyRecordCallback)
else:
self.stop = self.recorder.listen_energy_and_audio_in_background(self.source, audioRecordCallback, phrase_time_limit=self.record_timeout)
self.stop, self.pause, self.resume = self.recorder.listen_energy_and_audio_in_background(
source=self.source,
callback=audioRecordCallback,
phrase_time_limit=self.record_timeout,
callback_energy=energyRecordCallback if energy_queue is not None else None)
class SelectedMicEnergyAndAudioRecorder(BaseEnergyAndAudioRecorder):
def __init__(self, device, energy_threshold, dynamic_energy_threshold, record_timeout):

View File

@@ -519,6 +519,12 @@ class View():
VAR_ENABLE_AUTO_EXPORT_MESSAGE_LOGS=BooleanVar(value=config.ENABLE_LOGGER),
VAR_LABEL_ENABLE_VRC_MIC_MUTE_SYNC=StringVar(value=i18n.t("config_window.vrc_mic_mute_sync.label")),
VAR_DESC_ENABLE_VRC_MIC_MUTE_SYNC=StringVar(value=i18n.t("config_window.vrc_mic_mute_sync.desc")),
CALLBACK_SET_ENABLE_VRC_MIC_MUTE_SYNC=None,
VAR_ENABLE_VRC_MIC_MUTE_SYNC=BooleanVar(value=config.ENABLE_VRC_MIC_MUTE_SYNC),
VAR_LABEL_ENABLE_SEND_MESSAGE_TO_VRC=StringVar(value=i18n.t("config_window.send_message_to_vrc.label")),
VAR_DESC_ENABLE_SEND_MESSAGE_TO_VRC=StringVar(value=i18n.t("config_window.send_message_to_vrc.desc")),
CALLBACK_SET_ENABLE_SEND_MESSAGE_TO_VRC=None,
@@ -770,6 +776,7 @@ class View():
self.view_variable.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY=config_window_registers.get("callback_set_enable_notice_xsoverlay", None)
self.view_variable.CALLBACK_SET_ENABLE_AUTO_EXPORT_MESSAGE_LOGS=config_window_registers.get("callback_set_enable_auto_export_message_logs", None)
self.view_variable.CALLBACK_SET_ENABLE_VRC_MIC_MUTE_SYNC=config_window_registers.get("callback_set_enable_vrc_mic_mute_sync", None)
self.view_variable.CALLBACK_SET_ENABLE_SEND_MESSAGE_TO_VRC=config_window_registers.get("callback_set_enable_send_message_to_vrc", None)
self.view_variable.CALLBACK_SET_SEND_MESSAGE_FORMAT=config_window_registers.get("callback_set_send_message_format", None)

View File

@@ -25,6 +25,9 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v
def buttonAutoExportMessageLogsCallback():
callFunctionIfCallable(view_variable.CALLBACK_OPEN_FILEPATH_LOGS)
def checkboxVrcMuteSyncCallback(checkbox_box_widget):
callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_VRC_MIC_MUTE_SYNC, checkbox_box_widget.get())
def checkboxEnableSendMessageToVrcCallback(checkbox_box_widget):
callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_SEND_MESSAGE_TO_VRC, checkbox_box_widget.get())
@@ -74,6 +77,17 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v
row+=1
config_window.sb__vrc_mic_mute_sync = createSettingBoxCheckbox(
for_var_label_text=view_variable.VAR_LABEL_ENABLE_VRC_MIC_MUTE_SYNC,
for_var_desc_text=view_variable.VAR_DESC_ENABLE_VRC_MIC_MUTE_SYNC,
checkbox_attr_name="sb__checkbox_vrc_mic_mute_sync",
command=lambda: checkboxVrcMuteSyncCallback(config_window.sb__checkbox_vrc_mic_mute_sync),
variable=view_variable.VAR_ENABLE_VRC_MIC_MUTE_SYNC,
)
config_window.sb__vrc_mic_mute_sync.grid(row=row)
row+=1
config_window.sb__enable_send_message_to_vrc = createSettingBoxCheckbox(
for_var_label_text=view_variable.VAR_LABEL_ENABLE_SEND_MESSAGE_TO_VRC,
for_var_desc_text=view_variable.VAR_DESC_ENABLE_SEND_MESSAGE_TO_VRC,