diff --git a/README.md b/README.md index 72eae057..20da4395 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ +
+ +![](docs/vrct_logo.png) + # VRCT (VRChat Chatbox Translator & Transcription) +
+ ## Overview VRChatのChatBoxにOSC経由でメッセージを送信するツール 翻訳エンジンを使用してメッセージとその翻訳部分を同時に送信することができる @@ -15,31 +21,17 @@ VRChatのChatBoxにOSC経由でメッセージを送信するツール - translators(https://github.com/misyaguziya/translators) - custom_speech_recognition(https://github.com/misyaguziya/custom_speech_recognition) -deepl-translate/translators/custom_speech_recognitionについては追加実装をしています。`pip install`でinstallした場合、動かないので注意 +**deepl-translate/translators/custom_speech_recognitionについては追加実装をしています** +**`pip install`でinstallした場合、動かないので注意** ## install ```bash -pip install -r requirements.txt -``` - -```bash -git clone https://github.com/misyaguziya/translators.git -cd translators -python ./setup.py install -cd ../ -git clone https://github.com/misyaguziya/deepl-translate.git -cd deepl-translate -python ./setup.py install -cd ../ -git clone https://github.com/misyaguziya/custom_speech_recognition.git -cd custom_speech_recognition -python ./setup.py install -cd ../ +./install.bat ``` ## Usage ```bash -ptyhon VRCT.py +python VRCT.py ``` ## Features @@ -75,26 +67,26 @@ ptyhon VRCT.py - Appearance Theme: ウィンドウテーマを選択 - UI Scaling: UIサイズを調整 - Font Family: 表示フォントを選択 - - **(New!) UI Language: UIの表示言語を選択** + - UI Language: UIの表示言語を選択 - Translation tab - Select Translator: 翻訳エンジンの変更 - Send Language: 送信するメッセージに対して翻訳する言語[source, target]を選択 - Receive Language: 受信したメッセージに対して翻訳する言語[source, target]を選択 - Transcription tab - - **(New!) Input Mic Host: マイクのホストAPIを選択** + - Input Mic Host: マイクのホストAPIを選択 - Input Mic Device: マイクを選択 - Input Mic Voice Language: 入力する音声の言語 - Input Mic Energy Threshold: 音声取得のしきい値 - - **(New!) Check threshold point: Input Mic Energy Thresholdのしきい値を視覚化** + - Check threshold point: Input Mic Energy Thresholdのしきい値を視覚化 - Input Mic Dynamic Energy Threshold: 音声取得のしきい値の自動調整 - Input Mic Phase Timeout: 文字起こしする音声時間の上限 - Input Mic Record Timeout: 音声の区切りの無音時間 - Input Mic Max Phrases: 保留する単語の上限 - - **(New!) Input Mic Word Filter: MICの文字起こし時にWord Filterで設定した文字が入っていた場合にChatboxに表示しない (ex AAA,BBB,CCC)** + - Input Mic Word Filter: MICの文字起こし時にWord Filterで設定した文字が入っていた場合にChatboxに表示しない (ex AAA,BBB,CCC) - Input Speaker Device: スピーカーを選択 - Input Speaker Voice Language: 受信する音声の言語 - Input Speaker Energy Threshold: 音声取得のしきい値 - - **(New!) Check threshold point: (New!)Input Speaker Energy Thresholdのしきい値を視覚化** + - Check threshold point: Input Speaker Energy Thresholdのしきい値を視覚化 - Input Speaker Dynamic Energy Threshold: 音声取得のしきい値の自動調整 - Input Speaker Record Timeout: 音声の区切りの無音時間 - Input Speaker Phase Timeout: 文字起こしする音声時間の上限 @@ -108,7 +100,8 @@ ptyhon VRCT.py - [translation]が翻訳されたメッセージに置換される - 初期フォーマット:`[message]([translation])` - Others tab - - **(New!) Auto clear chat box: メッセージ送信後に書き込んだメッセージを空にする** + - Auto clear chat box: メッセージ送信後に書き込んだメッセージを空にする + - **(New!) Notification XSOverlay: XSOverlayの通知機能を有効(VR only)** ## Author みしゃ(misyaguzi) diff --git a/README.txt b/README.txt index 831d308e..10647b74 100644 --- a/README.txt +++ b/README.txt @@ -42,26 +42,26 @@ VRChatで使用されるChatBoxをOSC経由でメッセージを送信するツ Appearance Theme: ウィンドウテーマを選択 UI Scaling: UIサイズを調整 Font Family: 表示フォントを選択 - (New!) UI Language: UIの表示言語を選択 + UI Language: UIの表示言語を選択 Translationタブ Select Translator: 翻訳エンジンの変更 Send Language: 送信するメッセージに対して翻訳する言語[source, target]を選択 Receive Language: 受信したメッセージに対して翻訳する言語[source, target]を選択 Transcriptionタブ - (New!) Input Mic Host: マイクのホストAPIを選択 + Input Mic Host: マイクのホストAPIを選択 Input Mic Device: マイクを選択 Input Mic Voice Language: 入力する音声の言語 Input Mic Energy Threshold: 音声取得のしきい値 - (New!) Check threshold point: Input Mic Energy Thresholdのしきい値を視覚化 + Check threshold point: Input Mic Energy Thresholdのしきい値を視覚化 Input Mic Dynamic Energy Threshold: 音声取得のしきい値の自動調整 Input Mic Record Timeout: 音声の区切りの無音時間 Input Mic Phase Timeout: 文字起こしする音声時間の上限 Input Mic Max Phrases: 保留する単語の上限 - (New!) Input Mic Word Filter: MICの文字起こし時にWord Filterで設定した文字が入っていた場合にChatboxに表示しない (ex AAA,BBB,CCC) + Input Mic Word Filter: MICの文字起こし時にWord Filterで設定した文字が入っていた場合にChatboxに表示しない (ex AAA,BBB,CCC) Input Speaker Device: スピーカーを選択 Input Speaker Voice Language: 受信する音声の言語 Input Speaker Energy Threshold: 音声取得のしきい値 - (New!) Check threshold point: (New!)Input Speaker Energy Thresholdのしきい値を視覚化 + Check threshold point: Input Speaker Energy Thresholdのしきい値を視覚化 Input Speaker Dynamic Energy Threshold: 音声取得のしきい値の自動調整 Input Speaker Record Timeout: 音声の区切りの無音時間 Input Speaker Phase Timeout: 文字起こしする音声時間の上限 @@ -75,7 +75,8 @@ VRChatで使用されるChatBoxをOSC経由でメッセージを送信するツ [translation]が翻訳されたメッセージに置換される 初期フォーマット:"[message]([translation])" Othersタブ - (New!) Auto clear chat box: メッセージ送信後に書き込んだメッセージを空にする + Auto clear chat box: メッセージ送信後に書き込んだメッセージを空にする + (New!) Notification XSOverlay: XSOverlayの通知機能を有効(VR only) 設定の初期化 config.jsonを削除 @@ -121,6 +122,11 @@ https://twitter.com/misya_ai - 半角入力時に一部の文字が書き込めないバグを修正 [2023-07-22: v1.3.1] - UIの表示言語選択に韓国語を追加 +[2023-07-30: v1.3.2] +- 試験的にXSOverlayへの通知機能を追加 +- checkbox ONの状態でもConfigを開けるように変更 +- 文字起こし言語の表示を修正 +- いくつかのバグを修正 # 注意事項 再配布とかはやめてね \ No newline at end of file diff --git a/VRCT.py b/VRCT.py index 84c72f23..b3d3cd92 100644 --- a/VRCT.py +++ b/VRCT.py @@ -1,3 +1,4 @@ +from time import sleep from os import path as os_path from json import load as json_load from json import dump as json_dump @@ -10,7 +11,7 @@ from flashtext import KeywordProcessor from threading import Thread from utils import save_json, print_textbox, thread_fnc, get_localized_text, widget_main_window_label_setter -from osc_tools import send_typing, send_message +from osc_tools import send_typing, send_message, send_test_action, receive_osc_parameters from window_config import ToplevelWindowConfig from window_information import ToplevelWindowInformation from languages import transcription_lang, translators, translation_lang, selectable_languages @@ -18,6 +19,7 @@ from audio_utils import get_input_device_list, get_output_device_list, get_defau from audio_recorder import SelectedMicRecorder, SelectedSpeakerRecorder from audio_transcriber import AudioTranscriber from translation import Translator +from notification import notification_xsoverlay_for_vrct class App(CTk): def __init__(self, *args, **kwargs): @@ -32,8 +34,8 @@ class App(CTk): ## main window self.ENABLE_TRANSLATION = False - self.ENABLE_TRANSCRIPTION_SEND = False - self.ENABLE_TRANSCRIPTION_RECEIVE = False + # self.ENABLE_TRANSCRIPTION_SEND = False + # self.ENABLE_TRANSCRIPTION_RECEIVE = False self.ENABLE_FOREGROUND = False ## UI self.TRANSPARENCY = 100 @@ -43,10 +45,10 @@ class App(CTk): self.UI_LANGUAGE = "en" ## Translation self.CHOICE_TRANSLATOR = translators[0] - self.INPUT_SOURCE_LANG = list(translation_lang[self.CHOICE_TRANSLATOR].keys())[0] - self.INPUT_TARGET_LANG = list(translation_lang[self.CHOICE_TRANSLATOR].keys())[1] - self.OUTPUT_SOURCE_LANG = list(translation_lang[self.CHOICE_TRANSLATOR].keys())[1] - self.OUTPUT_TARGET_LANG = list(translation_lang[self.CHOICE_TRANSLATOR].keys())[0] + self.INPUT_SOURCE_LANG = list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys())[0] + self.INPUT_TARGET_LANG = list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys())[1] + self.OUTPUT_SOURCE_LANG = list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys())[1] + self.OUTPUT_TARGET_LANG = list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys())[0] ## Transcription Send self.CHOICE_MIC_HOST = get_default_input_device()["host"]["name"] self.CHOICE_MIC_DEVICE = get_default_input_device()["device"]["name"] @@ -65,6 +67,7 @@ class App(CTk): self.INPUT_SPEAKER_RECORD_TIMEOUT = 3 self.INPUT_SPEAKER_PHRASE_TIMEOUT = 3 self.INPUT_SPEAKER_MAX_PHRASES = 10 + ## Parameter self.OSC_IP_ADDRESS = "127.0.0.1" self.OSC_PORT = 9000 @@ -77,6 +80,8 @@ class App(CTk): self.MESSAGE_FORMAT = "[message]([translation])" # Others self.ENABLE_AUTO_CLEAR_CHATBOX = False + self.ENABLE_OSC = False + self.ENABLE_NOTICE_XSOVERLAY =False # load config if os_path.isfile(self.PATH_CONFIG) is not False: @@ -123,16 +128,16 @@ class App(CTk): if config["CHOICE_TRANSLATOR"] in list(self.translator.translator_status.keys()): self.CHOICE_TRANSLATOR = config["CHOICE_TRANSLATOR"] if "INPUT_SOURCE_LANG" in config.keys(): - if config["INPUT_SOURCE_LANG"] in list(translation_lang[self.CHOICE_TRANSLATOR].keys()): + if config["INPUT_SOURCE_LANG"] in list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys()): self.INPUT_SOURCE_LANG = config["INPUT_SOURCE_LANG"] if "INPUT_TARGET_LANG" in config.keys(): - if config["INPUT_SOURCE_LANG"] in list(translation_lang[self.CHOICE_TRANSLATOR].keys()): + if config["INPUT_TARGET_LANG"] in list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys()): self.INPUT_TARGET_LANG = config["INPUT_TARGET_LANG"] if "OUTPUT_SOURCE_LANG" in config.keys(): - if config["INPUT_SOURCE_LANG"] in list(translation_lang[self.CHOICE_TRANSLATOR].keys()): + if config["OUTPUT_SOURCE_LANG"] in list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys()): self.OUTPUT_SOURCE_LANG = config["OUTPUT_SOURCE_LANG"] if "OUTPUT_TARGET_LANG" in config.keys(): - if config["INPUT_SOURCE_LANG"] in list(translation_lang[self.CHOICE_TRANSLATOR].keys()): + if config["OUTPUT_TARGET_LANG"] in list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys()): self.OUTPUT_TARGET_LANG = config["OUTPUT_TARGET_LANG"] # Transcription @@ -209,6 +214,9 @@ class App(CTk): if "ENABLE_AUTO_CLEAR_CHATBOX" in config.keys(): if type(config["ENABLE_AUTO_CLEAR_CHATBOX"]) is bool: self.ENABLE_AUTO_CLEAR_CHATBOX = config["ENABLE_AUTO_CLEAR_CHATBOX"] + if "ENABLE_NOTICE_XSOVERLAY" in config.keys(): + if type(config["ENABLE_NOTICE_XSOVERLAY"]) is bool: + self.ENABLE_NOTICE_XSOVERLAY = config["ENABLE_NOTICE_XSOVERLAY"] with open(self.PATH_CONFIG, 'w') as fp: config = { @@ -247,6 +255,7 @@ class App(CTk): "AUTH_KEYS": self.AUTH_KEYS, "MESSAGE_FORMAT": self.MESSAGE_FORMAT, "ENABLE_AUTO_CLEAR_CHATBOX": self.ENABLE_AUTO_CLEAR_CHATBOX, + "ENABLE_NOTICE_XSOVERLAY": self.ENABLE_NOTICE_XSOVERLAY, } json_dump(config, fp, indent=4) @@ -316,19 +325,18 @@ class App(CTk): # add button information self.button_information = CTkButton( self.sidebar_frame, - text="", - width=25, + text=None, + width=36, command=self.button_information_callback, image=CTkImage(Image_open(os_path.join(os_path.dirname(__file__), "img", "info-icon-white.png"))) ) self.button_information.grid(row=5, column=0, padx=(10, 5), pady=(5, 5), sticky="wse") - self.information_window = None # add button config self.button_config = CTkButton( self.sidebar_frame, - text="", - width=25, + text=None, + width=36, command=self.button_config_callback, image=CTkImage(Image_open(os_path.join(os_path.dirname(__file__), "img", "config-icon-white.png"))) ) @@ -402,36 +410,49 @@ class App(CTk): self.protocol("WM_DELETE_WINDOW", self.delete_window) self.config_window = ToplevelWindowConfig(self) + self.information_window = ToplevelWindowInformation(self) + + # start receive osc + th_receive_osc_parameters = Thread(target=receive_osc_parameters, args=(self.check_osc_receive,)) + th_receive_osc_parameters.daemon = True + th_receive_osc_parameters.start() + + # check osc started + send_test_action() def button_config_callback(self): + self.foreground_stop() + self.transcription_stop() self.checkbox_translation.configure(state="disabled") self.checkbox_transcription_send.configure(state="disabled") self.checkbox_transcription_receive.configure(state="disabled") + self.checkbox_foreground.configure(state="disabled") + self.tabview_logs.configure(state="disabled") + self.textbox_message_log.configure(state="disabled") + self.textbox_message_send_log.configure(state="disabled") + self.textbox_message_receive_log.configure(state="disabled") + self.textbox_message_system_log.configure(state="disabled") + self.entry_message_box.configure(state="disabled") self.button_config.configure(state="disabled", fg_color=["gray92", "gray14"]) - + self.button_information.configure(state="disabled", fg_color=["gray92", "gray14"]) self.config_window.deiconify() self.config_window.focus_set() self.config_window.focus() + self.config_window.grab_set() def button_information_callback(self): - if self.information_window is None or not self.information_window.winfo_exists(): - self.information_window = ToplevelWindowInformation(self) + self.information_window.deiconify() + self.information_window.focus_set() self.information_window.focus() def checkbox_translation_callback(self): self.ENABLE_TRANSLATION = self.checkbox_translation.get() - if self.ENABLE_TRANSLATION: - self.button_config.configure(state="disabled", fg_color=["gray92", "gray14"]) + if self.ENABLE_TRANSLATION is True: print_textbox(self.textbox_message_log, "Start translation", "INFO") print_textbox(self.textbox_message_system_log, "Start translation", "INFO") else: - if ((self.checkbox_translation.get() is False) and - (self.checkbox_transcription_send.get() is False) and - (self.checkbox_transcription_receive.get() is False)): - self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) print_textbox(self.textbox_message_log, "Stop translation", "INFO") print_textbox(self.textbox_message_system_log, "Stop translation", "INFO") - save_json(self.PATH_CONFIG, "ENABLE_TRANSLATION", self.ENABLE_TRANSLATION) def transcription_send_start(self): self.mic_audio_queue = Queue() @@ -477,8 +498,12 @@ class App(CTk): voice_message = self.MESSAGE_FORMAT.replace("[message]", message).replace("[translation]", result) if self.checkbox_transcription_send.get() is True: - # send OSC message - send_message(voice_message, self.OSC_IP_ADDRESS, self.OSC_PORT) + if self.ENABLE_OSC is True: + # send OSC message + send_message(voice_message, self.OSC_IP_ADDRESS, self.OSC_PORT) + else: + print_textbox(self.textbox_message_log, "OSC is not enabled, please enable OSC and rejoin.", "ERROR") + print_textbox(self.textbox_message_system_log, "OSC is not enabled, please enable OSC and rejoin.", "ERROR") # update textbox message log print_textbox(self.textbox_message_log, f"{voice_message}", "SEND") print_textbox(self.textbox_message_send_log, f"{voice_message}", "SEND") @@ -490,6 +515,7 @@ class App(CTk): print_textbox(self.textbox_message_system_log, "Start voice2chatbox", "INFO") self.checkbox_transcription_send.configure(state="normal") self.checkbox_transcription_receive.configure(state="normal") + self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) def transcription_send_stop(self): if isinstance(self.mic_print_transcript, thread_fnc): @@ -500,20 +526,25 @@ class App(CTk): print_textbox(self.textbox_message_log, "Stop voice2chatbox", "INFO") print_textbox(self.textbox_message_system_log, "Stop voice2chatbox", "INFO") - if ((self.checkbox_translation.get() is False) and - (self.checkbox_transcription_send.get() is False) and - (self.checkbox_transcription_receive.get() is False)): - self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) self.checkbox_transcription_send.configure(state="normal") self.checkbox_transcription_receive.configure(state="normal") + self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) + + def transcription_send_stop_for_config(self): + if isinstance(self.mic_print_transcript, thread_fnc): + self.mic_print_transcript.stop() + if self.mic_audio_recorder.stop != None: + self.mic_audio_recorder.stop() + self.mic_audio_recorder.stop = None + + print_textbox(self.textbox_message_log, "Stop voice2chatbox", "INFO") + print_textbox(self.textbox_message_system_log, "Stop voice2chatbox", "INFO") def checkbox_transcription_send_callback(self): self.checkbox_transcription_send.configure(state="disabled") self.checkbox_transcription_receive.configure(state="disabled") self.button_config.configure(state="disabled", fg_color=["gray92", "gray14"]) - self.update() - self.ENABLE_TRANSCRIPTION_SEND = self.checkbox_transcription_send.get() - if self.ENABLE_TRANSCRIPTION_SEND is True: + if self.checkbox_transcription_send.get() is True: th_transcription_send_start = Thread(target=self.transcription_send_start) th_transcription_send_start.daemon = True th_transcription_send_start.start() @@ -521,60 +552,62 @@ class App(CTk): th_transcription_send_stop = Thread(target=self.transcription_send_stop) th_transcription_send_stop.daemon = True th_transcription_send_stop.start() - save_json(self.PATH_CONFIG, "ENABLE_TRANSCRIPTION_SEND", self.ENABLE_TRANSCRIPTION_SEND) def transcription_receive_start(self): - self.spk_audio_queue = Queue() - spk_device = [device for device in get_output_device_list() if device["name"] == self.CHOICE_SPEAKER_DEVICE][0] - self.spk_audio_recorder = SelectedSpeakerRecorder( - spk_device, - self.INPUT_SPEAKER_ENERGY_THRESHOLD, - self.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, - self.INPUT_SPEAKER_RECORD_TIMEOUT, - ) - self.spk_audio_recorder.record_into_queue(self.spk_audio_queue) - self.spk_transcriber = AudioTranscriber( - speaker=True, - source=self.spk_audio_recorder.source, - language=transcription_lang[self.INPUT_SPEAKER_VOICE_LANGUAGE], - phrase_timeout=self.INPUT_SPEAKER_PHRASE_TIMEOUT, - max_phrases=self.INPUT_SPEAKER_MAX_PHRASES, - ) + self.spk_audio_queue = Queue() + spk_device = [device for device in get_output_device_list() if device["name"] == self.CHOICE_SPEAKER_DEVICE][0] + self.spk_audio_recorder = SelectedSpeakerRecorder( + spk_device, + self.INPUT_SPEAKER_ENERGY_THRESHOLD, + self.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, + self.INPUT_SPEAKER_RECORD_TIMEOUT, + ) + self.spk_audio_recorder.record_into_queue(self.spk_audio_queue) + self.spk_transcriber = AudioTranscriber( + speaker=True, + source=self.spk_audio_recorder.source, + language=transcription_lang[self.INPUT_SPEAKER_VOICE_LANGUAGE], + phrase_timeout=self.INPUT_SPEAKER_PHRASE_TIMEOUT, + max_phrases=self.INPUT_SPEAKER_MAX_PHRASES, + ) - def spk_transcript_to_textbox(): - self.spk_transcriber.transcribe_audio_queue(self.spk_audio_queue) - message = self.spk_transcriber.get_transcript() - if len(message) > 0: - # translate - if self.checkbox_translation.get() is False: - voice_message = f"{message}" - elif self.translator.translator_status[self.CHOICE_TRANSLATOR] is False: - print_textbox(self.textbox_message_log, "Auth Key or language setting is incorrect", "ERROR") - print_textbox(self.textbox_message_system_log, "Auth Key or language setting is incorrect", "ERROR") - voice_message = f"{message}" - else: - result = self.translator.translate( - translator_name=self.CHOICE_TRANSLATOR, - source_language=self.OUTPUT_SOURCE_LANG, - target_language=self.OUTPUT_TARGET_LANG, - message=message - ) - voice_message = self.MESSAGE_FORMAT.replace("[message]", message).replace("[translation]", result) - # send OSC message - # send_message(voice_message, self.OSC_IP_ADDRESS, self.OSC_PORT) + def spk_transcript_to_textbox(): + self.spk_transcriber.transcribe_audio_queue(self.spk_audio_queue) + message = self.spk_transcriber.get_transcript() + if len(message) > 0: + # translate + if self.checkbox_translation.get() is False: + voice_message = f"{message}" + elif self.translator.translator_status[self.CHOICE_TRANSLATOR] is False: + print_textbox(self.textbox_message_log, "Auth Key or language setting is incorrect", "ERROR") + print_textbox(self.textbox_message_system_log, "Auth Key or language setting is incorrect", "ERROR") + voice_message = f"{message}" + else: + result = self.translator.translate( + translator_name=self.CHOICE_TRANSLATOR, + source_language=self.OUTPUT_SOURCE_LANG, + target_language=self.OUTPUT_TARGET_LANG, + message=message + ) + voice_message = self.MESSAGE_FORMAT.replace("[message]", message).replace("[translation]", result) + # send OSC message + # send_message(voice_message, self.OSC_IP_ADDRESS, self.OSC_PORT) - if self.checkbox_transcription_receive.get() is True: - # update textbox message receive log - print_textbox(self.textbox_message_log, f"{voice_message}", "RECEIVE") - print_textbox(self.textbox_message_receive_log, f"{voice_message}", "RECEIVE") + if self.checkbox_transcription_receive.get() is True: + # update textbox message receive log + print_textbox(self.textbox_message_log, f"{voice_message}", "RECEIVE") + print_textbox(self.textbox_message_receive_log, f"{voice_message}", "RECEIVE") + if self.ENABLE_NOTICE_XSOVERLAY is True: + notification_xsoverlay_for_vrct(content=f"{voice_message}") - self.spk_print_transcript = thread_fnc(spk_transcript_to_textbox) - self.spk_print_transcript.daemon = True - self.spk_print_transcript.start() - print_textbox(self.textbox_message_log, "Start speaker2log", "INFO") - print_textbox(self.textbox_message_system_log, "Start speaker2log", "INFO") - self.checkbox_transcription_send.configure(state="normal") - self.checkbox_transcription_receive.configure(state="normal") + self.spk_print_transcript = thread_fnc(spk_transcript_to_textbox) + self.spk_print_transcript.daemon = True + self.spk_print_transcript.start() + print_textbox(self.textbox_message_log, "Start speaker2log", "INFO") + print_textbox(self.textbox_message_system_log, "Start speaker2log", "INFO") + self.checkbox_transcription_send.configure(state="normal") + self.checkbox_transcription_receive.configure(state="normal") + self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) def transcription_receive_stop(self): if isinstance(self.spk_print_transcript, thread_fnc): @@ -585,20 +618,25 @@ class App(CTk): print_textbox(self.textbox_message_log, "Stop speaker2log", "INFO") print_textbox(self.textbox_message_system_log, "Stop speaker2log", "INFO") - if ((self.checkbox_translation.get() is False) and - (self.checkbox_transcription_send.get() is False) and - (self.checkbox_transcription_receive.get() is False)): - self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) self.checkbox_transcription_send.configure(state="normal") self.checkbox_transcription_receive.configure(state="normal") + self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) + + def transcription_receive_stop_for_config(self): + if isinstance(self.spk_print_transcript, thread_fnc): + self.spk_print_transcript.stop() + if self.spk_audio_recorder.stop != None: + self.spk_audio_recorder.stop() + self.spk_audio_recorder.stop = None + + print_textbox(self.textbox_message_log, "Stop speaker2log", "INFO") + print_textbox(self.textbox_message_system_log, "Stop speaker2log", "INFO") def checkbox_transcription_receive_callback(self): self.checkbox_transcription_send.configure(state="disabled") self.checkbox_transcription_receive.configure(state="disabled") self.button_config.configure(state="disabled", fg_color=["gray92", "gray14"]) - self.update() - self.ENABLE_TRANSCRIPTION_RECEIVE = self.checkbox_transcription_receive.get() - if self.ENABLE_TRANSCRIPTION_RECEIVE is True: + if self.checkbox_transcription_receive.get() is True: th_transcription_receive_start = Thread(target=self.transcription_receive_start) th_transcription_receive_start.daemon = True th_transcription_receive_start.start() @@ -607,7 +645,26 @@ class App(CTk): th_transcription_receive_stop.daemon = True th_transcription_receive_stop.start() - save_json(self.PATH_CONFIG, "ENABLE_TRANSCRIPTION_RECEIVE", self.ENABLE_TRANSCRIPTION_RECEIVE) + def transcription_start(self): + if self.checkbox_transcription_send.get() is True: + th_transcription_send_start = Thread(target=self.transcription_send_start) + th_transcription_send_start.daemon = True + th_transcription_send_start.start() + sleep(2) + if self.checkbox_transcription_receive.get() is True: + th_transcription_receive_start = Thread(target=self.transcription_receive_start) + th_transcription_receive_start.daemon = True + th_transcription_receive_start.start() + + def transcription_stop(self): + if self.checkbox_transcription_send.get() is True: + th_transcription_send_stop = Thread(target=self.transcription_send_stop_for_config) + th_transcription_send_stop.daemon = True + th_transcription_send_stop.start() + if self.checkbox_transcription_receive.get() is True: + th_transcription_receive_stop = Thread(target=self.transcription_receive_stop_for_config) + th_transcription_receive_stop.daemon = True + th_transcription_receive_stop.start() def checkbox_foreground_callback(self): self.ENABLE_FOREGROUND = self.checkbox_foreground.get() @@ -619,7 +676,20 @@ class App(CTk): self.attributes("-topmost", False) print_textbox(self.textbox_message_log, "Stop foreground", "INFO") print_textbox(self.textbox_message_system_log, "Stop foreground", "INFO") - save_json(self.PATH_CONFIG, "ENABLE_FOREGROUND", self.ENABLE_FOREGROUND) + + def foreground_start(self): + self.ENABLE_FOREGROUND = self.checkbox_foreground.get() + if self.ENABLE_FOREGROUND: + self.attributes("-topmost", True) + print_textbox(self.textbox_message_log, "Start foreground", "INFO") + print_textbox(self.textbox_message_system_log, "Start foreground", "INFO") + + def foreground_stop(self): + if self.ENABLE_FOREGROUND: + self.attributes("-topmost", False) + print_textbox(self.textbox_message_log, "Stop foreground", "INFO") + print_textbox(self.textbox_message_system_log, "Stop foreground", "INFO") + self.ENABLE_FOREGROUND = False def entry_message_box_press_key_enter(self, event): # send OSC typing @@ -647,14 +717,18 @@ class App(CTk): chat_message = self.MESSAGE_FORMAT.replace("[message]", message).replace("[translation]", result) # send OSC message - send_message(chat_message, self.OSC_IP_ADDRESS, self.OSC_PORT) + if self.ENABLE_OSC is True: + send_message(chat_message, self.OSC_IP_ADDRESS, self.OSC_PORT) + else: + print_textbox(self.textbox_message_log, "OSC is not enabled, please enable OSC and rejoin.", "ERROR") + print_textbox(self.textbox_message_system_log, "OSC is not enabled, please enable OSC and rejoin.", "ERROR") # update textbox message log print_textbox(self.textbox_message_log, f"{chat_message}", "SEND") print_textbox(self.textbox_message_send_log, f"{chat_message}", "SEND") # delete message in entry message box - if self.ENABLE_AUTO_CLEAR_CHATBOX == True: + if self.ENABLE_AUTO_CLEAR_CHATBOX is True: self.entry_message_box.delete(0, customtkinter.END) BREAK_KEYSYM_LIST = [ @@ -747,6 +821,11 @@ class App(CTk): widget_main_window_label_setter(self, language_yaml_data) + def check_osc_receive(self, address, osc_arguments): + if self.ENABLE_OSC is False: + self.ENABLE_OSC = True + # print(address, osc_arguments) + if __name__ == "__main__": try: app = App() diff --git a/docs/vrct_logo.png b/docs/vrct_logo.png new file mode 100644 index 00000000..de568b6e Binary files /dev/null and b/docs/vrct_logo.png differ diff --git a/img/xsoverlay.png b/img/xsoverlay.png new file mode 100644 index 00000000..35c793ea Binary files /dev/null and b/img/xsoverlay.png differ diff --git a/install.bat b/install.bat new file mode 100644 index 00000000..d54ef800 --- /dev/null +++ b/install.bat @@ -0,0 +1,4 @@ +pip install -r requirements.txt +pip install git+https://github.com/misyaguziya/translators +pip install git+https://github.com/misyaguziya/deepl-translate +pip install git+https://github.com/misyaguziya/custom_speech_recognition \ No newline at end of file diff --git a/languages.py b/languages.py index 2c5c1615..3862f4ff 100644 --- a/languages.py +++ b/languages.py @@ -1,100 +1,101 @@ transcription_lang = { - "Japanese Japan":"ja-JP", - "English United States":"en-US", - "English United Kingdom":"en-GB", - "Afrikaans South Africa":"af-ZA", - "Arabic Algeria":"ar-DZ", - "Arabic Bahrain":"ar-BH", - "Arabic Egypt":"ar-EG", - "Arabic Israel":"ar-IL", - "Arabic Iraq":"ar-IQ", - "Arabic Jordan":"ar-JO", - "Arabic Kuwait":"ar-KW", - "Arabic Lebanon":"ar-LB", - "Arabic Morocco":"ar-MA", - "Arabic Oman":"ar-OM", - "Arabic Palestinian Territory":"ar-PS", - "Arabic Qatar":"ar-QA", - "Arabic Saudi Arabia":"ar-SA", - "Arabic Tunisia":"ar-TN", - "Arabic UAE":"ar-AE", - "Euskara Spain":"eu-ES", - "Bulgarian Bulgaria":"bg-BG", - "Catalan Spain":"ca-ES", - "Mongolian China (Simp.)":"cmn-Hans-CN", - "Mongolian Hong Kong SAR (Trad.)":"cmn-Hans-HK", - "Mongolian Taiwan (Trad.)":"cmn-Hant-TW", - "Cantonese Hong Kong":"yue-Hant-HK", - "Hrvatska Croatia":"hr_HR", - "Czech Republic":"cs-CZ", - "Danish Denmark":"da-DK", - "English Australia":"en-AU", - "English Canada":"en-CA", - "English India":"en-IN", - "English Ireland":"en-IE", - "English New Zealand":"en-NZ", - "English Philippines":"en-PH", - "English South Africa":"en-ZA", - "Persian Iran":"fa-IR", - "French France":"fr-FR", - "Filipino Philippines":"fil-PH", - "Galician Spain":"gl-ES", - "German Germany":"de-DE", - "Greek Greece":"el-GR", - "Finnish Finland":"fi-FI", - "Hebrew Israel":"he-IL", - "Hindi India":"hi-IN", - "Hungarian Hungary":"hu-HU", - "Indonesian Indonesia":"id-ID", - "Icelandic Iceland":"is-IS", - "Italian Italy":"it-IT", - "Italian Switzerland":"it-CH", - "Korean Korea":"ko-KR", - "Lithuanian Lithuania":"lt-LT", - "Malay Malaysia":"ms-MY", - "Nederlands Netherlands":"nl-NL", - "Bokmål Norway":"nb-NO", - "Polski Poland":"pl-PL", - "Português Brazil":"pt-BR", - "Português Portugal":"pt-PT", - "limba română Romania":"ro-RO", - "Russian Russia":"ru-RU", - "Serbian Serbia":"sr-RS", - "Slovak Slovakia":"sk-SK", - "Slovenian Slovenia":"sl-SI", - "Spanish Argentina":"es-AR", - "Spanish Bolivia":"es-BO", - "Spanish Chile":"es-CL", - "Spanish Colombia":"es-CO", - "Spanish Costa Rica":"es-CR", - "Spanish Dominican Republic":"es-DO", - "Spanish Ecuador":"es-EC", - "Spanish El Salvador":"es-SV", - "Spanish Guatemala":"es-GT", - "Spanish Honduras":"es-HN", - "Spanish México":"es-MX", - "Spanish Nicaragua":"es-NI", - "Spanish Panamá":"es-PA", - "Spanish Paraguay":"es-PY", - "Spanish Perú":"es-PE", - "Spanish Puerto Rico":"es-PR", - "Spanish Spain":"es-ES", - "Spanish Uruguay":"es-UY", - "Spanish United States":"es-US", - "Spanish Venezuela":"es-VE", - "Swedish Sweden":"sv-SE", - "Thai Thailand":"th-TH", - "Turkish Turkey":"tr-TR", - "Ukrainian Ukraine":"uk-UA", - "Vietnamese Viet Nam":"vi-VN", - "Zulu South Africa":"zu-ZA" + "Japanese (Japan)":"ja-JP", + "English (United States)":"en-US", + "English (United Kingdom)":"en-GB", + "Afrikaans (South Africa)":"af-ZA", + "Arabic (Algeria)":"ar-DZ", + "Arabic (Bahrain)":"ar-BH", + "Arabic (Egypt)":"ar-EG", + "Arabic (Israel)":"ar-IL", + "Arabic (Iraq)":"ar-IQ", + "Arabic (Jordan)":"ar-JO", + "Arabic (Kuwait)":"ar-KW", + "Arabic (Lebanon)":"ar-LB", + "Arabic (Morocco)":"ar-MA", + "Arabic (Oman)":"ar-OM", + "Arabic (State of Palestine)":"ar-PS", + "Arabic (Qatar)":"ar-QA", + "Arabic (Saudi Arabia)":"ar-SA", + "Arabic (Tunisia)":"ar-TN", + "Arabic (United Arab Emirates)":"ar-AE", + "Basque (Spain)":"eu-ES", + "Bulgarian (Bulgaria)":"bg-BG", + "Catalan (Spain)":"ca-ES", + "Chinese, Mandarin (Simplified, China)":"cmn-Hans-CN", + "Chinese, Mandarin (Simplified, Hong Kong)":"cmn-Hans-HK", + "Chinese, Mandarin (Traditional, Taiwan)":"cmn-Hant-TW", + "Chinese, Cantonese (Traditional Hong Kong)":"yue-Hant-HK", + "Croatian (Croatia)":"hr-HR", + "Czech (Czech Republic)":"cs-CZ", + "Danish (Denmark)":"da-DK", + "English (Australia)":"en-AU", + "English (Canada)":"en-CA", + "English (India)":"en-IN", + "English (Ireland)":"en-IE", + "English (New Zealand)":"en-NZ", + "English (Philippines)":"en-PH", + "English (South Africa)":"en-ZA", + "Persian (Iran)":"fa-IR", + "French (France)":"fr-FR", + "Filipino (Philippines)":"fil-PH", + "Galician (Spain)":"gl-ES", + "German (Germany)":"de-DE", + "Greek (Greece)":"el-GR", + "Finnish (Finland)":"fi-FI", + "Hebrew (Israel)":"he-IL", + "Hindi (India)":"hi-IN", + "Hungarian (Hungary)":"hu-HU", + "Indonesian (Indonesia)":"id-ID", + "Icelandic (Iceland)":"is-IS", + "Italian (Italy)":"it-IT", + "Italian (Switzerland)":"it-CH", + "Korean (South Korea)":"ko-KR", + "Lithuanian (Lithuania)":"lt-LT", + "Malay (Malaysia)":"ms-MY", + "Dutch (Netherlands)":"nl-NL", + "Norwegian Bokmål (Norway)":"nb-NO", + "Polish (Poland)":"pl-PL", + "Portuguese (Brazil)":"pt-BR", + "Portuguese (Portugal)":"pt-PT", + "Romanian (Romania)":"ro-RO", + "Russian (Russia)":"ru-RU", + "Serbian (Serbia)":"sr-RS", + "Slovak (Slovakia)":"sk-SK", + "Slovenian (Slovenia)":"sl-SI", + "Spanish (Argentina)":"es-AR", + "Spanish (Bolivia)":"es-BO", + "Spanish (Chile)":"es-CL", + "Spanish (Colombia)":"es-CO", + "Spanish (Costa Rica)":"es-CR", + "Spanish (Dominican Republic)":"es-DO", + "Spanish (Ecuador)":"es-EC", + "Spanish (El Salvador)":"es-SV", + "Spanish (Guatemala)":"es-GT", + "Spanish (Honduras)":"es-HN", + "Spanish (Mexico)":"es-MX", + "Spanish (Nicaragua)":"es-NI", + "Spanish (Panama)":"es-PA", + "Spanish (Paraguay)":"es-PY", + "Spanish (Peru)":"es-PE", + "Spanish (Puerto Rico)":"es-PR", + "Spanish (Spain)":"es-ES", + "Spanish (Uruguay)":"es-UY", + "Spanish (United States)":"es-US", + "Spanish (Venezuela)":"es-VE", + "Swedish (Sweden)":"sv-SE", + "Thai (Thailand)":"th-TH", + "Turkish (Turkey)":"tr-TR", + "Ukrainian (Ukraine)":"uk-UA", + "Vietnamese (Vietnam)":"vi-VN", + "Zulu (South Africa)":"zu-ZA" } translators = ["DeepL(web)", "DeepL(auth)", "Google(web)", "Bing(web)"] translation_lang = {} -translation_lang["DeepL(web)"] = { +dict_deepl_web_languages = { "Japanese":"JA", "English":"EN", + "Korean":"KO", "Bulgarian":"BG", "Chinese":"ZH", "Czech":"CS", @@ -116,9 +117,49 @@ translation_lang["DeepL(web)"] = { "Slovak":"SK", "Slovenian":"SL", "Spanish":"ES", - "Swedish":"SV" + "Swedish":"SV", + "Indonesian":"ID", + "Ukrainian":"UK", + "Turkish":"TR", + "Norwegian":"NB", } -translation_lang["DeepL(auth)"] = { +translation_lang["DeepL(web)"] = { + "source":dict_deepl_web_languages, + "target":dict_deepl_web_languages, +} + +dict_deepl_auth_source_languages = { + "Japanese":"ja", + "English":"en", + "Bulgarian":"bg", + "Czech":"cs", + "Danish":"da", + "German":"de", + "Greek":"el", + "Spanish":"es", + "Estonian":"et", + "Finnish":"fi", + "French":"fr", + "Hungarian":"hu", + "Indonesian":"id", + "Italian":"it", + "Korean":"ko", + "Lithuanian":"lt", + "Latvian":"lv", + "Norwegian":"nb", + "Dutch":"nl", + "Polish":"pl", + "Portuguese":"pt", + "Romanian":"ro", + "Russian":"ru", + "Slovak":"sk", + "Slovenian":"sl", + "Swedish":"sv", + "Turkish":"tr", + "Ukrainian":"uk", + "Chinese":"zh" +} +dict_deepl_auth_target_languages = { "Japanese":"ja", "English American":"en-US", "English British":"en-GB", @@ -141,7 +182,6 @@ translation_lang["DeepL(auth)"] = { "Norwegian":"nb", "Dutch":"nl", "Polish":"pl", - "Portuguese":"pt", "Portuguese Brazilian":"pt-BR", "Portuguese European":"pt-PT", "Romanian":"ro", @@ -153,7 +193,12 @@ translation_lang["DeepL(auth)"] = { "Ukrainian":"uk", "Chinese":"zh" } -translation_lang["Google(web)"] = { +translation_lang["DeepL(auth)"] = { + "source": dict_deepl_auth_source_languages, + "target": dict_deepl_auth_target_languages, +} + +dict_google_web_languages = { "Japanese":"ja", "English":"en", "Chinese":"zh", @@ -217,7 +262,12 @@ translation_lang["Google(web)"] = { "Basque":"eu", "Irish":"ga" } -translation_lang["Bing(web)"] = { +translation_lang["Google(web)"] = { + "source":dict_google_web_languages, + "target":dict_google_web_languages, +} + +dict_bing_web_languages = { "Japanese":"ja", "English":"en", "Chinese":"zh", @@ -279,6 +329,10 @@ translation_lang["Bing(web)"] = { "Punjabi":"pa", "Irish":"ga" } +translation_lang["Bing(web)"] = { + "source":dict_bing_web_languages, + "target":dict_bing_web_languages, +} selectable_languages = { "en": "English", diff --git a/locales.yml b/locales.yml index 79b5fd86..f4b80300 100644 --- a/locales.yml +++ b/locales.yml @@ -60,6 +60,7 @@ en: # tab Others label_checkbox_auto_clear_chatbox: "Auto clear chat box" + label_checkbox_notice_xsoverlay: "Notification XSOverlay" ja: @@ -125,6 +126,7 @@ ja: # tab Others label_checkbox_auto_clear_chatbox: "送信後はチャットボックスを空にする" + label_checkbox_notice_xsoverlay: "XSOverlayの通知機能を有効" ko: diff --git a/notification.py b/notification.py new file mode 100644 index 00000000..61e18725 --- /dev/null +++ b/notification.py @@ -0,0 +1,72 @@ +# ########################################################################################################################### +# DOCUMENT:https://xiexe.github.io/XSOverlayDocumentation/#/NotificationsAPI +# SOURCE:https://zenn.dev/eeharumt/scraps/95f49a62dd809a +# messageType: int = 0 # 1: ポップアップ通知, 2: メディアプレーヤー情報 +# index: int = 0 # メディアプレーヤーでのみ使用され、手首のアイコンを変更する +# timeout: float = 0.5 # 通知インジケータが表示され続ける時間[秒] +# height: float = 175 # 通知インジケータの高さ +# opacity: float = 1 # 通知インジケータの透明度。0.0-1.0の範囲で低いほど透明に +# volume: float = 0.7 # 通知音の大きさ +# audioPath: str = "" # 通知音ファイルのパス。規定音として"default", "error", "warning"を指定可能。空文字列で通知音なしにできる。 +# title: str = "" # 通知タイトル、リッチテキストフォーマットをサポート。 +# content: str = "" # 通知内容、リッチテキストフォーマットをサポート。省略することで小サイズ通知となる。 +# useBase64Icon: bool = False # TrueにすることでBase64の画像を表示する +# icon: str = "" # Base64画像イメージまたは画像ファイルパス。規定アイコンとして"default", "error", or "warning"を指定可能 +# sourceApp: str = "" # 通知したアプリ名(デバック用) +# ########################################################################################################################## + +import socket +import json +import base64 + +def notification_xsoverlay( + endpoint:tuple=("127.0.0.1", 42069), messageType:int=1, index:int=0, timeout:float=2, + height:float=120.0, opacity:float=1.0, volume:float=0.0, audioPath:str="", + title:str="", content:str="", useBase64Icon:bool=False, icon:str="default", sourceApp:str="" +) -> int: + + if icon in ["default", "error", "warning"]: + icon_data = icon + elif useBase64Icon: + try: + with open(icon, "rb") as f: + icon_data_bytes = f.read() + icon_data = base64.b64encode(icon_data_bytes).decode("utf-8") + except: + icon_data = "default" + else: + icon_data = icon + + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + + data_msg = { + "messageType": messageType, + "index": index, + "timeout":timeout, + "height": height, + "opacity": opacity, + "volume": volume, + "audioPath": audioPath, + "title": title, + "content": content, + "useBase64Icon": useBase64Icon, + "icon": icon_data, + "sourceApp": sourceApp, + } + msg_str = json.dumps(data_msg) + response = sock.sendto(msg_str.encode("utf-8"), endpoint) + sock.close() + return response + +def notification_xsoverlay_for_vrct(content:str="") -> int: + response = notification_xsoverlay( + title="VRCT", + content=content, + useBase64Icon=True, + icon="./img/xsoverlay.png", + sourceApp="VRCT" + ) + return response + +if __name__ == "__main__": + notification_xsoverlay_for_vrct(content="notification test") \ No newline at end of file diff --git a/osc_tools.py b/osc_tools.py index dacda522..7c7d9504 100644 --- a/osc_tools.py +++ b/osc_tools.py @@ -1,5 +1,9 @@ +from time import sleep +from typing import List from pythonosc import osc_message_builder from pythonosc import udp_client +from pythonosc import dispatcher +from pythonosc import osc_server # send OSC message typing def send_typing(flag=False, ip_address="127.0.0.1", port=9000): @@ -18,4 +22,16 @@ def send_message(message=None, ip_address="127.0.0.1", port=9000): msg.add_arg(True) b_msg = msg.build() client = udp_client.SimpleUDPClient(ip_address, port) - client.send(b_msg) \ No newline at end of file + client.send(b_msg) + +def send_test_action(ip_address="127.0.0.1", port=9000): + client = udp_client.SimpleUDPClient(ip_address, port) + client.send_message("/input/Vertical", 1) + sleep(0.01) + client.send_message("/input/Vertical", False) + +def receive_osc_parameters(target, filter="/*", ip_address="127.0.0.1", port=9001): + _dispatcher = dispatcher.Dispatcher() + _dispatcher.map(filter, target) + server = osc_server.ThreadingOSCUDPServer((ip_address, port), _dispatcher) + server.serve_forever() \ No newline at end of file diff --git a/translation.py b/translation.py index 8eacf8a6..e251c8a9 100644 --- a/translation.py +++ b/translation.py @@ -35,8 +35,8 @@ class Translator(): def translate(self, translator_name, source_language, target_language, message): result = "" try: - source_language=translation_lang[translator_name][source_language] - target_language=translation_lang[translator_name][target_language] + source_language=translation_lang[translator_name]["source"][source_language] + target_language=translation_lang[translator_name]["target"][target_language] if translator_name == "DeepL(web)": result = deepl_web_Translator( source_language=source_language, diff --git a/utils.py b/utils.py index 0e63f179..ac2eb35d 100644 --- a/utils.py +++ b/utils.py @@ -100,15 +100,15 @@ def widget_config_window_label_setter(self, language_yaml_data): "label_input_speaker_phrase_timeout", "label_input_speaker_max_phrases", - # tab Parameter "label_ip_address", "label_port", "label_authkey", "label_message_format", - + # tab Others - "label_checkbox_auto_clear_chatbox" + "label_checkbox_auto_clear_chatbox", + "label_checkbox_notice_xsoverlay", ] for name in widget_names: widget = getattr(self, name) diff --git a/window_config.py b/window_config.py index 89adde9a..346c4a03 100644 --- a/window_config.py +++ b/window_config.py @@ -212,33 +212,33 @@ class ToplevelWindowConfig(CTkToplevel): print_textbox(self.parent.textbox_message_system_log, "Auth Key or language setting is incorrect", "ERROR") else: self.optionmenu_translation_input_source_language.configure( - values=list(translation_lang[choice].keys()), - variable=StringVar(value=list(translation_lang[choice].keys())[0])) + values=list(translation_lang[choice]["source"].keys()), + variable=StringVar(value=list(translation_lang[choice]["source"].keys())[0])) self.optionmenu_translation_input_target_language.configure( - values=list(translation_lang[choice].keys()), - variable=StringVar(value=list(translation_lang[choice].keys())[1])) + values=list(translation_lang[choice]["target"].keys()), + variable=StringVar(value=list(translation_lang[choice]["target"].keys())[1])) self.optionmenu_translation_output_source_language.configure( - values=list(translation_lang[choice].keys()), - variable=StringVar(value=list(translation_lang[choice].keys())[1])) + values=list(translation_lang[choice]["source"].keys()), + variable=StringVar(value=list(translation_lang[choice]["source"].keys())[1])) self.optionmenu_translation_output_target_language.configure( - values=list(translation_lang[choice].keys()), - variable=StringVar(value=list(translation_lang[choice].keys())[0])) + values=list(translation_lang[choice]["target"].keys()), + variable=StringVar(value=list(translation_lang[choice]["target"].keys())[0])) if SCROLLABLE_DROPDOWN: self.scrollableDropdown_translation_input_source_language.configure( - values=list(translation_lang[choice].keys())) + values=list(translation_lang[choice]["source"].keys())) self.scrollableDropdown_translation_input_target_language.configure( - values=list(translation_lang[choice].keys())) + values=list(translation_lang[choice]["target"].keys())) self.scrollableDropdown_translation_output_source_language.configure( - values=list(translation_lang[choice].keys())) + values=list(translation_lang[choice]["source"].keys())) self.scrollableDropdown_translation_output_target_language.configure( - values=list(translation_lang[choice].keys())) + values=list(translation_lang[choice]["target"].keys())) self.parent.CHOICE_TRANSLATOR = choice - self.parent.INPUT_SOURCE_LANG = list(translation_lang[choice].keys())[0] - self.parent.INPUT_TARGET_LANG = list(translation_lang[choice].keys())[1] - self.parent.OUTPUT_SOURCE_LANG = list(translation_lang[choice].keys())[1] - self.parent.OUTPUT_TARGET_LANG = list(translation_lang[choice].keys())[0] + self.parent.INPUT_SOURCE_LANG = list(translation_lang[choice]["source"].keys())[0] + self.parent.INPUT_TARGET_LANG = list(translation_lang[choice]["target"].keys())[1] + self.parent.OUTPUT_SOURCE_LANG = list(translation_lang[choice]["source"].keys())[1] + self.parent.OUTPUT_TARGET_LANG = list(translation_lang[choice]["target"].keys())[0] save_json(self.parent.PATH_CONFIG, "CHOICE_TRANSLATOR", self.parent.CHOICE_TRANSLATOR) save_json(self.parent.PATH_CONFIG, "INPUT_SOURCE_LANG", self.parent.INPUT_SOURCE_LANG) save_json(self.parent.PATH_CONFIG, "INPUT_TARGET_LANG", self.parent.INPUT_TARGET_LANG) @@ -492,17 +492,32 @@ class ToplevelWindowConfig(CTkToplevel): self.parent.ENABLE_AUTO_CLEAR_CHATBOX = value save_json(self.parent.PATH_CONFIG, "ENABLE_AUTO_CLEAR_CHATBOX", self.parent.ENABLE_AUTO_CLEAR_CHATBOX) + def checkbox_notice_xsoverlay_callback(self): + value = self.checkbox_notice_xsoverlay.get() + self.parent.ENABLE_NOTICE_XSOVERLAY = value + save_json(self.parent.PATH_CONFIG, "ENABLE_NOTICE_XSOVERLAY", self.parent.ENABLE_NOTICE_XSOVERLAY) + def delete_window(self): self.checkbox_input_mic_threshold_check.deselect() self.checkbox_input_speaker_threshold_check.deselect() self.checkbox_input_mic_threshold_check_callback() self.checkbox_input_speaker_threshold_check_callback() - + self.parent.transcription_start() + self.parent.foreground_start() self.parent.checkbox_translation.configure(state="normal") self.parent.checkbox_transcription_send.configure(state="normal") self.parent.checkbox_transcription_receive.configure(state="normal") + self.parent.checkbox_foreground.configure(state="normal") + self.parent.tabview_logs.configure(state="normal") + self.parent.textbox_message_log.configure(state="normal") + self.parent.textbox_message_send_log.configure(state="normal") + self.parent.textbox_message_receive_log.configure(state="normal") + self.parent.textbox_message_system_log.configure(state="normal") + self.parent.entry_message_box.configure(state="normal") self.parent.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) - self.parent.config_window.withdraw() + self.parent.button_information.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) + self.withdraw() + self.grab_release() def entry_message_format_callback(self, event): value = self.entry_message_format.get() @@ -752,7 +767,7 @@ class ToplevelWindowConfig(CTkToplevel): self.optionmenu_translation_input_source_language = CTkOptionMenu( self.tabview_config.tab(config_tab_title_translation), command=self.optionmenu_translation_input_source_language_callback, - values=list(translation_lang[self.parent.CHOICE_TRANSLATOR].keys()), + values=list(translation_lang[self.parent.CHOICE_TRANSLATOR]["source"].keys()), variable=StringVar(value=self.parent.INPUT_SOURCE_LANG), font=CTkFont(family=self.parent.FONT_FAMILY), dropdown_font=CTkFont(family=self.parent.FONT_FAMILY), @@ -763,7 +778,7 @@ class ToplevelWindowConfig(CTkToplevel): if SCROLLABLE_DROPDOWN: self.scrollableDropdown_translation_input_source_language = CTkScrollableDropdown( self.optionmenu_translation_input_source_language, - values=list(translation_lang[self.parent.CHOICE_TRANSLATOR].keys()), + values=list(translation_lang[self.parent.CHOICE_TRANSLATOR]["source"].keys()), justify="left", button_color="transparent", command=self.optionmenu_translation_input_source_language_callback, @@ -787,7 +802,7 @@ class ToplevelWindowConfig(CTkToplevel): self.optionmenu_translation_input_target_language = CTkOptionMenu( self.tabview_config.tab(config_tab_title_translation), command=self.optionmenu_translation_input_target_language_callback, - values=list(translation_lang[self.parent.CHOICE_TRANSLATOR].keys()), + values=list(translation_lang[self.parent.CHOICE_TRANSLATOR]["target"].keys()), variable=StringVar(value=self.parent.INPUT_TARGET_LANG), font=CTkFont(family=self.parent.FONT_FAMILY), dropdown_font=CTkFont(family=self.parent.FONT_FAMILY), @@ -798,7 +813,7 @@ class ToplevelWindowConfig(CTkToplevel): if SCROLLABLE_DROPDOWN: self.scrollableDropdown_translation_input_target_language = CTkScrollableDropdown( self.optionmenu_translation_input_target_language, - values=list(translation_lang[self.parent.CHOICE_TRANSLATOR].keys()), + values=list(translation_lang[self.parent.CHOICE_TRANSLATOR]["target"].keys()), justify="left", button_color="transparent", command=self.optionmenu_translation_input_target_language_callback, @@ -823,7 +838,7 @@ class ToplevelWindowConfig(CTkToplevel): self.optionmenu_translation_output_source_language = CTkOptionMenu( self.tabview_config.tab(config_tab_title_translation), command=self.optionmenu_translation_output_source_language_callback, - values=list(translation_lang[self.parent.CHOICE_TRANSLATOR].keys()), + values=list(translation_lang[self.parent.CHOICE_TRANSLATOR]["source"].keys()), variable=StringVar(value=self.parent.OUTPUT_SOURCE_LANG), font=CTkFont(family=self.parent.FONT_FAMILY), dropdown_font=CTkFont(family=self.parent.FONT_FAMILY), @@ -834,7 +849,7 @@ class ToplevelWindowConfig(CTkToplevel): if SCROLLABLE_DROPDOWN: self.scrollableDropdown_translation_output_source_language = CTkScrollableDropdown( self.optionmenu_translation_output_source_language, - values=list(translation_lang[self.parent.CHOICE_TRANSLATOR].keys()), + values=list(translation_lang[self.parent.CHOICE_TRANSLATOR]["source"].keys()), justify="left", button_color="transparent", command=self.optionmenu_translation_output_source_language_callback, @@ -858,7 +873,7 @@ class ToplevelWindowConfig(CTkToplevel): self.optionmenu_translation_output_target_language = CTkOptionMenu( self.tabview_config.tab(config_tab_title_translation), command=self.optionmenu_translation_output_target_language_callback, - values=list(translation_lang[self.parent.CHOICE_TRANSLATOR].keys()), + values=list(translation_lang[self.parent.CHOICE_TRANSLATOR]["target"].keys()), variable=StringVar(value=self.parent.OUTPUT_TARGET_LANG), font=CTkFont(family=self.parent.FONT_FAMILY), dropdown_font=CTkFont(family=self.parent.FONT_FAMILY), @@ -869,7 +884,7 @@ class ToplevelWindowConfig(CTkToplevel): if SCROLLABLE_DROPDOWN: self.scrollableDropdown_translation_output_target_language = CTkScrollableDropdown( self.optionmenu_translation_output_target_language, - values=list(translation_lang[self.parent.CHOICE_TRANSLATOR].keys()), + values=list(translation_lang[self.parent.CHOICE_TRANSLATOR]["target"].keys()), justify="left", button_color="transparent", command=self.optionmenu_translation_output_target_language_callback, @@ -1381,7 +1396,7 @@ class ToplevelWindowConfig(CTkToplevel): # tab Others ## checkbox auto clear chat box - row += 1 + row = 0 self.label_checkbox_auto_clear_chatbox = CTkLabel( self.tabview_config.tab(config_tab_title_others), text=init_lang_text, @@ -1403,4 +1418,26 @@ class ToplevelWindowConfig(CTkToplevel): else: self.checkbox_auto_clear_chatbox.deselect() + # checkbox notice xsoverlay + row += 1 + self.label_checkbox_notice_xsoverlay = CTkLabel( + self.tabview_config.tab(config_tab_title_others), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=self.parent.FONT_FAMILY) + ) + self.label_checkbox_notice_xsoverlay.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + self.checkbox_notice_xsoverlay = CTkCheckBox( + self.tabview_config.tab(config_tab_title_others), + text="", + onvalue=True, + offvalue=False, + command=self.checkbox_notice_xsoverlay_callback, + font=CTkFont(family=self.parent.FONT_FAMILY) + ) + self.checkbox_notice_xsoverlay.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") + if self.parent.ENABLE_NOTICE_XSOVERLAY is True: + self.checkbox_notice_xsoverlay.select() + else: + self.checkbox_notice_xsoverlay.deselect() widget_config_window_label_setter(self, language_yaml_data) \ No newline at end of file diff --git a/window_information.py b/window_information.py index aa5d05a6..8e912c0d 100644 --- a/window_information.py +++ b/window_information.py @@ -4,6 +4,7 @@ from customtkinter import CTkToplevel, CTkTextbox, CTkFont class ToplevelWindowInformation(CTkToplevel): def __init__(self, parent, *args, **kwargs): super().__init__(parent, *args, **kwargs) + self.withdraw() self.parent = parent self.grid_columnconfigure(0, weight=1) self.grid_rowconfigure(0, weight=1) @@ -18,7 +19,7 @@ class ToplevelWindowInformation(CTkToplevel): font=CTkFont(family=self.parent.FONT_FAMILY) ) self.textbox_information.grid(row=0, column=0, padx=(10, 10), pady=(10, 10), sticky="nsew") - textbox_information_message = """VRCT(v1.3.1) + textbox_information_message = """VRCT(v1.3.2) # 概要 VRChatで使用されるChatBoxをOSC経由でメッセージを送信するツールになります。 @@ -61,26 +62,26 @@ VRChatで使用されるChatBoxをOSC経由でメッセージを送信するツ Appearance Theme: ウィンドウテーマを選択 UI Scaling: UIサイズを調整 Font Family: 表示フォントを選択 - (New!) UI Language: UIの表示言語を選択 + UI Language: UIの表示言語を選択 Translationタブ Select Translator: 翻訳エンジンの変更 Send Language: 送信するメッセージに対して翻訳する言語[source, target]を選択 Receive Language: 受信したメッセージに対して翻訳する言語[source, target]を選択 Transcriptionタブ - (New!) Input Mic Host: マイクのホストAPIを選択 + Input Mic Host: マイクのホストAPIを選択 Input Mic Device: マイクを選択 Input Mic Voice Language: 入力する音声の言語 Input Mic Energy Threshold: 音声取得のしきい値 - (New!) Check threshold point: Input Mic Energy Thresholdのしきい値を視覚化 + Check threshold point: Input Mic Energy Thresholdのしきい値を視覚化 Input Mic Dynamic Energy Threshold: 音声取得のしきい値の自動調整 Input Mic Record Timeout: 音声の区切りの無音時間 Input Mic Phase Timeout: 文字起こしする音声時間の上限 Input Mic Max Phrases: 保留する単語の上限 - (New!) Input Mic Word Filter: MICの文字起こし時にWord Filterで設定した文字が入っていた場合にChatboxに表示しない (ex AAA,BBB,CCC) + Input Mic Word Filter: MICの文字起こし時にWord Filterで設定した文字が入っていた場合にChatboxに表示しない (ex AAA,BBB,CCC) Input Speaker Device: スピーカーを選択 Input Speaker Voice Language: 受信する音声の言語 Input Speaker Energy Threshold: 音声取得のしきい値 - (New!) Check threshold point: (New!)Input Speaker Energy Thresholdのしきい値を視覚化 + Check threshold point: Input Speaker Energy Thresholdのしきい値を視覚化 Input Speaker Dynamic Energy Threshold: 音声取得のしきい値の自動調整 Input Speaker Record Timeout: 音声の区切りの無音時間 Input Speaker Phase Timeout: 文字起こしする音声時間の上限 @@ -94,7 +95,8 @@ VRChatで使用されるChatBoxをOSC経由でメッセージを送信するツ [translation]が翻訳されたメッセージに置換される 初期フォーマット:"[message]([translation])" Othersタブ - (New!) Auto clear chat box: メッセージ送信後に書き込んだメッセージを空にする + Auto clear chat box: メッセージ送信後に書き込んだメッセージを空にする + (New!) Notification XSOverlay: XSOverlayの通知機能を有効(VR only) 設定の初期化 config.jsonを削除 @@ -140,10 +142,19 @@ https://twitter.com/misya_ai - 半角入力時に一部の文字が書き込めないバグを修正 [2023-07-22: v1.3.1] - UIの表示言語選択に韓国語を追加 +[2023-07-30: v1.3.2] +- 試験的にXSOverlayへの通知機能を追加 +- checkbox ONの状態でもConfigを開けるように変更 +- 文字起こし言語の表示を修正 +- いくつかのバグを修正 # 注意事項 再配布とかはやめてね """ self.textbox_information.insert("end", textbox_information_message) - self.textbox_information.configure(state='disabled') \ No newline at end of file + self.textbox_information.configure(state='disabled') + self.protocol("WM_DELETE_WINDOW", self.delete_window) + + def delete_window(self): + self.withdraw() \ No newline at end of file