diff --git a/.gitignore b/.gitignore index 0d95c681..2223cde5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ memo.txt VRCT.spec *.pyc logs/ -.venv/ \ No newline at end of file +.venv/ +weight/ \ No newline at end of file diff --git a/config.py b/config.py index 4a5e355d..f462dbc2 100644 --- a/config.py +++ b/config.py @@ -5,8 +5,10 @@ from json import load as json_load from json import dump as json_dump import tkinter as tk from tkinter import font -from models.translation.translation_languages import translatorEngine +from languages import selectable_languages +from models.translation.translation_languages import translation_lang from models.transcription.transcription_utils import getInputDevices, getDefaultInputDevice +from models.translation.utils import ctranslate2_weights from utils import generatePercentageStringsList, isUniqueStrings json_serializable_vars = {} @@ -43,8 +45,12 @@ class Config: return self._ENABLE_SPEAKER2CHATBOX @property - def LOCAL_PATH(self): - return self._LOCAL_PATH + def ENABLE_SPEAKER2CHATBOX(self): + return self._ENABLE_SPEAKER2CHATBOX + + @property + def PATH_LOCAL(self): + return self._PATH_LOCAL @property def PATH_CONFIG(self): @@ -90,6 +96,10 @@ class Config: def SELECTABLE_UI_LANGUAGES_DICT(self): return self._SELECTABLE_UI_LANGUAGES_DICT + @property + def CTRANSLATE2_WEIGHTS(self): + return self._CTRANSLATE2_WEIGHTS + @property def MAX_MIC_ENERGY_THRESHOLD(self): return self._MAX_MIC_ENERGY_THRESHOLD @@ -172,13 +182,22 @@ class Config: self._TARGET_LANGUAGE = value @property - def CHOICE_TRANSLATOR(self): - return self._CHOICE_TRANSLATOR + def CHOICE_INPUT_TRANSLATOR(self): + return self._CHOICE_INPUT_TRANSLATOR - @CHOICE_TRANSLATOR.setter - def CHOICE_TRANSLATOR(self, value): - if value in translatorEngine: - self._CHOICE_TRANSLATOR = value + @CHOICE_INPUT_TRANSLATOR.setter + def CHOICE_INPUT_TRANSLATOR(self, value): + if value in list(translation_lang.keys()): + self._CHOICE_INPUT_TRANSLATOR= value + + @property + def CHOICE_OUTPUT_TRANSLATOR(self): + return self._CHOICE_OUTPUT_TRANSLATOR + + @CHOICE_OUTPUT_TRANSLATOR.setter + def CHOICE_OUTPUT_TRANSLATOR(self, value): + if value in list(translation_lang.keys()): + self._CHOICE_OUTPUT_TRANSLATOR = value # Save Json Data ## Main Window @@ -193,6 +212,28 @@ class Config: self._SELECTED_TAB_NO = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + @property + @json_serializable('SELECTED_TAB_YOUR_TRANSLATOR_ENGINES') + def SELECTED_TAB_YOUR_TRANSLATOR_ENGINES(self): + return self._SELECTED_TAB_YOUR_TRANSLATOR_ENGINES + + @SELECTED_TAB_YOUR_TRANSLATOR_ENGINES.setter + def SELECTED_TAB_YOUR_TRANSLATOR_ENGINES(self, value): + if isinstance(value, dict): + self._SELECTED_TAB_YOUR_TRANSLATOR_ENGINES = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('SELECTED_TAB_TARGET_TRANSLATOR_ENGINES') + def SELECTED_TAB_TARGET_TRANSLATOR_ENGINES(self): + return self._SELECTED_TAB_TARGET_TRANSLATOR_ENGINES + + @SELECTED_TAB_TARGET_TRANSLATOR_ENGINES.setter + def SELECTED_TAB_TARGET_TRANSLATOR_ENGINES(self, value): + if isinstance(value, dict): + self._SELECTED_TAB_TARGET_TRANSLATOR_ENGINES = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + @property @json_serializable('SELECTED_TAB_YOUR_LANGUAGES') def SELECTED_TAB_YOUR_LANGUAGES(self): @@ -509,6 +550,30 @@ class Config: self._AUTH_KEYS[key] = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, self.AUTH_KEYS) + @property + @json_serializable('WEIGHT_TYPE') + def WEIGHT_TYPE(self): + return self._WEIGHT_TYPE + + @WEIGHT_TYPE.setter + def WEIGHT_TYPE(self, value): + if isinstance(value, str): + self._WEIGHT_TYPE = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('MESSAGE_FORMAT') + def MESSAGE_FORMAT(self): + return self._MESSAGE_FORMAT + + @MESSAGE_FORMAT.setter + def MESSAGE_FORMAT(self, value): + if isinstance(value, str): + if isUniqueStrings(["[message]", "[translation]"], value) is False: + value = "[message]([translation])" + self._MESSAGE_FORMAT = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + @property @json_serializable('ENABLE_AUTO_CLEAR_MESSAGE_BOX') def ENABLE_AUTO_CLEAR_MESSAGE_BOX(self): @@ -662,9 +727,9 @@ class Config: # Read Only self._VERSION = "2.0.2" self._ENABLE_SPEAKER2CHATBOX = False # Speaker2Chatbox - self._LOCAL_PATH = os_path.dirname(sys.argv[0]) - self._PATH_CONFIG = os_path.join(self._LOCAL_PATH, "config.json") - self._PATH_LOGS = os_path.join(self._LOCAL_PATH, "logs") + 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") os_makedirs(self._PATH_LOGS, exist_ok=True) self._GITHUB_URL = "https://api.github.com/repos/misyaguziya/VRCT/releases/latest" self._BOOTH_URL = "https://misyaguziya.booth.pm/" @@ -680,6 +745,7 @@ class Config: "ko": "한국어" # If you want to add a new language and key, please append it here. } + self._CTRANSLATE2_WEIGHTS = ctranslate2_weights self._MAX_MIC_ENERGY_THRESHOLD = 2000 self._MAX_SPEAKER_ENERGY_THRESHOLD = 4000 @@ -688,7 +754,8 @@ class Config: self._ENABLE_TRANSCRIPTION_SEND = False self._ENABLE_TRANSCRIPTION_RECEIVE = False self._ENABLE_FOREGROUND = False - self._CHOICE_TRANSLATOR = translatorEngine[0] + self._CHOICE_INPUT_TRANSLATOR = "CTranslate2" + self._CHOICE_OUTPUT_TRANSLATOR = "CTranslate2" self._SOURCE_LANGUAGE = "Japanese" self._SOURCE_COUNTRY = "Japan" self._TARGET_LANGUAGE = "English" @@ -697,6 +764,16 @@ class Config: # Save Json Data ## Main Window self._SELECTED_TAB_NO = "1" + self._SELECTED_TAB_YOUR_TRANSLATOR_ENGINES = { + "1":"CTranslate2", + "2":"CTranslate2", + "3":"CTranslate2", + } + self._SELECTED_TAB_TARGET_TRANSLATOR_ENGINES = { + "1":"CTranslate2", + "2":"CTranslate2", + "3":"CTranslate2", + } self._SELECTED_TAB_YOUR_LANGUAGES = { "1":"Japanese\n(Japan)", "2":"Japanese\n(Japan)", @@ -741,10 +818,8 @@ class Config: self._OSC_PORT = 9000 self._AUTH_KEYS = { "DeepL_API": None, - "DeepL": None, - "Bing": None, - "Google": None, } + self._WEIGHT_TYPE = "small" 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 42f17be6..00292d5c 100644 --- a/controller.py +++ b/controller.py @@ -21,8 +21,8 @@ def callbackFilepathLogs(): Popen(['explorer', config.PATH_LOGS.replace('/', '\\')], shell=True) def callbackFilepathConfigFile(): - print("callbackFilepathConfigFile", config.LOCAL_PATH.replace('/', '\\')) - Popen(['explorer', config.LOCAL_PATH.replace('/', '\\')], shell=True) + print("callbackFilepathConfigFile", config.PATH_LOCAL.replace('/', '\\')) + Popen(['explorer', config.PATH_LOCAL.replace('/', '\\')], shell=True) def callbackQuitVrct(): setMainWindowGeometry() @@ -66,10 +66,10 @@ def sendMicMessage(message): pass else: translation = model.getInputTranslate(message) - if translation is False: - config.ENABLE_TRANSLATION = False - translation = "" - view.translationEngineLimitErrorProcess() + # if translation is False: + # config.ENABLE_TRANSLATION = False + # translation = "" + # view.translationEngineLimitErrorProcess() if config.ENABLE_TRANSCRIPTION_SEND is True: if config.ENABLE_SEND_MESSAGE_TO_VRC is True: @@ -133,10 +133,10 @@ def receiveSpeakerMessage(message): pass else: translation = model.getOutputTranslate(message) - if translation is False: - config.ENABLE_TRANSLATION = False - translation = "" - view.translationEngineLimitErrorProcess() + # if translation is False: + # config.ENABLE_TRANSLATION = False + # translation = "" + # view.translationEngineLimitErrorProcess() if config.ENABLE_TRANSCRIPTION_RECEIVE is True: if config.ENABLE_NOTICE_XSOVERLAY is True: @@ -203,10 +203,10 @@ def sendChatMessage(message): pass else: translation = model.getInputTranslate(message) - if translation is False: - config.ENABLE_TRANSLATION = False - translation = "" - view.translationEngineLimitErrorProcess() + # if translation is False: + # config.ENABLE_TRANSLATION = False + # translation = "" + # view.translationEngineLimitErrorProcess() # send OSC message if config.ENABLE_SEND_MESSAGE_TO_VRC is True: @@ -250,6 +250,12 @@ def messageBoxFocusOut(e): model.oscStopSendTyping() # func select languages +def initSetTranslateEngine(): + engine = config.SELECTED_TAB_YOUR_TRANSLATOR_ENGINES[config.SELECTED_TAB_NO] + config.CHOICE_INPUT_TRANSLATOR = engine + engine = config.SELECTED_TAB_TARGET_TRANSLATOR_ENGINES[config.SELECTED_TAB_NO] + config.CHOICE_OUTPUT_TRANSLATOR = engine + def initSetLanguageAndCountry(): select = config.SELECTED_TAB_YOUR_LANGUAGES[config.SELECTED_TAB_NO] language, country = model.getLanguageAndCountry(select) @@ -259,7 +265,18 @@ def initSetLanguageAndCountry(): language, country = model.getLanguageAndCountry(select) config.TARGET_LANGUAGE = language config.TARGET_COUNTRY = country - config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) + +def setYourTranslateEngine(select): + engines = config.SELECTED_TAB_YOUR_TRANSLATOR_ENGINES + engines[config.SELECTED_TAB_NO] = select + config.SELECTED_TAB_YOUR_TRANSLATOR_ENGINES = engines + config.CHOICE_INPUT_TRANSLATOR = select + +def setTargetTranslateEngine(select): + engines = config.SELECTED_TAB_TARGET_TRANSLATOR_ENGINES + engines[config.SELECTED_TAB_NO] = select + config.SELECTED_TAB_TARGET_TRANSLATOR_ENGINES = engines + config.CHOICE_OUTPUT_TRANSLATOR = select def setYourLanguageAndCountry(select): languages = config.SELECTED_TAB_YOUR_LANGUAGES @@ -268,7 +285,6 @@ def setYourLanguageAndCountry(select): language, country = model.getLanguageAndCountry(select) config.SOURCE_LANGUAGE = language config.SOURCE_COUNTRY = country - config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) view.printToTextbox_selectedYourLanguages(select) def setTargetLanguageAndCountry(select): @@ -278,7 +294,6 @@ def setTargetLanguageAndCountry(select): language, country = model.getLanguageAndCountry(select) config.TARGET_LANGUAGE = language config.TARGET_COUNTRY = country - config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) view.printToTextbox_selectedTargetLanguages(select) def swapYourLanguageAndTargetLanguage(): @@ -293,17 +308,26 @@ def swapYourLanguageAndTargetLanguage(): def callbackSelectedLanguagePresetTab(selected_tab_no): config.SELECTED_TAB_NO = selected_tab_no view.updateGuiVariableByPresetTabNo(config.SELECTED_TAB_NO) + + engines = config.SELECTED_TAB_YOUR_TRANSLATOR_ENGINES + engine = engines[config.SELECTED_TAB_NO] + config.CHOICE_INPUT_TRANSLATOR = engine + + engines = config.SELECTED_TAB_TARGET_TRANSLATOR_ENGINES + engine = engines[config.SELECTED_TAB_NO] + config.CHOICE_OUTPUT_TRANSLATOR = engine + languages = config.SELECTED_TAB_YOUR_LANGUAGES select = languages[config.SELECTED_TAB_NO] language, country = model.getLanguageAndCountry(select) config.SOURCE_LANGUAGE = language config.SOURCE_COUNTRY = country + languages = config.SELECTED_TAB_TARGET_LANGUAGES select = languages[config.SELECTED_TAB_NO] language, country = model.getLanguageAndCountry(select) config.TARGET_LANGUAGE = language config.TARGET_COUNTRY = country - config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) view.printToTextbox_changedLanguagePresetTab(config.SELECTED_TAB_NO) # command func @@ -444,7 +468,7 @@ def callbackSetEnableRestoreMainWindowGeometry(value): def callbackSetDeeplAuthkey(value): print("callbackSetDeeplAuthkey", str(value)) if len(value) == 39: - result = model.authenticationTranslator(choice_translator="DeepL_API", auth_key=value) + result = model.authenticationTranslatorDeepLAuthKey(auth_key=value) if result is True: key = value view.printToTextbox_AuthenticationSuccess() @@ -454,12 +478,10 @@ def callbackSetDeeplAuthkey(value): auth_keys = config.AUTH_KEYS auth_keys["DeepL_API"] = key config.AUTH_KEYS = auth_keys - config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) elif len(value) == 0: auth_keys = config.AUTH_KEYS auth_keys["DeepL_API"] = None config.AUTH_KEYS = auth_keys - config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) # Transcription Tab # Transcription (Mic) @@ -801,15 +823,16 @@ def createMainWindow(): # init config initSetConfigByExeArguments() + initSetTranslateEngine() initSetLanguageAndCountry() - if model.authenticationTranslator(config.CHOICE_TRANSLATOR, config.AUTH_KEYS[config.CHOICE_TRANSLATOR]) is False: - # error update Auth key - auth_keys = config.AUTH_KEYS - auth_keys[config.CHOICE_TRANSLATOR] = None - config.AUTH_KEYS = auth_keys - view.printToTextbox_AuthenticationError() - config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) + if config.AUTH_KEYS["DeepL_API"] is not None: + if model.authenticationTranslatorDeepLAuthKey("DeepL_API", config.AUTH_KEYS["DeepL_API"]) is False: + # error update Auth key + auth_keys = config.AUTH_KEYS + auth_keys["DeepL_API"] = None + config.AUTH_KEYS = auth_keys + view.printToTextbox_AuthenticationError() # set word filter model.addKeywords() diff --git a/img/VRCT_now_downloading.png b/img/VRCT_now_downloading.png new file mode 100644 index 00000000..5e5b9daa Binary files /dev/null and b/img/VRCT_now_downloading.png differ diff --git a/install.bat b/install.bat index d54ef800..036f6a51 100644 --- a/install.bat +++ b/install.bat @@ -1,4 +1,4 @@ +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/deepl-translate pip install git+https://github.com/misyaguziya/custom_speech_recognition \ No newline at end of file diff --git a/main.py b/main.py index def1cb0b..00bd351a 100644 --- a/main.py +++ b/main.py @@ -7,6 +7,10 @@ if __name__ == "__main__": splash = SplashWindow() splash.showSplash() + from config import config + from models.translation.utils import downloadCTranslate2Weight + downloadCTranslate2Weight(config.PATH_LOCAL, config.WEIGHT_TYPE, config.CTRANSLATE2_WEIGHTS, splash.updateDownloadProgress) + import controller controller.createMainWindow() splash.destroySplash() diff --git a/model.py b/model.py index 5fbb5825..33c30973 100644 --- a/model.py +++ b/model.py @@ -22,7 +22,7 @@ from models.transcription.transcription_recorder import SelectedMicRecorder, Sel from models.transcription.transcription_recorder import SelectedMicEnergyRecorder, SelectedSpeakeEnergyRecorder from models.transcription.transcription_transcriber import AudioTranscriber from models.xsoverlay.notification import xsoverlayForVRCT -from models.translation.translation_languages import translatorEngine, translation_lang +from models.translation.translation_languages import translation_lang from models.transcription.transcription_languages import transcription_lang from config import config @@ -45,15 +45,6 @@ class threadFnc(Thread): self.fnc(*self._args, **self._kwargs) class Model: - # Languages available for both transcription and translation - SUPPORTED_LANGUAGES = [ - 'Afrikaans', 'Arabic', 'Basque', 'Bulgarian', 'Catalan', 'Chinese', 'Croatian', - 'Czech', 'Danish', 'Dutch', 'English', 'Filipino', 'Finnish', 'French', 'German', - 'Greek', 'Hebrew', 'Hindi', 'Hungarian', 'Indonesian', 'Italian', 'Japanese', - 'Korean', 'Lithuanian', 'Malay', 'Norwegian', 'Polish', 'Portuguese', 'Romanian', - 'Russian', 'Serbian', 'Slovak', 'Slovenian', 'Spanish', 'Swedish', 'Thai', 'Turkish', - 'Ukrainian', 'Vietnamese' - ] _instance = None def __new__(cls): @@ -72,24 +63,19 @@ class Model: self.speaker_audio_recorder = None self.speaker_energy_recorder = None self.speaker_energy_plot_progressbar = None - self.translator = Translator() + self.translator = Translator(config.PATH_LOCAL, config.CTRANSLATE2_WEIGHTS[config.WEIGHT_TYPE]) self.keyword_processor = KeywordProcessor() def resetTranslator(self): del self.translator - self.translator = Translator() + self.translator = Translator(config.PATH_LOCAL, config.CTRANSLATE2_WEIGHTS[config.WEIGHT_TYPE]) def resetKeywordProcessor(self): del self.keyword_processor self.keyword_processor = KeywordProcessor() - def authenticationTranslator(self, choice_translator=None, auth_key=None): - if choice_translator is None: - choice_translator = config.CHOICE_TRANSLATOR - if auth_key is None: - auth_key = config.AUTH_KEYS[choice_translator] - - result = self.translator.authentication(choice_translator, auth_key) + def authenticationTranslatorDeepLAuthKey(self, auth_key): + result = self.translator.authenticationDeepLAuthKey(auth_key) return result def startLogger(self): @@ -108,13 +94,21 @@ class Model: self.logger.disabled = True self.logger = None - @staticmethod - def getListLanguageAndCountry(): + def getListLanguageAndCountry(self): + transcription_langs = list(transcription_lang.keys()) + tl_keys = translation_lang.keys() + translation_langs = [] + for tl_key in tl_keys: + for lang in translation_lang[tl_key]["source"]: + translation_langs.append(lang) + translation_langs = list(set(translation_langs)) + supported_langs = list(filter(lambda x: x in transcription_langs, translation_langs)) + langs = [] - for lang in model.SUPPORTED_LANGUAGES: + for lang in supported_langs: for country in transcription_lang[lang]: langs.append(f"{lang}\n({country})") - return langs + return sorted(langs) @staticmethod def getLanguageAndCountry(select): @@ -123,83 +117,73 @@ class Model: country = parts[1][1:-1] return language, country - def findTranslationEngine(self, source_lang, target_lang): + def findTranslationEngines(self, source_lang, target_lang): compatible_engines = [] - for engine in translatorEngine: - source_languages = translation_lang.get(engine, {}).get("source", {}) - target_languages = translation_lang.get(engine, {}).get("target", {}) - if source_lang in source_languages and target_lang in target_languages: + for engine in list(translation_lang.keys()): + languages = translation_lang.get(engine, {}).get("source", {}) + if source_lang in languages and target_lang in languages: compatible_engines.append(engine) - engine_name = compatible_engines[0] + if "DeepL_API" in compatible_engines: + if self.translator.deepl_client is None: + compatible_engines.remove('DeepL_API') + return compatible_engines - if engine_name == "DeepL" and config.AUTH_KEYS["DeepL_API"] is not None: - if self.authenticationTranslator(engine_name, config.AUTH_KEYS["DeepL_API"]) is True: - engine_name = "DeepL_API" - elif engine_name == "DeepL_API" and config.AUTH_KEYS["DeepL_API"] is None: - engine_name = "DeepL" - - return engine_name - - def getInputTranslate(self, message): - translator_name=config.CHOICE_TRANSLATOR + def getInputTranslate(self, message, fnc=None): + translator_name=config.CHOICE_INPUT_TRANSLATOR source_language=config.SOURCE_LANGUAGE target_language=config.TARGET_LANGUAGE target_country = config.TARGET_COUNTRY - if translator_name == "DeepL_API": - if target_language == "English": - if target_country in ["United States", "Canada", "Philippines"]: - target_language = "English American" - else: - target_language = "English British" - elif target_language == "Portuguese": - if target_country in ["Portugal"]: - target_language = "Portuguese European" - else: - target_language = "Portuguese Brazilian" - elif translator_name == "DeepL": - if target_language in ["English American", "English British"]: - target_language = "English" - elif target_language in ["Portuguese European", "Portuguese Brazilian"]: - target_language = "Portuguese" - translation = self.translator.translate( translator_name=translator_name, source_language=source_language, target_language=target_language, + target_country=target_country, message=message ) + + # 翻訳失敗時のフェールセーフ処理 + if translation is False: + translation = self.translator.translate( + translator_name="CTranslate2", + source_language=source_language, + target_language=target_language, + target_country=target_country, + message=message + ) + try: + fnc() + except Exception: + pass return translation - def getOutputTranslate(self, message): - translator_name=config.CHOICE_TRANSLATOR + def getOutputTranslate(self, message, fnc=None): + translator_name=config.CHOICE_OUTPUT_TRANSLATOR source_language=config.TARGET_LANGUAGE target_language=config.SOURCE_LANGUAGE - target_country = config.SOURCE_COUNTRY - - if translator_name == "DeepL_API": - if target_language == "English": - if target_country in ["United States", "Canada", "Philippines"]: - target_language = "English American" - else: - target_language = "English British" - elif target_language == "Portuguese": - if target_country in ["Portugal"]: - target_language = "Portuguese European" - else: - target_language = "Portuguese Brazilian" - elif translator_name == "DeepL": - if target_language in ["English American", "English British"]: - target_language = "English" - elif target_language in ["Portuguese European", "Portuguese Brazilian"]: - target_language = "Portuguese" + target_country=config.SOURCE_COUNTRY translation = self.translator.translate( translator_name=translator_name, source_language=source_language, target_language=target_language, + target_country=target_country, message=message ) + + # 翻訳失敗時のフェールセーフ処理 + if translation is False: + translation = self.translator.translate( + translator_name="CTranslate2", + source_language=source_language, + target_language=target_language, + target_country=target_country, + message=message + ) + try: + fnc() + except Exception: + pass return translation def addKeywords(self): @@ -273,7 +257,7 @@ class Model: folder_name = '_internal' tmp_directory_name = 'tmp' batch_name = 'update.bat' - current_directory = config.LOCAL_PATH + current_directory = config.PATH_LOCAL try: res = requests_get(config.GITHUB_URL) @@ -306,7 +290,7 @@ class Model: program_name = 'VRCT.exe' folder_name = '_internal' batch_name = 'restart.bat' - current_directory = config.LOCAL_PATH + current_directory = config.PATH_LOCAL copyfile(os_path.join(current_directory, folder_name, "batch", batch_name), os_path.join(current_directory, batch_name)) command = [os_path.join(current_directory, batch_name), program_name] Popen(command, cwd=current_directory) @@ -492,4 +476,4 @@ class Model: def notificationXSOverlay(self, message): xsoverlayForVRCT(content=f"{message}") -model = Model() +model = Model() \ No newline at end of file diff --git a/models/translation/translation_languages.py b/models/translation/translation_languages.py index ae57d4cc..44051f8a 100644 --- a/models/translation/translation_languages.py +++ b/models/translation/translation_languages.py @@ -1,35 +1,40 @@ -translatorEngine = ["DeepL", "DeepL_API", "Google", "Bing"] translation_lang = {} dict_deepl_languages = { - "Japanese":"JA", - "English":"EN", - "Korean":"KO", - "Bulgarian":"BG", - "Chinese":"ZH", - "Czech":"CS", - "Danish":"DA", - "Dutch":"NL", - "Estonian":"ET", - "Finnish":"FI", - "French":"FR", - "German":"DE", - "Greek":"EL", - "Hungarian":"HU", - "Italian":"IT", - "Latvian":"LV", - "Lithuanian":"LT", - "Polish":"PL", - "Portuguese":"PT", - "Romanian":"RO", - "Russian":"RU", - "Slovak":"SK", - "Slovenian":"SL", - "Spanish":"ES", - "Swedish":"SV", - "Indonesian":"ID", - "Ukrainian":"UK", - "Turkish":"TR", - "Norwegian":"NB", + 'Arabic':'ar', + 'Bulgarian':'bg', + 'Czech':'cs', + 'Danish':'da', + 'German':'de', + 'Greek':'el', + 'English':'en', + 'Spanish':'es', + 'Estonian':'et', + 'Finnish':'fi', + 'French':'fr', + 'Irish':'ga', + 'Croatian':'hr', + 'Hungarian':'hu', + 'Indonesian':'id', + 'Icelandic':'is', + 'Italian':'it', + 'Japanese':'ja', + 'Korean':'ko', + 'Lithuanian':'lt', + 'Latvian':'lv', + 'Maltese':'mt', + 'Bokmal':'nb', + 'Dutch':'nl', + 'Norwegian':'no', + 'Polish':'pl', + 'Portuguese':'pt', + 'Romanian':'ro', + 'Russian':'ru', + 'Slovak':'sk', + 'Slovenian':'sl', + 'Swedish':'sv', + 'Turkish':'tr', + 'Ukrainian':'uk', + 'Chinese':'zh', } translation_lang["DeepL"] = { "source":dict_deepl_languages, @@ -240,4 +245,133 @@ dict_bing_languages = { translation_lang["Bing"] = { "source":dict_bing_languages, "target":dict_bing_languages, +} + +dict_papago_languages = { + 'German': 'de', + 'English': 'en', + 'Spanish':'es', + 'French': 'fr', + 'Hindi': 'hi', + 'Indonesian': 'id', + 'Italian': 'it', + 'Japanese': 'ja', + 'Korean': 'ko', + 'Portuguese': 'pt', + 'Russian': 'ru', + 'Thai': 'th', + 'Vietnamese': 'vi', + 'Chinese':'zh-CN', +} + +translation_lang["Papago"] = { + "source":dict_papago_languages, + "target":dict_papago_languages, +} + +dict_ctranslate2_languages = { + 'English': 'en', + 'Chinese': 'zh', + 'German': 'de', + 'Spanish': 'es', + 'Russian': 'ru', + 'Korean': 'ko', + 'French': 'fr', + 'Japanese': 'ja', + 'Portuguese': 'pt', + 'Turkish': 'tr', + 'Polish': 'pl', + 'Catalan': 'ca', + 'Dutch': 'nl', + 'Arabic': 'ar', + 'Swedish': 'sv', + 'Italian': 'it', + 'Indonesian': 'id', + 'Hindi': 'hi', + 'Finnish': 'fi', + 'Vietnamese': 'vi', + 'Hebrew': 'he', + 'Ukrainian': 'uk', + 'Greek': 'el', + 'Malay': 'ms', + 'Czech': 'cs', + 'Romanian': 'ro', + 'Danish': 'da', + 'Hungarian': 'hu', + 'Tamil': 'ta', + 'Norwegian': 'no', + 'Thai': 'th', + 'Urdu': 'ur', + 'Croatian': 'hr', + 'Bulgarian': 'bg', + 'Lithuanian': 'lt', + 'Latin': 'la', + 'Maori': 'mi', + 'Malayalam': 'ml', + 'Welsh': 'cy', + 'Slovak': 'sk', + 'Telugu': 'te', + 'Persian': 'fa', + 'Latvian': 'lv', + 'Bengali': 'bn', + 'Serbian': 'sr', + 'Azerbaijani': 'az', + 'Slovenian': 'sl', + 'Kannada': 'kn', + 'Estonian': 'et', + 'Macedonian': 'mk', + 'Breton': 'br', + 'Basque': 'eu', + 'Icelandic': 'is', + 'Armenian': 'hy', + 'Nepali': 'ne', + 'Mongolian': 'mn', + 'Bosnian': 'bs', + 'Kazakh': 'kk', + 'Albanian': 'sq', + 'Swahili': 'sw', + 'Galician': 'gl', + 'Marathi': 'mr', + 'Punjabi': 'pa', + 'Sinhala': 'si', + 'Khmer': 'km', + 'Shona': 'sn', + 'Yoruba': 'yo', + 'Somali': 'so', + 'Afrikaans': 'af', + 'Occitan': 'oc', + 'Georgian': 'ka', + 'Belarusian': 'be', + 'Tajik': 'tg', + 'Sindhi': 'sd', + 'Gujarati': 'gu', + 'Amharic': 'am', + 'Yiddish': 'yi', + 'Lao': 'lo', + 'Uzbek': 'uz', + 'Faroese': 'fo', + 'Haitian creole': 'ht', + 'Pashto': 'ps', + 'Turkmen': 'tk', + 'Nynorsk': 'nn', + 'Maltese': 'mt', + 'Sanskrit': 'sa', + 'Luxembourgish': 'lb', + 'Myanmar': 'my', + 'Tibetan': 'bo', + 'Filipino': 'tl', + 'Malagasy': 'mg', + 'Assamese': 'as', + 'Tatar': 'tt', + 'Hawaiian': 'haw', + 'Lingala': 'ln', + 'Hausa': 'ha', + 'Bashkir': 'ba', + 'Javanese': 'jw', + 'Sundanese': 'su' +} + +translation_lang["CTranslate2"] = { + "source":dict_ctranslate2_languages, + "target":dict_ctranslate2_languages, } \ No newline at end of file diff --git a/models/translation/translation_translator.py b/models/translation/translation_translator.py index c3a5682b..21710e2d 100644 --- a/models/translation/translation_translator.py +++ b/models/translation/translation_translator.py @@ -1,43 +1,69 @@ +import os from deepl import Translator as deepl_Translator -from deepl_translate import translate as deepl_web_Translator from translators import translate_text as other_web_Translator from .translation_languages import translation_lang +import ctranslate2 +import transformers + +TRANSLATE_MODELS = { + "small": "facebook/m2m100_418M", + "large": "facebook/m2m100_1.2B" +} + # Translator class Translator(): - def __init__(self): - pass + def __init__(self, path, weight_config): self.translator_status = {} + directory_name = weight_config["directory_name"] + tokenizer = weight_config["tokenizer"] + self.weight_path = os.path.join(path, "weight", directory_name) + self.translator = ctranslate2.Translator(self.weight_path, device="cpu", device_index=0, compute_type="int8", inter_threads=1, intra_threads=4) + self.tokenizer = transformers.AutoTokenizer.from_pretrained(tokenizer) + self.deepl_client = None - def authentication(self, translator_name, authkey=None): + def authenticationDeepLAuthKey(self, authkey): result = True - match translator_name: - case "DeepL_API": - try: - self.deepl_client = deepl_Translator(authkey) - self.deepl_client.translate_text(" ", target_lang="EN-US") - except Exception: - result = False + try: + self.deepl_client = deepl_Translator(authkey) + self.deepl_client.translate_text(" ", target_lang="EN-US") + except Exception: + self.deepl_client = None + result = False return result - def translate(self, translator_name, source_language, target_language, message): + def translate(self, translator_name, source_language, target_language, target_country, message): try: result = "" source_language=translation_lang[translator_name]["source"][source_language] target_language=translation_lang[translator_name]["target"][target_language] match translator_name: case "DeepL": - result = deepl_web_Translator( - source_language=source_language, - target_language=target_language, - text=message + result = other_web_Translator( + query_text=message, + translator="deepl", + from_language=source_language, + to_language=target_language, ) case "DeepL_API": - result = self.deepl_client.translate_text( - message, - source_lang=source_language, - target_lang=target_language, - ).text + if self.deepl_client is None: + result = False + else: + if target_language == "English": + if target_country in ["United States", "Canada", "Philippines"]: + target_language = "English American" + else: + target_language = "English British" + elif target_language == "Portuguese": + if target_country in ["Portugal"]: + target_language = "Portuguese European" + else: + target_language = "Portuguese Brazilian" + result = self.deepl_client.translate_text( + message, + source_lang=source_language, + target_lang=target_language, + ).text case "Google": result = other_web_Translator( query_text=message, @@ -52,6 +78,20 @@ class Translator(): from_language=source_language, to_language=target_language, ) + case "Papago": + result = other_web_Translator( + query_text=message, + translator="papago", + from_language=source_language, + to_language=target_language, + ) + case "CTranslate2": + self.tokenizer.src_lang = source_language + source = self.tokenizer.convert_ids_to_tokens(self.tokenizer.encode(message)) + target_prefix = [self.tokenizer.lang_code_to_token[target_language]] + results = self.translator.translate_batch([source], target_prefix=[target_prefix]) + target = results[0].hypotheses[0][1:] + result = self.tokenizer.decode(self.tokenizer.convert_tokens_to_ids(target)) except Exception: import traceback with open('error.log', 'a') as f: diff --git a/models/translation/utils.py b/models/translation/utils.py new file mode 100644 index 00000000..c41fbb88 --- /dev/null +++ b/models/translation/utils.py @@ -0,0 +1,81 @@ +import tempfile +from zipfile import ZipFile +from os import path as os_path +from os import makedirs as os_makedirs +from requests import get as requests_get, head as requests_head +from tqdm import tqdm +from typing import Callable +import hashlib + +ctranslate2_weights = { + "small": { # M2M-100 418M-parameter model + "url": "https://bit.ly/33fM1AO", + "directory_name": "m2m100_418m", + "tokenizer": "facebook/m2m100_418M", + "hash": { + "model.bin": "e7c26a9abb5260abd0268fbe3040714070dec254a990b4d7fd3f74c5230e3acb", + "sentencepiece.model": "d8f7c76ed2a5e0822be39f0a4f95a55eb19c78f4593ce609e2edbc2aea4d380a", + "shared_vocabulary.txt": "bd440aa21b8ca3453fc792a0018a1f3fe68b3464aadddd4d16a4b72f73c86d8c", + } + }, + "large": { # M2M-100 1.2B-parameter model + "url": "https://bit.ly/3GYiaed", + "directory_name": "m2m100_12b", + "tokenizer": "facebook/m2m100_1.2b", + "hash": { + "model.bin": "abb7bf4ba7e5e016b6e3ed480c752459b2f783ac8fca372e7587675e5bf3a919", + "sentencepiece.model": "d8f7c76ed2a5e0822be39f0a4f95a55eb19c78f4593ce609e2edbc2aea4d380a", + "shared_vocabulary.txt": "bd440aa21b8ca3453fc792a0018a1f3fe68b3464aadddd4d16a4b72f73c86d8c", + } + }, +} + +def calculate_file_hash(file_path, block_size=65536): + hash_object = hashlib.sha256() + + with open(file_path, 'rb') as file: + for block in iter(lambda: file.read(block_size), b''): + hash_object.update(block) + + return hash_object.hexdigest() + +def downloadCTranslate2Weight(path, weight_type="small", ctranslate2_weights=ctranslate2_weights, func=None): + url = ctranslate2_weights[weight_type]["url"] + filename = 'weight.zip' + 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"] + + # check already downloaded + if all(os_path.exists(os_path.join(current_directory, directory_name, 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)) + if original_hash != current_hash: + break + 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)) + pbar = tqdm(total=file_size, unit="B", unit_scale=True) + total_chunk = 0 + with open(os_path.join(tmp_path, filename), 'wb') as file: + for chunk in res.iter_content(chunk_size=1024*5): + file.write(chunk) + pbar.update(len(chunk)) + if isinstance(func, Callable): + total_chunk += len(chunk) + func(total_chunk/file_size) + pbar.close() + + with ZipFile(os_path.join(tmp_path, filename)) as zf: + zf.extractall(os_path.join(current_directory, directory_name)) + except Exception as e: + print("error:downloadCTranslate2Weight()", e) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index bff366c5..b6e14d85 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,7 @@ flashtext == 2.7 pyyaml == 6.0.1 python-i18n == 0.3.9 CTkToolTip == 0.8 -pyinstaller==6.2.0 \ No newline at end of file +pyinstaller==6.2.0 +transformers[torch] +sentencepiece==0.1.99 +ctranslate2==3.21.0 \ No newline at end of file diff --git a/vrct_gui/splash_window/SplashWindow.py b/vrct_gui/splash_window/SplashWindow.py index 2a2db684..c7af9802 100644 --- a/vrct_gui/splash_window/SplashWindow.py +++ b/vrct_gui/splash_window/SplashWindow.py @@ -1,4 +1,4 @@ -from customtkinter import CTkImage, CTkLabel, CTkToplevel +from customtkinter import CTkImage, CTkLabel, CTkToplevel, CTkProgressBar from ..ui_utils import openImageKeepAspectRatio, getImageFileFromUiUtils, setGeometryToCenterOfScreen, fadeInAnimation class SplashWindow(CTkToplevel): @@ -10,6 +10,7 @@ class SplashWindow(CTkToplevel): self.title("SplashWindow") self.wm_attributes("-toolwindow", True) + self.is_showed_progressbar = False sw=self.winfo_screenwidth() @@ -28,6 +29,37 @@ class SplashWindow(CTkToplevel): label.grid(row=1, column=1, padx=int(desired_width/7), pady=int(height/3)) + self.progressbar_widget = CTkProgressBar( + self, + height=10, + corner_radius=0, + fg_color="#4b4c4f", + progress_color="#48a495", + ) + self.progressbar_widget.set(0) + + + (img, desired_width, height) = openImageKeepAspectRatio(getImageFileFromUiUtils("VRCT_now_downloading.png"), 320) + self.text_label = CTkLabel( + self, + text=None, + height=0, + fg_color="#292a2d", + image=CTkImage(img, size=(desired_width, height)) + ) + + + + def updateDownloadProgress(self, progress:float): + if self.is_showed_progressbar is False: + self.progressbar_widget.place(relwidth=0.9, relx=0.5, rely=0.9, anchor="s") + self.text_label.place(relx=0.98, rely=0.98, anchor="se") + self.is_showed_progressbar = True + + self.progressbar_widget.set(progress) + self.update_idletasks() + + def showSplash(self): self.attributes("-alpha", 0) self.deiconify()