[Update] Optimize initialization process: reduce startup time, implement caching for model weight checks, and enhance parallel processing for AI model checks.

This commit is contained in:
misyaguziya
2026-01-03 08:35:39 +09:00
parent b0cf8bf335
commit 312999a50b
3 changed files with 364 additions and 175 deletions

View File

@@ -2,6 +2,7 @@ from typing import Callable, Any, List, Optional
from time import sleep from time import sleep
from subprocess import Popen from subprocess import Popen
from threading import Thread from threading import Thread
from concurrent.futures import ThreadPoolExecutor, as_completed
import re import re
from device_manager import device_manager from device_manager import device_manager
from config import config from config import config
@@ -2815,8 +2816,14 @@ class Controller:
return cleaned_text return cleaned_text
def updateDownloadedCTranslate2ModelWeight(self) -> None: def updateDownloadedCTranslate2ModelWeight(self) -> None:
for weight_type in config.SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT.keys(): # キャッシュされた結果を使用(起動時の重複チェックを回避)
config.SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT[weight_type] = model.checkTranslatorCTranslate2ModelWeight(weight_type) if hasattr(self, '_ctranslate2_available_cache'):
# 起動時のキャッシュを使用: 選択中の重みタイプのみ設定
config.SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT[config.CTRANSLATE2_WEIGHT_TYPE] = self._ctranslate2_available_cache
else:
# 通常時は全重みタイプをチェック
for weight_type in config.SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT.keys():
config.SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT[weight_type] = model.checkTranslatorCTranslate2ModelWeight(weight_type)
def updateTranslationEngineAndEngineList(self): def updateTranslationEngineAndEngineList(self):
engines = config.SELECTED_TRANSLATION_ENGINES engines = config.SELECTED_TRANSLATION_ENGINES
@@ -2838,8 +2845,14 @@ class Controller:
self.run(200, self.run_mapping["translation_engines"], selectable_engines) self.run(200, self.run_mapping["translation_engines"], selectable_engines)
def updateDownloadedWhisperModelWeight(self) -> None: def updateDownloadedWhisperModelWeight(self) -> None:
for weight_type in config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT.keys(): # キャッシュされた結果を使用(起動時の重複チェックを回避)
config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT[weight_type] = model.checkTranscriptionWhisperModelWeight(weight_type) if hasattr(self, '_whisper_available_cache'):
# 起動時のキャッシュを使用: 選択中の重みタイプのみ設定
config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT[config.WHISPER_WEIGHT_TYPE] = self._whisper_available_cache
else:
# 通常時は全重みタイプをチェック
for weight_type in config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT.keys():
config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT[weight_type] = model.checkTranscriptionWhisperModelWeight(weight_type)
def updateTranscriptionEngine(self): def updateTranscriptionEngine(self):
weight_type = config.WHISPER_WEIGHT_TYPE weight_type = config.WHISPER_WEIGHT_TYPE
@@ -3038,19 +3051,27 @@ class Controller:
}) })
def init(self, *args, **kwargs) -> None: def init(self, *args, **kwargs) -> None:
import time
total_start_time = time.time()
removeLog() removeLog()
printLog("Start Initialization") printLog("Start Initialization")
# Network check
section_start = time.time()
connected_network = isConnectedNetwork() connected_network = isConnectedNetwork()
if connected_network is True: if connected_network is True:
self.connectedNetwork() self.connectedNetwork()
else: else:
self.disconnectedNetwork() self.disconnectedNetwork()
printLog(f"Connected Network: {connected_network}") printLog(f"Connected Network: {connected_network}")
printLog(f"[TIME] Network Check: {time.time() - section_start:.2f}s")
self.initializationProgress(1) self.initializationProgress(1)
# Download weights
if connected_network is True: if connected_network is True:
# download CTranslate2 Model Weight section_start = time.time()
printLog("Download CTranslate2 Model Weight") printLog("Download CTranslate2 Model Weight")
weight_type = config.CTRANSLATE2_WEIGHT_TYPE weight_type = config.CTRANSLATE2_WEIGHT_TYPE
th_download_ctranslate2 = None th_download_ctranslate2 = None
@@ -3059,7 +3080,6 @@ class Controller:
th_download_ctranslate2.daemon = True th_download_ctranslate2.daemon = True
th_download_ctranslate2.start() th_download_ctranslate2.start()
# download Whisper Model Weight
printLog("Download Whisper Model Weight") printLog("Download Whisper Model Weight")
weight_type = config.WHISPER_WEIGHT_TYPE weight_type = config.WHISPER_WEIGHT_TYPE
th_download_whisper = None th_download_whisper = None
@@ -3072,226 +3092,352 @@ class Controller:
th_download_ctranslate2.join() th_download_ctranslate2.join()
if isinstance(th_download_whisper, Thread): if isinstance(th_download_whisper, Thread):
th_download_whisper.join() th_download_whisper.join()
printLog(f"[TIME] Weight Download: {time.time() - section_start:.2f}s")
if (model.checkTranslatorCTranslate2ModelWeight(config.CTRANSLATE2_WEIGHT_TYPE) is False or # Check and disable/enable AI models (parallel)
model.checkTranscriptionWhisperModelWeight(config.WHISPER_WEIGHT_TYPE) is False): section_start = time.time()
def check_ctranslate2() -> bool:
return model.checkTranslatorCTranslate2ModelWeight(config.CTRANSLATE2_WEIGHT_TYPE) is True
def check_whisper() -> bool:
return model.checkTranscriptionWhisperModelWeight(config.WHISPER_WEIGHT_TYPE) is True
with ThreadPoolExecutor(max_workers=2) as executor:
future_ctranslate2 = executor.submit(check_ctranslate2)
future_whisper = executor.submit(check_whisper)
ctranslate2_available = future_ctranslate2.result()
whisper_available = future_whisper.result()
# インスタンス変数にキャッシュ(後続の処理で再利用)
self._ctranslate2_available_cache = ctranslate2_available
self._whisper_available_cache = whisper_available
if not ctranslate2_available or not whisper_available:
self.disableAiModels() self.disableAiModels()
else: else:
self.enableAiModels() self.enableAiModels()
printLog(f"[TIME] AI Models Check: {time.time() - section_start:.2f}s")
# Init Translation Engine Status (with parallel processing)
section_start = time.time()
printLog("Init Translation Engine Status") printLog("Init Translation Engine Status")
for engine in config.SELECTABLE_TRANSLATION_ENGINE_LIST:
match engine: # バックグラウンドチェック対象エンジンLMStudio/Ollama
case "CTranslate2": background_check_engines = {"LMStudio", "Ollama"}
if model.checkTranslatorCTranslate2ModelWeight(config.CTRANSLATE2_WEIGHT_TYPE) is True:
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True def check_translation_engine(engine: str) -> tuple:
else: """翻訳エンジンのステータスをチェック(並列実行用)"""
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False engine_start = time.time()
case "DeepL_API": status = False
printLog("Start check DeepL API Key") auth_key_invalid = False
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False model_list = None
if config.AUTH_KEYS[engine] is not None: selected_model = None
if model.authenticationTranslatorDeepLAuthKey(auth_key=config.AUTH_KEYS[engine]) is True:
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True try:
printLog("DeepL API Key is valid") match engine:
case "CTranslate2":
# 既に前のステップでチェック済み、結果を再利用
status = ctranslate2_available
case "DeepL_API":
if config.AUTH_KEYS[engine] is None:
status = False
else: else:
# error update Auth key if model.authenticationTranslatorDeepLAuthKey(auth_key=config.AUTH_KEYS[engine]) is True:
auth_keys = config.AUTH_KEYS status = True
auth_keys[engine] = None else:
config.AUTH_KEYS = auth_keys auth_key_invalid = True
printLog("DeepL API Key is invalid") case "Plamo_API":
case "Plamo_API": if config.AUTH_KEYS[engine] is None:
printLog("Start check Plamo API Key") status = False
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False
if config.AUTH_KEYS[engine] is not None:
if model.authenticationTranslatorPlamoAuthKey(auth_key=config.AUTH_KEYS[engine]) is True:
config.SELECTABLE_PLAMO_MODEL_LIST = model.getTranslatorPlamoModelList()
if config.SELECTED_PLAMO_MODEL not in config.SELECTABLE_PLAMO_MODEL_LIST:
config.SELECTED_PLAMO_MODEL = config.SELECTABLE_PLAMO_MODEL_LIST[0]
model.setTranslatorPlamoModel(config.SELECTED_PLAMO_MODEL)
model.updateTranslatorPlamoClient()
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True
printLog("Plamo API Key is valid")
else: else:
# error update Auth key if model.authenticationTranslatorPlamoAuthKey(auth_key=config.AUTH_KEYS[engine]) is True:
auth_keys = config.AUTH_KEYS model_list = model.getTranslatorPlamoModelList()
auth_keys[engine] = None selected_model = config.SELECTED_PLAMO_MODEL if config.SELECTED_PLAMO_MODEL in model_list else model_list[0]
config.AUTH_KEYS = auth_keys status = True
printLog("Plamo API Key is invalid") else:
case "Gemini_API": auth_key_invalid = True
printLog("Start check Gemini API Key") case "Gemini_API":
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False if config.AUTH_KEYS[engine] is None:
if config.AUTH_KEYS[engine] is not None: status = False
if model.authenticationTranslatorGeminiAuthKey(auth_key=config.AUTH_KEYS[engine]) is True:
config.SELECTABLE_GEMINI_MODEL_LIST = model.getTranslatorGeminiModelList()
if config.SELECTED_GEMINI_MODEL not in config.SELECTABLE_GEMINI_MODEL_LIST:
config.SELECTED_GEMINI_MODEL = config.SELECTABLE_GEMINI_MODEL_LIST[0]
model.setTranslatorGeminiModel(config.SELECTED_GEMINI_MODEL)
model.updateTranslatorGeminiClient()
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True
printLog("Gemini API Key is valid")
else: else:
# error update Auth key if model.authenticationTranslatorGeminiAuthKey(auth_key=config.AUTH_KEYS[engine]) is True:
auth_keys = config.AUTH_KEYS model_list = model.getTranslatorGeminiModelList()
auth_keys[engine] = None selected_model = config.SELECTED_GEMINI_MODEL if config.SELECTED_GEMINI_MODEL in model_list else model_list[0]
config.AUTH_KEYS = auth_keys status = True
printLog("Gemini API Key is invalid") else:
case "OpenAI_API": auth_key_invalid = True
printLog("Start check OpenAI API Key") case "OpenAI_API":
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False if config.AUTH_KEYS[engine] is None:
if config.AUTH_KEYS[engine] is not None: status = False
if model.authenticationTranslatorOpenAIAuthKey(auth_key=config.AUTH_KEYS[engine]) is True:
config.SELECTABLE_OPENAI_MODEL_LIST = model.getTranslatorOpenAIModelList()
if config.SELECTED_OPENAI_MODEL not in config.SELECTABLE_OPENAI_MODEL_LIST:
config.SELECTED_OPENAI_MODEL = config.SELECTABLE_OPENAI_MODEL_LIST[0]
model.setTranslatorOpenAIModel(config.SELECTED_OPENAI_MODEL)
model.updateTranslatorOpenAIClient()
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True
printLog("OpenAI API Key is valid")
else: else:
# error update Auth key if model.authenticationTranslatorOpenAIAuthKey(auth_key=config.AUTH_KEYS[engine]) is True:
auth_keys = config.AUTH_KEYS model_list = model.getTranslatorOpenAIModelList()
auth_keys[engine] = None selected_model = config.SELECTED_OPENAI_MODEL if config.SELECTED_OPENAI_MODEL in model_list else model_list[0]
config.AUTH_KEYS = auth_keys status = True
printLog("OpenAI API Key is invalid") else:
case "Groq_API": auth_key_invalid = True
printLog("Start check Groq API Key") case "Groq_API":
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False if config.AUTH_KEYS[engine] is None:
if config.AUTH_KEYS[engine] is not None: status = False
if model.authenticationTranslatorGroqAuthKey(auth_key=config.AUTH_KEYS[engine]) is True:
config.SELECTABLE_GROQ_MODEL_LIST = model.getTranslatorGroqModelList()
if config.SELECTED_GROQ_MODEL not in config.SELECTABLE_GROQ_MODEL_LIST:
config.SELECTED_GROQ_MODEL = config.SELECTABLE_GROQ_MODEL_LIST[0]
model.setTranslatorGroqModel(config.SELECTED_GROQ_MODEL)
model.updateTranslatorGroqClient()
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True
printLog("Groq API Key is valid")
else: else:
# error update Auth key if model.authenticationTranslatorGroqAuthKey(auth_key=config.AUTH_KEYS[engine]) is True:
auth_keys = config.AUTH_KEYS model_list = model.getTranslatorGroqModelList()
auth_keys[engine] = None selected_model = config.SELECTED_GROQ_MODEL if config.SELECTED_GROQ_MODEL in model_list else model_list[0]
config.AUTH_KEYS = auth_keys status = True
printLog("Groq API Key is invalid") else:
case "OpenRouter_API": auth_key_invalid = True
printLog("Start check OpenRouter API Key") case "OpenRouter_API":
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False if config.AUTH_KEYS[engine] is None:
if config.AUTH_KEYS[engine] is not None: status = False
if model.authenticationTranslatorOpenRouterAuthKey(auth_key=config.AUTH_KEYS[engine]) is True:
config.SELECTABLE_OPENROUTER_MODEL_LIST = model.getTranslatorOpenRouterModelList()
if config.SELECTED_OPENROUTER_MODEL not in config.SELECTABLE_OPENROUTER_MODEL_LIST:
config.SELECTED_OPENROUTER_MODEL = config.SELECTABLE_OPENROUTER_MODEL_LIST[0]
model.setTranslatorOpenRouterModel(config.SELECTED_OPENROUTER_MODEL)
model.updateTranslatorOpenRouterClient()
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True
printLog("OpenRouter API Key is valid")
else: else:
# error update Auth key if model.authenticationTranslatorOpenRouterAuthKey(auth_key=config.AUTH_KEYS[engine]) is True:
auth_keys = config.AUTH_KEYS model_list = model.getTranslatorOpenRouterModelList()
auth_keys[engine] = None selected_model = config.SELECTED_OPENROUTER_MODEL if config.SELECTED_OPENROUTER_MODEL in model_list else model_list[0]
config.AUTH_KEYS = auth_keys status = True
printLog("OpenRouter API Key is invalid") else:
case "LMStudio": auth_key_invalid = True
printLog("Start check LMStudio Server") case "LMStudio":
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False # バックグラウンドチェックにスキップ
status = False
case "Ollama":
# バックグラウンドチェックにスキップ
status = False
case _:
status = connected_network is True
except Exception as e:
printLog(f"Error checking engine {engine}: {str(e)}")
errorLogging()
status = False
elapsed = time.time() - engine_start
return engine, status, auth_key_invalid, model_list, selected_model, elapsed
def check_local_server_engine_background(engine: str):
"""ローカルサーバー系エンジンをバックグラウンドでチェック"""
try:
printLog(f"[Background] Start check {engine}")
engine_start = time.time()
status = False
model_list = None
selected_model = None
if engine == "LMStudio":
if config.LMSTUDIO_URL is not None: if config.LMSTUDIO_URL is not None:
if model.authenticationTranslatorLMStudio(base_url=config.LMSTUDIO_URL) is True: if model.authenticationTranslatorLMStudio(base_url=config.LMSTUDIO_URL) is True:
config.SELECTABLE_LMSTUDIO_MODEL_LIST = model.getTranslatorLMStudioModelList() model_list = model.getTranslatorLMStudioModelList()
if len(config.SELECTABLE_LMSTUDIO_MODEL_LIST) == 0: if len(model_list) > 0:
printLog("LMStudio model list is empty") selected_model = config.SELECTED_LMSTUDIO_MODEL if config.SELECTED_LMSTUDIO_MODEL in model_list else model_list[0]
break config.SELECTABLE_LMSTUDIO_MODEL_LIST = model_list
if config.SELECTED_LMSTUDIO_MODEL not in config.SELECTABLE_LMSTUDIO_MODEL_LIST: config.SELECTED_LMSTUDIO_MODEL = selected_model
config.SELECTED_LMSTUDIO_MODEL = config.SELECTABLE_LMSTUDIO_MODEL_LIST[0] model.setTranslatorLMStudioModel(selected_model)
model.setTranslatorLMStudioModel(config.SELECTED_LMSTUDIO_MODEL) model.updateTranslatorLMStudioClient()
model.updateTranslatorLMStudioClient() status = True
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True elif engine == "Ollama":
printLog("LMStudio is available")
else:
printLog("LMStudio is not available")
case "Ollama":
printLog("Start check Ollama Server")
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False
if model.authenticationTranslatorOllama() is True: if model.authenticationTranslatorOllama() is True:
config.SELECTABLE_OLLAMA_MODEL_LIST = model.getTranslatorOllamaModelList() model_list = model.getTranslatorOllamaModelList()
if len(config.SELECTABLE_OLLAMA_MODEL_LIST) == 0: if len(model_list) > 0:
printLog("Ollama model list is empty") selected_model = config.SELECTED_OLLAMA_MODEL if config.SELECTED_OLLAMA_MODEL in model_list else model_list[0]
break config.SELECTABLE_OLLAMA_MODEL_LIST = model_list
if config.SELECTED_OLLAMA_MODEL not in config.SELECTABLE_OLLAMA_MODEL_LIST: config.SELECTED_OLLAMA_MODEL = selected_model
config.SELECTED_OLLAMA_MODEL = config.SELECTABLE_OLLAMA_MODEL_LIST[0] model.setTranslatorOllamaModel(selected_model)
model.setTranslatorOllamaModel(config.SELECTED_OLLAMA_MODEL) model.updateTranslatorOllamaClient()
model.updateTranslatorOllamaClient() status = True
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True
printLog("Ollama is available")
else:
printLog("Ollama is not available")
case _:
if connected_network is True:
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True
else:
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = status
elapsed = time.time() - engine_start
printLog(f"[Background] {engine} check completed: {status} ({elapsed:.2f}s)")
# 更新通知もしrun_mappingがあれば
if status:
self.updateTranslationEngineAndEngineList()
except Exception as e:
printLog(f"[Background] Error checking {engine}: {str(e)}")
errorLogging()
# 並列実行(バックグラウンドチェック対象を除外)
engine_results = {}
engines_to_check = [e for e in config.SELECTABLE_TRANSLATION_ENGINE_LIST if e not in background_check_engines]
with ThreadPoolExecutor(max_workers=4) as executor:
future_to_engine = {executor.submit(check_translation_engine, engine): engine
for engine in engines_to_check}
for future in as_completed(future_to_engine):
engine, status, auth_key_invalid, model_list, selected_model, elapsed = future.result()
engine_results[engine] = (status, auth_key_invalid, model_list, selected_model, elapsed)
# バックグラウンドチェック対象エンジンは初期値Falseで即座に設定
for engine in background_check_engines:
if engine in config.SELECTABLE_TRANSLATION_ENGINE_LIST:
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False
printLog(f"Start check {engine}")
printLog(f"[TIME] Engine '{engine}': 0.00s (deferred to background)")
# バックグラウンドスレッドで実行
bg_thread = Thread(target=check_local_server_engine_background, args=(engine,))
bg_thread.daemon = True
bg_thread.start()
# 結果を順番に適用(メインスレッドで実行)
for engine in engines_to_check:
if engine not in engine_results:
continue
status, auth_key_invalid, model_list, selected_model, elapsed = engine_results[engine]
# ログ出力
printLog(f"Start check {engine}")
# ステータス設定
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = status
# 認証キー無効化
if auth_key_invalid:
auth_keys = config.AUTH_KEYS
auth_keys[engine] = None
config.AUTH_KEYS = auth_keys
printLog(f"{engine} auth key is invalid")
elif status:
printLog(f"{engine} is valid/available")
# モデルリストと選択モデルの設定
if model_list is not None and status:
match engine:
case "Plamo_API":
config.SELECTABLE_PLAMO_MODEL_LIST = model_list
config.SELECTED_PLAMO_MODEL = selected_model
model.setTranslatorPlamoModel(selected_model)
model.updateTranslatorPlamoClient()
case "Gemini_API":
config.SELECTABLE_GEMINI_MODEL_LIST = model_list
config.SELECTED_GEMINI_MODEL = selected_model
model.setTranslatorGeminiModel(selected_model)
model.updateTranslatorGeminiClient()
case "OpenAI_API":
config.SELECTABLE_OPENAI_MODEL_LIST = model_list
config.SELECTED_OPENAI_MODEL = selected_model
model.setTranslatorOpenAIModel(selected_model)
model.updateTranslatorOpenAIClient()
case "Groq_API":
config.SELECTABLE_GROQ_MODEL_LIST = model_list
config.SELECTED_GROQ_MODEL = selected_model
model.setTranslatorGroqModel(selected_model)
model.updateTranslatorGroqClient()
case "OpenRouter_API":
config.SELECTABLE_OPENROUTER_MODEL_LIST = model_list
config.SELECTED_OPENROUTER_MODEL = selected_model
model.setTranslatorOpenRouterModel(selected_model)
model.updateTranslatorOpenRouterClient()
printLog(f"[TIME] Engine '{engine}': {elapsed:.2f}s")
printLog(f"[TIME] Translation Engine Status Init: {time.time() - section_start:.2f}s")
# Init Transcription Engine Status
section_start = time.time()
for engine in config.SELECTABLE_TRANSCRIPTION_ENGINE_LIST: for engine in config.SELECTABLE_TRANSCRIPTION_ENGINE_LIST:
match engine: match engine:
case "Whisper": case "Whisper":
if model.checkTranscriptionWhisperModelWeight(config.WHISPER_WEIGHT_TYPE) is True: # キャッシュされた結果を使用(重複チェックを回避)
config.SELECTABLE_TRANSCRIPTION_ENGINE_STATUS[engine] = True config.SELECTABLE_TRANSCRIPTION_ENGINE_STATUS[engine] = self._whisper_available_cache
else:
config.SELECTABLE_TRANSCRIPTION_ENGINE_STATUS[engine] = False
case _: case _:
if connected_network is True: if connected_network is True:
config.SELECTABLE_TRANSCRIPTION_ENGINE_STATUS[engine] = True config.SELECTABLE_TRANSCRIPTION_ENGINE_STATUS[engine] = True
else: else:
config.SELECTABLE_TRANSCRIPTION_ENGINE_STATUS[engine] = False config.SELECTABLE_TRANSCRIPTION_ENGINE_STATUS[engine] = False
printLog(f"[TIME] Transcription Engine Status Init: {time.time() - section_start:.2f}s")
self.initializationProgress(2) self.initializationProgress(2)
# set Translation Engine # Set Translation Engine
section_start = time.time()
printLog("Set Translation Engine") printLog("Set Translation Engine")
self.updateDownloadedCTranslate2ModelWeight() self.updateDownloadedCTranslate2ModelWeight()
self.updateTranslationEngineAndEngineList() self.updateTranslationEngineAndEngineList()
printLog(f"[TIME] Set Translation Engine: {time.time() - section_start:.2f}s")
# set Transcription Engine # Set Transcription Engine
section_start = time.time()
printLog("Set Transcription Engine") printLog("Set Transcription Engine")
self.updateDownloadedWhisperModelWeight() self.updateDownloadedWhisperModelWeight()
self.updateTranscriptionEngine() self.updateTranscriptionEngine()
printLog(f"[TIME] Set Transcription Engine: {time.time() - section_start:.2f}s")
# set Transliteration status # Set Transliteration
section_start = time.time()
printLog("Set Transliteration") printLog("Set Transliteration")
if config.CONVERT_MESSAGE_TO_ROMAJI is True or config.CONVERT_MESSAGE_TO_HIRAGANA is True: if config.CONVERT_MESSAGE_TO_ROMAJI is True or config.CONVERT_MESSAGE_TO_HIRAGANA is True:
model.startTransliteration() model.startTransliteration()
printLog(f"[TIME] Set Transliteration: {time.time() - section_start:.2f}s")
self.initializationProgress(3) self.initializationProgress(3)
# set word filter # Set Word Filter
section_start = time.time()
printLog("Set Word Filter") printLog("Set Word Filter")
model.addKeywords() model.addKeywords()
printLog(f"[TIME] Set Word Filter: {time.time() - section_start:.2f}s")
# check Software Updated # Check Software Updated (Background)
printLog("Check Software Updated") section_start = time.time()
self.checkSoftwareUpdated() printLog("Check Software Updated (Background)")
# init logger def check_software_updated_background():
"""ソフトウェア更新チェックをバックグラウンドで実行"""
bg_start = time.time()
try:
self.checkSoftwareUpdated()
printLog(f"[Background] Software update check completed: {time.time() - bg_start:.2f}s")
except Exception:
errorLogging()
printLog("[Background] Software update check failed")
bg_thread = Thread(target=check_software_updated_background)
bg_thread.daemon = True
bg_thread.start()
printLog(f"[TIME] Check Software Updated (Background): {time.time() - section_start:.2f}s")
# Init Logger
section_start = time.time()
printLog("Init Logger") printLog("Init Logger")
if config.LOGGER_FEATURE is True: if config.LOGGER_FEATURE is True:
model.startLogger() model.startLogger()
printLog(f"[TIME] Init Logger: {time.time() - section_start:.2f}s")
self.initializationProgress(4) self.initializationProgress(4)
# init OSC receive # Init OSC Receive (Background)
printLog("Init OSC Receive") section_start = time.time()
model.startReceiveOSC() printLog("Init OSC Receive (Background)")
osc_query_enabled = model.getIsOscQueryEnabled()
if osc_query_enabled is True:
self.enableOscQuery()
if config.VRC_MIC_MUTE_SYNC is True:
self.setEnableVrcMicMuteSync()
else:
# OSC Query is disabled, so disable VRC some features
mute_sync_info_flag = False
if config.VRC_MIC_MUTE_SYNC is True:
self.setDisableVrcMicMuteSync()
mute_sync_info_flag = True
self.disableOscQuery(mute_sync_info=mute_sync_info_flag)
# init Auto device selection def init_osc_receive_background():
"""OSC Receiveの初期化をバックグラウンドで実行"""
bg_start = time.time()
try:
model.startReceiveOSC()
osc_query_enabled = model.getIsOscQueryEnabled()
if osc_query_enabled is True:
self.enableOscQuery()
if config.VRC_MIC_MUTE_SYNC is True:
self.setEnableVrcMicMuteSync()
else:
# OSC Query is disabled, so disable VRC some features
mute_sync_info_flag = False
if config.VRC_MIC_MUTE_SYNC is True:
self.setDisableVrcMicMuteSync()
mute_sync_info_flag = True
self.disableOscQuery(mute_sync_info=mute_sync_info_flag)
printLog(f"[Background] OSC Receive initialization completed: {time.time() - bg_start:.2f}s")
except Exception:
errorLogging()
printLog("[Background] OSC Receive initialization failed")
bg_thread = Thread(target=init_osc_receive_background)
bg_thread.daemon = True
bg_thread.start()
printLog(f"[TIME] Init OSC Receive (Background): {time.time() - section_start:.2f}s")
# Init Device Manager
section_start = time.time()
printLog("Init Device Manager") printLog("Init Device Manager")
device_manager.setCallbackHostList(self.updateMicHostList) device_manager.setCallbackHostList(self.updateMicHostList)
device_manager.setCallbackMicDeviceList(self.updateMicDeviceList) device_manager.setCallbackMicDeviceList(self.updateMicDeviceList)
@@ -3302,11 +3448,17 @@ class Controller:
self.applyAutoMicSelect() self.applyAutoMicSelect()
if config.AUTO_SPEAKER_SELECT is True: if config.AUTO_SPEAKER_SELECT is True:
self.applyAutoSpeakerSelect() self.applyAutoSpeakerSelect()
printLog(f"[TIME] Init Device Manager: {time.time() - section_start:.2f}s")
# Init Overlay
section_start = time.time()
printLog("Init Overlay") printLog("Init Overlay")
if (config.OVERLAY_SMALL_LOG is True or config.OVERLAY_LARGE_LOG is True): if (config.OVERLAY_SMALL_LOG is True or config.OVERLAY_LARGE_LOG is True):
model.startOverlay() model.startOverlay()
printLog(f"[TIME] Init Overlay: {time.time() - section_start:.2f}s")
# Init WebSocket Server
section_start = time.time()
printLog("Init WebSocket Server") printLog("Init WebSocket Server")
if config.WEBSOCKET_SERVER is True: if config.WEBSOCKET_SERVER is True:
if isAvailableWebSocketServer(config.WEBSOCKET_HOST, config.WEBSOCKET_PORT) is True: if isAvailableWebSocketServer(config.WEBSOCKET_HOST, config.WEBSOCKET_PORT) is True:
@@ -3315,12 +3467,20 @@ class Controller:
config.WEBSOCKET_SERVER = False config.WEBSOCKET_SERVER = False
model.stopWebSocketServer() model.stopWebSocketServer()
printLog("WebSocket server host or port is not available") printLog("WebSocket server host or port is not available")
printLog(f"[TIME] Init WebSocket Server: {time.time() - section_start:.2f}s")
# Revalidate Selected Models
section_start = time.time()
printLog("Revalidate Selected Models") printLog("Revalidate Selected Models")
config.revalidate_selected_models() config.revalidate_selected_models()
printLog(f"[TIME] Revalidate Selected Models: {time.time() - section_start:.2f}s")
# Update Settings
section_start = time.time()
printLog("Update settings") printLog("Update settings")
self.updateConfigSettings() self.updateConfigSettings()
printLog(f"[TIME] Update settings: {time.time() - section_start:.2f}s")
printLog("End Initialization") printLog("End Initialization")
printLog(f"[TIME] Total Initialization: {time.time() - total_start_time:.2f}s")
self.startWatchdog() self.startWatchdog()

View File

@@ -4,6 +4,15 @@
`controller.py` は VRCT アプリケーションのビジネスロジック層であり、フロントエンドUIとバックエンドModelの間の制御フローを担当する。音声認識、翻訳、OSC通信、オーバーレイ表示など、VRCT の全機能の調整役として動作し、各種設定の取得・更新、デバイス管理、エラーハンドリングを提供する。 `controller.py` は VRCT アプリケーションのビジネスロジック層であり、フロントエンドUIとバックエンドModelの間の制御フローを担当する。音声認識、翻訳、OSC通信、オーバーレイ表示など、VRCT の全機能の調整役として動作し、各種設定の取得・更新、デバイス管理、エラーハンドリングを提供する。
## 最近の更新 (2026-01-03)
- 起動高速化: 初期化時間を約12.6s→8.9sに短縮
- AI Models Check 並列化: CTranslate2/Whisperの重みチェックを2並列で実行
- 翻訳エンジン判定の非同期化: LMStudio/Ollamaをバックグラウンド判定、他APIは4並列
- 重みチェック結果のキャッシュ: `_ctranslate2_available_cache` / `_whisper_available_cache` を導入し後続処理で再利用
- 音声認識エンジン判定の高速化: Whisperはキャッシュ結果を利用し0.56s→0.00s
- ソフトウェア更新チェックの非同期化: GitHub APIチェックをバックグラウンド化
## アーキテクチャ上の位置づけ ## アーキテクチャ上の位置づけ
``` ```

View File

@@ -4,6 +4,26 @@
VRCTアプリケーションのビジネスロジックを制御するコントローラークラスです。UI層とモデル層の間に位置し、ユーザーの入力を適切な処理に変換し、結果を UI に返す役割を担います。全ての機能制御、設定管理、状態管理を一元的に行います。 VRCTアプリケーションのビジネスロジックを制御するコントローラークラスです。UI層とモデル層の間に位置し、ユーザーの入力を適切な処理に変換し、結果を UI に返す役割を担います。全ての機能制御、設定管理、状態管理を一元的に行います。
## 最近の更新 (2026-01-03)
### 起動高速化・非同期化
- 初期化時間を約12.6s→8.9sに短縮(環境計測値)
- AI Models Check を2並列化CTranslate2/Whisperし、結果を `_ctranslate2_available_cache` / `_whisper_available_cache` に保存
- 翻訳エンジン判定を並列化ThreadPoolExecutor, max_workers=4し、LMStudio/Ollamaはバックグラウンド判定に変更
- ソフトウェア更新チェックをバックグラウンド化
- OSC受信初期化をバックグラウンド化し、OSCQueryサービス生成は接続成功まで継続リトライ
- 翻訳/音声認識エンジンのセット処理で重みチェックキャッシュを再利用し再計測を排除0.98s/0.52s→0.00s
### 影響
| 項目 | 内容 |
|------|------|
| 起動時間 | 約3.7s短縮12.6s→8.9s |
| 並列・非同期化 | 翻訳・音声認識エンジン判定を並列/バックグラウンド化 |
| 安定性 | OSCQuery起動のリトライ上限でブロッキングを抑制 |
| 再利用性 | 重みチェック結果をキャッシュし重複I/Oを削減 |
## 最近の更新 (2025-10-20) ## 最近の更新 (2025-10-20)
### 新規ローカルLLM翻訳エンジン統合 ### 新規ローカルLLM翻訳エンジン統合