From 86e43371e6701c2ea2a0f6ed6b68657656f1a96c Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 29 Jan 2024 16:38:13 +0900 Subject: [PATCH 01/43] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model=20:=20tokeni?= =?UTF-8?q?zer=E3=82=92=E3=83=AD=E3=83=BC=E3=82=AB=E3=83=AB=E3=81=AE?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E3=83=91=E3=82=B9=E3=81=AB=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- models/translation/translation_translator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/models/translation/translation_translator.py b/models/translation/translation_translator.py index f3d3c99e..d73bb0fb 100644 --- a/models/translation/translation_translator.py +++ b/models/translation/translation_translator.py @@ -28,6 +28,7 @@ class Translator(): directory_name = ctranslate2_weights[model_type]["directory_name"] tokenizer = ctranslate2_weights[model_type]["tokenizer"] weight_path = os.path.join(path, "weight", directory_name) + tokenizer_path = os.path.join(path, "weight", directory_name, "tokenizer") self.ctranslate2_translator = ctranslate2.Translator( weight_path, device="cpu", @@ -36,7 +37,7 @@ class Translator(): inter_threads=1, intra_threads=4 ) - self.ctranslate2_tokenizer = transformers.AutoTokenizer.from_pretrained(tokenizer) + self.ctranslate2_tokenizer = transformers.AutoTokenizer.from_pretrained(tokenizer, cache_dir=tokenizer_path) @staticmethod def getLanguageCode(translator_name, target_country, source_language, target_language): From 82eab0db3c8aaa9bd87afab142b72ab71440a6f0 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 29 Jan 2024 23:27:33 +0900 Subject: [PATCH 02/43] =?UTF-8?q?[bugfix]=20Model=20:=20AutoTokenizer.from?= =?UTF-8?q?=5Fpretrained=E3=81=AF=E9=9D=9EASCII=E6=96=87=E5=AD=97=E3=81=AB?= =?UTF-8?q?=E5=AF=BE=E5=BF=9C=E3=81=97=E3=81=A6=E3=81=84=E3=81=AA=E3=81=84?= =?UTF-8?q?=E3=81=9F=E3=82=81=E3=80=81=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ユーザー名が非ASCII文字の場合、絶対バスの場合失敗するので相対パスで対応 --- models/translation/translation_translator.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/models/translation/translation_translator.py b/models/translation/translation_translator.py index d73bb0fb..ea02e490 100644 --- a/models/translation/translation_translator.py +++ b/models/translation/translation_translator.py @@ -37,7 +37,12 @@ class Translator(): inter_threads=1, intra_threads=4 ) - self.ctranslate2_tokenizer = transformers.AutoTokenizer.from_pretrained(tokenizer, cache_dir=tokenizer_path) + 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("./weight", directory_name, "tokenizer") + self.ctranslate2_tokenizer = transformers.AutoTokenizer.from_pretrained(tokenizer, cache_dir=tokenizer_path) @staticmethod def getLanguageCode(translator_name, target_country, source_language, target_language): From 1ba580302ab443446556f52a4cda4809b34b8da6 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 30 Jan 2024 16:29:14 +0900 Subject: [PATCH 03/43] [Update] Main Window: Message Box. Add Undo and Redo feature. --- .../main_window/widgets/create_entry_message_box.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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..efc8284f 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=0, ) main_window.entry_message_box.grid(row=0, column=0, padx=settings.uism.TEXTBOX_ENTRY_PADX, pady=settings.uism.TEXTBOX_ENTRY_PADY, sticky="nsew") @@ -35,6 +38,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") From ba12e39bbc4187bad8831fa67d8e04350c3cd874 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 30 Jan 2024 02:15:05 +0900 Subject: [PATCH 04/43] =?UTF-8?q?[WIP/TEST]=20Model=20:=20faster-whisper?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../transcription_transcriber.py | 28 +++++++++++++++++++ requirements.txt | 3 +- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/models/transcription/transcription_transcriber.py b/models/transcription/transcription_transcriber.py index bf78566e..fbea0e74 100644 --- a/models/transcription/transcription_transcriber.py +++ b/models/transcription/transcription_transcriber.py @@ -6,6 +6,10 @@ from datetime import timedelta from pyaudiowpatch import get_sample_size, paInt16 from .transcription_languages import transcription_lang +import torch +import numpy as np +from faster_whisper import WhisperModel + PHRASE_TIMEOUT = 3 MAX_PHRASES = 10 @@ -26,6 +30,7 @@ class AudioTranscriber: "new_phrase": True, "process_data_func": self.processSpeakerData if speaker else self.processSpeakerData } + self.whisper_model = WhisperModel("base", device="cpu", device_index=0, compute_type="int8", cpu_threads=4, num_workers=1) def transcribeAudioQueue(self, audio_queue, language, country): # while True: @@ -38,6 +43,29 @@ class AudioTranscriber: # os.close(fd) audio_data = self.audio_sources["process_data_func"]() text = self.audio_recognizer.recognize_google(audio_data, language=transcription_lang[language][country]) + + 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="ja", + word_timestamps=False, + without_timestamps=True, + task="transcribe", + vad_filter=False, + ) + _text = "" + for s in segments: + if s.avg_logprob < -0.8 or s.no_speech_prob > 0.6: + continue + _text += s.text + print(_text) + except Exception: pass finally: diff --git a/requirements.txt b/requirements.txt index b6e14d85..68a6ce15 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,5 @@ CTkToolTip == 0.8 pyinstaller==6.2.0 transformers[torch] sentencepiece==0.1.99 -ctranslate2==3.21.0 \ No newline at end of file +ctranslate2==3.21.0 +faster-whisper==0.10.0 \ No newline at end of file From 9cd1831ecbb4f313347c90d6971eb3c7a075812b Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 30 Jan 2024 18:21:55 +0900 Subject: [PATCH 05/43] =?UTF-8?q?[WIP/TEST]=20faster-whisper=E3=81=8C?= =?UTF-8?q?=E6=9C=80=E4=BD=8E=E9=99=90=E5=8B=95=E3=81=8F=E5=BD=A2=E3=81=A7?= =?UTF-8?q?=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit config.jsonで設定変更で実行可能 --- config.py | 71 ++- controller.py | 4 +- main.py | 2 +- model.py | 14 +- .../transcription/transcription_languages.py | 443 ++++++++++++++---- .../transcription_transcriber.py | 70 +-- models/transcription/transcription_utils.py | 40 +- view.py | 4 +- 8 files changed, 511 insertions(+), 137 deletions(-) diff --git a/config.py b/config.py index 371ec121..6acf5e3f 100644 --- a/config.py +++ b/config.py @@ -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 @@ -263,6 +267,17 @@ class Config: self._SELECTED_TAB_TARGET_LANGUAGES = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + @property + @json_serializable('SELECTED_RECOGNIZER') + def SELECTED_RECOGNIZER(self): + return self._SELECTED_RECOGNIZER + + @SELECTED_RECOGNIZER.setter + def SELECTED_RECOGNIZER(self, value): + if isinstance(value, str): + self._SELECTED_RECOGNIZER = 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 +584,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_RECOGNIZER_FEATURE') + def USE_RECOGNIZER_FEATURE(self): + return self._USE_RECOGNIZER_FEATURE - @WEIGHT_TYPE.setter - def WEIGHT_TYPE(self, value): + @USE_RECOGNIZER_FEATURE.setter + def USE_RECOGNIZER_FEATURE(self, value): + if isinstance(value, bool): + self._USE_RECOGNIZER_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 @@ -756,6 +793,23 @@ class Config: "Small": "Small", "Large": "Large", } + + self._SELECTABLE_WHISPER_WEIGHT_TYPE_DICT = { + # {Save json str}: {i18n_placeholder} pairs + "tiny": "tiny", + "tiny.en": "tiny.en", + "base": "base", + "base.en": "base.en", + "small": "small", + "small.en": "small.en", + "medium": "medium", + "medium.en": "medium.en", + "large-v1": "large-v1", + "large-v2": "large-v2", + "large-v3": "large-v3", + "large": "large", + } + self._MAX_MIC_ENERGY_THRESHOLD = 2000 self._MAX_SPEAKER_ENERGY_THRESHOLD = 4000 @@ -795,6 +849,7 @@ class Config: "2":"English\n(United States)", "3":"English\n(United States)", } + self._SELECTED_RECOGNIZER = "Google" self._IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = False ## Config Window @@ -831,7 +886,9 @@ class Config: "DeepL_API": None, } self._USE_TRANSLATION_FEATURE = True - self._WEIGHT_TYPE = "Small" + self._CTRANSLATE2_WEIGHT_TYPE = "Small" + self._USE_RECOGNIZER_FEATURE = True + self._WHISPER_WEIGHT_TYPE = "base" self._SEND_MESSAGE_FORMAT = "[message]" self._SEND_MESSAGE_FORMAT_WITH_T = "[message]([translation])" self._RECEIVED_MESSAGE_FORMAT = "[message]" diff --git a/controller.py b/controller.py index f9e9a5b3..9d44b491 100644 --- a/controller.py +++ b/controller.py @@ -505,8 +505,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 diff --git a/main.py b/main.py index 4810cbe5..cf80e289 100644 --- a/main.py +++ b/main.py @@ -10,7 +10,7 @@ if __name__ == "__main__": from config import config from models.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) splash.toProgress(0) import controller diff --git a/model.py b/model.py index 573659a7..61ff24d7 100644 --- a/model.py +++ b/model.py @@ -65,14 +65,14 @@ class Model: 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.translator.changeCTranslate2Model(config.PATH_LOCAL, config.CTRANSLATE2_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 resetKeywordProcessor(self): del self.keyword_processor @@ -335,9 +335,12 @@ class Model: source=self.mic_audio_recorder.source, phrase_timeout=phase_timeout, max_phrases=config.INPUT_MIC_MAX_PHRASES, + whisper_enabled=config.USE_RECOGNIZER_FEATURE, + whisper_weight_type=config.WHISPER_WEIGHT_TYPE, + whisper_weight_path=os_path.join(config.PATH_LOCAL, "weight", "whisper"), ) def sendMicTranscript(): - mic_transcriber.transcribeAudioQueue(mic_audio_queue, config.SOURCE_LANGUAGE, config.SOURCE_COUNTRY) + mic_transcriber.transcribeAudioQueue(config.SELECTED_RECOGNIZER, mic_audio_queue, config.SOURCE_LANGUAGE, config.SOURCE_COUNTRY) message = mic_transcriber.getTranscript() try: fnc(message) @@ -416,6 +419,9 @@ class Model: source=self.speaker_audio_recorder.source, phrase_timeout=phase_timeout, max_phrases=config.INPUT_SPEAKER_MAX_PHRASES, + whisper_enabled=config.USE_RECOGNIZER_FEATURE, + whisper_weight_type=config.WHISPER_WEIGHT_TYPE, + whisper_weight_path=os_path.join(config.PATH_LOCAL, "weight", "whisper"), ) def sendSpeakerTranscript(): speaker_transcriber.transcribeAudioQueue(speaker_audio_queue, config.TARGET_LANGUAGE, config.TARGET_COUNTRY) 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_transcriber.py b/models/transcription/transcription_transcriber.py index fbea0e74..526c12dc 100644 --- a/models/transcription/transcription_transcriber.py +++ b/models/transcription/transcription_transcriber.py @@ -14,7 +14,7 @@ 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, whisper_enabled, whisper_weight_type, whisper_weight_path): self.speaker = speaker self.phrase_timeout = phrase_timeout self.max_phrases = max_phrases @@ -30,47 +30,59 @@ class AudioTranscriber: "new_phrase": True, "process_data_func": self.processSpeakerData if speaker else self.processSpeakerData } - self.whisper_model = WhisperModel("base", device="cpu", device_index=0, compute_type="int8", cpu_threads=4, num_workers=1) + if whisper_enabled is True: + self.whisper_model = WhisperModel( + model_size_or_path=whisper_weight_type, + device="cpu", + device_index=0, + compute_type="int8", + cpu_threads=4, + num_workers=1, + download_root=whisper_weight_path) + else: + self.whisper_model = None - def transcribeAudioQueue(self, audio_queue, language, country): + def transcribeAudioQueue(self, recognizer, 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]) + # Whisperが使用できない場合はGoogle Speech-to-Textを使用する + if recognizer == "Whisper": + if self.whisper_model is None: + recognizer = "Google" - 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="ja", - word_timestamps=False, - without_timestamps=True, - task="transcribe", - vad_filter=False, - ) - _text = "" - for s in segments: - if s.avg_logprob < -0.8 or s.no_speech_prob > 0.6: - continue - _text += s.text - print(_text) + audio_data = self.audio_sources["process_data_func"]() + match recognizer: + case "Google": + text = self.audio_recognizer.recognize_google(audio_data, language=transcription_lang[language][country][recognizer]) + 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][recognizer], + 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_utils.py b/models/transcription/transcription_utils.py index f40defeb..8de17e7e 100644 --- a/models/transcription/transcription_utils.py +++ b/models/transcription/transcription_utils.py @@ -1,4 +1,8 @@ from pyaudiowpatch import PyAudio, paWASAPI +from faster_whisper.utils import download_model +import logging +logger = logging.getLogger('faster_whisper') +logger.setLevel(logging.CRITICAL) def getInputDevices(): devices = {} @@ -44,4 +48,38 @@ def getDefaultOutputDevice(): if default_speakers["name"] in loopback["name"]: default_device = loopback return default_device - return {"name":"NoDevice"} \ No newline at end of file + return {"name":"NoDevice"} + +def downloadWhisperWeight(weight_type, path): + result = False + try: + download_model( + weight_type, + cache_dir=path) + result = True + except Exception: + pass + return result + +def checkWhisperWeight(weight_type, path): + result = False + try: + result = download_model( + weight_type, + local_files_only=True, + cache_dir=path) + result = True + except Exception: + pass + return result + +if __name__ == "__main__": + + + downloadWhisperWeight("base", "./weight/whisper/") + + from faster_whisper import WhisperModel + whisper_model = WhisperModel("base", device="cpu", device_index=0, compute_type="int8", cpu_threads=4, num_workers=1, download_root="./weight/whisper/") + + print(checkWhisperWeight("base", "./weight/whisper/")) + print(checkWhisperWeight("tiny", "./weight/whisper/")) \ No newline at end of file diff --git a/view.py b/view.py index 34711688..cf90dcfa 100644 --- a/view.py +++ b/view.py @@ -280,7 +280,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( @@ -1069,7 +1069,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) From 10b8d115a118f3cfeaf400af76186115c084950b Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 31 Jan 2024 22:50:31 +0900 Subject: [PATCH 06/43] =?UTF-8?q?[WIP/TEST]=20faster-whisper=20model=20wei?= =?UTF-8?q?ght=20=E3=81=AE=E3=83=80=E3=82=A6=E3=83=B3=E3=83=AD=E3=83=BC?= =?UTF-8?q?=E3=83=89/=E3=83=99=E3=83=AA=E3=83=95=E3=82=A1=E3=82=A4?= =?UTF-8?q?=E5=87=A6=E7=90=86=E3=82=92=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 8 +- model.py | 4 +- .../transcription_transcriber.py | 13 +-- models/transcription/transcription_utils.py | 40 +------- models/transcription/transcription_whisper.py | 98 +++++++++++++++++++ 5 files changed, 111 insertions(+), 52 deletions(-) create mode 100644 models/transcription/transcription_whisper.py diff --git a/main.py b/main.py index cf80e289..4aaa7232 100644 --- a/main.py +++ b/main.py @@ -11,8 +11,14 @@ if __name__ == "__main__": from models.translation.utils import downloadCTranslate2Weight if config.USE_TRANSLATION_FEATURE is True: downloadCTranslate2Weight(config.PATH_LOCAL, config.CTRANSLATE2_WEIGHT_TYPE, splash.updateDownloadProgress) - splash.toProgress(0) + + # whisperのダウンロードの説明に変更する必要あり + if config.USE_RECOGNIZER_FEATURE is True: + from models.transcription.transcription_whisper import downloadWhisperWeight + 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 61ff24d7..6b73bece 100644 --- a/model.py +++ b/model.py @@ -337,7 +337,7 @@ class Model: max_phrases=config.INPUT_MIC_MAX_PHRASES, whisper_enabled=config.USE_RECOGNIZER_FEATURE, whisper_weight_type=config.WHISPER_WEIGHT_TYPE, - whisper_weight_path=os_path.join(config.PATH_LOCAL, "weight", "whisper"), + root=config.PATH_LOCAL, ) def sendMicTranscript(): mic_transcriber.transcribeAudioQueue(config.SELECTED_RECOGNIZER, mic_audio_queue, config.SOURCE_LANGUAGE, config.SOURCE_COUNTRY) @@ -421,7 +421,7 @@ class Model: max_phrases=config.INPUT_SPEAKER_MAX_PHRASES, whisper_enabled=config.USE_RECOGNIZER_FEATURE, whisper_weight_type=config.WHISPER_WEIGHT_TYPE, - whisper_weight_path=os_path.join(config.PATH_LOCAL, "weight", "whisper"), + root=config.PATH_LOCAL, ) def sendSpeakerTranscript(): speaker_transcriber.transcribeAudioQueue(speaker_audio_queue, config.TARGET_LANGUAGE, config.TARGET_COUNTRY) diff --git a/models/transcription/transcription_transcriber.py b/models/transcription/transcription_transcriber.py index 526c12dc..0f5b1790 100644 --- a/models/transcription/transcription_transcriber.py +++ b/models/transcription/transcription_transcriber.py @@ -5,16 +5,16 @@ 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 import torch import numpy as np -from faster_whisper import WhisperModel PHRASE_TIMEOUT = 3 MAX_PHRASES = 10 class AudioTranscriber: - def __init__(self, speaker, source, phrase_timeout, max_phrases, whisper_enabled, whisper_weight_type, whisper_weight_path): + def __init__(self, speaker, source, phrase_timeout, max_phrases, whisper_enabled, whisper_weight_type, root): self.speaker = speaker self.phrase_timeout = phrase_timeout self.max_phrases = max_phrases @@ -31,14 +31,7 @@ class AudioTranscriber: "process_data_func": self.processSpeakerData if speaker else self.processSpeakerData } if whisper_enabled is True: - self.whisper_model = WhisperModel( - model_size_or_path=whisper_weight_type, - device="cpu", - device_index=0, - compute_type="int8", - cpu_threads=4, - num_workers=1, - download_root=whisper_weight_path) + self.whisper_model = getWhisperModel(root, whisper_weight_type) else: self.whisper_model = None diff --git a/models/transcription/transcription_utils.py b/models/transcription/transcription_utils.py index 8de17e7e..f40defeb 100644 --- a/models/transcription/transcription_utils.py +++ b/models/transcription/transcription_utils.py @@ -1,8 +1,4 @@ from pyaudiowpatch import PyAudio, paWASAPI -from faster_whisper.utils import download_model -import logging -logger = logging.getLogger('faster_whisper') -logger.setLevel(logging.CRITICAL) def getInputDevices(): devices = {} @@ -48,38 +44,4 @@ def getDefaultOutputDevice(): if default_speakers["name"] in loopback["name"]: default_device = loopback return default_device - return {"name":"NoDevice"} - -def downloadWhisperWeight(weight_type, path): - result = False - try: - download_model( - weight_type, - cache_dir=path) - result = True - except Exception: - pass - return result - -def checkWhisperWeight(weight_type, path): - result = False - try: - result = download_model( - weight_type, - local_files_only=True, - cache_dir=path) - result = True - except Exception: - pass - return result - -if __name__ == "__main__": - - - downloadWhisperWeight("base", "./weight/whisper/") - - from faster_whisper import WhisperModel - whisper_model = WhisperModel("base", device="cpu", device_index=0, compute_type="int8", cpu_threads=4, num_workers=1, download_root="./weight/whisper/") - - print(checkWhisperWeight("base", "./weight/whisper/")) - print(checkWhisperWeight("tiny", "./weight/whisper/")) \ No newline at end of file + return {"name":"NoDevice"} \ No newline at end of file diff --git a/models/transcription/transcription_whisper.py b/models/transcription/transcription_whisper.py new file mode 100644 index 00000000..dc606cb7 --- /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.en": "Systran/faster-whisper-tiny.en", + "tiny": "Systran/faster-whisper-tiny", + "base.en": "Systran/faster-whisper-base.en", + "base": "Systran/faster-whisper-base", + "small.en": "Systran/faster-whisper-small.en", + "small": "Systran/faster-whisper-small", + "medium.en": "Systran/faster-whisper-medium.en", + "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", + "large": "Systran/faster-whisper-large-v3", +} + +_FILENAMES = [ + "config.json", + "preprocessor_config.json", + "model.bin", + "tokenizer.json", + "vocabulary.txt", +] + +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(path): + 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, "weight", "whisper", weight_type) + os_makedirs(path, exist_ok=True) + if checkWhisperWeight(path) 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, "weight", "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) + + downloadWhisperWeight("./", "tiny", callback) + downloadWhisperWeight("./", "base", callback) + downloadWhisperWeight("./", "small", callback) + downloadWhisperWeight("./", "medium", callback) + downloadWhisperWeight("./", "large", callback) \ No newline at end of file From e4c685d3822bf8efd7636e3b302e3f1e729eb7e5 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Thu, 1 Feb 2024 13:40:24 +0900 Subject: [PATCH 07/43] [WIP/TEST] Config : USE_RECOGNIZER_FEATURE -> USE_WHISPER_FEATURE --- config.py | 19 +++++++------------ main.py | 2 +- model.py | 4 ++-- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/config.py b/config.py index 6acf5e3f..c59c0f17 100644 --- a/config.py +++ b/config.py @@ -584,14 +584,14 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property - @json_serializable('USE_RECOGNIZER_FEATURE') - def USE_RECOGNIZER_FEATURE(self): - return self._USE_RECOGNIZER_FEATURE + @json_serializable('USE_WHISPER_FEATURE') + def USE_WHISPER_FEATURE(self): + return self._USE_WHISPER_FEATURE - @USE_RECOGNIZER_FEATURE.setter - def USE_RECOGNIZER_FEATURE(self, value): + @USE_WHISPER_FEATURE.setter + def USE_WHISPER_FEATURE(self, value): if isinstance(value, bool): - self._USE_RECOGNIZER_FEATURE = value + self._USE_WHISPER_FEATURE = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property @@ -797,17 +797,12 @@ class Config: self._SELECTABLE_WHISPER_WEIGHT_TYPE_DICT = { # {Save json str}: {i18n_placeholder} pairs "tiny": "tiny", - "tiny.en": "tiny.en", "base": "base", - "base.en": "base.en", "small": "small", - "small.en": "small.en", "medium": "medium", - "medium.en": "medium.en", "large-v1": "large-v1", "large-v2": "large-v2", "large-v3": "large-v3", - "large": "large", } self._MAX_MIC_ENERGY_THRESHOLD = 2000 @@ -887,7 +882,7 @@ class Config: } self._USE_TRANSLATION_FEATURE = True self._CTRANSLATE2_WEIGHT_TYPE = "Small" - self._USE_RECOGNIZER_FEATURE = True + self._USE_WHISPER_FEATURE = True self._WHISPER_WEIGHT_TYPE = "base" self._SEND_MESSAGE_FORMAT = "[message]" self._SEND_MESSAGE_FORMAT_WITH_T = "[message]([translation])" diff --git a/main.py b/main.py index 4aaa7232..37bc53af 100644 --- a/main.py +++ b/main.py @@ -14,7 +14,7 @@ if __name__ == "__main__": splash.toProgress(0) # whisperのダウンロードの説明に変更する必要あり - if config.USE_RECOGNIZER_FEATURE is True: + if config.USE_WHISPER_FEATURE is True: from models.transcription.transcription_whisper import downloadWhisperWeight downloadWhisperWeight(config.PATH_LOCAL, config.WHISPER_WEIGHT_TYPE, splash.updateDownloadProgress) splash.toProgress(0) diff --git a/model.py b/model.py index 6b73bece..98d0a896 100644 --- a/model.py +++ b/model.py @@ -335,7 +335,7 @@ class Model: source=self.mic_audio_recorder.source, phrase_timeout=phase_timeout, max_phrases=config.INPUT_MIC_MAX_PHRASES, - whisper_enabled=config.USE_RECOGNIZER_FEATURE, + whisper_enabled=config.USE_WHISPER_FEATURE, whisper_weight_type=config.WHISPER_WEIGHT_TYPE, root=config.PATH_LOCAL, ) @@ -419,7 +419,7 @@ class Model: source=self.speaker_audio_recorder.source, phrase_timeout=phase_timeout, max_phrases=config.INPUT_SPEAKER_MAX_PHRASES, - whisper_enabled=config.USE_RECOGNIZER_FEATURE, + whisper_enabled=config.USE_WHISPER_FEATURE, whisper_weight_type=config.WHISPER_WEIGHT_TYPE, root=config.PATH_LOCAL, ) From 7cb8c473d4adb8dc1377fa13eb8f14a29bee2afe Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Thu, 1 Feb 2024 13:41:31 +0900 Subject: [PATCH 08/43] =?UTF-8?q?[WIP/TEST]=20Model=20:=20large=E3=83=A2?= =?UTF-8?q?=E3=83=87=E3=83=AB=E3=82=92=E3=83=80=E3=82=A6=E3=83=B3=E3=83=AD?= =?UTF-8?q?=E3=83=BC=E3=83=89=E5=87=A6=E7=90=86=E3=82=92=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?/=20en=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- models/transcription/transcription_whisper.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/models/transcription/transcription_whisper.py b/models/transcription/transcription_whisper.py index dc606cb7..67ad61f0 100644 --- a/models/transcription/transcription_whisper.py +++ b/models/transcription/transcription_whisper.py @@ -8,18 +8,13 @@ logger = logging.getLogger('faster_whisper') logger.setLevel(logging.CRITICAL) _MODELS = { - "tiny.en": "Systran/faster-whisper-tiny.en", "tiny": "Systran/faster-whisper-tiny", - "base.en": "Systran/faster-whisper-base.en", "base": "Systran/faster-whisper-base", - "small.en": "Systran/faster-whisper-small.en", "small": "Systran/faster-whisper-small", - "medium.en": "Systran/faster-whisper-medium.en", "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", - "large": "Systran/faster-whisper-large-v3", } _FILENAMES = [ @@ -28,6 +23,7 @@ _FILENAMES = [ "model.bin", "tokenizer.json", "vocabulary.txt", + "vocabulary.json", ] def downloadFile(url, path, func=None): @@ -67,6 +63,7 @@ def downloadWhisperWeight(root, weight_type, callbackFunc): path = os_path.join(root, "weight", "whisper", weight_type) os_makedirs(path, exist_ok=True) if checkWhisperWeight(path) is True: + print("weight_type:", weight_type, checkWhisperWeight(path)) return for filename in _FILENAMES: @@ -75,6 +72,8 @@ def downloadWhisperWeight(root, weight_type, callbackFunc): url = huggingface_hub.hf_hub_url(_MODELS[weight_type], filename) downloadFile(url, file_path, func=callbackFunc) + print("weight_type:", weight_type, checkWhisperWeight(path)) + def getWhisperModel(root, weight_type): path = os_path.join(root, "weight", "whisper", weight_type) return WhisperModel( @@ -90,9 +89,12 @@ def getWhisperModel(root, weight_type): if __name__ == "__main__": def callback(value): print(value) + pass downloadWhisperWeight("./", "tiny", callback) downloadWhisperWeight("./", "base", callback) downloadWhisperWeight("./", "small", callback) downloadWhisperWeight("./", "medium", callback) - downloadWhisperWeight("./", "large", callback) \ No newline at end of file + downloadWhisperWeight("./", "large-v1", callback) + downloadWhisperWeight("./", "large-v2", callback) + downloadWhisperWeight("./", "large-v3", callback) \ No newline at end of file From 1de239549f7dc3c00b55fefb1dc46da35aec2b24 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Thu, 1 Feb 2024 15:49:17 +0900 Subject: [PATCH 09/43] =?UTF-8?q?[WIP/TEST]=20Model=20:=20=E3=83=A2?= =?UTF-8?q?=E3=83=87=E3=83=AB=E3=81=AE=E4=BF=9D=E5=AD=98=E4=BD=8D=E7=BD=AE?= =?UTF-8?q?=E3=81=AE=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - speakerの文字起こし処理のバグを修正 --- .gitignore | 2 +- main.py | 5 ++-- model.py | 4 ++-- models/transcription/transcription_whisper.py | 7 ++---- models/translation/translation_translator.py | 8 +++---- .../{utils.py => translation_utils.py} | 24 +++++++++---------- 6 files changed, 23 insertions(+), 27 deletions(-) rename models/translation/{utils.py => translation_utils.py} (78%) 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/main.py b/main.py index 37bc53af..0df15326 100644 --- a/main.py +++ b/main.py @@ -8,14 +8,13 @@ if __name__ == "__main__": splash.showSplash() from config import config - from models.translation.utils import downloadCTranslate2Weight + from models.translation.translation_utils import downloadCTranslate2Weight if config.USE_TRANSLATION_FEATURE is True: downloadCTranslate2Weight(config.PATH_LOCAL, config.CTRANSLATE2_WEIGHT_TYPE, splash.updateDownloadProgress) - splash.toProgress(0) + from models.transcription.transcription_whisper import downloadWhisperWeight # whisperのダウンロードの説明に変更する必要あり if config.USE_WHISPER_FEATURE is True: - from models.transcription.transcription_whisper import downloadWhisperWeight downloadWhisperWeight(config.PATH_LOCAL, config.WHISPER_WEIGHT_TYPE, splash.updateDownloadProgress) splash.toProgress(0) diff --git a/model.py b/model.py index 98d0a896..2c29d4c7 100644 --- a/model.py +++ b/model.py @@ -23,7 +23,7 @@ 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 config import config class threadFnc(Thread): @@ -424,7 +424,7 @@ class Model: root=config.PATH_LOCAL, ) def sendSpeakerTranscript(): - speaker_transcriber.transcribeAudioQueue(speaker_audio_queue, config.TARGET_LANGUAGE, config.TARGET_COUNTRY) + speaker_transcriber.transcribeAudioQueue(config.SELECTED_RECOGNIZER, speaker_audio_queue, config.TARGET_LANGUAGE, config.TARGET_COUNTRY) message = speaker_transcriber.getTranscript() try: fnc(message) diff --git a/models/transcription/transcription_whisper.py b/models/transcription/transcription_whisper.py index 67ad61f0..e30fee2d 100644 --- a/models/transcription/transcription_whisper.py +++ b/models/transcription/transcription_whisper.py @@ -60,10 +60,9 @@ def checkWhisperWeight(path): return result def downloadWhisperWeight(root, weight_type, callbackFunc): - path = os_path.join(root, "weight", "whisper", weight_type) + path = os_path.join(root, "weights", "whisper", weight_type) os_makedirs(path, exist_ok=True) if checkWhisperWeight(path) is True: - print("weight_type:", weight_type, checkWhisperWeight(path)) return for filename in _FILENAMES: @@ -72,10 +71,8 @@ def downloadWhisperWeight(root, weight_type, callbackFunc): url = huggingface_hub.hf_hub_url(_MODELS[weight_type], filename) downloadFile(url, file_path, func=callbackFunc) - print("weight_type:", weight_type, checkWhisperWeight(path)) - def getWhisperModel(root, weight_type): - path = os_path.join(root, "weight", "whisper", weight_type) + path = os_path.join(root, "weights", "whisper", weight_type) return WhisperModel( path, device="cpu", diff --git a/models/translation/translation_translator.py b/models/translation/translation_translator.py index ea02e490..c966c672 100644 --- a/models/translation/translation_translator.py +++ b/models/translation/translation_translator.py @@ -2,7 +2,7 @@ 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,8 +27,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) - tokenizer_path = os.path.join(path, "weight", directory_name, "tokenizer") + 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", @@ -41,7 +41,7 @@ class Translator(): 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("./weight", directory_name, "tokenizer") + tokenizer_path = os.path.join("./weights", "ctranslate2", directory_name, "tokenizer") self.ctranslate2_tokenizer = transformers.AutoTokenizer.from_pretrained(tokenizer, cache_dir=tokenizer_path) @staticmethod 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 From 6fbc2ede3a942f58cfc6282d2922ce6fc49809f6 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 1 Feb 2024 15:54:22 +0900 Subject: [PATCH 10/43] =?UTF-8?q?[Update]=20Main=20Window:=20Message=20box?= =?UTF-8?q?.=20=E9=80=81=E4=BF=A1=E3=81=97=E3=81=9F=E3=83=A1=E3=83=83?= =?UTF-8?q?=E3=82=BB=E3=83=BC=E3=82=B8=E3=82=92=E3=80=81=E3=82=AD=E3=83=BC?= =?UTF-8?q?=E3=83=9C=E3=83=BC=E3=83=89=E7=9F=A2=E5=8D=B0=E3=82=AD=E3=83=BC?= =?UTF-8?q?=E4=B8=8A=E4=B8=8B=E3=81=A7=E5=91=BC=E3=81=B3=E5=87=BA=E3=81=9B?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 20 ++++++++++++++++ controller.py | 23 +++++++++++++++++++ view.py | 13 +++++++++++ .../widgets/create_entry_message_box.py | 8 +++++++ 4 files changed, 64 insertions(+) diff --git a/config.py b/config.py index 371ec121..9a7c4e83 100644 --- a/config.py +++ b/config.py @@ -197,6 +197,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 @@ -770,6 +788,8 @@ 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 # Save Json Data diff --git a/controller.py b/controller.py index f9e9a5b3..f7b3f635 100644 --- a/controller.py +++ b/controller.py @@ -200,6 +200,8 @@ def stopThreadingTranscriptionReceiveMessageOnOpenConfigWindow(): # func message box def sendChatMessage(message): if len(message) > 0: + config.SENT_MESSAGES_LOG.append(view.getTextFromMessageBox()) + config.CURRENT_SENT_MESSAGES_LOG_INDEX = len(config.SENT_MESSAGES_LOG) translation = "" if config.ENABLE_TRANSLATION is False: pass @@ -249,6 +251,25 @@ def messageBoxFocusOut(e): if config.ENABLE_SEND_MESSAGE_TO_VRC is True: model.oscStopSendTyping() +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) @@ -949,6 +970,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={ diff --git a/view.py b/view.py index 34711688..720648ce 100644 --- a/view.py +++ b/view.py @@ -184,6 +184,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=""), @@ -562,6 +564,10 @@ class View(): 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")) entry_message_box.bind("", main_window_registers.get("message_box_bind_FocusOut")) @@ -1637,6 +1643,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/main_window/widgets/create_entry_message_box.py b/vrct_gui/main_window/widgets/create_entry_message_box.py index efc8284f..0d01d030 100644 --- a/vrct_gui/main_window/widgets/create_entry_message_box.py +++ b/vrct_gui/main_window/widgets/create_entry_message_box.py @@ -31,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) From 6d949858b8c9a633c6d6e2154ff3d34d2539bccf Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 1 Feb 2024 16:16:09 +0900 Subject: [PATCH 11/43] =?UTF-8?q?[Update/Refactor]=20Main=20Window:=20?= =?UTF-8?q?=E9=80=81=E4=BF=A1=E6=B8=88=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC?= =?UTF-8?q?=E3=82=B8=E3=81=AE=E3=83=AD=E3=83=BC=E3=83=AB=E3=83=90=E3=83=83?= =?UTF-8?q?=E3=82=AF=E6=A9=9F=E8=83=BD=E8=AA=BF=E6=95=B4(=E3=83=AD?= =?UTF-8?q?=E3=83=BC=E3=83=AB=E3=83=90=E3=83=83=E3=82=AF=E5=8F=AF=E8=83=BD?= =?UTF-8?q?=E3=81=AA=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=81=AB?= =?UTF-8?q?=E3=80=81=E3=83=9E=E3=82=A4=E3=82=AF=E3=81=8B=E3=82=89=E3=81=AE?= =?UTF-8?q?=E5=85=A5=E5=8A=9B=E6=99=82=E3=82=92=E8=BF=BD=E5=8A=A0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Redo Undoの履歴保存サイズを設定。理由は、上記の機能使用時もRedo Undoの保存タイミングになるので、使用の際膨大な数になる可能性があるため上限を設定。設定値は今後変更する可能性は全然ある。 --- controller.py | 8 ++++++-- vrct_gui/main_window/widgets/create_entry_message_box.py | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/controller.py b/controller.py index f7b3f635..5a80c98f 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,8 +201,7 @@ def stopThreadingTranscriptionReceiveMessageOnOpenConfigWindow(): # func message box def sendChatMessage(message): if len(message) > 0: - config.SENT_MESSAGES_LOG.append(view.getTextFromMessageBox()) - config.CURRENT_SENT_MESSAGES_LOG_INDEX = len(config.SENT_MESSAGES_LOG) + addSentMessageLog(message) translation = "" if config.ENABLE_TRANSLATION is False: pass @@ -251,6 +251,10 @@ 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 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 0d01d030..d153749e 100644 --- a/vrct_gui/main_window/widgets/create_entry_message_box.py +++ b/vrct_gui/main_window/widgets/create_entry_message_box.py @@ -21,7 +21,7 @@ def createEntryMessageBox(settings, main_window, view_variable): font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.TEXTBOX_ENTRY_FONT_SIZE, weight="normal"), undo=True, autoseparators=True, - maxundo=0, + 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") From 78b8cb590984a36b722801db0d3f1a63953cad93 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Fri, 2 Feb 2024 13:14:56 +0900 Subject: [PATCH 12/43] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20install.bat=20:=20?= =?UTF-8?q?package=20version=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- install.bat | 4 +--- requirements.txt | 8 +++++--- 2 files changed, 6 insertions(+), 6 deletions(-) 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/requirements.txt b/requirements.txt index 68a6ce15..cedd1568 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,9 @@ 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 -faster-whisper==0.10.0 \ No newline at end of file +ctranslate2==3.24.0 +faster-whisper==0.10.0 +translators @ git+https://github.com/misyaguziya/translators@master +SpeechRecognition @ git+https://github.com/misyaguziya/custom_speech_recognition@master \ No newline at end of file From ee5c4c05ce0c5c9aa9a5aa6279ba3605cd0eede1 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 2 Feb 2024 18:08:18 +0900 Subject: [PATCH 13/43] =?UTF-8?q?[WIP/TEST]=20UI:=20=E6=A9=9F=E8=83=BD?= =?UTF-8?q?=E3=81=A8=E8=A6=8B=E3=81=9F=E7=9B=AE=E3=82=92=E7=B9=8B=E3=81=8E?= =?UTF-8?q?=E3=81=BE=E3=81=97=E3=81=9F=E3=80=82=E8=A8=AD=E5=AE=9A=E7=94=BB?= =?UTF-8?q?=E9=9D=A2=E3=81=8B=E3=82=89=E3=81=84=E3=81=98=E3=82=8C=E3=81=BE?= =?UTF-8?q?=E3=81=99=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 33 ++++++++++++ locales/en.yml | 16 ++++++ view.py | 52 +++++++++++++++++++ .../createSideMenuAndSettingsBoxContainers.py | 6 ++- .../setting_box_transcription/__init__.py | 3 +- .../createSettingBox_InternalModel.py | 37 +++++++++++++ 6 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_InternalModel.py diff --git a/controller.py b/controller.py index 9d44b491..724d2cf3 100644 --- a/controller.py +++ b/controller.py @@ -767,6 +767,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() + else: + view.closeWhisperWeightTypeWidget() + +def callbackSetWhisperWeightType(value): + print("callbackSetWhisperWeightType", value) + config.WHISPER_WEIGHT_TYPE = str(value) + view.updateSelectedWhisperWeightType(config.WHISPER_WEIGHT_TYPE) + # view.setWidgetsStatus_changeWeightType_Pending() + # if model.checkCTranslatorCTranslate2ModelWeight(): + # config.IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION = False + # def callback(): + # model.changeTranslatorCTranslate2Model() + # view.useTranslationFeatureProcess("Normal") + # view.setWidgetsStatus_changeWeightType_Done() + # th_callback = Thread(target=callback) + # th_callback.daemon = True + # th_callback.start() + # else: + # config.IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION = True + # view.useTranslationFeatureProcess("Restart") + # view.setWidgetsStatus_changeWeightType_Done() + # view.showRestartButtonIfRequired() + # Others Tab def callbackSetEnableAutoClearMessageBox(value): @@ -993,6 +1022,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/locales/en.yml b/locales/en.yml index 2806ea91..f68aa32c 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -79,6 +79,7 @@ config_window: transcription: Transcription transcription_mic: Mic transcription_speaker: Speaker + transcription_internal_model: Internal Model others: Others others_send_message_formats: Message Formats (Send) others_received_message_formats: Message Formats (Received) @@ -125,6 +126,21 @@ config_window: small: "Basic model (%{capacity})" large: "High accuracy model (%{capacity})" + use_whisper_feature: + label: Use Whisper Feature + desc: Description + + whisper_weight_type: + label: Select Whisper Model + desc: Description + tiny: "tiny model (%{capacity})" + base: "base model (%{capacity})" + small: "small model (%{capacity})" + medium: "medium model (%{capacity})" + large_v1: "large_v1 model (%{capacity})" + large_v2: "large_v2 model (%{capacity})" + large_v3: "large_v3 model (%{capacity})" + 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. diff --git a/view.py b/view.py index cf90dcfa..6f7a6d7e 100644 --- a/view.py +++ b/view.py @@ -211,6 +211,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")), @@ -381,6 +382,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, @@ -624,6 +638,11 @@ class View(): 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) @@ -678,6 +697,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", @@ -919,6 +943,17 @@ class View(): vrct_gui.update() vrct_gui.config_window.lift() + @staticmethod + def getSelectableWhisperWeightTypeDict(): + return { + config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["tiny"]: i18n.t("config_window.whisper_weight_type.tiny", capacity="t"), + config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["base"]: i18n.t("config_window.whisper_weight_type.base", capacity="b"), + config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["small"]: i18n.t("config_window.whisper_weight_type.small", capacity="s"), + config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["medium"]: i18n.t("config_window.whisper_weight_type.medium", capacity="m"), + config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["large-v1"]: i18n.t("config_window.whisper_weight_type.large_v1", capacity="l_v1"), + config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["large-v2"]: i18n.t("config_window.whisper_weight_type.large_v2", capacity="l_v2"), + config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["large-v3"]: i18n.t("config_window.whisper_weight_type.large_v3", capacity="l_v3"), + } # Open Webpage Functions def openWebPage_Booth(self): @@ -1082,6 +1117,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")) 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/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 From 801d948513b1dd0d891f3b85cf582f524f537063 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sat, 3 Feb 2024 02:35:40 +0900 Subject: [PATCH 14/43] =?UTF-8?q?[WIP/TEST]=20Wisper=E3=81=AE=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=81=AB=E3=81=A4=E3=81=84=E3=81=A6UI=E3=81=A8?= =?UTF-8?q?=E5=86=85=E9=83=A8=E3=81=AE=E5=87=A6=E7=90=86=E3=82=92=E6=8E=A5?= =?UTF-8?q?=E7=B6=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 24 ++++++++++----- controller.py | 30 +++++++++---------- model.py | 12 +++++--- .../transcription_transcriber.py | 28 ++++++++--------- models/transcription/transcription_whisper.py | 5 ++-- view.py | 4 ++- 6 files changed, 57 insertions(+), 46 deletions(-) diff --git a/config.py b/config.py index c59c0f17..6ce32035 100644 --- a/config.py +++ b/config.py @@ -210,6 +210,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 @@ -268,14 +277,14 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property - @json_serializable('SELECTED_RECOGNIZER') - def SELECTED_RECOGNIZER(self): - return self._SELECTED_RECOGNIZER + @json_serializable('SELECTED_TRANSCRIPTION_ENGINE') + def SELECTED_TRANSCRIPTION_ENGINE(self): + return self._SELECTED_TRANSCRIPTION_ENGINE - @SELECTED_RECOGNIZER.setter - def SELECTED_RECOGNIZER(self, value): + @SELECTED_TRANSCRIPTION_ENGINE.setter + def SELECTED_TRANSCRIPTION_ENGINE(self, value): if isinstance(value, str): - self._SELECTED_RECOGNIZER = value + self._SELECTED_TRANSCRIPTION_ENGINE = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property @@ -820,6 +829,7 @@ class Config: self._TARGET_LANGUAGE = "English" self._TARGET_COUNTRY = "United States" self._IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION = False + self._IS_RESET_BUTTON_DISPLAYED_FOR_WHISPER = False # Save Json Data ## Main Window @@ -844,7 +854,7 @@ class Config: "2":"English\n(United States)", "3":"English\n(United States)", } - self._SELECTED_RECOGNIZER = "Google" + self._SELECTED_TRANSCRIPTION_ENGINE = "Google" self._IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = False ## Config Window diff --git a/controller.py b/controller.py index 724d2cf3..e63101b2 100644 --- a/controller.py +++ b/controller.py @@ -773,29 +773,27 @@ def 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() + view.showRestartButtonIfRequired() def callbackSetWhisperWeightType(value): print("callbackSetWhisperWeightType", value) config.WHISPER_WEIGHT_TYPE = str(value) view.updateSelectedWhisperWeightType(config.WHISPER_WEIGHT_TYPE) - # view.setWidgetsStatus_changeWeightType_Pending() - # if model.checkCTranslatorCTranslate2ModelWeight(): - # config.IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION = False - # def callback(): - # model.changeTranslatorCTranslate2Model() - # view.useTranslationFeatureProcess("Normal") - # view.setWidgetsStatus_changeWeightType_Done() - # th_callback = Thread(target=callback) - # th_callback.daemon = True - # th_callback.start() - # else: - # config.IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION = True - # view.useTranslationFeatureProcess("Restart") - # view.setWidgetsStatus_changeWeightType_Done() - # view.showRestartButtonIfRequired() - + 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): diff --git a/model.py b/model.py index 2c29d4c7..228bc253 100644 --- a/model.py +++ b/model.py @@ -24,6 +24,7 @@ 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.translation_utils import checkCTranslate2Weight +from models.transcription.transcription_whisper import checkWhisperWeight from config import config class threadFnc(Thread): @@ -74,6 +75,9 @@ class Model: def changeTranslatorCTranslate2Model(self): self.translator.changeCTranslate2Model(config.PATH_LOCAL, config.CTRANSLATE2_WEIGHT_TYPE) + def checkTranscriptionWhisperModelWeight(self): + return checkWhisperWeight(config.PATH_LOCAL, config.WHISPER_WEIGHT_TYPE) + def resetKeywordProcessor(self): del self.keyword_processor self.keyword_processor = KeywordProcessor() @@ -335,12 +339,12 @@ class Model: source=self.mic_audio_recorder.source, phrase_timeout=phase_timeout, max_phrases=config.INPUT_MIC_MAX_PHRASES, - whisper_enabled=config.USE_WHISPER_FEATURE, + transcription_engine=config.SELECTED_TRANSCRIPTION_ENGINE, whisper_weight_type=config.WHISPER_WEIGHT_TYPE, root=config.PATH_LOCAL, ) def sendMicTranscript(): - mic_transcriber.transcribeAudioQueue(config.SELECTED_RECOGNIZER, mic_audio_queue, config.SOURCE_LANGUAGE, config.SOURCE_COUNTRY) + mic_transcriber.transcribeAudioQueue(mic_audio_queue, config.SOURCE_LANGUAGE, config.SOURCE_COUNTRY) message = mic_transcriber.getTranscript() try: fnc(message) @@ -419,12 +423,12 @@ class Model: source=self.speaker_audio_recorder.source, phrase_timeout=phase_timeout, max_phrases=config.INPUT_SPEAKER_MAX_PHRASES, - whisper_enabled=config.USE_WHISPER_FEATURE, + transcription_engine=config.SELECTED_TRANSCRIPTION_ENGINE, whisper_weight_type=config.WHISPER_WEIGHT_TYPE, root=config.PATH_LOCAL, ) def sendSpeakerTranscript(): - speaker_transcriber.transcribeAudioQueue(config.SELECTED_RECOGNIZER, speaker_audio_queue, config.TARGET_LANGUAGE, config.TARGET_COUNTRY) + speaker_transcriber.transcribeAudioQueue(speaker_audio_queue, config.TARGET_LANGUAGE, config.TARGET_COUNTRY) message = speaker_transcriber.getTranscript() try: fnc(message) diff --git a/models/transcription/transcription_transcriber.py b/models/transcription/transcription_transcriber.py index 0f5b1790..b24d3163 100644 --- a/models/transcription/transcription_transcriber.py +++ b/models/transcription/transcription_transcriber.py @@ -14,7 +14,7 @@ PHRASE_TIMEOUT = 3 MAX_PHRASES = 10 class AudioTranscriber: - def __init__(self, speaker, source, phrase_timeout, max_phrases, whisper_enabled, whisper_weight_type, root): + def __init__(self, speaker, source, phrase_timeout, max_phrases, transcription_engine, whisper_weight_type=None, root=None): self.speaker = speaker self.phrase_timeout = phrase_timeout self.max_phrases = max_phrases @@ -30,38 +30,34 @@ class AudioTranscriber: "new_phrase": True, "process_data_func": self.processSpeakerData if speaker else self.processSpeakerData } - if whisper_enabled is True: - self.whisper_model = getWhisperModel(root, whisper_weight_type) - else: - self.whisper_model = None + self.transcription_engine = transcription_engine + match self.transcription_engine: + case "Google": + self.audio_recognizer = Recognizer() + case "Whisper": + self.audio_recognizer = getWhisperModel(root, whisper_weight_type) - def transcribeAudioQueue(self, recognizer, audio_queue, language, country): - # while True: + def transcribeAudioQueue(self, audio_queue, language, country): audio, time_spoken = audio_queue.get() self.updateLastSampleAndPhraseStatus(audio, time_spoken) text = '' try: - # Whisperが使用できない場合はGoogle Speech-to-Textを使用する - if recognizer == "Whisper": - if self.whisper_model is None: - recognizer = "Google" - audio_data = self.audio_sources["process_data_func"]() - match recognizer: + match self.transcription_engine: case "Google": - text = self.audio_recognizer.recognize_google(audio_data, language=transcription_lang[language][country][recognizer]) + 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( + segments, _ = self.audio_recognizer.transcribe( audio_data, beam_size=5, temperature=0.0, log_prob_threshold=-0.8, no_speech_threshold=0.6, - language=transcription_lang[language][country][recognizer], + language=transcription_lang[language][country][self.transcription_engine], word_timestamps=False, without_timestamps=True, task="transcribe", diff --git a/models/transcription/transcription_whisper.py b/models/transcription/transcription_whisper.py index e30fee2d..c6412d35 100644 --- a/models/transcription/transcription_whisper.py +++ b/models/transcription/transcription_whisper.py @@ -42,7 +42,8 @@ def downloadFile(url, path, func=None): except Exception as e: print("error:downloadFile()", e) -def checkWhisperWeight(path): +def checkWhisperWeight(root, weight_type): + path = os_path.join(root, "weights", "whisper", weight_type) result = False try: WhisperModel( @@ -62,7 +63,7 @@ def checkWhisperWeight(path): def downloadWhisperWeight(root, weight_type, callbackFunc): path = os_path.join(root, "weights", "whisper", weight_type) os_makedirs(path, exist_ok=True) - if checkWhisperWeight(path) is True: + if checkWhisperWeight(root, weight_type) is True: return for filename in _FILENAMES: diff --git a/view.py b/view.py index 6f7a6d7e..84ebd550 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: @@ -1049,7 +1050,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: From 7aafce6e2e78187086db7602f9de3f48e270847b Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sun, 4 Feb 2024 01:03:38 +0900 Subject: [PATCH 15/43] =?UTF-8?q?[WIP/TEST]=20distil-wisper=E3=81=AE?= =?UTF-8?q?=E5=87=A6=E7=90=86=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 3 +++ controller.py | 6 ++++++ locales/en.yml | 3 +++ main.py | 2 +- models/transcription/transcription_whisper.py | 3 +++ view.py | 17 ++++++++++------- 6 files changed, 26 insertions(+), 8 deletions(-) diff --git a/config.py b/config.py index 6ce32035..ff1f7263 100644 --- a/config.py +++ b/config.py @@ -812,6 +812,9 @@ class Config: "large-v1": "large-v1", "large-v2": "large-v2", "large-v3": "large-v3", + "distil-small": "distil-small", + "distil-medium": "distil-medium", + "distil-large-v2": "distil-large-v2", } self._MAX_MIC_ENERGY_THRESHOLD = 2000 diff --git a/controller.py b/controller.py index e63101b2..e5b747d4 100644 --- a/controller.py +++ b/controller.py @@ -925,6 +925,12 @@ 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() diff --git a/locales/en.yml b/locales/en.yml index f68aa32c..c799c9d0 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -140,6 +140,9 @@ config_window: large_v1: "large_v1 model (%{capacity})" large_v2: "large_v2 model (%{capacity})" large_v3: "large_v3 model (%{capacity})" + distil_small: "distil-small model (%{capacity})" + distil_medium: "distil-medium model (%{capacity})" + distil_large_v2: "distil-large-v2 model (%{capacity})" deepl_auth_key: label: DeepL Auth Key diff --git a/main.py b/main.py index 0df15326..6b6c0e3e 100644 --- a/main.py +++ b/main.py @@ -13,9 +13,9 @@ if __name__ == "__main__": downloadCTranslate2Weight(config.PATH_LOCAL, config.CTRANSLATE2_WEIGHT_TYPE, splash.updateDownloadProgress) from models.transcription.transcription_whisper import downloadWhisperWeight - # whisperのダウンロードの説明に変更する必要あり if config.USE_WHISPER_FEATURE is True: downloadWhisperWeight(config.PATH_LOCAL, config.WHISPER_WEIGHT_TYPE, splash.updateDownloadProgress) + splash.toProgress(0) import controller diff --git a/models/transcription/transcription_whisper.py b/models/transcription/transcription_whisper.py index c6412d35..148b2edb 100644 --- a/models/transcription/transcription_whisper.py +++ b/models/transcription/transcription_whisper.py @@ -15,6 +15,9 @@ _MODELS = { "large-v1": "Systran/faster-whisper-large-v1", "large-v2": "Systran/faster-whisper-large-v2", "large-v3": "Systran/faster-whisper-large-v3", + "distil-small": "Systran/faster-distil-whisper-small.en", + "distil-medium": "Systran/faster-distil-whisper-medium.en", + "distil-large-v2": "Systran/faster-distil-whisper-large-v2" } _FILENAMES = [ diff --git a/view.py b/view.py index 84ebd550..1efb3f22 100644 --- a/view.py +++ b/view.py @@ -947,13 +947,16 @@ class View(): @staticmethod def getSelectableWhisperWeightTypeDict(): return { - config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["tiny"]: i18n.t("config_window.whisper_weight_type.tiny", capacity="t"), - config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["base"]: i18n.t("config_window.whisper_weight_type.base", capacity="b"), - config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["small"]: i18n.t("config_window.whisper_weight_type.small", capacity="s"), - config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["medium"]: i18n.t("config_window.whisper_weight_type.medium", capacity="m"), - config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["large-v1"]: i18n.t("config_window.whisper_weight_type.large_v1", capacity="l_v1"), - config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["large-v2"]: i18n.t("config_window.whisper_weight_type.large_v2", capacity="l_v2"), - config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["large-v3"]: i18n.t("config_window.whisper_weight_type.large_v3", capacity="l_v3"), + config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["tiny"]: i18n.t("config_window.whisper_weight_type.tiny", capacity="74.5MB"), + config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["base"]: i18n.t("config_window.whisper_weight_type.base", capacity="141MB"), + config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["small"]: i18n.t("config_window.whisper_weight_type.small", capacity="463MB"), + config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["medium"]: i18n.t("config_window.whisper_weight_type.medium", capacity="1.42GB"), + config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["large-v1"]: i18n.t("config_window.whisper_weight_type.large_v1", capacity="2.87GB"), + config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["large-v2"]: i18n.t("config_window.whisper_weight_type.large_v2", capacity="2.87GB"), + config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["large-v3"]: i18n.t("config_window.whisper_weight_type.large_v3", capacity="2.87GB"), + config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["distil-small"]: i18n.t("config_window.whisper_weight_type.distil_small", capacity="319MB"), + config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["distil-medium"]: i18n.t("config_window.whisper_weight_type.distil_medium", capacity="755MB"), + config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["distil-large-v2"]: i18n.t("config_window.whisper_weight_type.distil_large_v2", capacity="1.41GB"), } # Open Webpage Functions From 61a6eb792b2a8c3aad67f3ef94b67f898561636d Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sun, 4 Feb 2024 02:42:08 +0900 Subject: [PATCH 16/43] =?UTF-8?q?[WIP/TEST]=20distil-wisper=E3=82=92?= =?UTF-8?q?=E5=89=8A=E9=99=A4/faster-wisper=E3=81=AE=E5=87=A6=E7=90=86?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 5 +--- locales/en.yml | 3 -- model.py | 10 +++---- .../transcription_transcriber.py | 29 ++++++++++--------- models/transcription/transcription_whisper.py | 3 -- view.py | 3 -- 6 files changed, 21 insertions(+), 32 deletions(-) diff --git a/config.py b/config.py index ff1f7263..55ba2d40 100644 --- a/config.py +++ b/config.py @@ -812,9 +812,6 @@ class Config: "large-v1": "large-v1", "large-v2": "large-v2", "large-v3": "large-v3", - "distil-small": "distil-small", - "distil-medium": "distil-medium", - "distil-large-v2": "distil-large-v2", } self._MAX_MIC_ENERGY_THRESHOLD = 2000 @@ -895,7 +892,7 @@ class Config: } self._USE_TRANSLATION_FEATURE = True self._CTRANSLATE2_WEIGHT_TYPE = "Small" - self._USE_WHISPER_FEATURE = True + self._USE_WHISPER_FEATURE = False self._WHISPER_WEIGHT_TYPE = "base" self._SEND_MESSAGE_FORMAT = "[message]" self._SEND_MESSAGE_FORMAT_WITH_T = "[message]([translation])" diff --git a/locales/en.yml b/locales/en.yml index c799c9d0..f68aa32c 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -140,9 +140,6 @@ config_window: large_v1: "large_v1 model (%{capacity})" large_v2: "large_v2 model (%{capacity})" large_v3: "large_v3 model (%{capacity})" - distil_small: "distil-small model (%{capacity})" - distil_medium: "distil-medium model (%{capacity})" - distil_large_v2: "distil-large-v2 model (%{capacity})" deepl_auth_key: label: DeepL Auth Key diff --git a/model.py b/model.py index 228bc253..5b17e167 100644 --- a/model.py +++ b/model.py @@ -339,12 +339,11 @@ class Model: source=self.mic_audio_recorder.source, phrase_timeout=phase_timeout, max_phrases=config.INPUT_MIC_MAX_PHRASES, - transcription_engine=config.SELECTED_TRANSCRIPTION_ENGINE, - whisper_weight_type=config.WHISPER_WEIGHT_TYPE, 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) + mic_transcriber.transcribeAudioQueue(mic_audio_queue, config.SOURCE_LANGUAGE, config.SOURCE_COUNTRY, config.SELECTED_TRANSCRIPTION_ENGINE) message = mic_transcriber.getTranscript() try: fnc(message) @@ -423,12 +422,11 @@ class Model: source=self.speaker_audio_recorder.source, phrase_timeout=phase_timeout, max_phrases=config.INPUT_SPEAKER_MAX_PHRASES, - transcription_engine=config.SELECTED_TRANSCRIPTION_ENGINE, - whisper_weight_type=config.WHISPER_WEIGHT_TYPE, 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) + speaker_transcriber.transcribeAudioQueue(speaker_audio_queue, config.TARGET_LANGUAGE, config.TARGET_COUNTRY, config.SELECTED_TRANSCRIPTION_ENGINE) message = speaker_transcriber.getTranscript() try: fnc(message) diff --git a/models/transcription/transcription_transcriber.py b/models/transcription/transcription_transcriber.py index b24d3163..08cc6a1a 100644 --- a/models/transcription/transcription_transcriber.py +++ b/models/transcription/transcription_transcriber.py @@ -5,7 +5,7 @@ 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 +from .transcription_whisper import getWhisperModel, checkWhisperWeight import torch import numpy as np @@ -14,7 +14,7 @@ PHRASE_TIMEOUT = 3 MAX_PHRASES = 10 class AudioTranscriber: - def __init__(self, speaker, source, phrase_timeout, max_phrases, transcription_engine, whisper_weight_type=None, root=None): + def __init__(self, speaker, source, phrase_timeout, max_phrases, root=None, whisper_weight_type=None, ): self.speaker = speaker self.phrase_timeout = phrase_timeout self.max_phrases = max_phrases @@ -30,34 +30,37 @@ class AudioTranscriber: "new_phrase": True, "process_data_func": self.processSpeakerData if speaker else self.processSpeakerData } - self.transcription_engine = transcription_engine - match self.transcription_engine: - case "Google": - self.audio_recognizer = Recognizer() - case "Whisper": - self.audio_recognizer = getWhisperModel(root, whisper_weight_type) + if whisper_weight_type is not None and root is not None and checkWhisperWeight(root, whisper_weight_type) is True: + self.whisper_model = getWhisperModel(root, whisper_weight_type) + else: + self.whisper_model = None - def transcribeAudioQueue(self, audio_queue, language, country): + def transcribeAudioQueue(self, audio_queue, language, country, transcription_engine): audio, time_spoken = audio_queue.get() self.updateLastSampleAndPhraseStatus(audio, time_spoken) text = '' try: + # Whisperが使用できない場合はGoogle Speech-to-Textを使用する + if transcription_engine == "Whisper": + if self.whisper_model is None: + transcription_engine = "Google" + audio_data = self.audio_sources["process_data_func"]() - match self.transcription_engine: + match transcription_engine: case "Google": - text = self.audio_recognizer.recognize_google(audio_data, language=transcription_lang[language][country][self.transcription_engine]) + text = self.audio_recognizer.recognize_google(audio_data, language=transcription_lang[language][country][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.audio_recognizer.transcribe( + 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], + language=transcription_lang[language][country][transcription_engine], word_timestamps=False, without_timestamps=True, task="transcribe", diff --git a/models/transcription/transcription_whisper.py b/models/transcription/transcription_whisper.py index 148b2edb..c6412d35 100644 --- a/models/transcription/transcription_whisper.py +++ b/models/transcription/transcription_whisper.py @@ -15,9 +15,6 @@ _MODELS = { "large-v1": "Systran/faster-whisper-large-v1", "large-v2": "Systran/faster-whisper-large-v2", "large-v3": "Systran/faster-whisper-large-v3", - "distil-small": "Systran/faster-distil-whisper-small.en", - "distil-medium": "Systran/faster-distil-whisper-medium.en", - "distil-large-v2": "Systran/faster-distil-whisper-large-v2" } _FILENAMES = [ diff --git a/view.py b/view.py index 1efb3f22..94a4af8c 100644 --- a/view.py +++ b/view.py @@ -954,9 +954,6 @@ class View(): config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["large-v1"]: i18n.t("config_window.whisper_weight_type.large_v1", capacity="2.87GB"), config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["large-v2"]: i18n.t("config_window.whisper_weight_type.large_v2", capacity="2.87GB"), config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["large-v3"]: i18n.t("config_window.whisper_weight_type.large_v3", capacity="2.87GB"), - config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["distil-small"]: i18n.t("config_window.whisper_weight_type.distil_small", capacity="319MB"), - config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["distil-medium"]: i18n.t("config_window.whisper_weight_type.distil_medium", capacity="755MB"), - config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["distil-large-v2"]: i18n.t("config_window.whisper_weight_type.distil_large_v2", capacity="1.41GB"), } # Open Webpage Functions From 52682a5c2e7dad82b9a7970b20cce2f03d0968f5 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 7 Feb 2024 23:00:51 +0900 Subject: [PATCH 17/43] [WIP/TEST] Add OSCQuery --- models/osc/osc_tools.py | 30 ++++++++++++++++++++++++------ requirements.txt | 3 ++- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/models/osc/osc_tools.py b/models/osc/osc_tools.py index 80f2b785..0d869375 100644 --- a/models/osc/osc_tools.py +++ b/models/osc/osc_tools.py @@ -3,6 +3,8 @@ 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 +47,28 @@ 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) +# 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(target, filter="/avatar/parameters/*", ip_address="127.0.0.1", title="VRCT"): + osc_port = get_open_udp_port() + http_port = get_open_tcp_port() + osc_dispatcher = dispatcher.Dispatcher() + osc_dispatcher.map(filter, target) + osc_udp_server = osc_server.ThreadingOSCUDPServer((ip_address, osc_port), osc_dispatcher) + osc_client = OSCQueryService(title, http_port, osc_port) + osc_client.advertise_endpoint(filter) + return osc_udp_server, osc_client + if __name__ == "__main__": - sendChangeVoice() - sendChangeVoice() \ No newline at end of file + import threading + + def print_handler(address, *args): + print(f"{address}: {args}") + + server, client = receiveOscParameters(print_handler, filter="/input/*") + server_thread = threading.Thread(target=server.serve_forever) + server_thread.start() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index b6e14d85..f0c80d3b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,5 @@ CTkToolTip == 0.8 pyinstaller==6.2.0 transformers[torch] sentencepiece==0.1.99 -ctranslate2==3.21.0 \ No newline at end of file +ctranslate2==3.21.0 +tinyoscquery @ git+https://github.com/cyberkitsune/tinyoscquery@0.1.2 \ No newline at end of file From ea10573a3d01858a4dadc8862448b4eb98903b46 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 8 Feb 2024 02:27:41 +0900 Subject: [PATCH 18/43] =?UTF-8?q?[Update/Chore]=20Config=20Window:=20Trans?= =?UTF-8?q?cription=20Tab.=20Whisper=E9=96=A2=E9=80=A3=E3=81=AE=E6=96=87?= =?UTF-8?q?=E8=A8=80=E8=BF=BD=E5=8A=A0=E3=82=84=E5=A4=89=E6=9B=B4=E3=80=82?= =?UTF-8?q?=20config.py=E3=81=8B=E3=82=89=E3=81=AE=E8=AA=AD=E3=81=BF?= =?UTF-8?q?=E8=BE=BC=E3=81=BF=E6=99=82=E3=81=AB=E9=96=93=E9=81=95=E3=81=A3?= =?UTF-8?q?=E3=81=9F=E6=96=B9=E6=B3=95=E3=81=A7=E8=AA=AD=E3=81=BF=E8=BE=BC?= =?UTF-8?q?=E3=82=93=E3=81=A7=E3=81=84=E3=81=9F=E3=81=AE=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en.yml | 32 ++++++++++++++++---------------- locales/ja.yml | 16 ++++++++++++++++ view.py | 4 ++-- 3 files changed, 34 insertions(+), 18 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index f68aa32c..e2bbc7f3 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -79,7 +79,7 @@ config_window: transcription: Transcription transcription_mic: Mic transcription_speaker: Speaker - transcription_internal_model: Internal Model + transcription_internal_model: Transcription Model others: Others others_send_message_formats: Message Formats (Send) others_received_message_formats: Message Formats (Received) @@ -126,21 +126,6 @@ config_window: small: "Basic model (%{capacity})" large: "High accuracy model (%{capacity})" - use_whisper_feature: - label: Use Whisper Feature - desc: Description - - whisper_weight_type: - label: Select Whisper Model - desc: Description - tiny: "tiny model (%{capacity})" - base: "base model (%{capacity})" - small: "small model (%{capacity})" - medium: "medium model (%{capacity})" - large_v1: "large_v1 model (%{capacity})" - large_v2: "large_v2 model (%{capacity})" - large_v3: "large_v3 model (%{capacity})" - 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. @@ -203,6 +188,21 @@ 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. + tiny: "tiny model (%{capacity})" + base: "base model (%{capacity}) (Recommended)" + small: "small model (%{capacity})" + medium: "medium model (%{capacity})" + large_v1: "large_v1 model (%{capacity})" + large_v2: "large_v2 model (%{capacity})" + large_v3: "large_v3 model (%{capacity})" + auto_clear_the_message_box: label: Auto Clear The Message Box diff --git a/locales/ja.yml b/locales/ja.yml index 6bedafbd..7976e639 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -79,6 +79,7 @@ config_window: transcription: 音声認識 transcription_mic: マイク transcription_speaker: スピーカー + transcription_internal_model: 音声認識モデル others: その他 advanced_settings: 高度な設定 @@ -184,6 +185,21 @@ config_window: desc: 文字起こしされた単語数の下限値で、この数値を超えた場合のみ結果をログに表示します。 error_message: 0以上の数値を設定できます。 + use_whisper_feature: + label: 音声認識にWhisperモデルを使用する + desc: 一部の言語では、音声認識の精度が向上するかもしれません。音声認識使用中、CPUの使用率が上がるので、お使いのPCスペックと相談してこの機能を使用してください。 + + whisper_weight_type: + label: Whisperモデルのタイプ + desc: 基本的に、容量が多いモデルほど精度は高いですが、文字起こしまでの時間が伸び、CPU使用率も増加します。各モデルの説明はドキュメントをご覧ください。 + tiny: "tiny モデル (%{capacity})" + base: "base モデル (%{capacity}) (推奨)" + small: "small モデル (%{capacity})" + medium: "medium モデル (%{capacity})" + large_v1: "large_v1 モデル (%{capacity})" + large_v2: "large_v2 モデル (%{capacity})" + large_v3: "large_v3 モデル (%{capacity})" + auto_clear_the_message_box: label: 送信後はチャットボックスを空にする diff --git a/view.py b/view.py index 91074d93..aac79d14 100644 --- a/view.py +++ b/view.py @@ -913,8 +913,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): From c459b97ca3f7c4995a435b4f36e2b2dbfa4adfa6 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 8 Feb 2024 04:31:27 +0900 Subject: [PATCH 19/43] =?UTF-8?q?[Refactor]=20Config=20Window:=20Transcrip?= =?UTF-8?q?tion=20tab.=20whisper=E9=96=A2=E4=BF=82=E3=81=AE=E6=96=87?= =?UTF-8?q?=E8=A8=80=E3=82=92i18n=E3=82=92=E4=BD=BF=E3=81=A3=E3=81=A6?= =?UTF-8?q?=E5=91=BC=E3=81=B3=E5=87=BA=E3=81=99=E9=83=A8=E5=88=86=E3=80=81?= =?UTF-8?q?=E9=87=8D=E8=A4=87=E3=81=97=E3=81=9F=E9=83=A8=E5=88=86=E3=81=8C?= =?UTF-8?q?=E5=A4=9A=E3=81=84=E3=81=AE=E3=81=A7=E3=83=AA=E3=83=95=E3=82=A1?= =?UTF-8?q?=E3=82=AF=E3=82=BF=E3=83=AA=E3=83=B3=E3=82=B0=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en.yml | 10 ++-------- locales/ja.yml | 9 ++------- view.py | 21 ++++++++++++++------- 3 files changed, 18 insertions(+), 22 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index e2bbc7f3..93f07195 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -195,14 +195,8 @@ config_window: 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. - tiny: "tiny model (%{capacity})" - base: "base model (%{capacity}) (Recommended)" - small: "small model (%{capacity})" - medium: "medium model (%{capacity})" - large_v1: "large_v1 model (%{capacity})" - large_v2: "large_v2 model (%{capacity})" - large_v3: "large_v3 model (%{capacity})" - + 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 diff --git a/locales/ja.yml b/locales/ja.yml index 7976e639..5d0b6acf 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -192,13 +192,8 @@ config_window: whisper_weight_type: label: Whisperモデルのタイプ desc: 基本的に、容量が多いモデルほど精度は高いですが、文字起こしまでの時間が伸び、CPU使用率も増加します。各モデルの説明はドキュメントをご覧ください。 - tiny: "tiny モデル (%{capacity})" - base: "base モデル (%{capacity}) (推奨)" - small: "small モデル (%{capacity})" - medium: "medium モデル (%{capacity})" - large_v1: "large_v1 モデル (%{capacity})" - large_v2: "large_v2 モデル (%{capacity})" - large_v3: "large_v3 モデル (%{capacity})" + model_template: "%{model_name} モデル (%{capacity})" + recommended_model_template: "%{model_name} モデル (%{capacity}) (推奨)" auto_clear_the_message_box: diff --git a/view.py b/view.py index aac79d14..2d9b8fb0 100644 --- a/view.py +++ b/view.py @@ -952,14 +952,21 @@ class View(): @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 { - config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["tiny"]: i18n.t("config_window.whisper_weight_type.tiny", capacity="74.5MB"), - config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["base"]: i18n.t("config_window.whisper_weight_type.base", capacity="141MB"), - config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["small"]: i18n.t("config_window.whisper_weight_type.small", capacity="463MB"), - config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["medium"]: i18n.t("config_window.whisper_weight_type.medium", capacity="1.42GB"), - config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["large-v1"]: i18n.t("config_window.whisper_weight_type.large_v1", capacity="2.87GB"), - config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["large-v2"]: i18n.t("config_window.whisper_weight_type.large_v2", capacity="2.87GB"), - config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT["large-v3"]: i18n.t("config_window.whisper_weight_type.large_v3", capacity="2.87GB"), + 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 From c46901802d7afcc94ee1424bea44fd70a4fd8da2 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 8 Feb 2024 10:26:37 +0900 Subject: [PATCH 20/43] =?UTF-8?q?[Update]=20Splash=20Window:=20=E3=83=A2?= =?UTF-8?q?=E3=83=87=E3=83=AB=E3=83=80=E3=82=A6=E3=83=B3=E3=83=AD=E3=83=BC?= =?UTF-8?q?=E3=83=89=E6=99=82=E3=81=AB=E5=8F=B3=E4=B8=8B=E3=81=AB=E8=A1=A8?= =?UTF-8?q?=E7=A4=BA=E3=81=95=E3=82=8C=E3=82=8B=E6=96=87=E7=AB=A0=E3=82=92?= =?UTF-8?q?=E5=A4=89=E6=9B=B4=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- img/VRCT_now_downloading.png | Bin 31294 -> 31072 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/img/VRCT_now_downloading.png b/img/VRCT_now_downloading.png index 5e5b9daa328daa530d270f8b35fd3ee6eb02da54..8e372d87e30f010303ba814fd7d3b7fc9cc9c032 100644 GIT binary patch literal 31072 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 From fe1054212b8ce712bcefdfc3075c7e525f567587 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Thu, 8 Feb 2024 22:58:22 +0900 Subject: [PATCH 21/43] =?UTF-8?q?=F0=9F=91=8D[Update]=20Model=20:=20OSCQue?= =?UTF-8?q?ry=20Receive=E5=87=A6=E7=90=86=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 辞書型をインプットにコールバックで動作するように設定 --- models/osc/osc_tools.py | 43 +++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/models/osc/osc_tools.py b/models/osc/osc_tools.py index 0d869375..51608f66 100644 --- a/models/osc/osc_tools.py +++ b/models/osc/osc_tools.py @@ -1,4 +1,5 @@ from time import sleep +import threading from pythonosc import osc_message_builder from pythonosc import udp_client from pythonosc import dispatcher @@ -47,28 +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(target, filter="/avatar/parameters/*", ip_address="127.0.0.1", title="VRCT"): +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() - osc_dispatcher.map(filter, target) + 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) - osc_client.advertise_endpoint(filter) - return osc_udp_server, osc_client + for filter, target in dict_filter_and_target.items(): + osc_client.advertise_endpoint(filter) if __name__ == "__main__": - import threading + osc_parameter_prefix = "/avatar/parameters/" + osc_avatar_change_path = "/avatar/change" + param_MuteSelf = "MuteSelf" + param_Voice = "Voice" - def print_handler(address, *args): - print(f"{address}: {args}") + def print_handler_all(address, *args): + print(f"all {address}: {args}") - server, client = receiveOscParameters(print_handler, filter="/input/*") - server_thread = threading.Thread(target=server.serve_forever) - server_thread.start() \ No newline at end of file + 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 From 45e77aa7a78aa4111a22549c91f1c85c3f575202 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Fri, 9 Feb 2024 00:23:20 +0900 Subject: [PATCH 22/43] =?UTF-8?q?=F0=9F=91=8D[Update]=20Config=20:=20Speak?= =?UTF-8?q?er2Chatbox=E3=81=ABpassword=E8=A8=AD=E5=AE=9A=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/config.py b/config.py index 638918da..a5405100 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): @@ -111,6 +107,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 @@ -751,9 +756,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): @@ -797,6 +811,7 @@ class Config: # Read Only self._VERSION = "2.1.1" 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") @@ -924,6 +939,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 @@ -937,6 +953,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) From 2857d8f543f28e9cde764ac40334a438046d9e19 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Fri, 9 Feb 2024 02:03:44 +0900 Subject: [PATCH 23/43] [WIP/TEST] Model : Add energy indicator --- model.py | 22 ++++++++++-- .../transcription/transcription_recorder.py | 35 +++++++++++++++++++ 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/model.py b/model.py index 5b17e167..0b3b4887 100644 --- a/model.py +++ b/model.py @@ -18,6 +18,7 @@ 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 SelectedMicEnergyAndAudioRecorder from models.transcription.transcription_recorder import SelectedMicEnergyRecorder, SelectedSpeakeEnergyRecorder from models.transcription.transcription_transcriber import AudioTranscriber from models.xsoverlay.notification import xsoverlayForVRCT @@ -321,19 +322,20 @@ 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) + self.mic_audio_recorder.recordIntoQueue(mic_audio_queue, mic_energy_queue) mic_transcriber = AudioTranscriber( speaker=False, source=self.mic_audio_recorder.source, @@ -350,15 +352,29 @@ class Model: except Exception: pass + 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) 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 diff --git a/models/transcription/transcription_recorder.py b/models/transcription/transcription_recorder.py index 9abe5eb4..987e948c 100644 --- a/models/transcription/transcription_recorder.py +++ b/models/transcription/transcription_recorder.py @@ -87,4 +87,39 @@ 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) + + self.stop = self.recorder.listen_energy_and_audio_in_background(self.source, audioRecordCallback, phrase_time_limit=self.record_timeout, callback_energy=energyRecordCallback) + +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() \ No newline at end of file From e08a3ab42d2d87d819ab5ca15c2f137a2c0040a8 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Fri, 9 Feb 2024 02:29:49 +0900 Subject: [PATCH 24/43] [WIP/TEST] Model : Add energy indicator for speaker --- model.py | 32 +++++++++++++++---- .../transcription/transcription_recorder.py | 12 +++++++ 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/model.py b/model.py index 0b3b4887..06a9cb7d 100644 --- a/model.py +++ b/model.py @@ -17,8 +17,7 @@ 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 SelectedMicEnergyAndAudioRecorder +from models.transcription.transcription_recorder import SelectedMicEnergyAndAudioRecorder, SelectedSpeakerEnergyAndAudioRecorder from models.transcription.transcription_recorder import SelectedMicEnergyRecorder, SelectedSpeakeEnergyRecorder from models.transcription.transcription_transcriber import AudioTranscriber from models.xsoverlay.notification import xsoverlayForVRCT @@ -355,7 +354,7 @@ class Model: def sendMicEnergy(): if mic_energy_queue.empty() is False: energy = mic_energy_queue.get() - print("mic energy:", energy) + # print("mic energy:", energy) try: fnc(energy) except Exception: @@ -377,6 +376,9 @@ class Model: 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": @@ -421,18 +423,19 @@ 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) + self.speaker_audio_recorder.recordIntoQueue(speaker_audio_queue, speaker_energy_queue) speaker_transcriber = AudioTranscriber( speaker=True, source=self.speaker_audio_recorder.source, @@ -449,17 +452,34 @@ class Model: except Exception: pass + 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) 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() diff --git a/models/transcription/transcription_recorder.py b/models/transcription/transcription_recorder.py index 987e948c..281c48b6 100644 --- a/models/transcription/transcription_recorder.py +++ b/models/transcription/transcription_recorder.py @@ -122,4 +122,16 @@ class SelectedMicEnergyAndAudioRecorder(BaseEnergyAndAudioRecorder): 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 From d5554487f472dc0d6bc142ca9fa6250bbdfa2c74 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Fri, 9 Feb 2024 22:33:11 +0900 Subject: [PATCH 25/43] [Update] requirements.txt : install version fix --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 3d0b771f..350a73fb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,6 +12,6 @@ transformers[torch]==4.37.2 sentencepiece==0.1.99 ctranslate2==3.24.0 faster-whisper==0.10.0 -translators @ git+https://github.com/misyaguziya/translators@master -SpeechRecognition @ git+https://github.com/misyaguziya/custom_speech_recognition@master +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 From c34aac28f64b1a534fb2befdd497b2baabbaf8ff Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 12 Feb 2024 22:29:32 +0900 Subject: [PATCH 26/43] =?UTF-8?q?=E2=9C=A8[Feature]=20utils=20:=20weight?= =?UTF-8?q?=E3=83=95=E3=82=A9=E3=83=AB=E3=83=80=E5=90=8D=E3=82=92weights?= =?UTF-8?q?=E3=81=AB=E5=A4=89=E6=9B=B4=E3=81=99=E3=82=8B=E9=96=A2=E6=95=B0?= =?UTF-8?q?=E3=82=92=E4=B8=80=E6=99=82=E7=9A=84=E3=81=AB=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 4 ++++ utils.py | 10 ++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index 6b6c0e3e..257a3a63 100644 --- a/main.py +++ b/main.py @@ -8,6 +8,10 @@ if __name__ == "__main__": splash.showSplash() from config import config + # 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.CTRANSLATE2_WEIGHT_TYPE, splash.updateDownloadProgress) 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 From fe768be48d0201c8c3764fa12058e1e89457299a Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 12 Feb 2024 22:47:35 +0900 Subject: [PATCH 27/43] typo SelectedSpeakeEnergyRecorder -> SelectedSpeakerEnergyRecorder --- model.py | 6 +++--- models/transcription/transcription_recorder.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/model.py b/model.py index 06a9cb7d..7d9f8c74 100644 --- a/model.py +++ b/model.py @@ -18,7 +18,7 @@ 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 SelectedMicEnergyAndAudioRecorder, SelectedSpeakerEnergyAndAudioRecorder -from models.transcription.transcription_recorder import SelectedMicEnergyRecorder, SelectedSpeakeEnergyRecorder +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 @@ -500,7 +500,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 @@ -510,7 +510,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/transcription/transcription_recorder.py b/models/transcription/transcription_recorder.py index 281c48b6..694df7d0 100644 --- a/models/transcription/transcription_recorder.py +++ b/models/transcription/transcription_recorder.py @@ -78,7 +78,7 @@ class SelectedMicEnergyRecorder(BaseEnergyRecorder): super().__init__(source=source) # self.adjustForNoise() -class SelectedSpeakeEnergyRecorder(BaseEnergyRecorder): +class SelectedSpeakerEnergyRecorder(BaseEnergyRecorder): def __init__(self, device): source = Microphone(speaker=True, From c2715adab78591cce99112151b92e77847a44a35 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 12 Feb 2024 23:07:29 +0900 Subject: [PATCH 28/43] =?UTF-8?q?=F0=9F=91=8D[Update]=20Model=20:=20?= =?UTF-8?q?=E6=96=87=E5=AD=97=E8=B5=B7=E3=81=93=E3=81=97=E3=81=AEENERGY?= =?UTF-8?q?=E5=8F=96=E5=BE=97=E5=87=A6=E7=90=86=E3=82=92=E4=B8=80=E6=99=82?= =?UTF-8?q?=E7=9A=84=E3=81=AB=E3=83=9E=E3=82=B9=E3=82=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 70 ++++++++++--------- .../transcription/transcription_recorder.py | 6 +- 2 files changed, 41 insertions(+), 35 deletions(-) diff --git a/model.py b/model.py index 7d9f8c74..6bc62b0c 100644 --- a/model.py +++ b/model.py @@ -321,7 +321,7 @@ class Model: return mic_audio_queue = Queue() - mic_energy_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 @@ -334,7 +334,8 @@ class Model: dynamic_energy_threshold=config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD, record_timeout=record_timeout, ) - self.mic_audio_recorder.recordIntoQueue(mic_audio_queue, mic_energy_queue) + # self.mic_audio_recorder.recordIntoQueue(mic_audio_queue, mic_energy_queue) + self.mic_audio_recorder.recordIntoQueue(mic_audio_queue, None) mic_transcriber = AudioTranscriber( speaker=False, source=self.mic_audio_recorder.source, @@ -351,23 +352,23 @@ class Model: except Exception: pass - 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) + # 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) 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() + # 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): @@ -376,9 +377,9 @@ class Model: 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 + # 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": @@ -423,7 +424,7 @@ class Model: return speaker_audio_queue = Queue() - speaker_energy_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: @@ -435,7 +436,8 @@ class Model: dynamic_energy_threshold=config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, record_timeout=record_timeout, ) - self.speaker_audio_recorder.recordIntoQueue(speaker_audio_queue, speaker_energy_queue) + # self.speaker_audio_recorder.recordIntoQueue(speaker_audio_queue, speaker_energy_queue) + self.speaker_audio_recorder.recordIntoQueue(speaker_audio_queue ,None) speaker_transcriber = AudioTranscriber( speaker=True, source=self.speaker_audio_recorder.source, @@ -452,23 +454,23 @@ class Model: except Exception: pass - 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) + # 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) 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() + # 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): @@ -477,9 +479,9 @@ class Model: 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 + # 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() diff --git a/models/transcription/transcription_recorder.py b/models/transcription/transcription_recorder.py index 694df7d0..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): @@ -113,7 +114,10 @@ class BaseEnergyAndAudioRecorder: def energyRecordCallback(energy): energy_queue.put(energy) - self.stop = self.recorder.listen_energy_and_audio_in_background(self.source, audioRecordCallback, phrase_time_limit=self.record_timeout, callback_energy=energyRecordCallback) + 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): From 8ae5ee4de91c526687da5c3e492b1f31b729c9a1 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 13 Feb 2024 00:43:07 +0900 Subject: [PATCH 29/43] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20installer=20:=20ze?= =?UTF-8?q?roconf=E3=81=8Cexe=E4=BD=9C=E6=88=90=E6=99=82=E3=81=AB=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=E3=81=95=E3=82=8C=E3=81=AA=E3=81=84=E5=95=8F=E9=A1=8C?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 34c5a05fda58fedee0d28cc962fe032cd557f136 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 13 Feb 2024 08:55:55 +0900 Subject: [PATCH 30/43] =?UTF-8?q?[bugfix]=20Main=20Window:=20=E5=88=9D?= =?UTF-8?q?=E5=9B=9E=E8=B5=B7=E5=8B=95=E6=99=82=E3=81=AE=E3=83=A1=E3=82=A4?= =?UTF-8?q?=E3=83=B3=E7=94=BB=E9=9D=A2=E3=82=B5=E3=82=A4=E3=82=BA=E3=82=92?= =?UTF-8?q?=E6=8B=A1=E5=A4=A7=E3=80=82=20CTranslate2=E5=AE=9F=E8=A3=85?= =?UTF-8?q?=E6=99=82=E3=81=AB=E3=80=81=E8=8B=A5=E5=B9=B2=E9=AB=98=E3=81=95?= =?UTF-8?q?=E3=81=8C=E5=A2=97=E3=81=88=E3=81=9F=E3=81=AE=E3=81=A7=E3=81=9D?= =?UTF-8?q?=E3=81=AE=E5=88=86=E3=82=92=E8=BF=BD=E5=8A=A0=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.py b/config.py index a5405100..d565bfe8 100644 --- a/config.py +++ b/config.py @@ -905,7 +905,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"] From 158b1394158a5f02c485bba274f0461f4b8db56f Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 13 Feb 2024 09:05:05 +0900 Subject: [PATCH 31/43] =?UTF-8?q?[Chore]=20view.py=20register=E9=83=A8?= =?UTF-8?q?=E5=88=86=20=E8=A8=98=E8=BF=B0=E3=81=AE=E7=B5=B1=E4=B8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 128 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/view.py b/view.py index 2d9b8fb0..c792824e 100644 --- a/view.py +++ b/view.py @@ -37,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 @@ -158,7 +158,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, @@ -169,13 +169,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")), @@ -544,23 +544,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: @@ -572,11 +572,11 @@ 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") @@ -590,8 +590,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 @@ -600,79 +600,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) + 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: From 5a0c6392e02a03269f72102d27a6c926b1b75578 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 13 Feb 2024 22:23:13 +0900 Subject: [PATCH 32/43] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Controller=20:=20w?= =?UTF-8?q?hisper=E4=B8=8D=E4=BD=BF=E7=94=A8=E6=99=82=E3=81=AE=E3=83=95?= =?UTF-8?q?=E3=83=A9=E3=82=B0=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 1 + 1 file changed, 1 insertion(+) diff --git a/controller.py b/controller.py index e977a5ef..a40282df 100644 --- a/controller.py +++ b/controller.py @@ -806,6 +806,7 @@ def callbackSetUserWhisperFeature(value): config.SELECTED_TRANSCRIPTION_ENGINE = "Google" else: view.closeWhisperWeightTypeWidget() + config.SELECTED_TRANSCRIPTION_ENGINE = "Google" view.showRestartButtonIfRequired() def callbackSetWhisperWeightType(value): From 07b3c92f1b74dc1805af3f7cc8a64212a41b0346 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 13 Feb 2024 23:20:14 +0900 Subject: [PATCH 33/43] =?UTF-8?q?=F0=9F=9A=A7[WIP/TEST]=20Model=20:=20?= =?UTF-8?q?=E6=96=87=E5=AD=97=E8=B5=B7=E3=81=93=E3=81=97=E8=B5=B7=E5=8B=95?= =?UTF-8?q?=E6=99=82=E3=81=AB=E3=82=A8=E3=83=B3=E3=82=B8=E3=83=B3=E3=82=92?= =?UTF-8?q?=E9=81=B8=E6=8A=9E=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 2 ++ models/transcription/transcription_transcriber.py | 10 ++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/model.py b/model.py index 6bc62b0c..e68c01bc 100644 --- a/model.py +++ b/model.py @@ -341,6 +341,7 @@ class Model: 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, ) @@ -443,6 +444,7 @@ class Model: 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, ) diff --git a/models/transcription/transcription_transcriber.py b/models/transcription/transcription_transcriber.py index 08cc6a1a..35f79c43 100644 --- a/models/transcription/transcription_transcriber.py +++ b/models/transcription/transcription_transcriber.py @@ -14,13 +14,15 @@ PHRASE_TIMEOUT = 3 MAX_PHRASES = 10 class AudioTranscriber: - def __init__(self, speaker, source, phrase_timeout, max_phrases, root=None, whisper_weight_type=None, ): + 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, @@ -30,10 +32,10 @@ class AudioTranscriber: "new_phrase": True, "process_data_func": self.processSpeakerData if speaker else self.processSpeakerData } - if whisper_weight_type is not None and root is not None and checkWhisperWeight(root, whisper_weight_type) is True: + + if transcription_engine == "Whisper" and checkWhisperWeight(root, whisper_weight_type) is True: self.whisper_model = getWhisperModel(root, whisper_weight_type) - else: - self.whisper_model = None + self.transcription_engine = "Whisper" def transcribeAudioQueue(self, audio_queue, language, country, transcription_engine): audio, time_spoken = audio_queue.get() From 7cdd9d19d7561966b0dd7f2f46c598880a7cf7fc Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 13 Feb 2024 23:25:31 +0900 Subject: [PATCH 34/43] =?UTF-8?q?=F0=9F=9A=A7[WIP/TEST]=20Model=20:=20?= =?UTF-8?q?=E6=96=87=E5=AD=97=E8=B5=B7=E3=81=93=E3=81=97=E8=B5=B7=E5=8B=95?= =?UTF-8?q?=E6=99=82=E3=81=AB=E3=82=A8=E3=83=B3=E3=82=B8=E3=83=B3=E3=82=92?= =?UTF-8?q?=E9=81=B8=E6=8A=9E=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4#2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 4 ++-- models/transcription/transcription_transcriber.py | 13 ++++--------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/model.py b/model.py index e68c01bc..62d727cd 100644 --- a/model.py +++ b/model.py @@ -346,7 +346,7 @@ class Model: whisper_weight_type=config.WHISPER_WEIGHT_TYPE, ) def sendMicTranscript(): - mic_transcriber.transcribeAudioQueue(mic_audio_queue, config.SOURCE_LANGUAGE, config.SOURCE_COUNTRY, config.SELECTED_TRANSCRIPTION_ENGINE) + mic_transcriber.transcribeAudioQueue(mic_audio_queue, config.SOURCE_LANGUAGE, config.SOURCE_COUNTRY) message = mic_transcriber.getTranscript() try: fnc(message) @@ -449,7 +449,7 @@ class Model: whisper_weight_type=config.WHISPER_WEIGHT_TYPE, ) def sendSpeakerTranscript(): - speaker_transcriber.transcribeAudioQueue(speaker_audio_queue, config.TARGET_LANGUAGE, config.TARGET_COUNTRY, config.SELECTED_TRANSCRIPTION_ENGINE) + speaker_transcriber.transcribeAudioQueue(speaker_audio_queue, config.TARGET_LANGUAGE, config.TARGET_COUNTRY) message = speaker_transcriber.getTranscript() try: fnc(message) diff --git a/models/transcription/transcription_transcriber.py b/models/transcription/transcription_transcriber.py index 35f79c43..c5a6cbff 100644 --- a/models/transcription/transcription_transcriber.py +++ b/models/transcription/transcription_transcriber.py @@ -37,21 +37,16 @@ class AudioTranscriber: self.whisper_model = getWhisperModel(root, whisper_weight_type) self.transcription_engine = "Whisper" - def transcribeAudioQueue(self, audio_queue, language, country, transcription_engine): + def transcribeAudioQueue(self, audio_queue, language, country): audio, time_spoken = audio_queue.get() self.updateLastSampleAndPhraseStatus(audio, time_spoken) text = '' try: - # Whisperが使用できない場合はGoogle Speech-to-Textを使用する - if transcription_engine == "Whisper": - if self.whisper_model is None: - transcription_engine = "Google" - audio_data = self.audio_sources["process_data_func"]() - match transcription_engine: + match self.transcription_engine: case "Google": - text = self.audio_recognizer.recognize_google(audio_data, language=transcription_lang[language][country][transcription_engine]) + 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): @@ -62,7 +57,7 @@ class AudioTranscriber: temperature=0.0, log_prob_threshold=-0.8, no_speech_threshold=0.6, - language=transcription_lang[language][country][transcription_engine], + language=transcription_lang[language][country][self.transcription_engine], word_timestamps=False, without_timestamps=True, task="transcribe", From bc0d2f246b31173b09a9a10e11511efaa31f7643 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 14 Feb 2024 00:00:14 +0900 Subject: [PATCH 35/43] =?UTF-8?q?=F0=9F=9A=A7[WIP/TEST]=20Model=20:=20?= =?UTF-8?q?=E6=96=87=E5=AD=97=E8=B5=B7=E3=81=93=E3=81=97=E6=A9=9F=E8=83=BD?= =?UTF-8?q?=E3=81=AE=E7=B5=82=E4=BA=86=E6=99=82=E3=81=AB=E3=82=AD=E3=83=A3?= =?UTF-8?q?=E3=83=83=E3=82=B7=E3=83=A5=E3=82=AF=E3=83=AA=E3=82=A2=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/model.py b/model.py index 62d727cd..f5913d17 100644 --- a/model.py +++ b/model.py @@ -1,3 +1,4 @@ +import gc import tempfile from zipfile import ZipFile from subprocess import Popen @@ -336,7 +337,7 @@ class Model: ) # self.mic_audio_recorder.recordIntoQueue(mic_audio_queue, mic_energy_queue) self.mic_audio_recorder.recordIntoQueue(mic_audio_queue, None) - mic_transcriber = AudioTranscriber( + self.mic_transcriber = AudioTranscriber( speaker=False, source=self.mic_audio_recorder.source, phrase_timeout=phase_timeout, @@ -346,13 +347,19 @@ class Model: 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 + 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() @@ -363,7 +370,7 @@ class Model: # pass # sleep(0.01) - self.mic_print_transcript = threadFnc(sendMicTranscript) + self.mic_print_transcript = threadFnc(sendMicTranscript, end_fnc=endMicTranscript) self.mic_print_transcript.daemon = True self.mic_print_transcript.start() @@ -439,7 +446,7 @@ class Model: ) # self.speaker_audio_recorder.recordIntoQueue(speaker_audio_queue, speaker_energy_queue) self.speaker_audio_recorder.recordIntoQueue(speaker_audio_queue ,None) - speaker_transcriber = AudioTranscriber( + self.speaker_transcriber = AudioTranscriber( speaker=True, source=self.speaker_audio_recorder.source, phrase_timeout=phase_timeout, @@ -449,13 +456,19 @@ class Model: 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 + 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() @@ -466,7 +479,7 @@ class Model: # pass # sleep(0.01) - self.speaker_print_transcript = threadFnc(sendSpeakerTranscript) + self.speaker_print_transcript = threadFnc(sendSpeakerTranscript, end_fnc=endSpeakerTranscript) self.speaker_print_transcript.daemon = True self.speaker_print_transcript.start() From 4eb811aac4b113043f7b9e0bbfb3ae149dd9ce97 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 14 Feb 2024 00:21:56 +0900 Subject: [PATCH 36/43] =?UTF-8?q?=F0=9F=9A=A7[WIP/TEST]=20Model=20:=20?= =?UTF-8?q?=E7=BF=BB=E8=A8=B3=E8=B5=B7=E5=8B=95=E6=99=82=E3=81=AB=E3=83=A2?= =?UTF-8?q?=E3=83=87=E3=83=AB=E3=82=92=E3=83=AD=E3=83=BC=E3=83=89=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 2 ++ model.py | 5 +++-- models/translation/translation_translator.py | 8 ++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/controller.py b/controller.py index a40282df..34ea3a14 100644 --- a/controller.py +++ b/controller.py @@ -377,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): diff --git a/model.py b/model.py index f5913d17..e39b718f 100644 --- a/model.py +++ b/model.py @@ -66,8 +66,6 @@ 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.CTRANSLATE2_WEIGHT_TYPE) self.keyword_processor = KeywordProcessor() def checkCTranslatorCTranslate2ModelWeight(self): @@ -76,6 +74,9 @@ class Model: def changeTranslatorCTranslate2Model(self): 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) diff --git a/models/translation/translation_translator.py b/models/translation/translation_translator.py index c966c672..a71d0f55 100644 --- a/models/translation/translation_translator.py +++ b/models/translation/translation_translator.py @@ -1,3 +1,4 @@ +import gc import os from deepl import Translator as deepl_Translator from translators import translate_text as other_web_Translator @@ -44,6 +45,13 @@ class Translator(): 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): match translator_name: From e012f92e0676c8f770770a394ba126cbfc8b1bbf Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 14 Feb 2024 10:57:14 +0900 Subject: [PATCH 37/43] =?UTF-8?q?[Update]=20Config=20Window:=20Message=20F?= =?UTF-8?q?ormats.=20=E6=96=87=E8=A8=80=E3=81=AE=E5=A4=89=E6=9B=B4?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en.yml | 25 +++++++++++++------------ locales/ja.yml | 10 ++++++---- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index 93f07195..e8c3f925 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -82,7 +82,7 @@ config_window: 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 @@ -227,25 +227,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 5d0b6acf..121a287b 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -81,6 +81,8 @@ config_window: transcription_speaker: スピーカー transcription_internal_model: 音声認識モデル others: その他 + others_send_message_formats: メッセージフォーマット (送信) + others_received_message_formats: メッセージフォーマット (XSOverlay & Speaker2Chatbox) advanced_settings: 高度な設定 @@ -234,14 +236,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]という文字は使えません。" From 099b36e1bf1a40285ca86e9948cdcb540298f8f2 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 14 Feb 2024 16:08:26 +0900 Subject: [PATCH 38/43] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Config=20:=20?= =?UTF-8?q?=E4=B8=8D=E8=A6=81=E3=81=AAjson=E3=81=B8=E3=81=AE=E4=BF=9D?= =?UTF-8?q?=E5=AD=98=E5=87=A6=E7=90=86=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.py b/config.py index a5405100..600ea67b 100644 --- a/config.py +++ b/config.py @@ -308,7 +308,7 @@ class Config: 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) + # saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property @json_serializable('IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE') From 3a06ee5cf1ecbe60d196c4cc519ee30826aba04e Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 14 Feb 2024 19:37:03 +0900 Subject: [PATCH 39/43] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Controller=20:=20?= =?UTF-8?q?=E3=83=A2=E3=83=87=E3=83=AB=E3=81=8C=E3=83=80=E3=82=A6=E3=83=B3?= =?UTF-8?q?=E3=83=AD=E3=83=BC=E3=83=89=E3=81=95=E3=82=8C=E3=81=A6=E3=81=84?= =?UTF-8?q?=E7=8A=B6=E6=85=8B=E3=81=A7=20whisper=E6=A9=9F=E8=83=BD?= =?UTF-8?q?=E3=82=92=20ON=20->=20OFF=E3=81=97=E3=81=9F=E5=A0=B4=E5=90=88?= =?UTF-8?q?=E3=81=AB=E3=83=AA=E3=82=B9=E3=82=BF=E3=83=BC=E3=83=88=E3=83=9C?= =?UTF-8?q?=E3=82=BF=E3=83=B3=E3=81=8C=E8=A1=A8=E7=A4=BA=E3=81=95=E3=82=8C?= =?UTF-8?q?=E7=B6=9A=E3=81=91=E3=82=8B=E3=83=90=E3=82=B0=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 1 + 1 file changed, 1 insertion(+) diff --git a/controller.py b/controller.py index 34ea3a14..6cef394e 100644 --- a/controller.py +++ b/controller.py @@ -808,6 +808,7 @@ def callbackSetUserWhisperFeature(value): config.SELECTED_TRANSCRIPTION_ENGINE = "Google" else: view.closeWhisperWeightTypeWidget() + config.IS_RESET_BUTTON_DISPLAYED_FOR_WHISPER = False config.SELECTED_TRANSCRIPTION_ENGINE = "Google" view.showRestartButtonIfRequired() From 9e12d43fe0eee4a02bd4645f4cb23e89da9e5b3a Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 14 Feb 2024 23:00:54 +0900 Subject: [PATCH 40/43] =?UTF-8?q?[Update/bugfix]=20Config=20Window:=20Deep?= =?UTF-8?q?L=20Auth=20Key.=20DeepL=E3=82=A2=E3=82=AB=E3=82=A6=E3=83=B3?= =?UTF-8?q?=E3=83=88=E3=83=9A=E3=83=BC=E3=82=B8=E3=82=92=E9=96=8B=E3=81=91?= =?UTF-8?q?=E3=82=8B=E3=83=9C=E3=82=BF=E3=83=B3=E3=82=92=E8=BF=BD=E5=8A=A0?= =?UTF-8?q?=E3=80=82=20=E3=83=A1=E3=82=A4=E3=83=B3=E7=94=BB=E9=9D=A2?= =?UTF-8?q?=E7=BF=BB=E8=A8=B3=E3=82=A8=E3=83=B3=E3=82=B8=E3=83=B3=E9=81=B8?= =?UTF-8?q?=E6=8A=9E=E3=81=AE=E3=83=9C=E3=82=BF=E3=83=B3=E3=82=92=E6=8A=BC?= =?UTF-8?q?=E3=81=97=E3=81=9F=E6=99=82=E3=81=AE=E5=87=A6=E7=90=86=E3=81=8C?= =?UTF-8?q?2=E5=9B=9E=E8=B5=B0=E3=81=A3=E3=81=A6=E3=81=84=E3=81=9F?= =?UTF-8?q?=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 5 ++ img/link_icon_black.png | Bin 0 -> 361 bytes img/link_icon_white.png | Bin 0 -> 373 bytes locales/en.yml | 1 + locales/ja.yml | 1 + view.py | 6 ++ .../_SettingBoxGenerator.py | 71 +++++++++++++++++- .../createSettingBox_Translation.py | 7 +- vrct_gui/ui_managers/Themes/_darkTheme.py | 1 + vrct_gui/ui_managers/Themes/_lightTheme.py | 1 + vrct_gui/ui_managers/UiScalingManager.py | 7 ++ vrct_gui/ui_utils/ui_utils.py | 59 ++++++++++++--- 12 files changed, 147 insertions(+), 12 deletions(-) create mode 100644 img/link_icon_black.png create mode 100644 img/link_icon_white.png diff --git a/config.py b/config.py index d565bfe8..aba44f08 100644 --- a/config.py +++ b/config.py @@ -66,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 @@ -819,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) diff --git a/img/link_icon_black.png b/img/link_icon_black.png new file mode 100644 index 0000000000000000000000000000000000000000..e4790914bca8b01599137d70c50a2efc3c694b5e GIT binary patch literal 361 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?4jBOuH;Rhv(mfq_xl z)5S3)8s4_BKw~a{P0dL|c1t-t+TanF(C$r&Rqa7X8iB z_N;F6!L|PnWv~5Sdw)LzS4INgy*T~+`q<+>ZPIcIkJ+XjNY~yVV&~l`@Z$}O)5Fa| z5zO;@4@CXYVM%&$lQrpKBx}$CW}kaUmLB|lwsB8m$mH*eq8_~dS`Cc;Y7?0Dv&s8wOoSSZ6U@*#t1uIaK>tAx#L7#?X|j7&B-SbO7orr-lM w0meKA6slpr_>Xm$uheS?Fa5!$a_1+5x`^R|erJXnVAwHuy85}Sb4q9e044;CWdHyG literal 0 HcmV?d00001 diff --git a/img/link_icon_white.png b/img/link_icon_white.png new file mode 100644 index 0000000000000000000000000000000000000000..58a56d45a6794bc717b5b91fcffe8eed8f85a80e GIT binary patch literal 373 zcmV-*0gC>KP)RA}Dqne7dNFc5~1;tUSq7EB-!&?(%8B_Kd_0=IAn-J#T9 zH3l@Hw0GCWzCTMyo~JJ#M*xW7O~MV(w1cuaUvLZ9hP?rkbp3TJrMv;aPOk;izy$uT z*Av@o&@`vx!MOD)e42_(%JBgZKm%QWnZcJu{?x}eHakNDKmLRV@VJ2`@ZJh6h4)gx z1l~IVQ+Tfg*1&rsuom760ULOB0b6)h0T=K9A8+E&;PvD{-hiu}*C7>f4Noe70Z%62 z0={Vn_3hgQT>xw6bx@x|bNDnXi@h^=0K^6;;L!xA;L!w>f+Gbynm|?NwGiTr4G(~L z6IW$kV}k3jkJC|r8z2gB14IFCfGEHX5Cymaq5wC*-wV9QY3jd~t`d9$G2D3pFk0zq Tc+A-900000NkvXXu0mjfrRkJD literal 0 HcmV?d00001 diff --git a/locales/en.yml b/locales/en.yml index e8c3f925..1904261a 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -129,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 diff --git a/locales/ja.yml b/locales/ja.yml index 121a287b..2befc4bd 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -128,6 +128,7 @@ config_window: deepl_auth_key: label: DeepL 認証キー desc: "使用の際は、メイン画面にある %{translator} をDeepL_APIに変更してください。\n※対応していない言語もあります。" + open_auth_key_webpage: DeepLアカウントページを開く mic_host: label: マイク(ホスト/ドライバー) diff --git a/view.py b/view.py index c792824e..e7962850 100644 --- a/view.py +++ b/view.py @@ -103,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, @@ -295,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) @@ -978,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 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_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/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) From 10a18edd24b5c05db15366f55865282280987c5f Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 14 Feb 2024 23:29:45 +0900 Subject: [PATCH 41/43] =?UTF-8?q?[Update]=20Config=20Window:=20Whisper?= =?UTF-8?q?=E3=83=A2=E3=83=87=E3=83=AB=20=E8=AA=AC=E6=98=8E=E6=96=87?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0=E3=80=82=E5=AE=9F=E9=9A=9B=E3=81=ABmedium?= =?UTF-8?q?=E4=BB=A5=E4=B8=8A=E3=81=AE=E3=83=A2=E3=83=87=E3=83=AB=E9=81=B8?= =?UTF-8?q?=E6=8A=9E=E6=99=82=E3=80=81=E4=BD=BF=E7=94=A8=E3=81=99=E3=82=89?= =?UTF-8?q?=E5=9B=B0=E9=9B=A3=E3=81=AA=E3=81=8F=E3=82=89=E3=81=84=E9=87=8D?= =?UTF-8?q?=E3=81=84=E3=81=AE=E3=81=A7=E3=80=81=E8=AD=A6=E5=91=8A=E3=81=AE?= =?UTF-8?q?=E6=84=8F=E5=91=B3=E3=82=92=E8=BE=BC=E3=82=81=E3=81=A6=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en.yml | 6 +++--- locales/ja.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index 1904261a..c885795f 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -195,7 +195,7 @@ config_window: 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. + 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)" @@ -238,13 +238,13 @@ config_window: received_message_format: 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" + 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: 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" + 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]'." diff --git a/locales/ja.yml b/locales/ja.yml index 2befc4bd..d7d800e1 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -194,7 +194,7 @@ config_window: whisper_weight_type: label: Whisperモデルのタイプ - desc: 基本的に、容量が多いモデルほど精度は高いですが、文字起こしまでの時間が伸び、CPU使用率も増加します。各モデルの説明はドキュメントをご覧ください。 + desc: "基本的に、容量が多いモデルほど精度は高いですが、文字起こしまでの時間が伸び、CPU使用率も増加します。各モデルの説明はドキュメントをご覧ください。\n※特にmediumより容量の大きいモデルは、CPUの性能によっては使用すらも困難です。" model_template: "%{model_name} モデル (%{capacity})" recommended_model_template: "%{model_name} モデル (%{capacity}) (推奨)" From 42df4f4eefe2d69fd3e197576ac9bd7a16fc1be1 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Thu, 15 Feb 2024 23:01:36 +0900 Subject: [PATCH 42/43] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Controller=20:=20S?= =?UTF-8?q?peaker2Chatbox=E6=9C=89=E5=8A=B9=E6=99=82=E3=81=AE=E3=82=A2?= =?UTF-8?q?=E3=83=83=E3=83=97=E3=83=87=E3=83=BC=E3=83=88=E9=80=9A=E7=9F=A5?= =?UTF-8?q?=E3=81=AE=E3=83=9E=E3=82=B9=E3=82=AF=E5=87=A6=E7=90=86=E3=82=92?= =?UTF-8?q?=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/controller.py b/controller.py index 6cef394e..f695ee45 100644 --- a/controller.py +++ b/controller.py @@ -964,9 +964,8 @@ def createMainWindow(splash): 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: From c34a6c1c9f8297231e0eb95e21ae16993e429d23 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Thu, 15 Feb 2024 23:25:39 +0900 Subject: [PATCH 43/43] =?UTF-8?q?=F0=9F=91=8D[Update]=20Version=20:=202.1.?= =?UTF-8?q?1=20->=202.2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.py b/config.py index e36b9667..772773af 100644 --- a/config.py +++ b/config.py @@ -813,7 +813,7 @@ 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])