diff --git a/.gitignore b/.gitignore index 75c28a41..52825c27 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ VRCT.spec *.pyc logs/ .venv/ -weight/ +weights/ .vscode error.log *.exe diff --git a/build.bat b/build.bat index 10cb4c05..84c7eb30 100644 --- a/build.bat +++ b/build.bat @@ -1,2 +1,2 @@ -pyinstaller --windowed --clean --noconfirm --icon="./img/vrct_logo_mark_black.ico" --add-data "./img;img/" --add-data "./locales;locales/" --add-data "./batch;batch/" --name VRCT --add-data ".venv\Lib\site-packages\customtkinter;customtkinter/" --exclude-module pandas --exclude-module matplotlib --exclude-module PyQt5 main.py +pyinstaller --windowed --clean --noconfirm --icon="./img/vrct_logo_mark_black.ico" --add-data "./img;img/" --add-data "./locales;locales/" --add-data "./batch;batch/" --name VRCT --add-data ".venv\Lib\site-packages\customtkinter;customtkinter/" --add-data ".venv\Lib\site-packages\zeroconf;zeroconf/" --exclude-module pandas --exclude-module matplotlib --exclude-module PyQt5 main.py "C:\Program Files (x86)\NSIS\makensis.exe" installer/installer.nsi \ No newline at end of file diff --git a/config.py b/config.py index 371ec121..772773af 100644 --- a/config.py +++ b/config.py @@ -39,12 +39,8 @@ class Config: return self._VERSION @property - def ENABLE_SPEAKER2CHATBOX(self): - return self._ENABLE_SPEAKER2CHATBOX - - @property - def ENABLE_SPEAKER2CHATBOX(self): - return self._ENABLE_SPEAKER2CHATBOX + def ENABLE_SPEAKER2CHATBOX_PASS_CONFIRMATION(self): + return self._ENABLE_SPEAKER2CHATBOX_PASS_CONFIRMATION @property def PATH_LOCAL(self): @@ -70,6 +66,10 @@ class Config: def DOCUMENTS_URL(self): return self._DOCUMENTS_URL + @property + def DEEPL_AUTH_KEY_PAGE_URL(self): + return self._DEEPL_AUTH_KEY_PAGE_URL + @property def TRANSPARENCY_RANGE(self): return self._TRANSPARENCY_RANGE @@ -98,6 +98,10 @@ class Config: def SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT(self): return self._SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT + @property + def SELECTABLE_WHISPER_WEIGHT_TYPE_DICT(self): + return self._SELECTABLE_WHISPER_WEIGHT_TYPE_DICT + @property def MAX_MIC_ENERGY_THRESHOLD(self): return self._MAX_MIC_ENERGY_THRESHOLD @@ -107,6 +111,15 @@ class Config: return self._MAX_SPEAKER_ENERGY_THRESHOLD # Read Write + @property + def ENABLE_SPEAKER2CHATBOX(self): + return self._ENABLE_SPEAKER2CHATBOX + + @ENABLE_SPEAKER2CHATBOX.setter + def ENABLE_SPEAKER2CHATBOX(self, value): + if isinstance(value, bool): + self._ENABLE_SPEAKER2CHATBOX = value + @property def ENABLE_TRANSLATION(self): return self._ENABLE_TRANSLATION @@ -197,6 +210,24 @@ class Config: if value in list(translation_lang.keys()): self._CHOICE_OUTPUT_TRANSLATOR = value + @property + def SENT_MESSAGES_LOG(self): + return self._SENT_MESSAGES_LOG + + @SENT_MESSAGES_LOG.setter + def SENT_MESSAGES_LOG(self, value): + if isinstance(value, list): + self._SENT_MESSAGES_LOG = value + + @property + def CURRENT_SENT_MESSAGES_LOG_INDEX(self): + return self._CURRENT_SENT_MESSAGES_LOG_INDEX + + @CURRENT_SENT_MESSAGES_LOG_INDEX.setter + def CURRENT_SENT_MESSAGES_LOG_INDEX(self, value): + if isinstance(value, int): + self._CURRENT_SENT_MESSAGES_LOG_INDEX = value + @property def IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION(self): return self._IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION @@ -206,6 +237,15 @@ class Config: if isinstance(value, bool): self._IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION = value + @property + def IS_RESET_BUTTON_DISPLAYED_FOR_WHISPER(self): + return self._IS_RESET_BUTTON_DISPLAYED_FOR_WHISPER + + @IS_RESET_BUTTON_DISPLAYED_FOR_WHISPER.setter + def IS_RESET_BUTTON_DISPLAYED_FOR_WHISPER(self, value): + if isinstance(value, bool): + self._IS_RESET_BUTTON_DISPLAYED_FOR_WHISPER = value + # Save Json Data ## Main Window @property @@ -263,6 +303,17 @@ class Config: self._SELECTED_TAB_TARGET_LANGUAGES = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + @property + @json_serializable('SELECTED_TRANSCRIPTION_ENGINE') + def SELECTED_TRANSCRIPTION_ENGINE(self): + return self._SELECTED_TRANSCRIPTION_ENGINE + + @SELECTED_TRANSCRIPTION_ENGINE.setter + def SELECTED_TRANSCRIPTION_ENGINE(self, value): + if isinstance(value, str): + self._SELECTED_TRANSCRIPTION_ENGINE = value + # saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + @property @json_serializable('IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE') def IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE(self): @@ -569,15 +620,37 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property - @json_serializable('WEIGHT_TYPE') - def WEIGHT_TYPE(self): - return self._WEIGHT_TYPE + @json_serializable('USE_WHISPER_FEATURE') + def USE_WHISPER_FEATURE(self): + return self._USE_WHISPER_FEATURE - @WEIGHT_TYPE.setter - def WEIGHT_TYPE(self, value): + @USE_WHISPER_FEATURE.setter + def USE_WHISPER_FEATURE(self, value): + if isinstance(value, bool): + self._USE_WHISPER_FEATURE = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('CTRANSLATE2_WEIGHT_TYPE') + def CTRANSLATE2_WEIGHT_TYPE(self): + return self._CTRANSLATE2_WEIGHT_TYPE + + @CTRANSLATE2_WEIGHT_TYPE.setter + def CTRANSLATE2_WEIGHT_TYPE(self, value): # if isinstance(value, str) and value in self.SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT: if isinstance(value, str): - self._WEIGHT_TYPE = value + self._CTRANSLATE2_WEIGHT_TYPE = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('WHISPER_WEIGHT_TYPE') + def WHISPER_WEIGHT_TYPE(self): + return self._WHISPER_WEIGHT_TYPE + + @WHISPER_WEIGHT_TYPE.setter + def WHISPER_WEIGHT_TYPE(self, value): + if isinstance(value, str): + self._WHISPER_WEIGHT_TYPE = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property @@ -687,9 +760,18 @@ class Config: self._RECEIVED_MESSAGE_FORMAT_WITH_T = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - # Speaker2Chatbox------------------ + @property + @json_serializable('ENABLE_SPEAKER2CHATBOX_PASS') + def ENABLE_SPEAKER2CHATBOX_PASS(self): + return self._ENABLE_SPEAKER2CHATBOX_PASS + + @ENABLE_SPEAKER2CHATBOX_PASS.setter + def ENABLE_SPEAKER2CHATBOX_PASS(self, value): + if isinstance(value, str): + self._ENABLE_SPEAKER2CHATBOX_PASS = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + @property @json_serializable('ENABLE_SEND_RECEIVED_MESSAGE_TO_VRC') def ENABLE_SEND_RECEIVED_MESSAGE_TO_VRC(self): @@ -731,8 +813,9 @@ class Config: def init_config(self): # Read Only - self._VERSION = "2.1.1" + self._VERSION = "2.2.0" self._ENABLE_SPEAKER2CHATBOX = False # Speaker2Chatbox + self._ENABLE_SPEAKER2CHATBOX_PASS_CONFIRMATION = "123456789" self._PATH_LOCAL = os_path.dirname(sys.argv[0]) self._PATH_CONFIG = os_path.join(self._PATH_LOCAL, "config.json") self._PATH_LOGS = os_path.join(self._PATH_LOCAL, "logs") @@ -740,6 +823,7 @@ class Config: self._GITHUB_URL = "https://api.github.com/repos/misyaguziya/VRCT/releases/latest" self._BOOTH_URL = "https://misyaguziya.booth.pm/" self._DOCUMENTS_URL = "https://mzsoftware.notion.site/VRCT-Documents-be79b7a165f64442ad8f326d86c22246" + self._DEEPL_AUTH_KEY_PAGE_URL = "https://www.deepl.com/ja/account/summary" self._TRANSPARENCY_RANGE = (50, 100) self._APPEARANCE_THEME_LIST = ["Light", "Dark", "System"] self._UI_SCALING_LIST = generatePercentageStringsList(start=40, end=200, step=10) @@ -756,6 +840,18 @@ class Config: "Small": "Small", "Large": "Large", } + + self._SELECTABLE_WHISPER_WEIGHT_TYPE_DICT = { + # {Save json str}: {i18n_placeholder} pairs + "tiny": "tiny", + "base": "base", + "small": "small", + "medium": "medium", + "large-v1": "large-v1", + "large-v2": "large-v2", + "large-v3": "large-v3", + } + self._MAX_MIC_ENERGY_THRESHOLD = 2000 self._MAX_SPEAKER_ENERGY_THRESHOLD = 4000 @@ -770,7 +866,10 @@ class Config: self._SOURCE_COUNTRY = "Japan" self._TARGET_LANGUAGE = "English" self._TARGET_COUNTRY = "United States" + self._SENT_MESSAGES_LOG = [] + self._CURRENT_SENT_MESSAGES_LOG_INDEX = 0 self._IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION = False + self._IS_RESET_BUTTON_DISPLAYED_FOR_WHISPER = False # Save Json Data ## Main Window @@ -795,6 +894,7 @@ class Config: "2":"English\n(United States)", "3":"English\n(United States)", } + self._SELECTED_TRANSCRIPTION_ENGINE = "Google" self._IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = False ## Config Window @@ -810,7 +910,7 @@ class Config: "x_pos": "0", "y_pos": "0", "width": "870", - "height": "640", + "height": "654", } self._CHOICE_MIC_HOST = getDefaultInputDevice()["host"]["name"] self._CHOICE_MIC_DEVICE = getDefaultInputDevice()["device"]["name"] @@ -831,7 +931,9 @@ class Config: "DeepL_API": None, } self._USE_TRANSLATION_FEATURE = True - self._WEIGHT_TYPE = "Small" + self._CTRANSLATE2_WEIGHT_TYPE = "Small" + self._USE_WHISPER_FEATURE = False + self._WHISPER_WEIGHT_TYPE = "base" self._SEND_MESSAGE_FORMAT = "[message]" self._SEND_MESSAGE_FORMAT_WITH_T = "[message]([translation])" self._RECEIVED_MESSAGE_FORMAT = "[message]" @@ -842,6 +944,7 @@ class Config: self._ENABLE_NOTICE_XSOVERLAY = False self._ENABLE_SEND_MESSAGE_TO_VRC = True self._ENABLE_SEND_RECEIVED_MESSAGE_TO_VRC = False # Speaker2Chatbox + self._ENABLE_SPEAKER2CHATBOX_PASS = "000000000" self._ENABLE_LOGGER = False self._IS_CONFIG_WINDOW_COMPACT_MODE = False @@ -855,6 +958,11 @@ class Config: if key == "MESSAGE_FORMAT": old_message_format = config[key] setattr(self, key, config[key]) + + if key == "ENABLE_SPEAKER2CHATBOX_PASS": + if self.ENABLE_SPEAKER2CHATBOX_PASS_CONFIRMATION == config[key]: + self.ENABLE_SPEAKER2CHATBOX = True + if old_message_format is not None: setattr(self, "SEND_MESSAGE_FORMAT_WITH_T", old_message_format) diff --git a/controller.py b/controller.py index f9e9a5b3..f695ee45 100644 --- a/controller.py +++ b/controller.py @@ -64,6 +64,7 @@ def changeToCTranslate2Process(): # func transcription send message def sendMicMessage(message): if len(message) > 0: + addSentMessageLog(message) translation = "" if model.checkKeywords(message): view.printToTextbox_DetectedByWordFilter(detected_message=message) @@ -200,6 +201,7 @@ def stopThreadingTranscriptionReceiveMessageOnOpenConfigWindow(): # func message box def sendChatMessage(message): if len(message) > 0: + addSentMessageLog(message) translation = "" if config.ENABLE_TRANSLATION is False: pass @@ -249,6 +251,29 @@ def messageBoxFocusOut(e): if config.ENABLE_SEND_MESSAGE_TO_VRC is True: model.oscStopSendTyping() +def addSentMessageLog(sent_message): + config.SENT_MESSAGES_LOG.append(sent_message) + config.CURRENT_SENT_MESSAGES_LOG_INDEX = len(config.SENT_MESSAGES_LOG) + +def updateMessageBox(index_offset): + if len(config.SENT_MESSAGES_LOG) == 0: + return + try: + new_index = config.CURRENT_SENT_MESSAGES_LOG_INDEX + index_offset + target_message_text = config.SENT_MESSAGES_LOG[new_index] + view.replaceMessageBox(target_message_text) + config.CURRENT_SENT_MESSAGES_LOG_INDEX = new_index + except IndexError: + pass + +def messageBoxUpKeyPress(): + if config.CURRENT_SENT_MESSAGES_LOG_INDEX > 0: + updateMessageBox(-1) + +def messageBoxDownKeyPress(): + if config.CURRENT_SENT_MESSAGES_LOG_INDEX < len(config.SENT_MESSAGES_LOG) - 1: + updateMessageBox(1) + def updateTranslationEngineAndEngineList(): engine = config.CHOICE_INPUT_TRANSLATOR engines = model.findTranslationEngines(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) @@ -352,8 +377,10 @@ def callbackSelectedTranslationEngine(selected_translation_engine): def callbackToggleTranslation(is_turned_on): config.ENABLE_TRANSLATION = is_turned_on if config.ENABLE_TRANSLATION is True: + model.changeTranslatorCTranslate2Model() view.printToTextbox_enableTranslation() else: + model.clearTranslatorCTranslate2Model() view.printToTextbox_disableTranslation() def callbackToggleTranscriptionSend(is_turned_on): @@ -505,8 +532,8 @@ def callbackSetUseTranslationFeature(value): def callbackSetCtranslate2WeightType(value): print("callbackSetCtranslate2WeightType", value) - config.WEIGHT_TYPE = str(value) - view.updateSelectedCtranslate2WeightType(config.WEIGHT_TYPE) + config.CTRANSLATE2_WEIGHT_TYPE = str(value) + view.updateSelectedCtranslate2WeightType(config.CTRANSLATE2_WEIGHT_TYPE) view.setWidgetsStatus_changeWeightType_Pending() if model.checkCTranslatorCTranslate2ModelWeight(): config.IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION = False @@ -767,6 +794,35 @@ def callbackSetSpeakerMaxPhrases(value): except Exception: view.showErrorMessage_SpeakerMaxPhrases() +# Transcription (Internal AI Model) +def callbackSetUserWhisperFeature(value): + print("callbackSetUserWhisperFeature", value) + config.USE_WHISPER_FEATURE = value + if config.USE_WHISPER_FEATURE is True: + view.openWhisperWeightTypeWidget() + if model.checkTranscriptionWhisperModelWeight() is True: + config.IS_RESET_BUTTON_DISPLAYED_FOR_WHISPER = False + config.SELECTED_TRANSCRIPTION_ENGINE = "Whisper" + else: + config.IS_RESET_BUTTON_DISPLAYED_FOR_WHISPER = True + config.SELECTED_TRANSCRIPTION_ENGINE = "Google" + else: + view.closeWhisperWeightTypeWidget() + config.IS_RESET_BUTTON_DISPLAYED_FOR_WHISPER = False + config.SELECTED_TRANSCRIPTION_ENGINE = "Google" + view.showRestartButtonIfRequired() + +def callbackSetWhisperWeightType(value): + print("callbackSetWhisperWeightType", value) + config.WHISPER_WEIGHT_TYPE = str(value) + view.updateSelectedWhisperWeightType(config.WHISPER_WEIGHT_TYPE) + if model.checkTranscriptionWhisperModelWeight() is True: + config.IS_RESET_BUTTON_DISPLAYED_FOR_WHISPER = False + config.SELECTED_TRANSCRIPTION_ENGINE = "Whisper" + else: + config.IS_RESET_BUTTON_DISPLAYED_FOR_WHISPER = True + config.SELECTED_TRANSCRIPTION_ENGINE = "Google" + view.showRestartButtonIfRequired() # Others Tab def callbackSetEnableAutoClearMessageBox(value): @@ -898,13 +954,18 @@ def createMainWindow(splash): # set Translation Engine updateTranslationEngineAndEngineList() + # set Transcription Engine + if config.USE_WHISPER_FEATURE is True: + config.SELECTED_TRANSCRIPTION_ENGINE = "Whisper" + else: + config.SELECTED_TRANSCRIPTION_ENGINE = "Google" + # set word filter model.addKeywords() # check Software Updated - if config.ENABLE_SPEAKER2CHATBOX is False: - if model.checkSoftwareUpdated() is True: - view.showUpdateAvailableButton() + if model.checkSoftwareUpdated() is True: + view.showUpdateAvailableButton() # init logger if config.ENABLE_LOGGER is True: @@ -949,6 +1010,8 @@ def createMainWindow(splash): "message_box_bind_Any_KeyPress": messageBoxPressKeyAny, "message_box_bind_FocusIn": messageBoxFocusIn, "message_box_bind_FocusOut": messageBoxFocusOut, + "message_box_bind_Up_KeyPress": messageBoxUpKeyPress, + "message_box_bind_Down_KeyPress": messageBoxDownKeyPress, }, config_window_registers={ @@ -993,6 +1056,10 @@ def createMainWindow(splash): "callback_set_speaker_phrase_timeout": callbackSetSpeakerPhraseTimeout, "callback_set_speaker_max_phrases": callbackSetSpeakerMaxPhrases, + # Transcription Tab (Internal AI Model) + "callback_set_use_whisper_feature": callbackSetUserWhisperFeature, + "callback_set_whisper_weight_type": callbackSetWhisperWeightType, + # Others Tab "callback_set_enable_auto_clear_chatbox": callbackSetEnableAutoClearMessageBox, "callback_set_send_only_translated_messages": callbackSetEnableSendOnlyTranslatedMessages, diff --git a/img/VRCT_now_downloading.png b/img/VRCT_now_downloading.png index 5e5b9daa..8e372d87 100644 Binary files a/img/VRCT_now_downloading.png and b/img/VRCT_now_downloading.png differ diff --git a/img/link_icon_black.png b/img/link_icon_black.png new file mode 100644 index 00000000..e4790914 Binary files /dev/null and b/img/link_icon_black.png differ diff --git a/img/link_icon_white.png b/img/link_icon_white.png new file mode 100644 index 00000000..58a56d45 Binary files /dev/null and b/img/link_icon_white.png differ diff --git a/install.bat b/install.bat index 036f6a51..8d2a5d51 100644 --- a/install.bat +++ b/install.bat @@ -1,4 +1,2 @@ python.exe -m pip install --upgrade pip -pip install -r requirements.txt -pip install git+https://github.com/misyaguziya/translators -pip install git+https://github.com/misyaguziya/custom_speech_recognition \ No newline at end of file +pip install -r requirements.txt \ No newline at end of file diff --git a/locales/en.yml b/locales/en.yml index 2806ea91..c885795f 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -79,9 +79,10 @@ config_window: transcription: Transcription transcription_mic: Mic transcription_speaker: Speaker + transcription_internal_model: Transcription Model others: Others others_send_message_formats: Message Formats (Send) - others_received_message_formats: Message Formats (Received) + others_received_message_formats: Message Formats (XSOverlay & Speaker2Chatbox) others_speaker2chatbox: Speaker2Chatbox advanced_settings: Advanced Settings @@ -128,6 +129,7 @@ config_window: deepl_auth_key: label: DeepL Auth Key desc: Please select %{translator} on the main screen with DeepL_API when using. ※Some languages may not be supported. + open_auth_key_webpage: Open DeepL Account Webpage mic_host: label: Mic Host/Driver @@ -187,6 +189,15 @@ config_window: desc: It is the lower limit for the number of transcribed words, and only when this number is exceeded will the transcription results be displayed logs. error_message: You can set a number equal to or greater than 0. + use_whisper_feature: + label: Use Whisper Model As Transcription + desc: In some languages, the accuracy of speech recognition may improve. During speech recognition usage, CPU usage increases, so please consider your PC specs before using this feature. + + whisper_weight_type: + label: Select Whisper Model + desc: "Generally, models with larger capacity tend to have higher accuracy, but this also results in longer transcription times and increased CPU usage. Please refer to the documentation for explanations of each model.\n※Larger models, especially those exceeding medium size, can be challenging to run even depending on the CPU's performance." + model_template: "%{model_name} model (%{capacity})" + recommended_model_template: "%{model_name} model (%{capacity}) (Recommended)" auto_clear_the_message_box: label: Auto Clear The Message Box @@ -217,25 +228,26 @@ config_window: label: Message Format desc: "You can change the decoration of the message you want to send.\n[message] will be replaced with the message." example_text: This is an example sentence. Fonts, line breaks, etc. may differ from the actual display. - error_message: "The characters '[message]' cannot be used." + error_message: "Cannot use the term '[message]'." send_message_format_with_t: - label: Message Format (With translation) + label: Message Format (with Translation) desc: "You can change the decoration of the message you want to send.\n[message] will be replaced with the message, and [translation] will be replaced with the translated message." example_text: This is an example sentence. Fonts, line breaks, etc. may differ from the actual display. - error_message: "The characters '[message]' and '[translation]' cannot be used." + error_message: "Cannot use the terms '[message]' and '[translation]'." received_message_format: - label: Message Format - desc: "You can change the decoration of the received message you want to send.\n[message] will be replaced with the message.\nIt will be used in Notification XSOverlay too." - example_text: This is an example sentence. Fonts, line breaks, etc. may differ from the actual display. - error_message: "The characters '[message]' cannot be used." + label: Format of Received Messages + desc: "Used for XSOverlay notification receiving feature.\n[message] will be replaced with the message. \n※It will be used in Speaker2Chatbox too." + example_text: This is an example sentence. Actual display may vary, including font and line breaks. + error_message: "Cannot use the term '[message]'." received_message_format_with_t: - label: Message Format (With translation) - desc: It will be used in Notification XSOverlay too. - example_text: This is an example sentence. Fonts, line breaks, etc. may differ from the actual display. - error_message: "The characters '[message]' and '[translation]' cannot be used." + label: Format of Received Messages (with Translation) + desc: "Used for XSOverlay notification receiving feature.\n[message] will be replaced with the message, and [translation] will be replaced with the translated message.\n※It will be used in Speaker2Chatbox too." + example_text: This is an example sentence. Actual display may vary, including font and line breaks. + error_message: "Cannot use the terms '[message]' and '[translation]'." + # Note: Speaker2Chatbox localization is fine only in English. Do not translate it into other languages. # Speaker2Chatbox diff --git a/locales/ja.yml b/locales/ja.yml index 6bedafbd..d7d800e1 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -79,7 +79,10 @@ config_window: transcription: 音声認識 transcription_mic: マイク transcription_speaker: スピーカー + transcription_internal_model: 音声認識モデル others: その他 + others_send_message_formats: メッセージフォーマット (送信) + others_received_message_formats: メッセージフォーマット (XSOverlay & Speaker2Chatbox) advanced_settings: 高度な設定 @@ -125,6 +128,7 @@ config_window: deepl_auth_key: label: DeepL 認証キー desc: "使用の際は、メイン画面にある %{translator} をDeepL_APIに変更してください。\n※対応していない言語もあります。" + open_auth_key_webpage: DeepLアカウントページを開く mic_host: label: マイク(ホスト/ドライバー) @@ -184,6 +188,16 @@ config_window: desc: 文字起こしされた単語数の下限値で、この数値を超えた場合のみ結果をログに表示します。 error_message: 0以上の数値を設定できます。 + use_whisper_feature: + label: 音声認識にWhisperモデルを使用する + desc: 一部の言語では、音声認識の精度が向上するかもしれません。音声認識使用中、CPUの使用率が上がるので、お使いのPCスペックと相談してこの機能を使用してください。 + + whisper_weight_type: + label: Whisperモデルのタイプ + desc: "基本的に、容量が多いモデルほど精度は高いですが、文字起こしまでの時間が伸び、CPU使用率も増加します。各モデルの説明はドキュメントをご覧ください。\n※特にmediumより容量の大きいモデルは、CPUの性能によっては使用すらも困難です。" + model_template: "%{model_name} モデル (%{capacity})" + recommended_model_template: "%{model_name} モデル (%{capacity}) (推奨)" + auto_clear_the_message_box: label: 送信後はチャットボックスを空にする @@ -223,14 +237,14 @@ config_window: error_message: "[message]と[translation]という文字は使えません。" received_message_format: - label: 送信するメッセージのフォーマット - desc: "VRChatで相手に実際に見えるフォーマットを変更できます。\n[message]がメッセージに置換されます。\n※XSOverlayでの通知受け取り機能にも使われます。" + label: 受信するメッセージのフォーマット + desc: "XSOverlay通知受け取り機能で使用されます。\n[message]がメッセージに置換されます。\n※Speaker2Chatboxでの送信機能にも使われます。" example_text: これは例文です。フォントや改行箇所など、実際の表示とは異なる場合があります。 error_message: "[message]という文字は使えません。" received_message_format_with_t: - label: 送信するメッセージのフォーマット(翻訳付き) - desc: "VRChatで相手に実際に見えるフォーマットを変更できます。\n[message]がメッセージに置換され、[translation]が翻訳されたメッセージに置換されます。\n※XSOverlayでの通知受け取り機能にも使われます。" + label: 受信するメッセージのフォーマット(翻訳付き) + desc: "XSOverlay通知受け取り機能で使用されます。\n[message]がメッセージに置換され、[translation]が翻訳されたメッセージに置換されます。\n※Speaker2Chatboxでの送信機能にも使われます。" example_text: これは例文です。フォントや改行箇所など、実際の表示とは異なる場合があります。 error_message: "[message]と[translation]という文字は使えません。" diff --git a/main.py b/main.py index 4810cbe5..257a3a63 100644 --- a/main.py +++ b/main.py @@ -8,11 +8,20 @@ if __name__ == "__main__": splash.showSplash() from config import config - from models.translation.utils import downloadCTranslate2Weight + # version 2.2.0からweightフォルダをweightsに変更する + from utils import renameWeightFolder + renameWeightFolder(config.PATH_LOCAL) + + from models.translation.translation_utils import downloadCTranslate2Weight if config.USE_TRANSLATION_FEATURE is True: - downloadCTranslate2Weight(config.PATH_LOCAL, config.WEIGHT_TYPE, splash.updateDownloadProgress) + downloadCTranslate2Weight(config.PATH_LOCAL, config.CTRANSLATE2_WEIGHT_TYPE, splash.updateDownloadProgress) + + from models.transcription.transcription_whisper import downloadWhisperWeight + if config.USE_WHISPER_FEATURE is True: + downloadWhisperWeight(config.PATH_LOCAL, config.WHISPER_WEIGHT_TYPE, splash.updateDownloadProgress) splash.toProgress(0) + import controller controller.createMainWindow(splash) splash.destroySplash() diff --git a/model.py b/model.py index 573659a7..e39b718f 100644 --- a/model.py +++ b/model.py @@ -1,3 +1,4 @@ +import gc import tempfile from zipfile import ZipFile from subprocess import Popen @@ -17,13 +18,14 @@ from flashtext import KeywordProcessor from models.translation.translation_translator import Translator from models.transcription.transcription_utils import getInputDevices, getDefaultOutputDevice from models.osc.osc_tools import sendTyping, sendMessage, sendTestAction, receiveOscParameters -from models.transcription.transcription_recorder import SelectedMicRecorder, SelectedSpeakerRecorder -from models.transcription.transcription_recorder import SelectedMicEnergyRecorder, SelectedSpeakeEnergyRecorder +from models.transcription.transcription_recorder import SelectedMicEnergyAndAudioRecorder, SelectedSpeakerEnergyAndAudioRecorder +from models.transcription.transcription_recorder import SelectedMicEnergyRecorder, SelectedSpeakerEnergyRecorder from models.transcription.transcription_transcriber import AudioTranscriber from models.xsoverlay.notification import xsoverlayForVRCT from models.translation.translation_languages import translation_lang from models.transcription.transcription_languages import transcription_lang -from models.translation.utils import checkCTranslate2Weight +from models.translation.translation_utils import checkCTranslate2Weight +from models.transcription.transcription_whisper import checkWhisperWeight from config import config class threadFnc(Thread): @@ -64,15 +66,19 @@ class Model: self.speaker_energy_recorder = None self.speaker_energy_plot_progressbar = None self.translator = Translator() - if config.USE_TRANSLATION_FEATURE is True: - self.translator.changeCTranslate2Model(config.PATH_LOCAL, config.WEIGHT_TYPE) self.keyword_processor = KeywordProcessor() def checkCTranslatorCTranslate2ModelWeight(self): - return checkCTranslate2Weight(config.PATH_LOCAL, config.WEIGHT_TYPE) + return checkCTranslate2Weight(config.PATH_LOCAL, config.CTRANSLATE2_WEIGHT_TYPE) def changeTranslatorCTranslate2Model(self): - self.translator.changeCTranslate2Model(config.PATH_LOCAL, config.WEIGHT_TYPE) + self.translator.changeCTranslate2Model(config.PATH_LOCAL, config.CTRANSLATE2_WEIGHT_TYPE) + + def clearTranslatorCTranslate2Model(self): + self.translator.clearCTranslate2Model() + + def checkTranscriptionWhisperModelWeight(self): + return checkWhisperWeight(config.PATH_LOCAL, config.WHISPER_WEIGHT_TYPE) def resetKeywordProcessor(self): del self.keyword_processor @@ -317,44 +323,72 @@ class Model: return mic_audio_queue = Queue() + # mic_energy_queue = Queue() device = [device for device in getInputDevices()[config.CHOICE_MIC_HOST] if device["name"] == config.CHOICE_MIC_DEVICE][0] record_timeout = config.INPUT_MIC_RECORD_TIMEOUT phase_timeout = config.INPUT_MIC_PHRASE_TIMEOUT if record_timeout > phase_timeout: record_timeout = phase_timeout - self.mic_audio_recorder = SelectedMicRecorder( + self.mic_audio_recorder = SelectedMicEnergyAndAudioRecorder( device=device, energy_threshold=config.INPUT_MIC_ENERGY_THRESHOLD, dynamic_energy_threshold=config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD, record_timeout=record_timeout, ) - self.mic_audio_recorder.recordIntoQueue(mic_audio_queue) - mic_transcriber = AudioTranscriber( + # self.mic_audio_recorder.recordIntoQueue(mic_audio_queue, mic_energy_queue) + self.mic_audio_recorder.recordIntoQueue(mic_audio_queue, None) + self.mic_transcriber = AudioTranscriber( speaker=False, source=self.mic_audio_recorder.source, phrase_timeout=phase_timeout, max_phrases=config.INPUT_MIC_MAX_PHRASES, + transcription_engine=config.SELECTED_TRANSCRIPTION_ENGINE, + root=config.PATH_LOCAL, + whisper_weight_type=config.WHISPER_WEIGHT_TYPE, ) def sendMicTranscript(): - mic_transcriber.transcribeAudioQueue(mic_audio_queue, config.SOURCE_LANGUAGE, config.SOURCE_COUNTRY) - message = mic_transcriber.getTranscript() + self.mic_transcriber.transcribeAudioQueue(mic_audio_queue, config.SOURCE_LANGUAGE, config.SOURCE_COUNTRY) + message = self.mic_transcriber.getTranscript() try: fnc(message) except Exception: pass - self.mic_print_transcript = threadFnc(sendMicTranscript) + def endMicTranscript(): + mic_audio_queue.queue.clear() + # mic_energy_queue.queue.clear() + del self.mic_transcriber + gc.collect() + + # def sendMicEnergy(): + # if mic_energy_queue.empty() is False: + # energy = mic_energy_queue.get() + # # print("mic energy:", energy) + # try: + # fnc(energy) + # except Exception: + # pass + # sleep(0.01) + + self.mic_print_transcript = threadFnc(sendMicTranscript, end_fnc=endMicTranscript) self.mic_print_transcript.daemon = True self.mic_print_transcript.start() + # self.mic_get_energy = threadFnc(sendMicEnergy) + # self.mic_get_energy.daemon = True + # self.mic_get_energy.start() + def stopMicTranscript(self): if isinstance(self.mic_print_transcript, threadFnc): self.mic_print_transcript.stop() self.mic_print_transcript = None - if isinstance(self.mic_audio_recorder, SelectedMicRecorder): + if isinstance(self.mic_audio_recorder, SelectedMicEnergyAndAudioRecorder): self.mic_audio_recorder.stop() self.mic_audio_recorder = None + # if isinstance(self.mic_get_energy, threadFnc): + # self.mic_get_energy.stop() + # self.mic_get_energy = None def startCheckMicEnergy(self, fnc, end_fnc, error_fnc=None): if config.CHOICE_MIC_HOST == "NoHost" or config.CHOICE_MIC_DEVICE == "NoDevice": @@ -399,43 +433,71 @@ class Model: return speaker_audio_queue = Queue() + # speaker_energy_queue = Queue() record_timeout = config.INPUT_SPEAKER_RECORD_TIMEOUT phase_timeout = config.INPUT_SPEAKER_PHRASE_TIMEOUT if record_timeout > phase_timeout: record_timeout = phase_timeout - self.speaker_audio_recorder = SelectedSpeakerRecorder( + self.speaker_audio_recorder = SelectedSpeakerEnergyAndAudioRecorder( device=speaker_device, energy_threshold=config.INPUT_SPEAKER_ENERGY_THRESHOLD, dynamic_energy_threshold=config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, record_timeout=record_timeout, ) - self.speaker_audio_recorder.recordIntoQueue(speaker_audio_queue) - speaker_transcriber = AudioTranscriber( + # self.speaker_audio_recorder.recordIntoQueue(speaker_audio_queue, speaker_energy_queue) + self.speaker_audio_recorder.recordIntoQueue(speaker_audio_queue ,None) + self.speaker_transcriber = AudioTranscriber( speaker=True, source=self.speaker_audio_recorder.source, phrase_timeout=phase_timeout, max_phrases=config.INPUT_SPEAKER_MAX_PHRASES, + transcription_engine=config.SELECTED_TRANSCRIPTION_ENGINE, + root=config.PATH_LOCAL, + whisper_weight_type=config.WHISPER_WEIGHT_TYPE, ) def sendSpeakerTranscript(): - speaker_transcriber.transcribeAudioQueue(speaker_audio_queue, config.TARGET_LANGUAGE, config.TARGET_COUNTRY) - message = speaker_transcriber.getTranscript() + self.speaker_transcriber.transcribeAudioQueue(speaker_audio_queue, config.TARGET_LANGUAGE, config.TARGET_COUNTRY) + message = self.speaker_transcriber.getTranscript() try: fnc(message) except Exception: pass - self.speaker_print_transcript = threadFnc(sendSpeakerTranscript) + def endSpeakerTranscript(): + speaker_audio_queue.queue.clear() + # speaker_energy_queue.queue.clear() + del self.speaker_transcriber + gc.collect() + + # def sendSpeakerEnergy(): + # if speaker_energy_queue.empty() is False: + # energy = speaker_energy_queue.get() + # # print("speaker energy:", energy) + # try: + # fnc(energy) + # except Exception: + # pass + # sleep(0.01) + + self.speaker_print_transcript = threadFnc(sendSpeakerTranscript, end_fnc=endSpeakerTranscript) self.speaker_print_transcript.daemon = True self.speaker_print_transcript.start() + # self.speaker_get_energy = threadFnc(sendSpeakerEnergy) + # self.speaker_get_energy.daemon = True + # self.speaker_get_energy.start() + def stopSpeakerTranscript(self): if isinstance(self.speaker_print_transcript, threadFnc): self.speaker_print_transcript.stop() self.speaker_print_transcript = None - if isinstance(self.speaker_audio_recorder, SelectedSpeakerRecorder): + if isinstance(self.speaker_audio_recorder, SelectedSpeakerEnergyAndAudioRecorder): self.speaker_audio_recorder.stop() self.speaker_audio_recorder = None + # if isinstance(self.speaker_get_energy, threadFnc): + # self.speaker_get_energy.stop() + # self.speaker_get_energy = None def startCheckSpeakerEnergy(self, fnc, end_fnc, error_fnc=None): speaker_device = getDefaultOutputDevice() @@ -456,7 +518,7 @@ class Model: sleep(0.01) speaker_energy_queue = Queue() - self.speaker_energy_recorder = SelectedSpeakeEnergyRecorder(speaker_device) + self.speaker_energy_recorder = SelectedSpeakerEnergyRecorder(speaker_device) self.speaker_energy_recorder.recordIntoQueue(speaker_energy_queue) self.speaker_energy_plot_progressbar = threadFnc(sendSpeakerEnergy, end_fnc=end_fnc) self.speaker_energy_plot_progressbar.daemon = True @@ -466,7 +528,7 @@ class Model: if isinstance(self.speaker_energy_plot_progressbar, threadFnc): self.speaker_energy_plot_progressbar.stop() self.speaker_energy_plot_progressbar = None - if isinstance(self.speaker_energy_recorder, SelectedSpeakeEnergyRecorder): + if isinstance(self.speaker_energy_recorder, SelectedSpeakerEnergyRecorder): self.speaker_energy_recorder.stop() self.speaker_energy_recorder = None diff --git a/models/osc/osc_tools.py b/models/osc/osc_tools.py index 80f2b785..51608f66 100644 --- a/models/osc/osc_tools.py +++ b/models/osc/osc_tools.py @@ -1,8 +1,11 @@ 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.utility import get_open_udp_port, get_open_tcp_port # send OSC message typing def sendTyping(flag=False, ip_address="127.0.0.1", port=9000): @@ -45,12 +48,40 @@ def sendChangeVoice(ip_address="127.0.0.1", port=9000): sendInputVoice(flag=0, ip_address=ip_address, port=port) sleep(0.05) -def receiveOscParameters(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) - return server + +def receiveOscParameters(dict_filter_and_target, ip_address="127.0.0.1", title="VRCT"): + osc_port = get_open_udp_port() + http_port = get_open_tcp_port() + 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) if __name__ == "__main__": - sendChangeVoice() - sendChangeVoice() \ No newline at end of file + osc_parameter_prefix = "/avatar/parameters/" + osc_avatar_change_path = "/avatar/change" + param_MuteSelf = "MuteSelf" + param_Voice = "Voice" + + def print_handler_all(address, *args): + print(f"all {address}: {args}") + + def print_handler_muteself(address, *args): + print(f"muteself {address}: {args}") + + def print_handler_voice(address, *args): + print(f"voice {address}: {args}") + + dict_filter_and_target = { + # osc_parameter_prefix + "*": print_handler_all, + osc_parameter_prefix + param_MuteSelf: print_handler_muteself, + osc_parameter_prefix + param_Voice: print_handler_voice, + } + + receiveOscParameters(dict_filter_and_target) \ No newline at end of file diff --git a/models/transcription/transcription_languages.py b/models/transcription/transcription_languages.py index 26f2c3f6..63d92568 100644 --- a/models/transcription/transcription_languages.py +++ b/models/transcription/transcription_languages.py @@ -1,177 +1,438 @@ transcription_lang = { "Afrikaans":{ - "South Africa":"af-ZA", + "South Africa":{ + "Google": "af-ZA", + "Whisper": "af", + }, }, "Arabic":{ - "Algeria":"ar-DZ", - "Bahrain":"ar-BH", - "Egypt":"ar-EG", - "Israel":"ar-IL", - "Iraq":"ar-IQ", - "Jordan":"ar-JO", - "Kuwait":"ar-KW", - "Lebanon":"ar-LB", - "Morocco":"ar-MA", - "Oman":"ar-OM", - "State of Palestine":"ar-PS", - "Qatar":"ar-QA", - "Saudi Arabia":"ar-SA", - "Tunisia":"ar-TN", - "United Arab Emirates":"ar-AE", + "Algeria":{ + "Google": "ar-DZ", + "Whisper": "ar", + }, + "Bahrain":{ + "Google": "ar-BH", + "Whisper": "ar", + }, + "Egypt":{ + "Google": "ar-EG", + "Whisper": "ar", + }, + "Israel":{ + "Google": "ar-IL", + "Whisper": "ar", + }, + "Iraq":{ + "Google": "ar-IQ", + "Whisper": "ar", + }, + "Jordan":{ + "Google": "ar-JO", + "Whisper": "ar", + }, + "Kuwait":{ + "Google": "ar-KW", + "Whisper": "ar", + }, + "Lebanon":{ + "Google": "ar-LB", + "Whisper": "ar", + }, + "Morocco":{ + "Google": "ar-MA", + "Whisper": "ar", + }, + "Oman":{ + "Google": "ar-OM", + "Whisper": "ar", + }, + "State of Palestine":{ + "Google": "ar-PS", + "Whisper": "ar", + }, + "Qatar":{ + "Google": "ar-QA", + "Whisper": "ar", + }, + "Saudi Arabia":{ + "Google": "ar-SA", + "Whisper": "ar", + }, + "Tunisia":{ + "Google": "ar-TN", + "Whisper": "ar", + }, + "United Arab Emirates":{ + "Google": "ar-AE", + "Whisper": "ar", + }, }, "Basque":{ - "Spain":"eu-ES", + "Spain":{ + "Google": "eu-ES", + "Whisper": "eu", + }, }, "Bulgarian":{ - "Bulgaria":"bg-BG", + "Bulgaria":{ + "Google": "bg-BG", + "Whisper": "bg", + }, }, "Catalan":{ - "Spain":"ca-ES", + "Spain":{ + "Google": "ca-ES", + "Whisper": "ca", + }, }, "Chinese":{ - "Mandarin (Simplified, China)":"cmn-Hans-CN", - "Mandarin (Simplified, Hong Kong)":"cmn-Hans-HK", - "Mandarin (Traditional, Taiwan)":"cmn-Hant-TW", - "Cantonese (Traditional Hong Kong)":"yue-Hant-HK", + "Mandarin (Simplified, China)":{ + "Google": "cmn-Hans-CN", + "Whisper": "zh", + }, + "Mandarin (Simplified, Hong Kong)":{ + "Google": "cmn-Hans-HK", + "Whisper": "zh", + }, + "Mandarin (Traditional, Taiwan)":{ + "Google": "cmn-Hant-TW", + "Whisper": "zh", + }, + "Cantonese (Traditional Hong Kong)":{ + "Google": "yue-Hant-HK", + "Whisper": "yue", + }, }, "Croatian":{ - "Croatia":"hr-HR", + "Croatia":{ + "Google": "hr-HR", + "Whisper": "hr", + }, }, "Czech":{ - "Czech Republic":"cs-CZ", + "Czech Republic":{ + "Google": "cs-CZ", + "Whisper": "cs", + }, }, "Danish":{ - "Denmark":"da-DK", + "Denmark":{ + "Google": "da-DK", + "Whisper": "da", + }, }, "Dutch":{ - "Netherlands":"nl-NL", + "Netherlands":{ + "Google": "nl-NL", + "Whisper": "nl", + }, }, "English": { - "United States":"en-US", - "United Kingdom":"en-GB", - "Australia":"en-AU", - "Canada":"en-CA", - "India":"en-IN", - "Ireland":"en-IE", - "New Zealand":"en-NZ", - "Philippines":"en-PH", - "South Africa":"en-ZA", + "United States":{ + "Google": "en-US", + "Whisper": "en", + }, + "United Kingdom":{ + "Google": "en-GB", + "Whisper": "en", + }, + "Australia":{ + "Google": "en-AU", + "Whisper": "en", + }, + "Canada":{ + "Google": "en-CA", + "Whisper": "en", + }, + "India":{ + "Google": "en-IN", + "Whisper": "en", + }, + "Ireland":{ + "Google": "en-IE", + "Whisper": "en", + }, + "New Zealand":{ + "Google": "en-NZ", + "Whisper": "en", + }, + "Philippines":{ + "Google": "en-PH", + "Whisper": "en", + }, + "South Africa":{ + "Google": "en-ZA", + "Whisper": "en", + }, }, "Filipino":{ - "Philippines":"fil-PH", + "Philippines":{ + "Google": "fil-PH", + "Whisper": "tl", + }, }, "Finnish":{ - "Finland":"fi-FI", + "Finland":{ + "Google": "fi-FI", + "Whisper": "fi", + }, }, "French":{ - "France":"fr-FR", + "France":{ + "Google": "fr-FR", + "Whisper": "fr", + }, }, "Galician":{ - "Spain":"gl-ES", + "Spain":{ + "Google": "gl-ES", + "Whisper": "gl", + }, }, "German":{ - "Germany":"de-DE", + "Germany":{ + "Google": "de-DE", + "Whisper": "de", + }, }, "Greek":{ - "Greece":"el-GR", + "Greece":{ + "Google": "el-GR", + "Whisper": "el", + }, }, "Hebrew":{ - "Israel":"he-IL", + "Israel":{ + "Google": "he-IL", + "Whisper": "he", + }, }, "Hindi": { - "India":"hi-IN", + "India":{ + "Google": "hi-IN", + "Whisper": "hi", + }, }, "Hungarian":{ - "Hungary":"hu-HU", + "Hungary":{ + "Google": "hu-HU", + "Whisper": "hu", + }, }, "Indonesian":{ - "Indonesia":"id-ID", + "Indonesia":{ + "Google": "id-ID", + "Whisper": "id", + }, }, "Icelandic":{ - "Iceland":"is-IS", + "Iceland":{ + "Google": "is-IS", + "Whisper": "is", + }, }, "Italian":{ - "Italy":"it-IT", - "Switzerland":"it-CH", + "Italy":{ + "Google": "it-IT", + "Whisper": "it", + }, + "Switzerland":{ + "Google": "it-CH", + "Whisper": "it", + }, }, "Japanese":{ - "Japan":"ja-JP", + "Japan":{ + "Google": "ja-JP", + "Whisper": "ja", + }, }, "Korean":{ - "South Korea":"ko-KR", + "South Korea":{ + "Google": "ko-KR", + "Whisper": "ko", + }, }, "Lithuanian":{ - "Lithuania":"lt-LT", + "Lithuania":{ + "Google": "lt-LT", + "Whisper": "lt", + }, }, "Malay":{ - "Malaysia":"ms-MY", + "Malaysia":{ + "Google": "ms-MY", + "Whisper": "ms", + }, }, "Norwegian":{ - "Norway":"nb-NO", + "Norway":{ + "Google": "nb-NO", + "Whisper": "no", + }, }, "Persian":{ - "Iran":"fa-IR", + "Iran":{ + "Google": "fa-IR", + "Whisper": "fa", + }, }, "Polish":{ - "Poland":"pl-PL", + "Poland":{ + "Google": "pl-PL", + "Whisper": "pl", + }, }, "Portuguese":{ - "Brazil":"pt-BR", - "Portugal":"pt-PT", + "Brazil":{ + "Google": "pt-BR", + "Whisper": "pt", + }, + "Portugal":{ + "Google": "pt-PT", + "Whisper": "pt", + }, }, "Romanian":{ - "Romania":"ro-RO", + "Romania":{ + "Google": "ro-RO", + "Whisper": "ro", + }, }, "Russian":{ - "Russia":"ru-RU", + "Russia":{ + "Google": "ru-RU", + "Whisper": "ru", + }, }, "Serbian":{ - "Serbia":"sr-RS", + "Serbia":{ + "Google": "sr-RS", + "Whisper": "sr", + }, }, "Slovak":{ - "Slovakia":"sk-SK", + "Slovakia":{ + "Google": "sk-SK", + "Whisper": "sk", + }, }, "Slovenian":{ - "Slovenia":"sl-SI", + "Slovenia":{ + "Google": "sl-SI", + "Whisper": "sl", + }, }, "Spanish":{ - "Argentina":"es-AR", - "Bolivia":"es-BO", - "Chile":"es-CL", - "Colombia":"es-CO", - "Costa Rica":"es-CR", - "Dominican Republic":"es-DO", - "Ecuador":"es-EC", - "El Salvador":"es-SV", - "Guatemala":"es-GT", - "Honduras":"es-HN", - "Mexico":"es-MX", - "Nicaragua":"es-NI", - "Panama":"es-PA", - "Paraguay":"es-PY", - "Peru":"es-PE", - "Puerto Rico":"es-PR", - "Spain":"es-ES", - "Uruguay":"es-UY", - "United States":"es-US", - "Venezuela":"es-VE", + "Argentina":{ + "Google": "es-AR", + "Whisper": "es", + }, + "Bolivia":{ + "Google": "es-BO", + "Whisper": "es", + }, + "Chile":{ + "Google": "es-CL", + "Whisper": "es", + }, + "Colombia":{ + "Google": "es-CO", + "Whisper": "es", + }, + "Costa Rica":{ + "Google": "es-CR", + "Whisper": "es", + }, + "Dominican Republic":{ + "Google": "es-DO", + "Whisper": "es", + }, + "Ecuador":{ + "Google": "es-EC", + "Whisper": "es", + }, + "El Salvador":{ + "Google": "es-SV", + "Whisper": "es", + }, + "Guatemala":{ + "Google": "es-GT", + "Whisper": "es", + }, + "Honduras":{ + "Google": "es-HN", + "Whisper": "es", + }, + "Mexico":{ + "Google": "es-MX", + "Whisper": "es", + }, + "Nicaragua":{ + "Google": "es-NI", + "Whisper": "es", + }, + "Panama":{ + "Google": "es-PA", + "Whisper": "es", + }, + "Paraguay":{ + "Google": "es-PY", + "Whisper": "es", + }, + "Peru":{ + "Google": "es-PE", + "Whisper": "es", + }, + "Puerto Rico":{ + "Google": "es-PR", + "Whisper": "es", + }, + "Spain":{ + "Google": "es-ES", + "Whisper": "es", + }, + "Uruguay":{ + "Google": "es-UY", + "Whisper": "es", + }, + "United States":{ + "Google": "es-US", + "Whisper": "es", + }, + "Venezuela":{ + "Google": "es-VE", + "Whisper": "es", + }, }, "Swedish":{ - "Sweden":"sv-SE", + "Sweden":{ + "Google": "sv-SE", + "Whisper": "sv", + }, }, "Thai":{ - "Thailand":"th-TH", + "Thailand":{ + "Google": "th-TH", + "Whisper": "th", + }, }, "Turkish":{ - "Turkey":"tr-TR", + "Turkey":{ + "Google": "tr-TR", + "Whisper": "tr", + }, }, "Ukrainian":{ - "Ukraine":"uk-UA", + "Ukraine":{ + "Google": "uk-UA", + "Whisper": "uk", + }, }, "Vietnamese":{ - "Vietnam":"vi-VN", - }, - "Zulu":{ - "South Africa":"zu-ZA" + "Vietnam":{ + "Google": "vi-VN", + "Whisper": "vi", + }, }, } \ No newline at end of file diff --git a/models/transcription/transcription_recorder.py b/models/transcription/transcription_recorder.py index 9abe5eb4..0128a37b 100644 --- a/models/transcription/transcription_recorder.py +++ b/models/transcription/transcription_recorder.py @@ -1,6 +1,7 @@ from speech_recognition import Recognizer, Microphone from pyaudiowpatch import get_sample_size, paInt16 from datetime import datetime +from queue import Queue class BaseRecorder: def __init__(self, source, energy_threshold, dynamic_energy_threshold, record_timeout): @@ -78,7 +79,7 @@ class SelectedMicEnergyRecorder(BaseEnergyRecorder): super().__init__(source=source) # self.adjustForNoise() -class SelectedSpeakeEnergyRecorder(BaseEnergyRecorder): +class SelectedSpeakerEnergyRecorder(BaseEnergyRecorder): def __init__(self, device): source = Microphone(speaker=True, @@ -87,4 +88,54 @@ class SelectedSpeakeEnergyRecorder(BaseEnergyRecorder): channels=device["maxInputChannels"] ) super().__init__(source=source) + # self.adjustForNoise() + +class BaseEnergyAndAudioRecorder: + def __init__(self, source, energy_threshold, dynamic_energy_threshold, record_timeout): + self.recorder = Recognizer() + self.recorder.energy_threshold = energy_threshold + self.recorder.dynamic_energy_threshold = dynamic_energy_threshold + self.record_timeout = record_timeout + self.stop = None + + if source is None: + raise ValueError("audio source can't be None") + + self.source = source + + def adjustForNoise(self): + with self.source: + self.recorder.adjust_for_ambient_noise(self.source) + + def recordIntoQueue(self, audio_queue, energy_queue): + 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) + +class SelectedMicEnergyAndAudioRecorder(BaseEnergyAndAudioRecorder): + def __init__(self, device, energy_threshold, dynamic_energy_threshold, record_timeout): + source=Microphone( + device_index=device['index'], + sample_rate=int(device["defaultSampleRate"]), + ) + super().__init__(source=source, energy_threshold=energy_threshold, dynamic_energy_threshold=dynamic_energy_threshold, record_timeout=record_timeout) + # self.adjustForNoise() + +class SelectedSpeakerEnergyAndAudioRecorder(BaseEnergyAndAudioRecorder): + def __init__(self, device, energy_threshold, dynamic_energy_threshold, record_timeout): + + source = Microphone(speaker=True, + device_index= device["index"], + sample_rate=int(device["defaultSampleRate"]), + chunk_size=get_sample_size(paInt16), + channels=device["maxInputChannels"] + ) + super().__init__(source=source, energy_threshold=energy_threshold, dynamic_energy_threshold=dynamic_energy_threshold, record_timeout=record_timeout) # self.adjustForNoise() \ No newline at end of file diff --git a/models/transcription/transcription_transcriber.py b/models/transcription/transcription_transcriber.py index bf78566e..c5a6cbff 100644 --- a/models/transcription/transcription_transcriber.py +++ b/models/transcription/transcription_transcriber.py @@ -5,18 +5,24 @@ from speech_recognition import Recognizer, AudioData, AudioFile from datetime import timedelta from pyaudiowpatch import get_sample_size, paInt16 from .transcription_languages import transcription_lang +from .transcription_whisper import getWhisperModel, checkWhisperWeight + +import torch +import numpy as np PHRASE_TIMEOUT = 3 MAX_PHRASES = 10 class AudioTranscriber: - def __init__(self, speaker, source, phrase_timeout, max_phrases): + def __init__(self, speaker, source, phrase_timeout, max_phrases, transcription_engine, root=None, whisper_weight_type=None): self.speaker = speaker self.phrase_timeout = phrase_timeout self.max_phrases = max_phrases self.transcript_data = [] self.transcript_changed_event = Event() self.audio_recognizer = Recognizer() + self.transcription_engine = "Google" + self.whisper_model = None self.audio_sources = { "sample_rate": source.SAMPLE_RATE, "sample_width": source.SAMPLE_WIDTH, @@ -27,22 +33,45 @@ class AudioTranscriber: "process_data_func": self.processSpeakerData if speaker else self.processSpeakerData } + if transcription_engine == "Whisper" and checkWhisperWeight(root, whisper_weight_type) is True: + self.whisper_model = getWhisperModel(root, whisper_weight_type) + self.transcription_engine = "Whisper" + def transcribeAudioQueue(self, audio_queue, language, country): - # while True: audio, time_spoken = audio_queue.get() self.updateLastSampleAndPhraseStatus(audio, time_spoken) text = '' try: - # fd, path = tempfile.mkstemp(suffix=".wav") - # os.close(fd) audio_data = self.audio_sources["process_data_func"]() - text = self.audio_recognizer.recognize_google(audio_data, language=transcription_lang[language][country]) + match self.transcription_engine: + case "Google": + text = self.audio_recognizer.recognize_google(audio_data, language=transcription_lang[language][country][self.transcription_engine]) + case "Whisper": + audio_data = np.frombuffer(audio_data.get_raw_data(convert_rate=16000, convert_width=2), np.int16).flatten().astype(np.float32) / 32768.0 + if isinstance(audio_data, torch.Tensor): + audio_data = audio_data.detach().numpy() + segments, _ = self.whisper_model.transcribe( + audio_data, + beam_size=5, + temperature=0.0, + log_prob_threshold=-0.8, + no_speech_threshold=0.6, + language=transcription_lang[language][country][self.transcription_engine], + word_timestamps=False, + without_timestamps=True, + task="transcribe", + vad_filter=False, + ) + for s in segments: + if s.avg_logprob < -0.8 or s.no_speech_prob > 0.6: + continue + text += s.text + except Exception: pass finally: pass - # os.unlink(path) if text != '': self.updateTranscript(text) diff --git a/models/transcription/transcription_whisper.py b/models/transcription/transcription_whisper.py new file mode 100644 index 00000000..c6412d35 --- /dev/null +++ b/models/transcription/transcription_whisper.py @@ -0,0 +1,98 @@ +from os import path as os_path, makedirs as os_makedirs +from requests import get as requests_get +from typing import Callable +import huggingface_hub +from faster_whisper import WhisperModel +import logging +logger = logging.getLogger('faster_whisper') +logger.setLevel(logging.CRITICAL) + +_MODELS = { + "tiny": "Systran/faster-whisper-tiny", + "base": "Systran/faster-whisper-base", + "small": "Systran/faster-whisper-small", + "medium": "Systran/faster-whisper-medium", + "large-v1": "Systran/faster-whisper-large-v1", + "large-v2": "Systran/faster-whisper-large-v2", + "large-v3": "Systran/faster-whisper-large-v3", +} + +_FILENAMES = [ + "config.json", + "preprocessor_config.json", + "model.bin", + "tokenizer.json", + "vocabulary.txt", + "vocabulary.json", +] + +def downloadFile(url, path, func=None): + try: + res = requests_get(url, stream=True) + res.raise_for_status() + file_size = int(res.headers.get('content-length', 0)) + total_chunk = 0 + with open(os_path.join(path), 'wb') as file: + for chunk in res.iter_content(chunk_size=1024*5): + file.write(chunk) + if isinstance(func, Callable): + total_chunk += len(chunk) + func(total_chunk/file_size) + + except Exception as e: + print("error:downloadFile()", e) + +def checkWhisperWeight(root, weight_type): + path = os_path.join(root, "weights", "whisper", weight_type) + result = False + try: + WhisperModel( + path, + device="cpu", + device_index=0, + compute_type="int8", + cpu_threads=4, + num_workers=1, + local_files_only=True, + ) + result = True + except Exception: + pass + return result + +def downloadWhisperWeight(root, weight_type, callbackFunc): + path = os_path.join(root, "weights", "whisper", weight_type) + os_makedirs(path, exist_ok=True) + if checkWhisperWeight(root, weight_type) is True: + return + + for filename in _FILENAMES: + print("Downloading", filename, "...") + file_path = os_path.join(path, filename) + url = huggingface_hub.hf_hub_url(_MODELS[weight_type], filename) + downloadFile(url, file_path, func=callbackFunc) + +def getWhisperModel(root, weight_type): + path = os_path.join(root, "weights", "whisper", weight_type) + return WhisperModel( + path, + device="cpu", + device_index=0, + compute_type="int8", + cpu_threads=4, + num_workers=1, + local_files_only=True, + ) + +if __name__ == "__main__": + def callback(value): + print(value) + pass + + downloadWhisperWeight("./", "tiny", callback) + downloadWhisperWeight("./", "base", callback) + downloadWhisperWeight("./", "small", callback) + downloadWhisperWeight("./", "medium", callback) + downloadWhisperWeight("./", "large-v1", callback) + downloadWhisperWeight("./", "large-v2", callback) + downloadWhisperWeight("./", "large-v3", callback) \ No newline at end of file diff --git a/models/translation/translation_translator.py b/models/translation/translation_translator.py index f3d3c99e..a71d0f55 100644 --- a/models/translation/translation_translator.py +++ b/models/translation/translation_translator.py @@ -1,8 +1,9 @@ +import gc import os from deepl import Translator as deepl_Translator from translators import translate_text as other_web_Translator from .translation_languages import translation_lang -from .utils import ctranslate2_weights +from .translation_utils import ctranslate2_weights import ctranslate2 import transformers @@ -27,7 +28,8 @@ class Translator(): def changeCTranslate2Model(self, path, model_type): directory_name = ctranslate2_weights[model_type]["directory_name"] tokenizer = ctranslate2_weights[model_type]["tokenizer"] - weight_path = os.path.join(path, "weight", directory_name) + weight_path = os.path.join(path, "weights", "ctranslate2", directory_name) + tokenizer_path = os.path.join(path, "weights", "ctranslate2", directory_name, "tokenizer") self.ctranslate2_translator = ctranslate2.Translator( weight_path, device="cpu", @@ -36,7 +38,19 @@ class Translator(): inter_threads=1, intra_threads=4 ) - self.ctranslate2_tokenizer = transformers.AutoTokenizer.from_pretrained(tokenizer) + try: + self.ctranslate2_tokenizer = transformers.AutoTokenizer.from_pretrained(tokenizer, cache_dir=tokenizer_path) + except Exception as e: + print("Error: changeCTranslate2Model()", e) + tokenizer_path = os.path.join("./weights", "ctranslate2", directory_name, "tokenizer") + self.ctranslate2_tokenizer = transformers.AutoTokenizer.from_pretrained(tokenizer, cache_dir=tokenizer_path) + + def clearCTranslate2Model(self): + del self.ctranslate2_translator + del self.ctranslate2_tokenizer + gc.collect() + self.ctranslate2_translator = None + self.ctranslate2_tokenizer = None @staticmethod def getLanguageCode(translator_name, target_country, source_language, target_language): diff --git a/models/translation/utils.py b/models/translation/translation_utils.py similarity index 78% rename from models/translation/utils.py rename to models/translation/translation_utils.py index d47401cf..73805cdc 100644 --- a/models/translation/utils.py +++ b/models/translation/translation_utils.py @@ -39,36 +39,36 @@ def calculate_file_hash(file_path, block_size=65536): return hash_object.hexdigest() def checkCTranslate2Weight(path, weight_type="Small"): - directory_name = 'weight' - current_directory = path weight_directory_name = ctranslate2_weights[weight_type]["directory_name"] hash_data = ctranslate2_weights[weight_type]["hash"] - files = ["model.bin", "sentencepiece.model", "shared_vocabulary.txt"] + files = [ + "model.bin", + "sentencepiece.model", + "shared_vocabulary.txt" + ] # check already downloaded already_downloaded = False - if all(os_path.exists(os_path.join(current_directory, directory_name, weight_directory_name, file)) for file in files): + if all(os_path.exists(os_path.join(path, weight_directory_name, file)) for file in files): # check hash for file in files: original_hash = hash_data[file] - current_hash = calculate_file_hash(os_path.join(current_directory, directory_name, weight_directory_name, file)) + current_hash = calculate_file_hash(os_path.join(path, weight_directory_name, file)) if original_hash != current_hash: break already_downloaded = True return already_downloaded -def downloadCTranslate2Weight(path, weight_type="Small", func=None): +def downloadCTranslate2Weight(root, weight_type="Small", func=None): url = ctranslate2_weights[weight_type]["url"] - filename = 'weight.zip' - directory_name = 'weight' - current_directory = path + filename = "weight.zip" + path = os_path.join(root, "weights", "ctranslate2") + os_makedirs(path, exist_ok=True) if checkCTranslate2Weight(path, weight_type): return try: - os_makedirs(os_path.join(current_directory, directory_name), exist_ok=True) - print(os_path.join(current_directory, directory_name)) with tempfile.TemporaryDirectory() as tmp_path: res = requests_get(url, stream=True) file_size = int(res.headers.get('content-length', 0)) @@ -81,6 +81,6 @@ def downloadCTranslate2Weight(path, weight_type="Small", func=None): func(total_chunk/file_size) with ZipFile(os_path.join(tmp_path, filename)) as zf: - zf.extractall(os_path.join(current_directory, directory_name)) + zf.extractall(path) except Exception as e: print("error:downloadCTranslate2Weight()", e) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index b6e14d85..350a73fb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,10 @@ pyyaml == 6.0.1 python-i18n == 0.3.9 CTkToolTip == 0.8 pyinstaller==6.2.0 -transformers[torch] +transformers[torch]==4.37.2 sentencepiece==0.1.99 -ctranslate2==3.21.0 \ No newline at end of file +ctranslate2==3.24.0 +faster-whisper==0.10.0 +translators @ git+https://github.com/misyaguziya/translators@5.8.9 +SpeechRecognition @ git+https://github.com/misyaguziya/custom_speech_recognition@3.10.2 +tinyoscquery @ git+https://github.com/cyberkitsune/tinyoscquery@0.1.2 \ No newline at end of file diff --git a/utils.py b/utils.py index 6a05a08d..6add2b43 100644 --- a/utils.py +++ b/utils.py @@ -1,5 +1,5 @@ from typing import Union -from os import path as os_path +from os import path as os_path, rename as os_rename from PIL.Image import open as Image_open def getImageFile(file_name): @@ -49,4 +49,10 @@ def isUniqueStrings(unique_strings:Union[str, list], input_string:str, require=F return all(count == 1 for count in counts) and counts.count(1) == 2 else: # If require is False, check if unique strings are used exactly once - return all(count == 1 for count in counts) \ No newline at end of file + return all(count == 1 for count in counts) + +# path先のweightフォルダがある場合にはそのフォルダ名をweightsに変更する +def renameWeightFolder(path): + weight_path = os_path.join(path, "weight") + if os_path.exists(weight_path): + os_rename(weight_path, os_path.join(path, "weights")) \ No newline at end of file diff --git a/view.py b/view.py index 34711688..e7962850 100644 --- a/view.py +++ b/view.py @@ -29,6 +29,7 @@ class View(): font_family=config.FONT_FAMILY, ui_language=config.UI_LANGUAGE, is_reset_button_displayed_for_translation=config.IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION, + is_reset_button_displayed_for_whisper=config.IS_RESET_BUTTON_DISPLAYED_FOR_WHISPER, ) if config.ENABLE_SPEAKER2CHATBOX is False: @@ -36,7 +37,7 @@ class View(): else: VERSION_TEXT=i18n.t("config_window.version", version=config.VERSION) + " (Speaker2Chatbox)" - self.TEXT_TRANSLATOR_CTRANSLATE2 = i18n.t("main_window.translator") + ": " + i18n.t("main_window.translator_ctranslate2") + self.TEXT_TRANSLATOR_CTRANSLATE2=i18n.t("main_window.translator") + ": " + i18n.t("main_window.translator_ctranslate2") self.settings = SimpleNamespace() theme = get_appearance_mode() if config.APPEARANCE_THEME == "System" else config.APPEARANCE_THEME @@ -102,6 +103,7 @@ class View(): CALLBACK_UPDATE_SOFTWARE=None, CALLBACK_OPEN_FILEPATH_LOGS=None, CALLBACK_OPEN_FILEPATH_CONFIG_FILE=None, + CALLBACK_OPEN_WEBPAGE_DEEPL_AUTH_KEY=self.openWebPage_DeepL_Auth_Key, CALLBACK_DELETE_MAIN_WINDOW=self.quitVRCT, CALLBACK_QUIT_VRCT=None, @@ -157,7 +159,7 @@ class View(): CALLBACK_SELECTED_LANGUAGE_PRESET_TAB=None, VAR_LABEL_YOUR_LANGUAGE=StringVar(value=i18n.t("main_window.your_language")), - VAR_YOUR_LANGUAGE = StringVar(value=f"{config.SOURCE_LANGUAGE}\n({config.SOURCE_COUNTRY})"), + VAR_YOUR_LANGUAGE=StringVar(value=f"{config.SOURCE_LANGUAGE}\n({config.SOURCE_COUNTRY})"), CALLBACK_OPEN_SELECTABLE_YOUR_LANGUAGE_WINDOW=None, IS_OPENED_SELECTABLE_YOUR_LANGUAGE_WINDOW=False, CALLBACK_SELECTED_YOUR_LANGUAGE=None, @@ -168,13 +170,13 @@ class View(): CALLBACK_LEAVED_SWAP_LANGUAGES_BUTTON=self._leavedSwapLanguagesButton, VAR_LABEL_TARGET_LANGUAGE=StringVar(value=i18n.t("main_window.target_language")), - VAR_TARGET_LANGUAGE = StringVar(value=f"{config.TARGET_LANGUAGE}\n({config.TARGET_COUNTRY})"), + VAR_TARGET_LANGUAGE=StringVar(value=f"{config.TARGET_LANGUAGE}\n({config.TARGET_COUNTRY})"), CALLBACK_OPEN_SELECTABLE_TARGET_LANGUAGE_WINDOW=None, IS_OPENED_SELECTABLE_TARGET_LANGUAGE_WINDOW=False, CALLBACK_SELECTED_TARGET_LANGUAGE=None, - VAR_SELECTED_TRANSLATION_ENGINE = StringVar(value="Translator: INIT"), - CALLBACK_SELECTED_TRANSLATION_ENGINE = None, + VAR_SELECTED_TRANSLATION_ENGINE=StringVar(value="Translator: INIT"), + CALLBACK_SELECTED_TRANSLATION_ENGINE=None, VAR_LABEL_TEXTBOX_ALL=StringVar(value=i18n.t("main_window.textbox_tab_all")), VAR_LABEL_TEXTBOX_SENT=StringVar(value=i18n.t("main_window.textbox_tab_sent")), @@ -184,6 +186,8 @@ class View(): VAR_UPDATE_AVAILABLE=StringVar(value=i18n.t("main_window.update_available")), + CALLBACK_MESSAGE_BOX_BIND_KEYSYM__UP=None, + CALLBACK_MESSAGE_BOX_BIND_KEYSYM__DOWN=None, # Main Window Cover VAR_LABEL_MAIN_WINDOW_COVER_MESSAGE=StringVar(value=""), @@ -211,6 +215,7 @@ class View(): VAR_SIDE_MENU_LABEL_TRANSCRIPTION=StringVar(value=i18n.t("config_window.side_menu_labels.transcription")), VAR_SECOND_TITLE_TRANSCRIPTION_MIC=StringVar(value=i18n.t("config_window.side_menu_labels.transcription_mic")), VAR_SECOND_TITLE_TRANSCRIPTION_SPEAKER=StringVar(value=i18n.t("config_window.side_menu_labels.transcription_speaker")), + VAR_SECOND_TITLE_TRANSCRIPTION_INTERNAL_MODEL=StringVar(value=i18n.t("config_window.side_menu_labels.transcription_internal_model")), VAR_SIDE_MENU_LABEL_OTHERS=StringVar(value=i18n.t("config_window.side_menu_labels.others")), VAR_SIDE_MENU_LABEL_ADVANCED_SETTINGS=StringVar(value=i18n.t("config_window.side_menu_labels.advanced_settings")), @@ -280,7 +285,7 @@ class View(): VAR_DESC_CTRANSLATE2_WEIGHT_TYPE=StringVar(value=i18n.t("config_window.ctranslate2_weight_type.desc")), DICT_CTRANSLATE2_WEIGHT_TYPE=self.getSelectableCtranslate2WeightTypeDict(), CALLBACK_SET_CTRANSLATE2_WEIGHT_TYPE=None, - VAR_CTRANSLATE2_WEIGHT_TYPE=StringVar(value=self.getSelectableCtranslate2WeightTypeDict()[config.WEIGHT_TYPE]), + VAR_CTRANSLATE2_WEIGHT_TYPE=StringVar(value=self.getSelectableCtranslate2WeightTypeDict()[config.CTRANSLATE2_WEIGHT_TYPE]), VAR_LABEL_DEEPL_AUTH_KEY=StringVar(value=i18n.t( "config_window.deepl_auth_key.label")), VAR_DESC_DEEPL_AUTH_KEY=StringVar( @@ -291,6 +296,7 @@ class View(): ), CALLBACK_SET_DEEPL_AUTH_KEY=None, VAR_DEEPL_AUTH_KEY=StringVar(value=config.AUTH_KEYS["DeepL_API"]), + VAR_OPEN_DEEPL_WEB_PAGE=StringVar(value=i18n.t( "config_window.deepl_auth_key.open_auth_key_webpage")), # Transcription Tab (Mic) @@ -381,6 +387,19 @@ class View(): CALLBACK_FOCUS_OUT_SPEAKER_MAX_PHRASES=self.callbackBindFocusOut_SpeakerMaxPhrases, + # Transcription Tab (Whisper Internal AI Model) + VAR_LABEL_USE_WHISPER_FEATURE=StringVar(value=i18n.t("config_window.use_whisper_feature.label")), + VAR_DESC_USE_WHISPER_FEATURE=StringVar(value=i18n.t("config_window.use_whisper_feature.desc")), + CALLBACK_SET_USE_WHISPER_FEATURE=None, + VAR_USE_WHISPER_FEATURE=BooleanVar(value=config.USE_WHISPER_FEATURE), + + VAR_LABEL_WHISPER_WEIGHT_TYPE=StringVar(value=i18n.t("config_window.whisper_weight_type.label")), + VAR_DESC_WHISPER_WEIGHT_TYPE=StringVar(value=i18n.t("config_window.whisper_weight_type.desc")), + DICT_WHISPER_WEIGHT_TYPE=self.getSelectableWhisperWeightTypeDict(), + CALLBACK_SET_WHISPER_WEIGHT_TYPE=None, + VAR_WHISPER_WEIGHT_TYPE=StringVar(value=self.getSelectableWhisperWeightTypeDict()[config.WHISPER_WEIGHT_TYPE]), + + # Others Tab VAR_LABEL_ENABLE_AUTO_CLEAR_MESSAGE_BOX=StringVar(value=i18n.t("config_window.auto_clear_the_message_box.label")), VAR_DESC_ENABLE_AUTO_CLEAR_MESSAGE_BOX=None, @@ -527,23 +546,23 @@ class View(): if main_window_registers is not None: - self.view_variable.CALLBACK_ENABLE_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = main_window_registers.get("callback_enable_main_window_sidebar_compact_mode", None) - self.view_variable.CALLBACK_DISABLE_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = main_window_registers.get("callback_disable_main_window_sidebar_compact_mode", None) + self.view_variable.CALLBACK_ENABLE_MAIN_WINDOW_SIDEBAR_COMPACT_MODE=main_window_registers.get("callback_enable_main_window_sidebar_compact_mode", None) + self.view_variable.CALLBACK_DISABLE_MAIN_WINDOW_SIDEBAR_COMPACT_MODE=main_window_registers.get("callback_disable_main_window_sidebar_compact_mode", None) - self.view_variable.CALLBACK_TOGGLE_TRANSLATION = main_window_registers.get("callback_toggle_translation", None) - self.view_variable.CALLBACK_TOGGLE_TRANSCRIPTION_SEND = main_window_registers.get("callback_toggle_transcription_send", None) - self.view_variable.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE = main_window_registers.get("callback_toggle_transcription_receive", None) - self.view_variable.CALLBACK_TOGGLE_FOREGROUND = main_window_registers.get("callback_toggle_foreground", None) + self.view_variable.CALLBACK_TOGGLE_TRANSLATION=main_window_registers.get("callback_toggle_translation", None) + self.view_variable.CALLBACK_TOGGLE_TRANSCRIPTION_SEND=main_window_registers.get("callback_toggle_transcription_send", None) + self.view_variable.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE=main_window_registers.get("callback_toggle_transcription_receive", None) + self.view_variable.CALLBACK_TOGGLE_FOREGROUND=main_window_registers.get("callback_toggle_foreground", None) - self.view_variable.CALLBACK_SELECTED_YOUR_LANGUAGE = main_window_registers.get("callback_your_language", None) - self.view_variable.CALLBACK_SELECTED_TARGET_LANGUAGE = main_window_registers.get("callback_target_language", None) + self.view_variable.CALLBACK_SELECTED_YOUR_LANGUAGE=main_window_registers.get("callback_your_language", None) + self.view_variable.CALLBACK_SELECTED_TARGET_LANGUAGE=main_window_registers.get("callback_target_language", None) main_window_registers.get("values", None) and self.updateList_selectableLanguages(main_window_registers["values"]) - self.view_variable.CALLBACK_SWAP_LANGUAGES = main_window_registers.get("callback_swap_languages", None) + self.view_variable.CALLBACK_SWAP_LANGUAGES=main_window_registers.get("callback_swap_languages", None) - self.view_variable.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB = main_window_registers.get("callback_selected_language_preset_tab", None) + self.view_variable.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB=main_window_registers.get("callback_selected_language_preset_tab", None) - self.view_variable.CALLBACK_SELECTED_TRANSLATION_ENGINE = main_window_registers.get("callback_selected_translation_engine", None) + self.view_variable.CALLBACK_SELECTED_TRANSLATION_ENGINE=main_window_registers.get("callback_selected_translation_engine", None) def adjustedMessageBoxReturnFunction(_e): if self.view_variable.IS_ENTRY_MESSAGE_BOX_DISABLED is True: @@ -555,11 +574,15 @@ class View(): main_window_registers.get("message_box_bind_Return")() vrct_gui.entry_message_box.focus() - entry_message_box = getattr(vrct_gui, "entry_message_box") + entry_message_box=getattr(vrct_gui, "entry_message_box") entry_message_box.bind("", lambda _e: None) # This is to prevent message sending on Shift + Enter key press and just add a new line. entry_message_box.bind("", adjustedMessageBoxReturnFunction) entry_message_box.bind("", main_window_registers.get("message_box_bind_Any_KeyPress")) - self.view_variable.CALLBACK_CLICKED_SEND_MESSAGE_BUTTON = pressedSendMessageButtonFunction + self.view_variable.CALLBACK_CLICKED_SEND_MESSAGE_BUTTON=pressedSendMessageButtonFunction + + + self.view_variable.CALLBACK_MESSAGE_BOX_BIND_KEYSYM__UP=main_window_registers.get("message_box_bind_Up_KeyPress") + self.view_variable.CALLBACK_MESSAGE_BOX_BIND_KEYSYM__DOWN=main_window_registers.get("message_box_bind_Down_KeyPress") entry_message_box.bind("", main_window_registers.get("message_box_bind_FocusIn")) @@ -569,8 +592,8 @@ class View(): self.updateGuiVariableByPresetTabNo(config.SELECTED_TAB_NO) vrct_gui._setDefaultActiveLanguagePresetTab(tab_no=config.SELECTED_TAB_NO) - self.view_variable.CALLBACK_OPEN_SELECTABLE_YOUR_LANGUAGE_WINDOW = self.openSelectableLanguagesWindow_YourLanguage - self.view_variable.CALLBACK_OPEN_SELECTABLE_TARGET_LANGUAGE_WINDOW = self.openSelectableLanguagesWindow_TargetLanguage + self.view_variable.CALLBACK_OPEN_SELECTABLE_YOUR_LANGUAGE_WINDOW=self.openSelectableLanguagesWindow_YourLanguage + self.view_variable.CALLBACK_OPEN_SELECTABLE_TARGET_LANGUAGE_WINDOW=self.openSelectableLanguagesWindow_TargetLanguage # Config Window @@ -579,74 +602,79 @@ class View(): if config_window_registers is not None: # Compact Mode Switch - self.view_variable.CALLBACK_ENABLE_CONFIG_WINDOW_COMPACT_MODE = config_window_registers.get("callback_disable_config_window_compact_mode", None) - self.view_variable.CALLBACK_DISABLE_CONFIG_WINDOW_COMPACT_MODE = config_window_registers.get("callback_enable_config_window_compact_mode", None) + self.view_variable.CALLBACK_ENABLE_CONFIG_WINDOW_COMPACT_MODE=config_window_registers.get("callback_disable_config_window_compact_mode", None) + self.view_variable.CALLBACK_DISABLE_CONFIG_WINDOW_COMPACT_MODE=config_window_registers.get("callback_enable_config_window_compact_mode", None) # Appearance Tab - self.view_variable.CALLBACK_SET_TRANSPARENCY = config_window_registers.get("callback_set_transparency", None) + self.view_variable.CALLBACK_SET_TRANSPARENCY=config_window_registers.get("callback_set_transparency", None) - self.view_variable.CALLBACK_SET_APPEARANCE = config_window_registers.get("callback_set_appearance", None) - self.view_variable.CALLBACK_SET_UI_SCALING = config_window_registers.get("callback_set_ui_scaling", None) - self.view_variable.CALLBACK_SET_TEXTBOX_UI_SCALING = config_window_registers.get("callback_set_textbox_ui_scaling", None) - self.view_variable.CALLBACK_SET_MESSAGE_BOX_RATIO = config_window_registers.get("callback_set_message_box_ratio", None) - self.view_variable.CALLBACK_SET_FONT_FAMILY = config_window_registers.get("callback_set_font_family", None) - self.view_variable.CALLBACK_SET_UI_LANGUAGE = config_window_registers.get("callback_set_ui_language", None) - self.view_variable.CALLBACK_SET_ENABLE_RESTORE_MAIN_WINDOW_GEOMETRY = config_window_registers.get("callback_set_enable_restore_main_window_geometry", None) + self.view_variable.CALLBACK_SET_APPEARANCE=config_window_registers.get("callback_set_appearance", None) + self.view_variable.CALLBACK_SET_UI_SCALING=config_window_registers.get("callback_set_ui_scaling", None) + self.view_variable.CALLBACK_SET_TEXTBOX_UI_SCALING=config_window_registers.get("callback_set_textbox_ui_scaling", None) + self.view_variable.CALLBACK_SET_MESSAGE_BOX_RATIO=config_window_registers.get("callback_set_message_box_ratio", None) + self.view_variable.CALLBACK_SET_FONT_FAMILY=config_window_registers.get("callback_set_font_family", None) + self.view_variable.CALLBACK_SET_UI_LANGUAGE=config_window_registers.get("callback_set_ui_language", None) + self.view_variable.CALLBACK_SET_ENABLE_RESTORE_MAIN_WINDOW_GEOMETRY=config_window_registers.get("callback_set_enable_restore_main_window_geometry", None) # Translation Tab - self.view_variable.CALLBACK_SET_USE_TRANSLATION_FEATURE = config_window_registers.get("callback_set_use_translation_feature", None) - self.view_variable.CALLBACK_SET_CTRANSLATE2_WEIGHT_TYPE = config_window_registers.get("callback_set_ctranslate2_weight_type", None) - self.view_variable.CALLBACK_SET_DEEPL_AUTH_KEY = config_window_registers.get("callback_set_deepl_auth_key", None) + self.view_variable.CALLBACK_SET_USE_TRANSLATION_FEATURE=config_window_registers.get("callback_set_use_translation_feature", None) + self.view_variable.CALLBACK_SET_CTRANSLATE2_WEIGHT_TYPE=config_window_registers.get("callback_set_ctranslate2_weight_type", None) + self.view_variable.CALLBACK_SET_DEEPL_AUTH_KEY=config_window_registers.get("callback_set_deepl_auth_key", None) # Transcription Tab (Mic) - self.view_variable.CALLBACK_SET_MIC_HOST = config_window_registers.get("callback_set_mic_host", None) + self.view_variable.CALLBACK_SET_MIC_HOST=config_window_registers.get("callback_set_mic_host", None) config_window_registers.get("list_mic_host", None) and self.updateList_MicHost(config_window_registers["list_mic_host"]) - self.view_variable.CALLBACK_SET_MIC_DEVICE = config_window_registers.get("callback_set_mic_device", None) + self.view_variable.CALLBACK_SET_MIC_DEVICE=config_window_registers.get("callback_set_mic_device", None) config_window_registers.get("list_mic_device", None) and self.updateList_MicDevice(config_window_registers["list_mic_device"]) - self.view_variable.CALLBACK_SET_MIC_ENERGY_THRESHOLD = config_window_registers.get("callback_set_mic_energy_threshold", None) - self.view_variable.CALLBACK_SET_MIC_DYNAMIC_ENERGY_THRESHOLD = config_window_registers.get("callback_set_mic_dynamic_energy_threshold", None) - self.view_variable.CALLBACK_CHECK_MIC_THRESHOLD = config_window_registers.get("callback_check_mic_threshold", None) - self.view_variable.CALLBACK_SET_MIC_RECORD_TIMEOUT = config_window_registers.get("callback_set_mic_record_timeout", None) - self.view_variable.CALLBACK_SET_MIC_PHRASE_TIMEOUT = config_window_registers.get("callback_set_mic_phrase_timeout", None) - self.view_variable.CALLBACK_SET_MIC_MAX_PHRASES = config_window_registers.get("callback_set_mic_max_phrases", None) - self.view_variable.CALLBACK_SET_MIC_WORD_FILTER = config_window_registers.get("callback_set_mic_word_filter", None) - self.view_variable.CALLBACK_DELETE_MIC_WORD_FILTER = config_window_registers.get("callback_delete_mic_word_filter", None) + self.view_variable.CALLBACK_SET_MIC_ENERGY_THRESHOLD=config_window_registers.get("callback_set_mic_energy_threshold", None) + self.view_variable.CALLBACK_SET_MIC_DYNAMIC_ENERGY_THRESHOLD=config_window_registers.get("callback_set_mic_dynamic_energy_threshold", None) + self.view_variable.CALLBACK_CHECK_MIC_THRESHOLD=config_window_registers.get("callback_check_mic_threshold", None) + self.view_variable.CALLBACK_SET_MIC_RECORD_TIMEOUT=config_window_registers.get("callback_set_mic_record_timeout", None) + self.view_variable.CALLBACK_SET_MIC_PHRASE_TIMEOUT=config_window_registers.get("callback_set_mic_phrase_timeout", None) + self.view_variable.CALLBACK_SET_MIC_MAX_PHRASES=config_window_registers.get("callback_set_mic_max_phrases", None) + self.view_variable.CALLBACK_SET_MIC_WORD_FILTER=config_window_registers.get("callback_set_mic_word_filter", None) + self.view_variable.CALLBACK_DELETE_MIC_WORD_FILTER=config_window_registers.get("callback_delete_mic_word_filter", None) # Transcription Tab (Speaker) - self.view_variable.CALLBACK_SET_SPEAKER_ENERGY_THRESHOLD = config_window_registers.get("callback_set_speaker_energy_threshold", None) - self.view_variable.CALLBACK_SET_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = config_window_registers.get("callback_set_speaker_dynamic_energy_threshold", None) - self.view_variable.CALLBACK_CHECK_SPEAKER_THRESHOLD = config_window_registers.get("callback_check_speaker_threshold", None) - self.view_variable.CALLBACK_SET_SPEAKER_RECORD_TIMEOUT = config_window_registers.get("callback_set_speaker_record_timeout", None) - self.view_variable.CALLBACK_SET_SPEAKER_PHRASE_TIMEOUT = config_window_registers.get("callback_set_speaker_phrase_timeout", None) - self.view_variable.CALLBACK_SET_SPEAKER_MAX_PHRASES = config_window_registers.get("callback_set_speaker_max_phrases", None) + self.view_variable.CALLBACK_SET_SPEAKER_ENERGY_THRESHOLD=config_window_registers.get("callback_set_speaker_energy_threshold", None) + self.view_variable.CALLBACK_SET_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=config_window_registers.get("callback_set_speaker_dynamic_energy_threshold", None) + self.view_variable.CALLBACK_CHECK_SPEAKER_THRESHOLD=config_window_registers.get("callback_check_speaker_threshold", None) + self.view_variable.CALLBACK_SET_SPEAKER_RECORD_TIMEOUT=config_window_registers.get("callback_set_speaker_record_timeout", None) + self.view_variable.CALLBACK_SET_SPEAKER_PHRASE_TIMEOUT=config_window_registers.get("callback_set_speaker_phrase_timeout", None) + self.view_variable.CALLBACK_SET_SPEAKER_MAX_PHRASES=config_window_registers.get("callback_set_speaker_max_phrases", None) + + # Transcription Tab (Internal AI Model) + self.view_variable.CALLBACK_SET_USE_WHISPER_FEATURE=config_window_registers.get("callback_set_use_whisper_feature", None) + self.view_variable.CALLBACK_SET_WHISPER_WEIGHT_TYPE=config_window_registers.get("callback_set_whisper_weight_type", None) + # Others Tab - self.view_variable.CALLBACK_SET_ENABLE_AUTO_CLEAR_MESSAGE_BOX = config_window_registers.get("callback_set_enable_auto_clear_chatbox", None) - self.view_variable.CALLBACK_SET_ENABLE_SEND_ONLY_TRANSLATED_MESSAGES = config_window_registers.get("callback_set_send_only_translated_messages", None) - self.view_variable.CALLBACK_SET_SEND_MESSAGE_BUTTON_TYPE = config_window_registers.get("callback_set_send_message_button_type", None) - 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_AUTO_CLEAR_MESSAGE_BOX=config_window_registers.get("callback_set_enable_auto_clear_chatbox", None) + self.view_variable.CALLBACK_SET_ENABLE_SEND_ONLY_TRANSLATED_MESSAGES=config_window_registers.get("callback_set_send_only_translated_messages", None) + self.view_variable.CALLBACK_SET_SEND_MESSAGE_BUTTON_TYPE=config_window_registers.get("callback_set_send_message_button_type", None) + 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_SEND_MESSAGE_TO_VRC = config_window_registers.get("callback_set_enable_send_message_to_vrc", 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) - self.view_variable.CALLBACK_SET_SEND_MESSAGE_FORMAT_WITH_T = config_window_registers.get("callback_set_send_message_format_with_t", None) - self.view_variable.CALLBACK_SET_RECEIVED_MESSAGE_FORMAT = config_window_registers.get("callback_set_received_message_format", None) - self.view_variable.CALLBACK_SET_RECEIVED_MESSAGE_FORMAT_WITH_T = config_window_registers.get("callback_set_received_message_format_with_t", None) + self.view_variable.CALLBACK_SET_SEND_MESSAGE_FORMAT=config_window_registers.get("callback_set_send_message_format", None) + self.view_variable.CALLBACK_SET_SEND_MESSAGE_FORMAT_WITH_T=config_window_registers.get("callback_set_send_message_format_with_t", None) + self.view_variable.CALLBACK_SET_RECEIVED_MESSAGE_FORMAT=config_window_registers.get("callback_set_received_message_format", None) + self.view_variable.CALLBACK_SET_RECEIVED_MESSAGE_FORMAT_WITH_T=config_window_registers.get("callback_set_received_message_format_with_t", None) # Speaker2Chatbox---------------- - self.view_variable.CALLBACK_SET_ENABLE_SEND_RECEIVED_MESSAGE_TO_VRC = config_window_registers.get("callback_set_enable_send_received_message_to_vrc", None) + self.view_variable.CALLBACK_SET_ENABLE_SEND_RECEIVED_MESSAGE_TO_VRC=config_window_registers.get("callback_set_enable_send_received_message_to_vrc", None) # Speaker2Chatbox---------------- # Advanced Settings Tab - self.view_variable.CALLBACK_SET_OSC_IP_ADDRESS = config_window_registers.get("callback_set_osc_ip_address", None) - self.view_variable.CALLBACK_SET_OSC_PORT = config_window_registers.get("callback_set_osc_port", None) + self.view_variable.CALLBACK_SET_OSC_IP_ADDRESS=config_window_registers.get("callback_set_osc_ip_address", None) + self.view_variable.CALLBACK_SET_OSC_PORT=config_window_registers.get("callback_set_osc_port", None) # The initial processing after registration. if config.IS_CONFIG_WINDOW_COMPACT_MODE is True: @@ -678,6 +706,11 @@ class View(): ) self.replaceMicThresholdCheckButton_Disabled() + if config.USE_WHISPER_FEATURE is True: + self.openWhisperWeightTypeWidget() + else: + self.closeWhisperWeightTypeWidget() + if config.ENABLE_SPEAKER2CHATBOX is False: vrct_gui._changeConfigWindowWidgetsStatus( status="disabled", @@ -882,8 +915,8 @@ class View(): @staticmethod def getSelectableCtranslate2WeightTypeDict(): return { - config._SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT["Small"]: i18n.t("config_window.ctranslate2_weight_type.small", capacity="418MB"), - config._SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT["Large"]: i18n.t("config_window.ctranslate2_weight_type.large", capacity="1.2GB"), + config.SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT["Small"]: i18n.t("config_window.ctranslate2_weight_type.small", capacity="418MB"), + config.SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT["Large"]: i18n.t("config_window.ctranslate2_weight_type.large", capacity="1.2GB"), } def useTranslationFeatureProcess(self, state:str): @@ -919,6 +952,24 @@ class View(): vrct_gui.update() vrct_gui.config_window.lift() + @staticmethod + def getSelectableWhisperWeightTypeDict(): + def callI18n(model_name, capacity, is_recommended=False): + if is_recommended is True: + return i18n.t("config_window.whisper_weight_type.recommended_model_template", model_name=model_name, capacity=capacity) + else: + return i18n.t("config_window.whisper_weight_type.model_template", model_name=model_name, capacity=capacity) + + DICT_DATA = config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT + return { + DICT_DATA["tiny"]: callI18n("tiny", "74.5MB"), + DICT_DATA["base"]: callI18n("base", "141MB", True), + DICT_DATA["small"]: callI18n("small", "463MB"), + DICT_DATA["medium"]: callI18n("medium", "1.42GB"), + DICT_DATA["large-v1"]: callI18n("large-v1", "2.87GB"), + DICT_DATA["large-v2"]: callI18n("large-v2", "2.87GB"), + DICT_DATA["large-v3"]: callI18n("large-v3", "2.87GB"), + } # Open Webpage Functions def openWebPage_Booth(self): @@ -929,6 +980,10 @@ class View(): self.openWebPage(config.DOCUMENTS_URL) self._printToTextbox_Info(i18n.t("main_window.textbox_system_message.opened_web_page_vrct_documents")) + def openWebPage_DeepL_Auth_Key(self): + self.openWebPage(config.DEEPL_AUTH_KEY_PAGE_URL) + + # Widget Control # Common @@ -1014,7 +1069,8 @@ class View(): self.restart_required_configs_pre_data.ui_scaling == config.UI_SCALING and self.restart_required_configs_pre_data.font_family == config.FONT_FAMILY and self.restart_required_configs_pre_data.ui_language == config.UI_LANGUAGE and - self.restart_required_configs_pre_data.is_reset_button_displayed_for_translation == config.IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION + self.restart_required_configs_pre_data.is_reset_button_displayed_for_translation == config.IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION and + self.restart_required_configs_pre_data.is_reset_button_displayed_for_whisper == config.IS_RESET_BUTTON_DISPLAYED_FOR_WHISPER ) if locale is None: @@ -1069,7 +1125,7 @@ class View(): self.view_variable.VAR_CTRANSLATE2_WEIGHT_TYPE.set(self.getSelectableCtranslate2WeightTypeDict()[selected_weight_type]) def setLatestCTranslate2WeightType(self): - selected_weight_type = self.getSelectableCtranslate2WeightTypeDict()[config.WEIGHT_TYPE] + selected_weight_type = self.getSelectableCtranslate2WeightTypeDict()[config.CTRANSLATE2_WEIGHT_TYPE] self.view_variable.VAR_CTRANSLATE2_WEIGHT_TYPE.set(selected_weight_type) @@ -1082,6 +1138,23 @@ class View(): vrct_gui.config_window.sb__ctranslate2_weight_type.grid_remove() + def openWhisperWeightTypeWidget(self): + vrct_gui.config_window.sb__use_whisper_feature.grid() + vrct_gui.config_window.sb__whisper_weight_type.grid() + + def closeWhisperWeightTypeWidget(self): + vrct_gui.config_window.sb__use_whisper_feature.grid() + vrct_gui.config_window.sb__whisper_weight_type.grid_remove() + + + def updateSelectedWhisperWeightType(self, selected_weight_type:str): + self.view_variable.VAR_WHISPER_WEIGHT_TYPE.set(self.getSelectableWhisperWeightTypeDict()[selected_weight_type]) + + def setLatestCTranslate2WeightType(self): + selected_weight_type = self.getSelectableWhisperWeightTypeDict()[config.WHISPER_WEIGHT_TYPE] + self.view_variable.VAR_WHISPER_WEIGHT_TYPE.set(selected_weight_type) + + def openMicEnergyThresholdWidget(self): self.view_variable.VAR_LABEL_MIC_DYNAMIC_ENERGY_THRESHOLD.set(i18n.t("config_window.mic_dynamic_energy_threshold.label_for_manual")) self.view_variable.VAR_DESC_MIC_DYNAMIC_ENERGY_THRESHOLD.set(i18n.t("config_window.mic_dynamic_energy_threshold.desc_for_manual")) @@ -1637,6 +1710,13 @@ class View(): def clearMessageBox(self): self._clearTextBox(vrct_gui.entry_message_box) + @staticmethod + def insertMessageBox(text): + vrct_gui.entry_message_box.insert("end", text) + + def replaceMessageBox(self, text): + self.clearMessageBox() + self.insertMessageBox(text) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py index 30af50de..49272afc 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py @@ -7,7 +7,7 @@ from ._createSettingBoxContainer import _createSettingBoxContainer from .setting_box_containers.setting_box_appearance import createSettingBox_Appearance -from .setting_box_containers.setting_box_transcription import createSettingBox_Mic, createSettingBox_Speaker +from .setting_box_containers.setting_box_transcription import createSettingBox_Mic, createSettingBox_Speaker, createSettingBox_InternalModel from .setting_box_containers.setting_box_others import createSettingBox_Others, createSettingBox_Others_SendMessageFormats, createSettingBox_Others_ReceivedMessageFormats, createSettingBox_Others_Additional from .setting_box_containers.setting_box_advanced_settings import createSettingBox_AdvancedSettings from .setting_box_containers.setting_box_translation import createSettingBox_Translation @@ -94,6 +94,10 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings, view_variabl "var_section_title": view_variable.VAR_SECOND_TITLE_TRANSCRIPTION_SPEAKER, "setting_box": createSettingBox_Speaker }, + { + "var_section_title": view_variable.VAR_SECOND_TITLE_TRANSCRIPTION_INTERNAL_MODEL, + "setting_box": createSettingBox_InternalModel + }, ] }, }, diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index ade6913a..60386162 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -5,7 +5,7 @@ from typing import Union from customtkinter import CTkFont, CTkFrame, CTkLabel, CTkEntry, CTkSlider, CTkSwitch, CTkCheckBox, CTkProgressBar, CTkImage, CTkRadioButton from CTkToolTip import * -from vrct_gui.ui_utils import createButtonWithImage, getLatestWidth, createOptionMenuBox, getLatestHeight, bindButtonFunctionAndColor, bindEnterAndLeaveFunction, bindButtonReleaseFunction, bindButtonPressFunction +from vrct_gui.ui_utils import createButtonWithImage, getLatestWidth, createOptionMenuBox, getLatestHeight, bindButtonFunctionAndColor, bindEnterAndLeaveFunction, bindButtonReleaseFunction, bindButtonPressFunction, createLabelButton from vrct_gui import vrct_gui from utils import isEven, callFunctionIfCallable @@ -615,6 +615,75 @@ class _SettingBoxGenerator(): return setting_box_frame + def createSettingBoxEntry_AuthKey(self, + for_var_label_text, for_var_desc_text, + entry_attr_name, + entry_width, + entry_textvariable, + entry_bind__Any_KeyRelease, + entry_bind__FocusOut=None, + open_authkey_page_command=None, + open_authkey_text_variable=None, + image_file=None, + ): + + (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(entry_attr_name, for_var_label_text, for_var_desc_text) + + + all_wrapper = CTkFrame(setting_box_item_frame, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) + all_wrapper.grid(row=1, column=0, sticky="ew") + + all_wrapper.grid_columnconfigure(0, weight=1) + + + def adjusted_command__for_entry_bind__Any_KeyRelease(e): + entry_bind__Any_KeyRelease(e.widget.get()) + + entry_widget = CTkEntry( + all_wrapper, + text_color=self.settings.ctm.SB__ENTRY_TEXT_COLOR, + fg_color=self.settings.ctm.SB__ENTRY_BG_COLOR, + border_color=self.settings.ctm.SB__ENTRY_BORDER_COLOR, + width=entry_width, + height=self.settings.uism.SB__PROGRESSBAR_X_SLIDER__ENTRY_HEIGHT, + textvariable=entry_textvariable, + font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__ENTRY_FONT_SIZE, weight="normal"), + ) + entry_widget.bind("", adjusted_command__for_entry_bind__Any_KeyRelease) + setattr(self.config_window, entry_attr_name, entry_widget) + + + entry_widget.grid(row=0, column=SETTING_BOX_COLUMN, sticky="e") + + if entry_bind__FocusOut is not None: + entry_widget.bind("", entry_bind__FocusOut, "+") + + + + (open_page_button, label_button_label_widget, label_button_img_widget) = createLabelButton( + parent_widget=all_wrapper, + label_button_bg_color=self.settings.ctm.SB__BUTTON_COLOR, + label_button_hovered_bg_color=self.settings.ctm.SB__BUTTON_HOVERED_COLOR, + label_button_clicked_bg_color=self.settings.ctm.SB__BUTTON_CLICKED_COLOR, + label_button_ipadx=self.settings.uism.SB__AUTHKEY_WEBPAGE_BUTTON_IPADX, + label_button_ipady=self.settings.uism.SB__AUTHKEY_WEBPAGE_BUTTON_IPADY, + variable=open_authkey_text_variable, + font_family=self.settings.FONT_FAMILY, + font_size=self.settings.uism.SB__AUTHKEY_WEBPAGE_BUTTON_LABEL_FONT_SIZE, + text_color=self.settings.ctm.LABELS_TEXT_COLOR, + label_button_clicked_command=open_authkey_page_command, + + label_button_position="center", + + image_file=image_file, + image_size=self.settings.uism.SB__AUTHKEY_WEBPAGE_BUTTON_IMG_SIZE, + label_button_padx_between_img=self.settings.uism.SB__AUTHKEY_WEBPAGE_PADX_BETWEEN_LABEL_AND_ICON, + ) + open_page_button.grid(row=1, column=SETTING_BOX_COLUMN, pady=(self.settings.uism.SB__AUTHKEY_WEBPAGE_BUTTON_TOP_PADY,0)) + + + return setting_box_frame + diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/__init__.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/__init__.py index 5383094e..b06ff822 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/__init__.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/__init__.py @@ -1,2 +1,3 @@ from .createSettingBox_Mic import createSettingBox_Mic -from .createSettingBox_Speaker import createSettingBox_Speaker \ No newline at end of file +from .createSettingBox_Speaker import createSettingBox_Speaker +from .createSettingBox_InternalModel import createSettingBox_InternalModel \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_InternalModel.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_InternalModel.py new file mode 100644 index 00000000..0a6b3e69 --- /dev/null +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_InternalModel.py @@ -0,0 +1,37 @@ +from utils import callFunctionIfCallable + +from .._SettingBoxGenerator import _SettingBoxGenerator + +def createSettingBox_InternalModel(setting_box_wrapper, config_window, settings, view_variable): + sbg = _SettingBoxGenerator(setting_box_wrapper, config_window, settings, view_variable) + createSettingBoxSwitch = sbg.createSettingBoxSwitch + createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu + + def switchUseWhisperFeatureCallback(switch_widget): + callFunctionIfCallable(view_variable.CALLBACK_SET_USE_WHISPER_FEATURE, switch_widget.get()) + + def optionmenuWhisperWeightTypeCallback(value): + callFunctionIfCallable(view_variable.CALLBACK_SET_WHISPER_WEIGHT_TYPE, value) + + + row=0 + config_window.sb__use_whisper_feature = createSettingBoxSwitch( + for_var_label_text=view_variable.VAR_LABEL_USE_WHISPER_FEATURE, + for_var_desc_text=view_variable.VAR_DESC_USE_WHISPER_FEATURE, + switch_attr_name="sb__switch_use_whisper_feature", + command=lambda: switchUseWhisperFeatureCallback(config_window.sb__switch_use_whisper_feature), + variable=view_variable.VAR_USE_WHISPER_FEATURE + ) + config_window.sb__use_whisper_feature.grid(row=row, pady=0) + row+=1 + + config_window.sb__whisper_weight_type = createSettingBoxDropdownMenu( + for_var_label_text=view_variable.VAR_LABEL_WHISPER_WEIGHT_TYPE, + for_var_desc_text=view_variable.VAR_DESC_WHISPER_WEIGHT_TYPE, + optionmenu_attr_name="sb__optionmenu_whisper_weight_type", + dropdown_menu_values=view_variable.DICT_WHISPER_WEIGHT_TYPE, + command=lambda value: optionmenuWhisperWeightTypeCallback(value), + variable=view_variable.VAR_WHISPER_WEIGHT_TYPE, + ) + config_window.sb__whisper_weight_type.grid(row=row, pady=0) + row+=1 \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py index f245c387..7e3a9ba5 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py @@ -6,7 +6,7 @@ def createSettingBox_Translation(setting_box_wrapper, config_window, settings, v sbg = _SettingBoxGenerator(setting_box_wrapper, config_window, settings, view_variable) createSettingBoxSwitch = sbg.createSettingBoxSwitch createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu - createSettingBoxEntry = sbg.createSettingBoxEntry + createSettingBoxEntry_AuthKey = sbg.createSettingBoxEntry_AuthKey def switchUseTranslationFeatureCallback(switch_widget): callFunctionIfCallable(view_variable.CALLBACK_SET_USE_TRANSLATION_FEATURE, switch_widget.get()) @@ -41,13 +41,16 @@ def createSettingBox_Translation(setting_box_wrapper, config_window, settings, v row+=1 - config_window.sb__deepl_auth_key = createSettingBoxEntry( + config_window.sb__deepl_auth_key = createSettingBoxEntry_AuthKey( for_var_label_text=view_variable.VAR_LABEL_DEEPL_AUTH_KEY, for_var_desc_text=view_variable.VAR_DESC_DEEPL_AUTH_KEY, entry_attr_name="sb__entry_deepl_auth_key", entry_width=settings.uism.RESPONSIVE_UI_SIZE_INT_300, entry_bind__Any_KeyRelease=lambda value: deeplAuthKeyCallback(value), entry_textvariable=view_variable.VAR_DEEPL_AUTH_KEY, + open_authkey_page_command=lambda _e: callFunctionIfCallable(view_variable.CALLBACK_OPEN_WEBPAGE_DEEPL_AUTH_KEY), + open_authkey_text_variable=view_variable.VAR_OPEN_DEEPL_WEB_PAGE, + image_file=settings.image_file.LINK_ICON ) config_window.sb__deepl_auth_key.grid(row=row, pady=0) row+=1 \ No newline at end of file diff --git a/vrct_gui/main_window/widgets/create_entry_message_box.py b/vrct_gui/main_window/widgets/create_entry_message_box.py index f49664e5..d153749e 100644 --- a/vrct_gui/main_window/widgets/create_entry_message_box.py +++ b/vrct_gui/main_window/widgets/create_entry_message_box.py @@ -19,6 +19,9 @@ def createEntryMessageBox(settings, main_window, view_variable): border_width=settings.uism.TEXTBOX_ENTRY_BORDER_SIZE, height=0, font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.TEXTBOX_ENTRY_FONT_SIZE, weight="normal"), + undo=True, + autoseparators=True, + maxundo=64, ) main_window.entry_message_box.grid(row=0, column=0, padx=settings.uism.TEXTBOX_ENTRY_PADX, pady=settings.uism.TEXTBOX_ENTRY_PADY, sticky="nsew") @@ -28,6 +31,14 @@ def createEntryMessageBox(settings, main_window, view_variable): "Delete", "Select", "Up", "Down", "Next", "End", "Print", "Prior","Insert","Home", "Left", "Clear", "Right", "Linefeed" ] + if e.keysym == "Up": + callFunctionIfCallable(view_variable.CALLBACK_MESSAGE_BOX_BIND_KEYSYM__UP) + return "break" + + if e.keysym == "Down": + callFunctionIfCallable(view_variable.CALLBACK_MESSAGE_BOX_BIND_KEYSYM__DOWN) + return "break" + if e.keysym != "??": if len(e.char) != 0 and e.keysym in BREAK_KEYSYM_LIST: main_window.entry_message_box.insert("end", e.char) @@ -35,6 +46,15 @@ def createEntryMessageBox(settings, main_window, view_variable): main_window.entry_message_box.bind("", messageBoxAnyKeyPress) + def messageBoxRedoFunction(_e): + try: + main_window.entry_message_box.edit_redo() + except: + pass + + main_window.entry_message_box.bind("", messageBoxRedoFunction, "+") + main_window.entry_message_box.bind("", messageBoxRedoFunction, "+") + main_window.main_send_message_button_container = CTkFrame(main_window.main_entry_message_container, corner_radius=settings.uism.SEND_MESSAGE_BUTTON_CORNER_RADIUS, fg_color=settings.ctm.SEND_MESSAGE_BUTTON_BG_COLOR, width=0, height=0) main_window.main_send_message_button_container.grid(row=0, column=1, padx=(0, settings.uism.TEXTBOX_ENTRY_PADX), pady=settings.uism.TEXTBOX_ENTRY_PADY, sticky="nsew") diff --git a/vrct_gui/ui_managers/Themes/_darkTheme.py b/vrct_gui/ui_managers/Themes/_darkTheme.py index 46592b4f..1b5fc339 100644 --- a/vrct_gui/ui_managers/Themes/_darkTheme.py +++ b/vrct_gui/ui_managers/Themes/_darkTheme.py @@ -330,6 +330,7 @@ def _darkTheme(base_color): REDO_ICON = getImageFileFromUiUtils("redo_icon_white.png"), SWAP_ICON = getImageFileFromUiUtils("swap_icon_white.png"), FOLDER_OPEN_ICON = getImageFileFromUiUtils("folder_open_icon_white.png"), + LINK_ICON = getImageFileFromUiUtils("link_icon_white.png"), ), ) diff --git a/vrct_gui/ui_managers/Themes/_lightTheme.py b/vrct_gui/ui_managers/Themes/_lightTheme.py index 08caeae1..d953372c 100644 --- a/vrct_gui/ui_managers/Themes/_lightTheme.py +++ b/vrct_gui/ui_managers/Themes/_lightTheme.py @@ -324,6 +324,7 @@ def _lightTheme(base_color): REDO_ICON = getImageFileFromUiUtils("redo_icon_black.png"), SWAP_ICON = getImageFileFromUiUtils("swap_icon_black.png"), FOLDER_OPEN_ICON = getImageFileFromUiUtils("folder_open_icon_black.png"), + LINK_ICON = getImageFileFromUiUtils("link_icon_black.png"), ), ) diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index bbf7326d..85204af7 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -349,6 +349,13 @@ class UiScalingManager(): self.config_window.SB__MESSAGE_FORMAT__ENTRIES_BOTTOM_PADY = (0, self._calculateUiSize(14)) + self.config_window.SB__AUTHKEY_WEBPAGE_BUTTON_IPADX = self._calculateUiSize(12) + self.config_window.SB__AUTHKEY_WEBPAGE_BUTTON_IPADY = self._calculateUiSize(6) + self.config_window.SB__AUTHKEY_WEBPAGE_BUTTON_LABEL_FONT_SIZE = self._calculateUiSize(12) + self.config_window.SB__AUTHKEY_WEBPAGE_BUTTON_IMG_SIZE = self.dupTuple(self._calculateUiSize(12)) + self.config_window.SB__AUTHKEY_WEBPAGE_PADX_BETWEEN_LABEL_AND_ICON = self._calculateUiSize(10) + self.config_window.SB__AUTHKEY_WEBPAGE_BUTTON_TOP_PADY = self._calculateUiSize(10) + self.config_window.SB__BUTTON_IPADXY = self._calculateUiSize(16) self.config_window.SB__BUTTON_ICON_SIZE = self._calculateUiSize(24) diff --git a/vrct_gui/ui_utils/ui_utils.py b/vrct_gui/ui_utils/ui_utils.py index ca75416b..94f9adae 100644 --- a/vrct_gui/ui_utils/ui_utils.py +++ b/vrct_gui/ui_utils/ui_utils.py @@ -168,7 +168,27 @@ def createButtonWithImage(parent_widget, button_image_size, button_ipadxy, butto return button_wrapper -def createLabelButton(parent_widget, label_button_bg_color, label_button_hovered_bg_color, label_button_clicked_bg_color, label_button_ipadx, label_button_ipady, variable, font_family, font_size, text_color, label_button_clicked_command, label_button_position=None, label_button_padx_between_img=0, label_button_min_height=None, label_button_min_width=None): +def createLabelButton( + parent_widget, + label_button_bg_color, + label_button_hovered_bg_color, + label_button_clicked_bg_color, + label_button_ipadx, + label_button_ipady, + variable, + font_family, + font_size, + text_color, + label_button_clicked_command, + label_button_position=None, + label_button_padx_between_img=0, + image_file=None, + image_size=None, + image_widget_attr_name=None, + label_button_min_height=None, + label_button_min_width=None, + setattr_widget=None, + ): label_button_box = CTkFrame(parent_widget, corner_radius=6, fg_color=label_button_bg_color, cursor="hand2") @@ -181,7 +201,7 @@ def createLabelButton(parent_widget, label_button_bg_color, label_button_hovered label_button_box.grid_columnconfigure(0, minsize=label_button_min_width) label_button_label_wrapper = CTkFrame(label_button_box, corner_radius=0, fg_color=label_button_bg_color) - label_button_label_wrapper.grid(row=0, column=0, padx=label_button_ipadx, pady=label_button_ipady, sticky="ew") + label_button_label_wrapper.grid(row=0, column=0, padx=label_button_ipadx, pady=label_button_ipady) LABEL_COLUMN=0 if label_button_position == "center": @@ -198,25 +218,46 @@ def createLabelButton(parent_widget, label_button_bg_color, label_button_hovered label_button_label_widget.grid(row=0, column=LABEL_COLUMN, padx=(0, label_button_padx_between_img)) - bindEnterAndLeaveColor([label_button_label_wrapper, label_button_box, label_button_label_widget], label_button_hovered_bg_color, label_button_bg_color) - bindButtonPressColor([label_button_label_wrapper, label_button_box, label_button_label_widget], label_button_clicked_bg_color, label_button_hovered_bg_color) + register_widgets = [label_button_label_wrapper, label_button_box, label_button_label_widget] + if image_file is not None: + label_button_label_wrapper.grid_columnconfigure((0,3), weight=1) + label_button_img_widget = CTkLabel( + label_button_label_wrapper, + text=None, + corner_radius=0, + height=0, + image=CTkImage(image_file, size=image_size) + ) + + if image_widget_attr_name is not None: + setattr(setattr_widget, image_widget_attr_name, label_button_img_widget) + + label_button_img_widget.grid(row=0, column=LABEL_COLUMN+1) + register_widgets.append(label_button_img_widget) + bindEnterAndLeaveColor(register_widgets, label_button_hovered_bg_color, label_button_bg_color) + bindButtonPressColor(register_widgets, label_button_clicked_bg_color, label_button_hovered_bg_color) + - bindButtonReleaseFunction([label_button_label_wrapper, label_button_box, label_button_label_widget], label_button_clicked_command) def bindEventFromWidgets(): - bindButtonReleaseFunction([label_button_label_wrapper, label_button_box, label_button_label_widget], label_button_clicked_command) - bindEventFromWidgets() + bindButtonReleaseFunction(register_widgets, label_button_clicked_command) def unbindEventFromWidgets(): - unbindEnterLEaveButtonPressButtonReleaseFunction([label_button_label_wrapper, label_button_box, label_button_label_widget]) + unbindEnterLEaveButtonPressButtonReleaseFunction(register_widgets) + + bindEventFromWidgets() label_button_box.unbindFunction = unbindEventFromWidgets label_button_box.bindFunction = bindEventFromWidgets + if image_file is not None: + return (label_button_box, label_button_label_widget, label_button_img_widget) + else: + return (label_button_box, label_button_label_widget) + - return (label_button_box, label_button_label_widget)