43 KiB
controller.py 設計書
概要
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チェックをバックグラウンド化
アーキテクチャ上の位置づけ
┌─────────────┐
│ Frontend │ (Tauri/React)
│ (UI Layer) │
└──────┬──────┘
│ JSON-RPC (stdin/stdout)
┌──────▼──────┐
│ mainloop.py │ (Communication Layer)
└──────┬──────┘
│ Function Calls
┌──────▼──────┐
│controller.py│ ◄── このファイル
└──────┬──────┘
│ Facade Pattern
┌──────▼──────┐
│ model.py │ (Business Logic Facade)
└──────┬──────┘
│
┌──────▼──────┐
│ Subsystems │ (transcription, translation, osc, overlay, etc.)
└─────────────┘
主要コンポーネント
1. Controllerクラス
コンストラクタ __init__()
責務: Controller インスタンスの初期化と依存関係のセットアップ
初期化処理:
- マッピング辞書の初期化:
init_mapping: 初期化時に実行するエンドポイント群run_mapping: フロントエンドへの通知用エンドポイント
- コールバック関数の設定:
run: フロントエンドへの通知を送信する関数(デフォルトは no-op)
- Model の初期化:
model.init()を呼び出し、サブシステムを準備- 失敗時は
errorLogging()でログ記録して継続
- デバイスアクセス状態:
device_access_status: デバイスへの排他アクセス制御用フラグ
型ヒント:
self.init_mapping: dict
self.run_mapping: dict
self.run: Callable[[int, str, Any], None]
self.device_access_status: bool
セットアップメソッド
setInitMapping(init_mapping: dict) -> None
初期化時に実行するエンドポイントマッピングを設定。mainloop.py から呼び出される。
setRunMapping(run_mapping: dict) -> None
フロントエンド通知用のエンドポイントマッピングを設定。
setRun(run: Callable[[int, str, Any], None]) -> None
フロントエンドへの通知関数を設定。mainloop.py の printResponse() ラッパーが渡される。
ヘルパーメソッド
_is_overlay_available() -> bool
オーバーレイ機能が利用可能かを安全にチェック。Model が未初期化の場合の AttributeError を回避。
実装:
try:
overlay = getattr(model, "overlay", None)
return overlay is not None and getattr(overlay, "initialized", False)
except Exception:
errorLogging()
return False
2. 通知メソッド(Response Functions)
フロントエンドに状態変化を通知するメソッド群。すべて self.run() を介して JSON を stdout に送信。
ネットワーク関連
connectedNetwork() -> None
ネットワーク接続を検出したことを通知。
disconnectedNetwork() -> None
ネットワーク切断を検出したことを通知。
AI モデル関連
enableAiModels() -> None
AI モデル(CTranslate2/Whisper)が利用可能であることを通知。
disableAiModels() -> None
AI モデルが利用不可(ダウンロード失敗等)であることを通知。
デバイス管理関連
updateMicHostList() -> None
マイクホスト一覧(MME/WASAPI等)を更新。
updateMicDeviceList() -> None
マイクデバイス一覧を更新。
updateSpeakerDeviceList() -> None
スピーカーデバイス一覧を更新。
updateSelectedMicDevice(host: str, device: str) -> None
選択されたマイクデバイスを通知。自動デバイス選択時に使用。
updateSelectedSpeakerDevice(device: str) -> None
選択されたスピーカーデバイスを通知。
エネルギーレベル通知
progressBarMicEnergy(energy: Union[bool, int]) -> None
マイクの音量レベルを通知。False の場合はデバイスエラーを送信。
progressBarSpeakerEnergy(energy: Union[bool, int]) -> None
スピーカーの音量レベルを通知。
設定同期
updateConfigSettings() -> None
初期化完了時に全設定値をフロントエンドに送信。init_mapping の全エンドポイントを実行。
3. デバイス制御メソッド
再起動系
restartAccessMicDevices() -> None
マイクアクセスを再起動。以下の条件で各機能を開始:
config.ENABLE_TRANSCRIPTION_SENDが True: 音声認識開始config.ENABLE_CHECK_ENERGY_SENDが True: 音量監視開始
restartAccessSpeakerDevices() -> None
スピーカーアクセスを再起動。
停止系
stopAccessMicDevices() -> None
マイク関連機能を停止。
stopAccessSpeakerDevices() -> None
スピーカー関連機能を停止。
使用場面:
- デバイス変更時
- 自動デバイス選択によるデバイス切り替え時
- アプリケーション終了時
4. メッセージ処理メソッド
micMessage(result: dict) -> None
責務: マイク音声認識結果の処理と配信
処理フロー:
- 結果の検証:
result["text"]とresult["language"]を取得Falseの場合はデバイスエラーを通知して終了
- フィルタリング:
model.checkKeywords(): 禁止ワードチェックmodel.detectRepeatSendMessage(): 重複メッセージチェック
- 翻訳処理:
config.ENABLE_TRANSLATIONが True の場合:model.getInputTranslate()で翻訳実行- 翻訳エンジンエラー時は CTranslate2 に切り替え
- VRAM不足エラー時は翻訳機能を無効化
- 音訳処理:
config.CONVERT_MESSAGE_TO_HIRAGANA/ROMAJIが True の場合:model.convertMessageToTransliteration()で変換
- 配信処理:
- VRChat OSC:
config.SEND_MESSAGE_TO_VRCが True の場合messageFormatter()でフォーマットmodel.oscSendMessage()で送信
- UI通知:
self.run()で transcription_mic エンドポイントに通知 - オーバーレイ:
config.OVERLAY_LARGE_LOGが True の場合model.createOverlayImageLargeLog()で画像生成model.updateOverlayLargeLog()で表示更新
- WebSocket: サーバーが起動中の場合
model.websocketSendMessage()でブロードキャスト
- ログファイル:
config.LOGGER_FEATUREが True の場合
- VRChat OSC:
VRAM エラーハンドリング:
try:
translation, success = model.getInputTranslate(message, source_language=language)
except Exception as e:
is_vram_error, error_message = model.detectVRAMError(e)
if is_vram_error:
# 翻訳機能を無効化
self.setDisableTranslation()
self.run(400, self.run_mapping["error_translation_mic_vram_overflow"], {...})
return
speakerMessage(result: dict) -> None
責務: スピーカー音声認識結果の処理と配信
処理フロー: micMessage() と同様だが、以下の違いがある:
- オーバーレイ:
- Small Log: 受信メッセージ用の小さなログウィンドウ
- Large Log: 送受信両方を表示するログウィンドウ
- OSC送信:
config.SEND_RECEIVED_MESSAGE_TO_VRCの設定に依存 - 翻訳:
model.getOutputTranslate()を使用(受信メッセージ用)
chatMessage(data: dict) -> dict
責務: UI のチャットボックスからのメッセージ処理
パラメータ:
data["id"]: メッセージ ID(UI でのレスポンスマッピング用)data["message"]: 送信メッセージ
特殊処理:
- 除外ワード処理:
config.USE_EXCLUDE_WORDSが True の場合replaceExclamationsWithRandom():![word]を一時的なトークンに置換- 翻訳後に
restoreText()で復元 - 最終メッセージから
![...]を削除
- 同期レスポンス:
- 他のメッセージ処理と異なり、結果を
dictで返却 - UI が翻訳結果を待機する必要があるため
- 他のメッセージ処理と異なり、結果を
レスポンス形式:
{
"status": 200,
"result": {
"id": "msg-123",
"original": {
"message": "Hello",
"transliteration": ["he", "ro"]
},
"translations": [
{
"message": "こんにちは",
"transliteration": ["ko", "n", "ni", "chi", "wa"]
}
]
}
}
5. メッセージフォーマット
messageFormatter(format_type: str, translation: list, message: str) -> str
責務: OSC 送信用メッセージの整形
パラメータ:
format_type: "SEND" または "RECEIVED"translation: 翻訳結果のリストmessage: 元のメッセージ
処理ロジック:
- フォーマット設定を取得:
config.SEND_MESSAGE_FORMAT_PARTSまたはconfig.RECEIVED_MESSAGE_FORMAT_PARTS
- 各部分を構築:
message_part: prefix + message + suffixtranslation_part: prefix + separator.join(translation) + suffix
- 組み合わせ:
- 両方存在:
translation_firstの設定に応じて順序決定 - 翻訳のみ: translation_part のみ
- メッセージのみ: message_part のみ
- 両方存在:
設定例:
config.SEND_MESSAGE_FORMAT_PARTS = {
"message": {"prefix": "[", "suffix": "] "},
"translation": {"prefix": "", "suffix": "", "separator": " / "},
"translation_first": False,
"separator": ""
}
# 出力例: [Hello] こんにちは / 你好
6. 除外ワード処理
replaceExclamationsWithRandom(text: str) -> Tuple[str, dict]
責務: 翻訳対象外の単語を保護
処理:
![word]パターンを検出- 各マッチを
$<hex番号>に置換(4096から連番) - 置換マップを辞書で返却
用途: 固有名詞や翻訳不要な単語を保護
restoreText(escaped_text: str, escape_dict: dict) -> str
責務: 翻訳後のテキストに元の単語を復元
処理: 正規表現で $<hex番号> を検出し、元の単語に置換(大文字小文字を無視)
removeExclamations(text: str) -> str
責務: 最終メッセージから ![...] マーカーを削除
処理: ![word] を word に置換
7. 設定取得・更新メソッド(GET/SET)
Controller には約200個の設定項目に対する getter/setter が定義されている。以下、代表的なパターンを示す。
パターン1: 単純な設定値
@staticmethod
def getTransparency(*args, **kwargs) -> dict:
return {"status": 200, "result": config.TRANSPARENCY}
@staticmethod
def setTransparency(data, *args, **kwargs) -> dict:
config.TRANSPARENCY = int(data)
return {"status": 200, "result": config.TRANSPARENCY}
パターン2: 有効/無効の切り替え
@staticmethod
def getOverlaySmallLog(*args, **kwargs) -> dict:
return {"status": 200, "result": config.OVERLAY_SMALL_LOG}
@staticmethod
def setEnableOverlaySmallLog(*args, **kwargs) -> dict:
if config.OVERLAY_SMALL_LOG is False:
if config.OVERLAY_LARGE_LOG is False:
model.startOverlay() # 副作用: オーバーレイシステムを起動
config.OVERLAY_SMALL_LOG = True
return {"status": 200, "result": config.OVERLAY_SMALL_LOG}
@staticmethod
def setDisableOverlaySmallLog(*args, **kwargs) -> dict:
if config.OVERLAY_SMALL_LOG is True:
model.clearOverlayImageSmallLog()
if config.OVERLAY_LARGE_LOG is False:
model.shutdownOverlay() # 副作用: オーバーレイシステムを停止
config.OVERLAY_SMALL_LOG = False
return {"status": 200, "result": config.OVERLAY_SMALL_LOG}
パターン3: バリデーション付き設定
@staticmethod
def setMicThreshold(data, *args, **kwargs) -> dict:
try:
data = int(data)
if 0 <= data <= config.MAX_MIC_THRESHOLD:
config.MIC_THRESHOLD = data
status = 200
else:
raise ValueError()
except Exception:
response = {
"status": 400,
"result": {
"message": "Mic energy threshold value is out of range",
"data": config.MIC_THRESHOLD
}
}
else:
response = {"status": status, "result": config.MIC_THRESHOLD}
return response
パターン4: 依存関係のある設定
def setSelectedTranslationComputeDevice(self, device: str, *args, **kwargs) -> dict:
config.SELECTED_TRANSLATION_COMPUTE_DEVICE = device
config.SELECTED_TRANSLATION_COMPUTE_TYPE = "auto"
# 依存する設定を自動更新
self.run(200, self.run_mapping["selected_translation_compute_type"],
config.SELECTED_TRANSLATION_COMPUTE_TYPE)
# モデルの再読み込みフラグを設定
model.setChangedTranslatorParameters(True)
return {"status": 200, "result": config.SELECTED_TRANSLATION_COMPUTE_DEVICE}
8. 翻訳機能制御
setEnableTranslation(*args, **kwargs) -> dict
責務: 翻訳機能の有効化とモデルのロード
処理フロー:
- 既に有効な場合は何もしない
- モデル未ロードまたはパラメータ変更時:
model.changeTranslatorCTranslate2Model()でモデルをロード- VRAM不足エラーの場合:
- デフォルト設定に戻す
- エラー通知を送信
- 翻訳を無効化
config.ENABLE_TRANSLATION = Trueに設定
エラーハンドリング:
try:
model.changeTranslatorCTranslate2Model()
except Exception as e:
is_vram_error, error_message = model.detectVRAMError(e)
if is_vram_error:
self.run(400, self.run_mapping["error_translation_enable_vram_overflow"], {...})
self.setDisableTranslation()
setDisableTranslation(*args, **kwargs) -> dict
責務: 翻訳機能の無効化(メモリ解放)
changeToCTranslate2Process() -> None
責務: 外部翻訳APIエラー時に CTranslate2 へ切り替え
処理:
- 現在の翻訳エンジンを無効化
- CTranslate2 に切り替え
- フロントエンドに通知
9. 音声認識制御
スレッド管理メソッド
startTranscriptionSendMessage() -> None
マイク音声認識を開始。デバイスアクセスの排他制御を行う。
排他制御:
while self.device_access_status is False:
sleep(1) # 他の処理がデバイスを使用中なら待機
self.device_access_status = False # ロック取得
try:
model.startMicTranscript(self.micMessage)
finally:
self.device_access_status = True # ロック解放
VRAMエラーハンドリング:
model.detectVRAMError()でエラーを検出- 音声認識を停止
- フロントエンドに通知
stopTranscriptionSendMessage() -> None
マイク音声認識を停止。
startThreadingTranscriptionSendMessage() -> None
別スレッドで音声認識を開始。
stopThreadingTranscriptionSendMessage() -> None
別スレッドで音声認識を停止し、完了を待機(join())。
対応するスピーカー用メソッド:
startTranscriptionReceiveMessage()stopTranscriptionReceiveMessage()startThreadingTranscriptionReceiveMessage()stopThreadingTranscriptionReceiveMessage()
10. エネルギー監視
startCheckMicEnergy() -> None
マイクの音量レベル監視を開始。progressBarMicEnergy() をコールバックとして渡す。
stopCheckMicEnergy() -> None
マイクの音量レベル監視を停止。
startThreadingCheckMicEnergy() -> None
別スレッドでエネルギー監視を開始。
stopThreadingCheckMicEnergy() -> None
別スレッドでエネルギー監視を停止し、完了を待機。
対応するスピーカー用メソッド:
startCheckSpeakerEnergy()stopCheckSpeakerEnergy()startThreadingCheckSpeakerEnergy()stopThreadingCheckSpeakerEnergy()
11. モデルウェイト管理
DownloadCTranslate2 クラス
責務: CTranslate2 モデルのダウンロード進捗管理
メソッド:
progressBar(progress: float): 進捗率をフロントエンドに通知downloaded(): ダウンロード完了時の処理- モデルの存在確認
- 選択可能モデルリストに追加
- フロントエンドに通知
DownloadWhisper クラス
責務: Whisper モデルのダウンロード進捗管理(CTranslate2 と同様の構造)
downloadCtranslate2Weight(data: str, asynchronous: bool = True, *args, **kwargs) -> dict
責務: CTranslate2 モデルのダウンロード開始
パラメータ:
data: モデルタイプ("tiny", "small", "medium" 等)asynchronous: 非同期ダウンロードの有効化
処理:
DownloadCTranslate2インスタンスを作成asynchronousが True の場合:startThreadingDownloadCtranslate2Weight()で別スレッド実行
asynchronousが False の場合:model.downloadCTranslate2ModelWeight()で同期実行(初期化時に使用)
- トークナイザーのダウンロード
downloadWhisperWeight(data: str, asynchronous: bool = True, *args, **kwargs) -> dict
責務: Whisper モデルのダウンロード開始(CTranslate2 と同様の構造)
12. 自動デバイス選択
applyAutoMicSelect() -> None
責務: マイクの自動選択機能を適用
処理:
- コールバック設定:
device_manager.setCallbackProcessBeforeUpdateMicDevices(self.stopAccessMicDevices)device_manager.setCallbackDefaultMicDevice(self.updateSelectedMicDevice)device_manager.setCallbackProcessAfterUpdateMicDevices(self.restartAccessMicDevices)
- デバイス更新を強制実行:
device_manager.forceUpdateAndSetMicDevices() - 監視開始:
device_manager.startMonitoring()
動作フロー:
デバイス変更検出
↓
stopAccessMicDevices() ← デバイス使用中の処理を停止
↓
updateSelectedMicDevice() ← 新しいデフォルトデバイスを選択
↓
restartAccessMicDevices() ← 新しいデバイスで処理を再開
setEnableAutoMicSelect(*args, **kwargs) -> dict
自動マイク選択を有効化。
setDisableAutoMicSelect(*args, **kwargs) -> dict
自動マイク選択を無効化。両方の自動選択が無効になった場合のみ監視を停止。
対応するスピーカー用メソッド:
applyAutoSpeakerSelect()setEnableAutoSpeakerSelect()setDisableAutoSpeakerSelect()
13. 言語・翻訳エンジン管理
updateTranslationEngineAndEngineList() -> None
責務: 選択された言語に応じて利用可能な翻訳エンジンを更新
処理:
- 現在のタブの選択エンジンを取得
getTranslationEngines()で利用可能なエンジンリストを取得- 選択中のエンジンが利用不可の場合、CTranslate2 にフォールバック
- 特殊ケース: 入力言語と出力言語が同一の場合:
- CTranslate2 のみ利用可能(音訳のみ)
- フロントエンドに通知
getTranslationEngines(*args, **kwargs) -> dict
責務: 現在の言語設定で利用可能な翻訳エンジンを返却
ロジック:
model.findTranslationEngines()で言語ペアをサポートするエンジンを検索- 入力言語と出力言語が同一の場合:
- CTranslate2 が有効なら ["CTranslate2"]
- それ以外は []
setSelectedYourLanguages(select: dict, *args, **kwargs) -> dict
入力言語を設定し、updateTranslationEngineAndEngineList() を呼び出す。
setSelectedTargetLanguages(select: dict, *args, **kwargs) -> dict
出力言語を設定し、updateTranslationEngineAndEngineList() を呼び出す。
swapYourLanguageAndTargetLanguage(*args, **kwargs) -> dict
責務: 入力言語と出力言語を入れ替え
処理:
- 現在のタブの入力言語と出力言語(最初の1つ)を取得
- 相互に入れ替え
setSelectedYourLanguages()とsetSelectedTargetLanguages()を呼び出し- 両方の結果を返却
14. 音声認識エンジン管理
updateTranscriptionEngine() -> None
責務: Whisper モデルの利用可能状況に応じて音声認識エンジンを更新
処理:
- 現在選択されている Whisper モデルの存在確認
- 利用可能なエンジンリストを取得
- 現在のエンジンが利用不可の場合:
- Whisper ⇔ Google で切り替え
- どちらも利用不可なら Whisper にフォールバック
updateDownloadedWhisperModelWeight() -> None
責務: ダウンロード済み Whisper モデルの一覧を更新
処理:
全てのモデルタイプについて model.checkTranscriptionWhisperModelWeight() で存在確認。
15. OSC 通信制御
setOscIpAddress(data, *args, **kwargs) -> dict
責務: VRChat への送信先 IP アドレスを設定
処理:
isValidIpAddress()でバリデーションmodel.setOscIpAddress()で設定を適用- OSC Query の状態に応じて再初期化:
- 有効な場合:
enableOscQuery()を呼び出し - 無効な場合:
disableOscQuery()を呼び出し - マイクミュート同期が有効だった場合は無効化して通知
- 有効な場合:
エラーハンドリング:
- IP アドレスが無効: status 400
- 設定適用失敗: 元の IP に戻して status 400
setOscPort(data, *args, **kwargs) -> dict
OSC ポート番号を設定。
enableOscQuery() -> None
OSC Query 機能が有効になったことをフロントエンドに通知。
disableOscQuery(mute_sync_info: bool = False) -> None
OSC Query 機能が無効になったことを通知。無効化された機能リストも送信。
16. DeepL API 認証
setDeeplAuthKey(data, *args, **kwargs) -> dict
責務: DeepL API キーを設定し、認証を実行
処理:
- キー長のバリデーション(36 または 39 文字)
model.authenticationTranslatorDeepLAuthKey()で認証- 認証成功時:
config.AUTH_KEYS["DeepL_API"]に保存config.SELECTABLE_TRANSLATION_ENGINE_STATUS["DeepL_API"]を True にupdateTranslationEngineAndEngineList()を呼び出し
- 認証失敗時 (status 400):
- レスポンス
dataフィールドは 常に None(キーを返さない) delDeeplAuthKey()を呼び出してクリーンアップ
- レスポンス
認証失敗時の共通ポリシー(Plamo/Gemini/OpenAI/DeepL/Groq/OpenRouter 共通)
- レスポンス
dataはキーを含めずNoneを返す - 対応する
del*AuthKey()を呼び出し、保存済みキーとモデル選択をクリア
delDeeplAuthKey(*args, **kwargs) -> dict
責務: DeepL API キーを削除
処理:
config.AUTH_KEYS["DeepL_API"]を None にconfig.SELECTABLE_TRANSLATION_ENGINE_STATUS["DeepL_API"]を False にupdateTranslationEngineAndEngineList()を呼び出し
16-1. Groq API 認証・モデル管理
setGroqAuthKey(data, *args, **kwargs) -> dict
責務: Groq API キーを設定し、認証を実行
処理:
- キー長のバリデーション(
gskで始まり40文字以上) model.authenticationTranslatorGroqAuthKey()で認証- 認証成功時:
config.AUTH_KEYS["Groq_API"]に保存config.SELECTABLE_TRANSLATION_ENGINE_STATUS["Groq_API"]を True にconfig.SELECTABLE_GROQ_MODEL_LISTを取得- 未選択の場合は先頭モデルを自動選択
model.updateTranslatorGroqClient()でクライアント更新updateTranslationEngineAndEngineList()を呼び出し
- 認証失敗時 (status 400):
- レスポンス
dataフィールドを None に設定 (sensitive data を隠す) delGroqAuthKey()を呼び出してクリーンアップ
- レスポンス
API キー検証失敗時の処理:
- モデルリストをクリア (
config.SELECTABLE_GROQ_MODEL_LIST = []) - 選択モデルをクリア (
config.SELECTED_GROQ_MODEL = None) - フロントエンドに通知(レスポンス
dataは None)
delGroqAuthKey(*args, **kwargs) -> dict
責務: Groq API キーを削除
処理:
config.AUTH_KEYS["Groq_API"]を None にconfig.SELECTABLE_TRANSLATION_ENGINE_STATUS["Groq_API"]を False に- モデルリストと選択モデルをクリア
updateTranslationEngineAndEngineList()を呼び出し
getGroqAuthKey(*args, **kwargs) -> dict
現在の Groq API キーを取得(マスク処理なし)。
getGroqModelList(*args, **kwargs) -> dict
利用可能な Groq モデルリストを取得。
getGroqModel(*args, **kwargs) -> dict
現在選択中の Groq モデルを取得。
setGroqModel(data, *args, **kwargs) -> dict
責務: 使用する Groq モデルを変更
処理:
- モデル名のバリデーション(利用可能リスト内か確認)
model.setTranslatorGroqModel()でモデル設定model.updateTranslatorGroqClient()でクライアント再生成config.SELECTED_GROQ_MODELを更新
16-2. OpenRouter API 認証・モデル管理
setOpenRouterAuthKey(data, *args, **kwargs) -> dict
責務: OpenRouter API キーを設定し、認証を実行
処理:
- キー長のバリデーション(20文字以上)
model.authenticationTranslatorOpenRouterAuthKey()で認証- 認証成功時:
config.AUTH_KEYS["OpenRouter_API"]に保存config.SELECTABLE_TRANSLATION_ENGINE_STATUS["OpenRouter_API"]を True にconfig.SELECTABLE_OPENROUTER_MODEL_LISTを取得- 未選択の場合は先頭モデルを自動選択
model.updateTranslatorOpenRouterClient()でクライアント更新updateTranslationEngineAndEngineList()を呼び出し
- 認証失敗時 (status 400):
- レスポンス
dataフィールドを None に設定 (sensitive data を隠す) delOpenRouterAuthKey()を呼び出してクリーンアップ
- レスポンス
API キー検証失敗時の処理:
- モデルリストをクリア (
config.SELECTABLE_OPENROUTER_MODEL_LIST = []) - 選択モデルをクリア (
config.SELECTED_OPENROUTER_MODEL = None) - フロントエンドに通知(レスポンス
dataは None)
delOpenRouterAuthKey(*args, **kwargs) -> dict
責務: OpenRouter API キーを削除
処理:
config.AUTH_KEYS["OpenRouter_API"]を None にconfig.SELECTABLE_TRANSLATION_ENGINE_STATUS["OpenRouter_API"]を False に- モデルリストと選択モデルをクリア
updateTranslationEngineAndEngineList()を呼び出し
getOpenRouterAuthKey(*args, **kwargs) -> dict
現在の OpenRouter API キーを取得(マスク処理なし)。
getOpenRouterModelList(*args, **kwargs) -> dict
利用可能な OpenRouter モデルリストを取得。
getOpenRouterModel(*args, **kwargs) -> dict
現在選択中の OpenRouter モデルを取得。
setOpenRouterModel(data, *args, **kwargs) -> dict
責務: 使用する OpenRouter モデルを変更
処理:
- モデル名のバリデーション(利用可能リスト内か確認)
model.setTranslatorOpenRouterModel()でモデル設定model.updateTranslatorOpenRouterClient()でクライアント再生成config.SELECTED_OPENROUTER_MODELを更新
17. WebSocket サーバー制御
setWebSocketHost(data, *args, **kwargs) -> dict
責務: WebSocket サーバーのホストアドレスを変更
処理:
isValidIpAddress()でバリデーション- サーバーが停止中の場合:
- 設定のみ変更
- サーバーが起動中の場合:
- 新しいホストが利用可能か確認(
isAvailableWebSocketServer()) - サーバーを停止 → 再起動
- 利用不可の場合は status 400
- 新しいホストが利用可能か確認(
setWebSocketPort(data, *args, **kwargs) -> dict
WebSocket サーバーのポート番号を変更(ロジックは setWebSocketHost() と同様)。
setEnableWebSocketServer(*args, **kwargs) -> dict
責務: WebSocket サーバーを起動
処理:
- 既に起動中なら何もしない
- ホストとポートが利用可能か確認
model.startWebSocketServer()で起動- 利用不可の場合は status 400
setDisableWebSocketServer(*args, **kwargs) -> dict
WebSocket サーバーを停止。
18. VRChat マイクミュート同期
setEnableVrcMicMuteSync(*args, **kwargs) -> dict
責務: VRChat のマイクミュート状態と音声認識の連動を有効化
前提条件: OSC Query が有効であること
処理:
- OSC Query が無効の場合は status 400 を返却
model.setMuteSelfStatus(): 現在のミュート状態を取得model.changeMicTranscriptStatus(): ミュート状態に応じて音声認識を制御config.VRC_MIC_MUTE_SYNC = True
setDisableVrcMicMuteSync(*args, **kwargs) -> dict
マイクミュート同期を無効化し、model.changeMicTranscriptStatus() を呼び出す。
19. Watchdog 管理
Watchdog は UI とバックエンド間の通信監視機能。UI からの定期的な "feed" 信号がない場合、バックエンドを強制終了する。
startWatchdog(*args, **kwargs) -> dict
Watchdog を起動。
feedWatchdog(*args, **kwargs) -> dict
Watchdog にハートビート信号を送信(UI が定期的に呼び出す)。
setWatchdogCallback(callback) -> dict
Watchdog タイムアウト時に呼び出すコールバック関数を設定。mainloop.stop() が渡される。
stopWatchdog(*args, **kwargs) -> dict
Watchdog を停止。
20. ソフトウェアアップデート
checkSoftwareUpdated() -> dict
責務: 最新バージョンの確認
処理:
model.checkSoftwareUpdated()でバージョン情報を取得- フロントエンドに通知(
software_update_infoエンドポイント) - 結果を返却
バージョン情報形式:
{
"current_version": "1.2.3",
"latest_version": "1.2.4",
"update_available": True,
"download_url": "https://..."
}
updateSoftware(*args, **kwargs) -> dict
責務: 通常版のアップデートを実行
処理:
- 別スレッドで
model.updateSoftware()を起動(ブロッキングを避けるため) - 即座に status 200 を返却
updateCudaSoftware(*args, **kwargs) -> dict
責務: CUDA版のアップデートを実行
処理: updateSoftware() と同様だが、model.updateCudaSoftware() を呼び出す。
21. 初期化処理
init(*args, **kwargs) -> None
責務: アプリケーションの完全な初期化
処理フロー:
1. ログのクリア
removeLog()
printLog("Start Initialization")
2. ネットワーク接続確認
connected_network = isConnectedNetwork()
if connected_network:
self.connectedNetwork()
else:
self.disconnectedNetwork()
3. モデルウェイトのダウンロード(進捗1/4)
self.initializationProgress(1)
if connected_network:
# CTranslate2 と Whisper を並列ダウンロード
th_download_ctranslate2 = Thread(target=self.downloadCtranslate2Weight, args=(weight_type, False))
th_download_whisper = Thread(target=self.downloadWhisperWeight, args=(weight_type, False))
th_download_ctranslate2.start()
th_download_whisper.start()
th_download_ctranslate2.join()
th_download_whisper.join()
4. AI モデル状態の確認
if (model.checkTranslatorCTranslate2ModelWeight(...) is False or
model.checkTranscriptionWhisperModelWeight(...) is False):
self.disableAiModels()
else:
self.enableAiModels()
5. 翻訳・音声認識エンジンの初期化(進捗2/4)
self.initializationProgress(2)
# 翻訳エンジン
for engine in config.SELECTABLE_TRANSLATION_ENGINE_LIST:
match engine:
case "CTranslate2":
# モデルウェイトの存在確認
case "DeepL_API":
# API キーの認証
case _:
# ネットワーク接続が必要なエンジン
# 音声認識エンジン
for engine in config.SELECTABLE_TRANSCRIPTION_ENGINE_LIST:
# 同様のロジック
6. エンジンと音訳の設定(進捗3/4)
self.updateDownloadedCTranslate2ModelWeight()
self.updateTranslationEngineAndEngineList()
self.updateDownloadedWhisperModelWeight()
self.updateTranscriptionEngine()
if config.CONVERT_MESSAGE_TO_ROMAJI or config.CONVERT_MESSAGE_TO_HIRAGANA:
model.startTransliteration()
7. 周辺機能の初期化(進捗4/4)
self.initializationProgress(4)
model.addKeywords() # ワードフィルター
self.checkSoftwareUpdated() # バージョンチェック
if config.LOGGER_FEATURE:
model.startLogger() # ログ記録
model.startReceiveOSC() # OSC 受信
# OSC Query
osc_query_enabled = model.getIsOscQueryEnabled()
if osc_query_enabled:
self.enableOscQuery()
if config.VRC_MIC_MUTE_SYNC:
self.setEnableVrcMicMuteSync()
else:
# マイクミュート同期を無効化
self.disableOscQuery(...)
8. デバイス管理の初期化
device_manager.setCallbackHostList(self.updateMicHostList)
device_manager.setCallbackMicDeviceList(self.updateMicDeviceList)
device_manager.setCallbackSpeakerDeviceList(self.updateSpeakerDeviceList)
if config.AUTO_MIC_SELECT:
self.applyAutoMicSelect()
if config.AUTO_SPEAKER_SELECT:
self.applyAutoSpeakerSelect()
9. オーバーレイと WebSocket の起動
if config.OVERLAY_SMALL_LOG or config.OVERLAY_LARGE_LOG:
model.startOverlay()
if config.WEBSOCKET_SERVER:
if isAvailableWebSocketServer(...):
model.startWebSocketServer(...)
10. 設定の同期と完了
self.updateConfigSettings() # 全設定をフロントエンドに送信
printLog("End Initialization")
self.startWatchdog() # 監視開始
エラーハンドリング戦略
1. VRAM不足エラー
検出箇所:
- 翻訳実行時(
micMessage(),speakerMessage(),chatMessage()) - 翻訳機能有効化時(
setEnableTranslation()) - 音声認識開始時(
startTranscriptionSendMessage(),startTranscriptionReceiveMessage())
処理:
model.detectVRAMError(e)で VRAM エラーを検出- 該当機能を無効化
- フロントエンドにエラー通知
- ログファイルに記録
自動リカバリ:
- 翻訳機能: 無効化して継続
- 音声認識: 停止して継続
2. デバイスアクセスエラー
検出箇所:
- マイク・スピーカーのアクセス時
処理:
energyがFalseの場合error_deviceエンドポイントにエラー通知- 処理を継続(他の機能は影響を受けない)
3. ネットワークエラー
検出箇所:
- 翻訳APIの呼び出し時
- モデルウェイトのダウンロード時
処理:
- 外部API エラー:
changeToCTranslate2Process()で CTranslate2 に切り替え - ダウンロードエラー: エラー通知を送信、AI機能を無効化
4. 設定バリデーションエラー
処理:
- status 400 とエラーメッセージを返却
- 現在の有効な設定値を
dataフィールドに含める
例:
{
"status": 400,
"result": {
"message": "Mic energy threshold value is out of range",
"data": 1000 # 現在の有効な値
}
}
スレッド安全性
排他制御
デバイスアクセス制御
問題: 複数の機能が同時にデバイスにアクセスすると衝突
解決策: device_access_status フラグによる排他制御
while self.device_access_status is False:
sleep(1) # 待機
self.device_access_status = False # ロック取得
try:
# デバイスアクセス処理
finally:
self.device_access_status = True # ロック解放
使用箇所:
startTranscriptionSendMessage()startTranscriptionReceiveMessage()startCheckMicEnergy()startCheckSpeakerEnergy()
デーモンスレッド
すべてのワーカースレッドは daemon = True:
- メインスレッド終了時に自動的に終了
- 明示的な join は必要に応じて実行(停止処理等)
例:
th_startTranscriptionSendMessage = Thread(target=self.startTranscriptionSendMessage)
th_startTranscriptionSendMessage.daemon = True
th_startTranscriptionSendMessage.start()
パフォーマンス考慮事項
1. 非同期ダウンロード
初期化時: 同期ダウンロード(asynchronous=False)
- UI をブロックして確実にダウンロード完了を待つ
ユーザー操作時: 非同期ダウンロード(asynchronous=True)
- 別スレッドで実行し、進捗バーで通知
2. 並列初期化
CTranslate2 と Whisper のダウンロードを並列実行:
th_download_ctranslate2.start()
th_download_whisper.start()
th_download_ctranslate2.join()
th_download_whisper.join()
3. モデルの遅延ロード
翻訳モデルは setEnableTranslation() が呼ばれるまでロードされない。
依存関係
外部モジュール
from typing import Callable, Any, List, Optional
from time import sleep
from subprocess import Popen
from threading import Thread
import re
内部モジュール
from device_manager import device_manager
from config import config
from model import model
from utils import removeLog, printLog, errorLogging, isConnectedNetwork, isValidIpAddress, isAvailableWebSocketServer
設定項目の分類
UI関連(約20項目)
- 透明度、スケーリング、フォント、言語、ウィンドウ位置等
音声認識関連(約30項目)
- デバイス選択、閾値、タイムアウト、フィルター等
翻訳関連(約25項目)
- エンジン選択、言語ペア、モデルタイプ、計算デバイス等
OSC通信関連(約15項目)
- IP アドレス、ポート、メッセージフォーマット、送信設定等
オーバーレイ関連(約10項目)
- 表示設定、位置、サイズ、透明度等
その他(約20項目)
- WebSocket、ログ、ホットキー、プラグイン等
合計: 約120の設定項目(getter/setter で約240メソッド)
制限事項
1. グローバル状態依存
すべての設定が config モジュールのグローバル変数として管理されている。
- 利点: シンプルなアクセス
- 欠点: テスタビリティの低下、並列実行時の競合リスク
2. 同期レスポンスの制限
ほとんどのメソッドが同期的にレスポンスを返すため、重い処理(モデルロード等)は UI をブロックする可能性がある。
対策: 重い処理は別スレッドで実行し、完了通知は self.run() で送信
3. エラー回復の限界
一部のエラー(VRAM不足等)は自動回復するが、設定ファイル破損やモデルファイル破損等は手動対処が必要。
テストシナリオ
1. 初期化テスト
ケース:
- ネットワーク接続あり・なし
- モデルウェイトあり・なし
- 不正な設定値
確認項目:
- 全エンジンの状態が正しく設定されているか
- エラーがログに記録されているか
- フロントエンドに正しい初期設定が送信されているか
2. 音声認識テスト
ケース:
- デバイス切り替え中に音声認識
- VRAM不足エラーの発生
- 重複メッセージのフィルタリング
確認項目:
- 排他制御が正しく動作しているか
- エラー発生時に適切にリカバリしているか
3. 翻訳テスト
ケース:
- 複数の翻訳エンジンの切り替え
- API制限エラー
- 除外ワードの処理
確認項目:
- エンジン切り替えが正しく動作するか
- 除外ワードが正しく復元されるか
4. 設定変更テスト
ケース:
- 無効な値の設定
- 依存関係のある設定の変更
- 有効/無効の切り替え
確認項目:
- バリデーションが正しく動作するか
- 依存する設定が自動更新されるか
今後の拡張性
1. 非同期化の推進
asyncio への移行で UI ブロッキングを完全に排除。
2. 依存性注入
config と model を DI コンテナで管理し、テスタビリティを向上。
3. イベント駆動アーキテクチャ
設定変更時のイベントを発火し、各サブシステムが独立して反応。
4. エラーリカバリの強化
- 自動再試行メカニズム
- フォールバック設定の自動適用
- エラー発生時の部分的な機能継続
関連ファイル
- mainloop.py - 通信レイヤー、リクエストルーティング
- model.py - ビジネスロジックのファサード
- config.py - 設定管理
- device_manager.py - デバイス監視・自動選択
- utils.py - ログとユーティリティ関数
コーディング規約
- PEP 8 スタイルガイド
- 型ヒント:
typingモジュールを使用 - Docstring: Google スタイル(一部未実装)
- 静的メソッド: 状態を持たないメソッドは
@staticmethod - エラーハンドリング: 防御的プログラミングを徹底
まとめ
controller.py は VRCT の中核となるビジネスロジック制御レイヤーであり、約120の設定項目と約200のエンドポイントを管理する。フロントエンドとバックエンドの橋渡しとして、設定の取得・更新、機能の有効化・無効化、エラーハンドリング、デバイス管理など、アプリケーション全体の動作を制御する。排他制御とスレッド管理により、複数の機能が同時に動作する環境でも安定性を保っている。VRAM不足エラーや外部APIエラーに対する自動リカバリ機能により、ユーザーエクスペリエンスの向上を実現している。