Add documentation and coding guidelines for VRCT backend

- Introduced a comprehensive coding rules document outlining naming conventions, module structure, import order, type annotations, error handling, and testing practices.
- Created a specification document detailing project goals, target users, and functional/non-functional requirements for the VRCT project.
- Added a design document describing the application's architecture, initialization policies, concurrency models, and error handling strategies.
- Included a detailed design document specifying major classes, functions, data structures, and exception handling.
- Removed outdated mypy configuration and several unused scripts related to documentation verification and cleanup.
- Deleted test files for OSC and overlay imports as part of the cleanup process.
This commit is contained in:
misyaguziya
2025-10-13 22:55:48 +09:00
parent d4f89a734d
commit fcb1295302
43 changed files with 5829 additions and 2956 deletions

View File

@@ -1,27 +0,0 @@
# CHANGELOG
## 2025-10-09 — 型チェック整備と安全性向上
- 修正: `controller.py`
- `Controller.chatMessage` の戻り値注釈を `dict` に明示(関数は JSON 系の応答オブジェクトを返します)。
- `Controller.checkSoftwareUpdated` が実際に応答を返すように `return` を追加。
- 修正: `model.py`
- `startCheckMicEnergy` / `startCheckSpeakerEnergy` のコールバック引数を Optional に変更し、呼び出し前に `callable` チェックを追加。これにより None を渡しても安全に扱えるようになりました。
- `convertMessageToTransliteration` の返り値を常に list に統一。hiragana/romaji が False の場合は空リストを返します。
- `createOverlayImageLargeLog` 等の Overlay 作成関数で `target_language` を dict で受けた場合に内部で言語リストへ正規化する挙動を明確化。
- 目的: mypy の型チェックの警告/エラーを削減し、ランタイムでの None 呼び出しによるクラッシュを防止するための低リスクな変更です。
- 注記:
- 追加で `types-requests` をプロジェクト仮想環境にインストールし、mypy の外部型スタブ不足を解消しました。
- 本チェンジは内部の型注釈とガードを中心としており、動作ロジックの大きな変更は行っていません。動作確認は mypy型チェックと rufflintを通過したことをもって行っています。
## 1.0.0 (initial)
- 初回ドキュメント作成: ソースコードに基づく仕様書 / 詳細設計書を docs 配下に追加。
- 対象: utils, model, controller, device_manager, config, translation, transcription, overlay, websocket, osc, transliteration, watchdog
今後の作業候補:
- requirements.txt の自動生成とテストスイート追加
- ドキュメントの API サンプル(リクエスト/レスポンス)追加
- UML 図/シーケンス図の画像化

View File

@@ -1,18 +0,0 @@
# VRCT — ドキュメント
このドキュメントセットは、VRCT プロジェクト(`src-python`)に含まれる実装の仕様書 / 設計書 / 詳細設計書です。
目的
- ソースコード構造、モジュール間データフロー、API エンドポイント、設定、実行手順、トラブルシュートを網羅して開発・運用の参照を容易にする。
対象
- `utils.py`, `model.py`, `controller.py`, `mainloop.py`, `device_manager.py`, `config.py` および `models/` 以下の全モジュール。
ドキュメント構成(主要ファイル)
- `architecture.md` — アーキテクチャ概観
- `modules/` — 各モジュールごとの詳細設計(個別ファイル)
- `api.md` — 外部/内部向け API エンドポイント マッピング(`mainloop.py``mapping` / `run_mapping` に準拠)
- `runtime.md` — 実行/セットアップ手順、依存関係
- `diagrams.md` — システム図Mermaid とテキスト両方)
- `CODING_RULES.md` — プロジェクト固有のコーディング規約命名・型方針・lint/mypy 方針 等)
- `CHANGELOG.md` — 変更履歴

View File

@@ -1,701 +0,0 @@
---
<!-- プレースホルダ的な一般エンドポイントは削除済み。ドキュメントは `mainloop.py``mapping``run_mapping` に実装されている具体的なエンドポイントのみを列挙しています -->
## API エンドポイント仕様
概要
- このドキュメントは `mainloop.py``mapping``run_mapping` に定義された全エンドポイントを列挙します。
- すべてのリクエストは標準入力経由で JSON を一行送る形で受信され、標準出力へ JSON 応答を出力します。
共通リクエスト形式
- JSON オブジェクトを 1 行で標準入力に流します。
- フィールド:
- `endpoint`: エンドポイント文字列 (例: `/get/data/version`)
- `data`: 任意(多くの GET 系は null、SET 系は新しい値やオブジェクト)
```json
{"endpoint":"/get/data/version","data":null}
```
共通レスポンス形式
- mainloop は各リクエストの処理結果を次の形式で標準出力に出します(内部 util の `printResponse` を経由):
成功例:
```json
{"status":200,"endpoint":"/get/data/version","result":"3.2.2"}
```
エラー例:
```json
{"status":400,"endpoint":"/set/data/osc_ip_address","result":{"message":"Invalid IP address","data":"127.0.0.1"}}
```
ロック状態と再試行
- `mapping` にある各ハンドラは `"status": True|False` を持ちます。
- False の場合、`handleRequest` は 423 (Locked endpoint) を返し、メインのハンドラはその要求をキューに戻して待機します(遅延再実行のため)。
run イベント
- `controller` は UI 更新などの非同期通知を行うために `run(status, endpoint, payload)` を呼び出します。これらは `run_mapping` にマップされ、外部 UI には `/run/...` 形式のエンドポイントで配信されます。
以下は `controller.py` から抽出した run イベントと、実際に送られるペイロードの具体例です。UI 側はこれらの JSON 形状を期待することで正しく動作します。
`/run/connected_network` (200)
- payload: true | false
`/run/enable_ai_models` (200)
- payload: true | false
`/run/mic_host_list` (200)
- payload: ["Host 1", "Host 2"]
`/run/mic_device_list` (200)
- payload: ["Microphone (Realtek)", "Headset Microphone"]
`/run/speaker_device_list` (200)
- payload: ["Speakers (Realtek)", "Headset"]
`/run/initialization_complete` (200)
- payload: dict mapping endpoint -> current value (constructed from init_mapping)
- 例: {"/get/data/version":"3.2.2","/get/data/selected_tab_no":0}
`/run/selected_mic_device` (200)
- payload: {"host": <host>, "device": <device>}
`/run/selected_speaker_device` (200)
- payload: string (device name)
`/run/error_device` (400)
- payload: {"message":"No mic device detected","data": null}
`/run/check_mic_volume` (200)
- payload: numeric energy value (float)
`/run/check_speaker_volume` (200)
- payload: numeric energy value (float)
`/run/download_progress_ctranslate2_weight` (200)
- payload: {"weight_type":"m2m100_418m","progress":0.42}
`/run/downloaded_ctranslate2_weight` (200)
- payload: "m2m100_418m"
`/run/error_ctranslate2_weight` (400)
- payload: {"message":"CTranslate2 weight download error","data": null}
`/run/download_progress_whisper_weight` (200)
- payload: {"weight_type":"base","progress":0.78}
`/run/downloaded_whisper_weight` (200)
- payload: "base"
`/run/error_whisper_weight` (400)
- payload: {"message":"Whisper weight download error","data": null}
`/run/word_filter` (200)
- payload: {"message":"Detected by word filter: <matched_text>"}
`/run/error_translation_engine` (400)
- payload: {"message":"Translation engine limit error","data": null}
`/run/error_translation_mic_vram_overflow` (400)
- payload: {"message":"VRAM out of memory during translation of mic","data":"<error_message>"}
`/run/error_translation_speaker_vram_overflow` (400)
- payload: {"message":"VRAM out of memory during translation of speaker","data":"<error_message>"}
`/run/error_translation_chat_vram_overflow` (400)
- payload: {"message":"VRAM out of memory during translation of chat","data":"<error_message>"}
`/run/enable_translation` (200/400)
- payload: on OOM: {"message":"Translation disabled due to VRAM overflow","data": false}
`/run/transcription_send_mic_message` (200)
- payload:
{
"original": {"message": "Hello", "transliteration": []},
"translations": [ {"message":"こんにちは","transliteration":[]}, ... ]
}
`/run/transcription_receive_speaker_message` (200)
- payload: same shape as `/run/transcription_send_mic_message`
`/run/software_update_info` (200)
- payload: e.g. {"has_update": true, "latest_version": "3.3.0"}
`/run/selected_translation_compute_type` (200)
- payload: string ("auto"|"cpu"|"cuda:0")
`/run/selected_transcription_compute_type` (200)
- payload: string
`/run/selected_translation_engines` (200)
- payload: config.SELECTED_TRANSLATION_ENGINES (list/dict per tab)
`/run/translation_engines` (200)
- payload: ["CTranslate2"]
`/run/initialization_progress` (200)
- payload: integer (1..4)
`/run/enable_osc_query` (200)
- payload: {"data": true|false, "disabled_functions": ["vrc_mic_mute_sync"]}
エンドポイント一覧mapping にある全エンドポイント)
注: 各行の説明では、`method` 的な概念はありません。すべてのエンドポイントは JSON リクエストで同様に呼び出します。`data` の期待値は説明に記載しています。
1) メイン操作
- /set/enable/translation — data: null — 翻訳を有効にします。
- 成功応答例:
```json
{"status":200, "endpoint":"/set/enable/translation", "result": true}
```
- 失敗例VRAM OOM を検出して無効化されたケースは run イベントで通知されます):
```json
{"status":400, "endpoint":"/set/enable/translation", "result":{"message":"Translation disabled due to VRAM overflow","data":false}}
```
- /set/disable/translation — data: null — 翻訳を無効にします。
- 成功応答例:
```json
{"status":200, "endpoint":"/set/disable/translation", "result": false}
```
- /set/enable/transcription_send — data: null — マイク転写(送信)を有効化します。
- 実行はスレッドで開始される場合がある。成功例:
```json
{"status":200, "endpoint":"/set/enable/transcription_send", "result": true}
```
- /set/disable/transcription_send — data: null — 停止要求。成功例:
```json
{"status":200, "endpoint":"/set/disable/transcription_send", "result": false}
```
- /set/enable/transcription_receive — data: null — スピーカー側の転写を有効化します。
- /set/disable/transcription_receive — data: null — 無効化します。
- /set/enable/foreground — data: null — フォアグラウンド表示を有効化します。
- 成功例: {"status":200, "endpoint":"/set/enable/foreground", "result": true}
- /get/data/selected_tab_no — data: null — 現在のタブ番号を返します。
- 例: {"status":200, "endpoint":"/get/data/selected_tab_no", "result": 0}
- /get/data/main_window_sidebar_compact_mode — data: null — サイドバーのコンパクト表示の現在値を返します。
- 例: {"status":200, "endpoint":"/get/data/main_window_sidebar_compact_mode","result": false}
- /set/data/selected_tab_no — data: int — タブ番号を設定します。
- リクエスト例: {"endpoint":"/set/data/selected_tab_no","data":1}
- 成功応答例: {"status":200, "endpoint":"/set/data/selected_tab_no","result":1}
- /get/data/translation_engines — data: null — 利用可能な翻訳エンジン一覧を返します。
- 例: {"status":200, "endpoint":"/get/data/translation_engines","result":["CTranslate2"]}
- /get/data/selectable_language_list — data: null — 選択可能な言語一覧(言語コード, country 等を含むデータ構造)
- 例: {"status":200, "endpoint":"/get/data/selectable_language_list","result":[{"language":"English","country":"US"},{"language":"Japanese","country":"JP"}]}
- /get/data/transcription_engines — data: null — 利用可能な転写エンジン一覧
- 例: {"status":200, "endpoint":"/get/data/transcription_engines","result":["Google","Whisper"]}
- /run/send_message_box — data: {"id": <任意>, "message": "..."}
- 内部で `Controller.chatMessage` を呼び出します。戻りは変換済メッセージ構造体。
- リクエスト例:
```json
{"endpoint":"/run/send_message_box","data":{"id":123,"message":"Hello"}}
```
- 成功応答例:
```json
{"status":200,"endpoint":"/run/send_message_box","result":{"id":123,"original":{"message":"Hello","transliteration":[]},"translations":[{"message":"","transliteration":[]}]}}
```
- /run/typing_message_box — data: null — OSC でタイピング状態を伝える場合に使用。成功例: {"status":200,...}
- /run/stop_typing_message_box — data: null — 停止。
- /run/send_text_overlay — data: object — オーバーレイに表示するテキストを更新します。例: {"text":"Hello","lang":"English"}
- 成功応答は送信した data をそのまま返すことが多い。
- /run/swap_your_language_and_target_language — data: null — 選択中の入出力言語を入れ替えます。成功例: {"status":200, ...}
/run/update_software — data: null — 非同期でアップデート処理を開始します。成功応答: {"status":200, "result": true}
/run/update_cuda_software — data: null — CUDA アップデートを開始します。
/set/enable/transcription_receive — data: null — スピーカー側の転写(受信)を有効化
/set/disable/transcription_receive — data: null — 無効化
/set/enable/foreground — data: null — フォアグラウンド表示を有効化
/set/disable/foreground — data: null — 無効化
- /get/data/selected_tab_no — data: null — 現在のタブ番号を返す
- /set/data/selected_tab_no — data: int — タブ番号を設定
- /get/data/translation_engines — data: null — 使える翻訳エンジン一覧を返す
- /get/data/selected_translation_engines — data: null — 各タブで選択されている翻訳エンジン(タブ別辞書)
- 例: {"status":200, "endpoint":"/get/data/selected_translation_engines","result":{"0":["CTranslate2"],"1":["CTranslate2"]}}
- /get/data/selected_your_languages — data: null — 各タブの入力言語設定
- 例: {"status":200, "endpoint":"/get/data/selected_your_languages","result":{"0":{"language":"English","enable":true}}}
- /get/data/selected_target_languages — data: null — 各タブの出力言語設定
- 例: {"status":200, "endpoint":"/get/data/selected_target_languages","result":{"0":{"1":{"language":"Japanese","enable":true}}}}
- /get/data/selected_transcription_engine — data: null — 現在選択されている転写エンジン
- 例: {"status":200, "endpoint":"/get/data/selected_transcription_engine","result":"Whisper"}
- /run/send_message_box — data: {"id":..., "message": "..."} — チャット送信を実行chatMessage を内部呼び出し)
- /run/typing_message_box — data: null — タイピング開始通知OSC 経由で送信される場合あり)
- /run/stop_typing_message_box — data: null — タイピング停止
- /run/send_text_overlay — data: {text settings...} — オーバーレイ用のテキスト表示を更新
- /run/swap_your_language_and_target_language — data: null — 入出力言語を入れ替え
- /run/update_software — data: null — ソフト更新処理をスレッドで開始
- /run/update_cuda_software — data: null — CUDA 関連更新を開始
2) 表示・外観設定
- /get/data/version — data: null — アプリ版を返す
- /get/data/transparency — data: null — 透過率
- /set/data/transparency — data: int — 透過率を設定
- /get/data/ui_scaling — data: null — UI スケール
- /set/data/ui_scaling — data: int
- /get/data/textbox_ui_scaling, /set/data/textbox_ui_scaling
- /get/data/message_box_ratio, /set/data/message_box_ratio
- /get/data/send_message_button_type, /set/data/send_message_button_type
- /get/data/show_resend_button, /set/enable/show_resend_button, /set/disable/show_resend_button
- /get/data/font_family, /set/data/font_family
- /get/data/ui_language, /set/data/ui_language
- /get/data/main_window_geometry, /set/data/main_window_geometry
3) 計算デバイス関連
- /get/data/compute_mode — data: null — compute mode
- /get/data/translation_compute_device_list — data: null — 選択可能な翻訳デバイス一覧
- /get/data/selected_translation_compute_device — data: null
- /set/data/selected_translation_compute_device — data: device descriptor — 選択
- /get/data/transcription_compute_device_list — same as translation
- /get/data/selected_transcription_compute_device, /set/data/selected_transcription_compute_device
4) 翻訳設定
- /get/data/selectable_ctranslate2_weight_type_dict — data: null — 利用可能な ctranslate2 重みの辞書
- /get/data/ctranslate2_weight_type, /set/data/ctranslate2_weight_type
- /get/data/selected_translation_compute_type, /set/data/selected_translation_compute_type
- /run/download_ctranslate2_weight — data: "weight_type" — 指定した重みをダウンロード(非同期可)
- /get/data/deepl_auth_key — data: null — DeepL API キー(存在すれば返却、セキュリティ上の注意あり)
- /set/data/deepl_auth_key — data: "<key>" — DeepL キーを設定(キー検証あり)
- /delete/data/deepl_auth_key — data: null — DeepL キーを削除
- /set/data/selected_translation_engines — data: dict/list — 各タブの翻訳エンジン選択を設定します。
- 例: {"endpoint":"/set/data/selected_translation_engines","data":{"0":["CTranslate2"]}}
- /set/data/selected_transcription_engine — data: string — 現在の転写エンジンを設定します。
- 例: {"endpoint":"/set/data/selected_transcription_engine","data":"Whisper"}
- /set/enable/main_window_sidebar_compact_mode — data: null — サイドバーをコンパクト表示に設定
- 例: {"status":200,"endpoint":"/set/enable/main_window_sidebar_compact_mode","result": true}
- /set/disable/main_window_sidebar_compact_mode — data: null — サイドバーのコンパクト表示を解除
- 例: {"status":200,"endpoint":"/set/disable/main_window_sidebar_compact_mode","result": false}
- /get/data/convert_message_to_romaji, /set/enable/convert_message_to_romaji, /set/disable/convert_message_to_romaji
- /get/data/convert_message_to_hiragana, /set/enable/convert_message_to_hiragana, /set/disable/convert_message_to_hiragana
5) トランスクリプション / デバイス
- /get/data/mic_host_list, /get/data/mic_device_list, /get/data/speaker_device_list
- /get/data/auto_mic_select, /set/enable/auto_mic_select, /set/disable/auto_mic_select
- /get/data/selected_mic_host, /set/data/selected_mic_host
- /get/data/selected_mic_device, /set/data/selected_mic_device
- /get/data/mic_threshold, /set/data/mic_threshold
- /get/data/mic_automatic_threshold, /set/enable/mic_automatic_threshold, /set/disable/mic_automatic_threshold
- /get/data/mic_record_timeout, /set/data/mic_record_timeout
- /get/data/mic_phrase_timeout, /set/data/mic_phrase_timeout
- /get/data/mic_max_phrases, /set/data/mic_max_phrases
- /get/data/hotkeys, /set/data/hotkeys
- /get/data/plugins_status, /set/data/plugins_status
- /get/data/mic_avg_logprob, /set/data/mic_avg_logprob
- /get/data/mic_no_speech_prob, /set/data/mic_no_speech_prob
- /set/enable/check_mic_threshold, /set/disable/check_mic_threshold
- /get/data/mic_word_filter, /set/data/mic_word_filter
6) スピーカー側設定
- /get/data/auto_speaker_select, /set/enable/auto_speaker_select, /set/disable/auto_speaker_select
- /get/data/selected_speaker_device, /set/data/selected_speaker_device
- /get/data/speaker_threshold, /set/data/speaker_threshold
- /get/data/speaker_automatic_threshold, /set/enable/speaker_automatic_threshold, /set/disable/speaker_automatic_threshold
- /get/data/speaker_record_timeout, /set/data/speaker_record_timeout
- /get/data/speaker_phrase_timeout, /set/data/speaker_phrase_timeout
- /get/data/speaker_max_phrases, /set/data/speaker_max_phrases
- /get/data/speaker_avg_logprob, /set/data/speaker_avg_logprob
- /get/data/speaker_no_speech_prob, /set/data/speaker_no_speech_prob
- /set/enable/check_speaker_threshold, /set/disable/check_speaker_threshold
7) Whisper / トランスクリプション重み
- /get/data/selectable_whisper_weight_type_dict
- /get/data/whisper_weight_type, /set/data/whisper_weight_type
- /get/data/selected_transcription_compute_type, /set/data/selected_transcription_compute_type
- /run/download_whisper_weight — data: "weight_type"
8) VR / オーバーレイ
- /get/data/overlay_small_log, /set/enable/overlay_small_log, /set/disable/overlay_small_log
- /get/data/overlay_small_log_settings, /set/data/overlay_small_log_settings
- /get/data/overlay_large_log, /set/enable/overlay_large_log, /set/disable/overlay_large_log
- /get/data/overlay_large_log_settings, /set/data/overlay_large_log_settings
- /get/data/overlay_show_only_translated_messages, /set/enable/overlay_show_only_translated_messages, /set/disable/overlay_show_only_translated_messages
9) その他設定
- /get/data/send_message_format_parts, /set/data/send_message_format_parts
- /get/data/received_message_format_parts, /set/data/received_message_format_parts
- /get/data/auto_clear_message_box, /set/enable/auto_clear_message_box, /set/disable/auto_clear_message_box
- /get/data/send_only_translated_messages, /set/enable/send_only_translated_messages, /set/disable/send_only_translated_messages
- /get/data/logger_feature, /set/enable/logger_feature, /set/disable/logger_feature
- /run/open_filepath_logs
- /get/data/vrc_mic_mute_sync, /set/enable/vrc_mic_mute_sync, /set/disable/vrc_mic_mute_sync
- /get/data/send_message_to_vrc, /set/enable/send_message_to_vrc, /set/disable/send_message_to_vrc
- /get/data/send_received_message_to_vrc, /set/enable/send_received_message_to_vrc, /set/disable/send_received_message_to_vrc
10) WebSocket
- /get/data/websocket_host, /set/data/websocket_host
- /get/data/websocket_port, /set/data/websocket_port
- /get/data/websocket_server, /set/enable/websocket_server, /set/disable/websocket_server
11) OSC / 高度設定
- /get/data/osc_ip_address, /set/data/osc_ip_address
- /get/data/osc_port, /set/data/osc_port
- /get/data/notification_vrc_sfx, /set/enable/notification_vrc_sfx, /set/disable/notification_vrc_sfx
- /run/open_filepath_config_file
- /run/feed_watchdog
挙動メモ / 注意点
- `data` は受信時に `encodeBase64` が適用される場合があります(バイナリや特殊文字対策)。
- いくつかのエンドポイントは内部的にバックグラウンドスレッドを立ち上げますダウンロード・更新処理・transliteration 等)。
- 翻訳・転写関連は VRAM OOM を検知すると自動的に関連機能を無効化し、UI に 400 系の run イベントを送信します。API 消費者はこれらの run イベントを監視する必要があります。
次の作業
- `docs/modules/controller.md` に記載した Controller のメソッド詳細と紐付けて、各エンドポイントごとに具体的な request/response のサンプルbody の構造)を追加します。
### API / メッセージマッピング(詳細)
このアプリは stdin/stdout を通じた 1 行 JSON メッセージで制御します。内部では `mainloop.py` の `mapping` が受信 endpoint を Controller のメソッドに結び付け、`run_mapping` が非同期通知のエンドポイントを定義します。
受信メッセージstdin
```json
{ "endpoint": "/set/data/selected_tab_no", "data": 0 }
```
送信メッセージstdout
- 成功: printResponse が次を出力します。
```json
{ "status": 200, "endpoint": "/get/data/version", "result": "3.2.2" }
```
- エラー:
```json
{ "status": 400, "endpoint": "/set/data/osc_ip_address", "result": {"message":"Invalid IP address","data":"127.0.0.1"} }
```
動作原則
- `/get/data/*` : Controller の getter を呼び、設定やリストを返す。
- `/set/data/*` : Controller の setter を呼び、設定を変更して新値を返す。
- `/run/*` : 非同期アクションや UI ボタンが実行する処理(ダウンロード、更新、送信など)。
- `mapping` の `"status": False` はロック423 を返し、要求はキューに戻され再試行される)。
表記ルール
- Controller メソッドは `Controller.<method>` の形式で明記。
- `run events` は Controller が UI に通知する `run_mapping` の `/run/...` エンドポイント名を列挙します。
以下は `mainloop.py` の `mapping` に基づいた、主要エンドポイントの詳細(カテゴリ順)。
1) メイン操作(チャット/翻訳/転写)
- Endpoint: `/set/enable/translation`
- Controller: `Controller.setEnableTranslation`
- data: null
- success: {status:200, result: true}
- error example: {status:400, result:{message:"Translation disabled due to VRAM overflow", data: False}}
- run events: `/run/enable_translation` を発行して UI に状態を通知する。
- Endpoint: `/set/disable/translation`
- Controller: `Controller.setDisableTranslation`
- data: null
- success: {status:200, result: false}
- run events: `/run/enable_translation`
- Endpoint: `/set/enable/transcription_send`
- Controller: `Controller.setEnableTranscriptionSend`
- data: null
- success: {status:200, result: true}
- side-effect: `Controller.startThreadingTranscriptionSendMessage` を呼びバックグラウンドで音声転写を開始する。
- run events: `/run/enable_transcription_send`
- Endpoint: `/set/disable/transcription_send`
- Controller: `Controller.setDisableTranscriptionSend`
- data: null
- success: {status:200, result: false}
- Endpoint: `/run/send_message_box`
- Controller: `Controller.sendMessageBox` -> 内部で `Controller.chatMessage`
- data: {"id": <任意>, "message": "..."}
- success example: {status:200, result: {"id":123, "original":{...}, "translations":[...]}}
- run events: 転送先言語や翻訳結果があれば `/run/transcription_send_mic_message` などが発行される。
- Endpoint: `/run/send_text_overlay`
- Controller: `Controller.sendTextOverlay`
- data: object (例: {"text":"Hello","lang":"English"})
- success: echo back the data
- side-effect: オーバーレイ更新small/large に応じた出力)
2) 表示 / 外観設定
- Endpoint: `/get/data/version`
- Controller: `Controller.getVersion`
- data: null
- success: {status:200, result: config.VERSION}
- Endpoint: `/get/data/transparency` / `/set/data/transparency`
- Controller: `Controller.getTransparency` / `Controller.setTransparency`
- data for set: integer (0-255 等、設定側で検証)
- success example: {status:200, result: <int>}
UI スケーリング、textbox スケーリング、font_family, ui_language 等の /get と /set は同様のパターン: Controller の getXXX / setXXX を呼ぶ)
3) 計算デバイス関連
- Endpoint: `/get/data/translation_compute_device_list` -> `Controller.getComputeDeviceList`
- data: null
- result: list of device descriptors (構造は `config.SELECTABLE_COMPUTE_DEVICE_LIST` に従う)
- Endpoint: `/set/data/selected_translation_compute_device`
- Controller: `Controller.setSelectedTranslationComputeDevice`
- data: device descriptor (例: {"name":"cuda:0","type":"gpu"})
- side-effects: `model.setChangedTranslatorParameters(True)` が呼ばれ、実行時にモデル再ロードが必要な場合がある。
- success: {status:200, result: selected_device}
4) 翻訳/重み管理
- Endpoint: `/get/data/selectable_ctranslate2_weight_type_dict`
- Controller: `Controller.getSelectableCtranslate2WeightTypeDict`
- result: dict mapping weight_type -> bool
- Endpoint: `/run/download_ctranslate2_weight`
- Controller: `Controller.downloadCtranslate2Weight`
- data: "weight_type" (例: "m2m100_418m")
- behavior: 非同期フラグでスレッド起動可能。進捗は run events `/run/download_progress_ctranslate2_weight` を発行。完了時に `/run/downloaded_ctranslate2_weight`。
- Endpoint: `/set/data/deepl_auth_key`
- Controller: `Controller.setDeeplAuthKey`
- data: string (API key)
- behavior: 内部で `model.authenticationTranslatorDeepLAuthKey` を実行して検証。失敗時は 400 を返す。
5) トランスクリプション / デバイス
- Endpoint: `/get/data/mic_host_list` -> `Controller.getMicHostList`
- data: null
- result: dict/list of hosts
- Endpoint: `/set/data/selected_mic_host` -> `Controller.setSelectedMicHost`
- data: host identifier (string)
- side-effects: デフォルトデバイスを `model.getMicDefaultDevice()` で選択し、エネルギーチェックや転写スレッドの再起動が発生する場合がある。
- Endpoint: `/set/data/mic_threshold` -> `Controller.setMicThreshold`
- data: integer
- validation: 0 <= value <= config.MAX_MIC_THRESHOLD
- success: {status:200, result: new_value} error: 400 with message and old value
6) スピーカー関連(受信)
- Endpoint: `/set/data/selected_speaker_device` -> `Controller.setSelectedSpeakerDevice`
- data: device descriptor
- side-effects: スピーカー転写スレッドENABLE_CHECK_ENERGY_RECEIVEを再起動する可能性あり
7) Whisper / トランスクリプション重み
- Endpoint: `/run/download_whisper_weight`
- Controller: `Controller.downloadWhisperWeight`
- data: "weight_type"
- run events: `/run/download_progress_whisper_weight`, `/run/downloaded_whisper_weight`
8) オーバーレイ / VR
- Endpoint: `/set/enable/overlay_small_log` -> `Controller.setEnableOverlaySmallLog`
- side-effect: `model.startOverlay()` を呼び、`model.updateOverlaySmallLog` で描画が更新される
9) WebSocket / OSC / Watchdog
- Endpoint: `/set/data/websocket_host` -> `Controller.setWebSocketHost`
- validation: IP 形式チェック (`isValidIpAddress`)
- if WebSocket server running: attempts to restart server on new host/port (checks availability via `isAvailableWebSocketServer`)
- Endpoint: `/set/data/osc_ip_address` -> `Controller.setOscIpAddress`
- validation: IP 形式。失敗時は 400 を返す。
- Endpoint: `/run/feed_watchdog` -> `Controller.feedWatchdog`
- Controller: `Controller.feedWatchdog` ➜ `model.feedWatchdog()`
共通的な失敗モード(クライアント実装者向けメモ)
- 無効なパラメータ: 400 と {message,data} を返す。
- ロック: 423 (Locked endpoint) — UI 側はリトライまたはキュー内での再試行を待つ。
- 内部エラー: 500 とエラーメッセージ(詳細はログ)を返す。
- VRAM OOM / モデルエラー: Controller は `model.detectVRAMError` を使い、必要に応じて機能無効化と run イベントで通知する。
付録: すぐ使える呼び出し例
- バージョン取得
```json
{ "endpoint": "/get/data/version", "data": null }
```
- タブ切替
```json
{ "endpoint": "/set/data/selected_tab_no", "data": 1 }
```
- メッセージ送信(チャット)
```json
{ "endpoint": "/run/send_message_box", "data": {"id": 555, "message": "Hello world"} }
```
次の作業
- ① `docs/modules/controller.md` の各メソッドとこの `docs/api.md` を突き合わせ、未記載の `run_mapping` イベントのペイロード例を追加します。
- ② 軽い品質ゲートREADME と runtime 注意の草案作成)を実行します。
## エンドポイント別 JSON スキーマ(補完)
このセクションでは `mainloop.py` の `mapping` に定義された全エンドポイントをパターンごとに整理し、クライアントが送信すべき `request` と期待される `response` の JSON スキーマを明示します。多数のエンドポイントは共通パターンに従うため、パターン定義と代表例でほとんどのケースをカバーしています。
共通ルール
- リクエストは必ず 1 行 JSON: {"endpoint": "<path>", "data": <any|null>}。
- レスポンスは {"status": <int>, "endpoint": "<path>", "result": <any>} の形式(内部の `printResponse` により出力)。
1) /get/data/* パターン(読み取り)
- request.data: null
- response.result: 直ちに返せる JSON 値(数値/文字列/配列/辞書)
- schemaJSON Schema 風の簡易表記):
request:
{
"endpoint": "/get/data/<name>",
"data": null
}
response:
{
"status": 200,
"endpoint": "/get/data/<name>",
"result": <Any>
}
代表例:
- `/get/data/version` → result: string
{"status":200,"endpoint":"/get/data/version","result":"3.2.2"}
- `/get/data/mic_device_list` → result: ["Device 1", "Device 2"]
2) /set/data/* パターン(書き込み)
- request.data: セッタが期待する型(下に代表的な型を列挙)
- response.result: 新しい値または検証済の値(成功時)
- error: バリデーション失敗時は status 400 と {message,data}
共通 request/response:
request:
{
"endpoint": "/set/data/<name>",
"data": <value>
}
response (success):
{
"status":200,
"endpoint":"/set/data/<name>",
"result": <new_value>
}
response (validation error):
{
"status":400,
"endpoint":"/set/data/<name>",
"result": {"message": "<reason>", "data": <current_value>}
}
代表的リクエスト型一覧(多くはこの型いずれか):
- int: `/set/data/selected_tab_no`, `/set/data/transparency`, `/set/data/mic_threshold` など
- string: `/set/data/selected_mic_host`, `/set/data/selected_speaker_device`, `/set/data/deepl_auth_key` など
- dict/object: `/set/data/selected_your_languages`, `/set/data/selected_target_languages`, `/set/data/send_message_format_parts` など
- list: `/set/data/mic_word_filter` など
3) フラグ切替enable / disable
- 概要: 機能の有効化/無効化を行うエンドポイント群は、実装で定義された具体的なエンドポイント名(例: `/set/enable/translation`, `/set/disable/translation`, `/set/enable/foreground` など)で提供されています。本ドキュメントでは umbrella 的な汎用トークン(`/set/enable` や `/set/disable` 単体)は記載せず、実際に実装で定義されている concrete エンドポイントのみを列挙しています。
- 振る舞いの要点:
- リクエストの `data` は通常 `null` です。
- 成功応答は多くの場合 boolean を返します(例: `{ "status":200, "endpoint":"/set/enable/foreground", "result": true }`)。
- 条件により有効化/無効化ができない場合は 400 を返し、`{ "message": "...", "data": <current_value> }` の形で詳細が返されます。
具体的なフラグ切替エンドポイントはドキュメント本文の各該当箇所で個別に列挙しています(例: `/set/enable/translation`, `/set/disable/translation`, `/set/enable/transcription_send`, `/set/disable/transcription_send`, `/set/enable/main_window_sidebar_compact_mode`, など)。
4) /run/*(アクション・実行系)
- request.data: アクションに依存(例: `/run/send_message_box` は {id, message}
- response.result: 多くは action の結果True/False, objectを返す
- 非同期で UI 更新を行う場合は `Controller.run(...)` により `/run/...` 形式の通知が stdout に出力される
代表例:
- `/run/send_message_box`
request.data: {"id": <any>, "message": "<string>"}
response.result: {
"id": <any>,
"original": {"message": "<string>", "transliteration": [<strings>] },
"translations": [ {"message":"<string>", "transliteration":[...]}, ... ]
}
- `/run/download_ctranslate2_weight`
request.data: "<weight_type>" (string)
response.result: true
progress: `/run/download_progress_ctranslate2_weight` -> {"weight_type":"...","progress":0.0..1.0}
complete: `/run/downloaded_ctranslate2_weight` -> "<weight_type>"
5) WebSocket / OSC / Watchdog 関連
- `/set/data/websocket_host` : request.data:string(host) → response: {status:200, result: host} または 400 (not available)
- `/set/data/osc_ip_address` : request.data:string(ip) → validation via `isValidIpAddress` → 400 on invalid
- `/run/feed_watchdog`: request.data:null → response: {status:200,result:true}
6) エラー応答の標準形
- Validation / domain error : status 400, result: {"message": "<説明>", "data": <current_or_invalid_value>}
- Locked endpoint: status 423, result: "Locked endpoint"mainloop が再試行のためキューに戻す)
- Internal error: status 500, result: "<exception string>"
7) run eventsUI 更新通知)- 参考(主要イベントのみ再掲)
- `/run/connected_network` : bool
- `/run/enable_ai_models` : bool
- `/run/initialization_progress` : int (1..4)
- `/run/transcription_send_mic_message` / `/run/transcription_receive_speaker_message` : オブジェクトoriginal/translations, see above
追加の run イベント(ランタイム検証で未記載と判定されたため追記):
- `/run/enable_transcription_receive` : bool
- 説明: スピーカー側転写transcription receiveの有効/無効を UI に通知します。
- `/run/transcription_send_mic_message` : object
- payload: 同 `/run/transcription_send_mic_message` の構造original + translations
- 説明: マイク側で転写結果が生成され、UI に送信するための通知です。
- `/run/transcription_receive_speaker_message` : object
- payload: 同 `/run/transcription_receive_speaker_message` の構造
- 説明: スピーカー側で転写結果が生成されたときに発行されます。
- `/run/error_transcription_mic_vram_overflow` : object (400)
- payload: {"message": "VRAM out of memory during mic transcription", "data": "<error message>"}
- 説明: マイク転写中に VRAM OOM が発生した際に通知します。
- `/run/error_transcription_speaker_vram_overflow` : object (400)
- payload: {"message": "VRAM out of memory during speaker transcription", "data": "<error message>"}
- 説明: スピーカー転写中に VRAM OOM が発生した際に通知します。
補遺: 全エンドポイント一覧と期待型の速見表
- `/get/data/*` : data=null -> result: primitive|array|object
- `/set/data/*` : data: 型指定 (int|string|dict|list) -> result: new value or validation error
- `/set/enable/*` `/set/disable/*` : data=null -> result: bool
- `/run/*` : data: action-specific -> result: action result object / bool
ファイルの更新履歴
- このドキュメントは `mainloop.py` の `mapping` と `controller.py` の `run_mapping` を参照して作成しました。将来的にエンドポイントを追加した場合は同じ箇所を参照して本ドキュメントを更新してください。
----
完了: エンドポイント別スキーマの補完を行いました。次は軽い品質ゲートlint/typecheckの実行を提案します。

View File

@@ -1,21 +0,0 @@
# アーキテクチャ概観
VRCTsrc-pythonは、ローカル音声キャプチャ・音声認識・翻訳・VR 表示・OSC/ WebSocket 連携を統合するアプリケーションです。主な責務は次の通り。
- device_manager: オーディオ入出力デバイスの発見、監視、コールバック通知。
- transcription (models/transcription/*): マイク/スピーカーからの音声取得、認識Google/Whisper、議事録管理。
- translation (models/translation/*): 翻訳エンジンDeepL/API、CTranslate2、Google など)管理と実行。
- overlay (models/overlay/*): VR オーバーレイの画像生成と OpenVR を使った描画管理。
- osc (models/osc/osc.py): VRChat 等との OSCおよび OSCQueryでのやり取り。
- websocket (models/websocket/*): 外部クライアント向け WebSocket ブロードキャスト。
- model.py: 高レベルなファサード。各機能のインスタンス化とランタイム操作。
- controller.py: UI/外部メッセージを受け、config を更新・機能を起動するコマンド実行層。
- mainloop.py: stdin 経由のコマンド受付ループとマッピング定義。GUI からの操作を受ける想定。
- utils.py: ロギング、ネットワークチェック、デバイス/計算デバイスタイプ判定などのユーティリティ。
- config.py: シングルトン設定ストア。アプリ起動中に共有して使うすべての設定値。
設計上のポイント:
- シングルトン/ファサード: `model``config` はシングルトンでグローバルに参照される。これにより UI 層Controllerと低レイヤmodels/*)の橋渡しを行う。
- 非同期処理: デバイス監視、音声録音・認識、WebSocket サーバー、Overlay のループはそれぞれ別スレッド/非同期ループで実行される。
- フォールバック: 翻訳はまず選択されたエンジンを使い、失敗時に CTranslate2 にフォールバックする仕組みがある。
- VRAM エラー検出: Whisper / CTranslate2 等で VRAM 不足が起きた場合、特殊なエラー検出を行い翻訳/音声機能を無効化して回復を試みる。

433
src-python/docs/config.md Normal file
View File

@@ -0,0 +1,433 @@
# config.py ドキュメント
## 概要
`config.py` は、アプリケーションの全設定を一元管理するシングルトンクラス `Config` を提供するモジュール。設定値の読み込み・保存・検証を行い、JSON ファイルへの永続化をデバウンス機能付きで実現する。
## 主要機能
- シングルトンパターンによる設定の一元管理
- JSONファイル (`config.json`) からの設定読み込みと自動保存
- デバウンス機能による書き込み最適化デフォルト2秒
- 読み取り専用プロパティと読み書き可能プロパティの明確な分離
- オプショナルモジュールのセーフガードインポート(環境依存の依存関係を安全に処理)
- プロパティセッター内での型チェックとバリデーション
- `@json_serializable` デコレータによる永続化対象プロパティの管理
## アーキテクチャ
### デザインパターン
- **シングルトンパターン**: `__new__` メソッドで単一インスタンスを保証
- **プロパティパターン**: getter/setter による型安全なアクセス制御
### 設定の分類
1. **読み取り専用設定** (Read Only)
- アプリケーションバージョン、パス、URL、定数など
- プロパティのみsetter なし)
2. **ランタイム設定** (Read Write)
- 機能の有効/無効フラグ
- 実行時の状態管理
- JSON保存されない一時的な設定
3. **永続化設定** (Save Json Data)
- ユーザー設定、デバイス選択、UI設定など
- `@json_serializable` デコレータでマーク
- `saveConfig()` 経由で自動保存
## 使用方法
### 基本的な使い方
```python
from config import config
# 設定値の取得(読み取り専用)
version = config.VERSION
app_path = config.PATH_LOCAL
# 設定値の取得(読み書き可能)
current_tab = config.SELECTED_TAB_NO
mic_threshold = config.MIC_THRESHOLD
# 設定値の変更(自動保存される)
config.SELECTED_TAB_NO = "2"
config.MIC_THRESHOLD = 500
config.TRANSPARENCY = 80
# 即座に保存する場合
config.MAIN_WINDOW_GEOMETRY = {"x_pos": 100, "y_pos": 200, "width": 900, "height": 700}
# MESSAGE_BOX_RATIO と MAIN_WINDOW_GEOMETRY は immediate_save=True で即座に保存
```
### デバウンス保存の仕組み
```python
# 通常の設定変更: 2秒後に保存
config.UI_LANGUAGE = "ja"
config.FONT_FAMILY = "Arial" # 前のタイマーがキャンセルされ、新たに2秒のタイマー開始
# 即座保存が必要な設定: デバウンスなし
config.MESSAGE_BOX_RATIO = 15 # 即座にファイル書き込み
```
## 動作環境・依存関係
### 必須依存
- Python 3.10以上match-case 構文使用)
- `torch`: CUDA利用可否の判定に使用
- `threading`: デバウンスタイマー用
### オプション依存(セーフガード付き)
以下のモジュールはインポートに失敗しても動作する:
- `device_manager`: デバイス管理(マイク/スピーカー)
- `models.translation.translation_languages`: 翻訳言語リスト
- `models.translation.translation_utils`: CTranslate2 重みリスト
- `models.transcription.transcription_languages`: 音声認識言語リスト
- `models.transcription.transcription_whisper`: Whisper モデルリスト
### プロジェクト内依存
- `utils`: エラーロギング、辞書構造検証、計算デバイスリスト取得
## ファイル構成
### 主要クラス: `Config`
#### クラス属性
```python
_instance: Config | None # シングルトンインスタンス
_config_data: Dict[str, Any] # JSON保存用データ
_timer: Optional[threading.Timer] # デバウンスタイマー
_debounce_time: int = 2 # デバウンス時間(秒)
```
#### 主要メソッド
**初期化・保存**
- `__new__(cls)`: シングルトンインスタンス生成・初期化
- `init_config()`: デフォルト値の設定
- `load_config()`: JSONファイルから設定読み込み
- `saveConfig(key, value, immediate_save=False)`: 設定の保存(デバウンス付き)
- `saveConfigToFile()`: JSONファイルへの即座書き込み
**デコレータ**
- `@json_serializable(var_name)`: 永続化対象プロパティのマーク
### 設定プロパティ一覧
#### 読み取り専用設定23項目
| プロパティ名 | 型 | 説明 | デフォルト値 |
|------------|----|----|------------|
| `VERSION` | str | アプリケーションバージョン | "3.3.0" |
| `PATH_LOCAL` | str | アプリケーションローカルパス | 実行時決定 |
| `PATH_CONFIG` | str | 設定ファイルパス | `{PATH_LOCAL}/config.json` |
| `PATH_LOGS` | str | ログディレクトリパス | `{PATH_LOCAL}/logs` |
| `GITHUB_URL` | str | GitHub API URL | リポジトリURL |
| `UPDATER_URL` | str | アップデーターAPIの URL | アップデーターURL |
| `BOOTH_URL` | str | Booth 販売ページURL | Booth URL |
| `DOCUMENTS_URL` | str | ドキュメントURL | Notion URL |
| `DEEPL_AUTH_KEY_PAGE_URL` | str | DeepL認証キー取得ページ | DeepL URL |
| `MAX_MIC_THRESHOLD` | int | マイクしきい値の最大値 | 2000 |
| `MAX_SPEAKER_THRESHOLD` | int | スピーカーしきい値の最大値 | 4000 |
| `WATCHDOG_TIMEOUT` | int | Watchdog タイムアウト(秒) | 60 |
| `WATCHDOG_INTERVAL` | int | Watchdog チェック間隔(秒) | 20 |
| `SELECTABLE_TAB_NO_LIST` | List[str] | 選択可能タブ番号 | ["1", "2", "3"] |
| `SELECTED_TAB_TARGET_LANGUAGES_NO_LIST` | List[str] | ターゲット言語タブ番号 | ["1", "2", "3"] |
| `SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_LIST` | List[str] | CTranslate2重みタイプリスト | 動的取得 |
| `SELECTABLE_WHISPER_WEIGHT_TYPE_LIST` | List[str] | Whisper重みタイプリスト | 動的取得 |
| `SELECTABLE_TRANSLATION_ENGINE_LIST` | List[str] | 翻訳エンジンリスト | 動的取得 |
| `SELECTABLE_TRANSCRIPTION_ENGINE_LIST` | List[str] | 音声認識エンジンリスト | 動的取得 |
| `SELECTABLE_UI_LANGUAGE_LIST` | List[str] | UI言語リスト | ["en", "ja", "ko", "zh-Hant", "zh-Hans"] |
| `COMPUTE_MODE` | str | 計算モード | "cuda" or "cpu" |
| `SELECTABLE_COMPUTE_DEVICE_LIST` | List[Dict] | 選択可能な計算デバイスリスト | 動的取得 |
| `SEND_MESSAGE_BUTTON_TYPE_LIST` | List[str] | 送信ボタンタイプリスト | ["show", "hide", "show_and_disable_enter_key"] |
#### ランタイム設定10項目
| プロパティ名 | 型 | 説明 | デフォルト値 | JSON保存 |
|------------|----|----|-----------|---------|
| `ENABLE_TRANSLATION` | bool | 翻訳機能有効フラグ | False | なし |
| `ENABLE_TRANSCRIPTION_SEND` | bool | 送信音声認識有効フラグ | False | なし |
| `ENABLE_TRANSCRIPTION_RECEIVE` | bool | 受信音声認識有効フラグ | False | なし |
| `ENABLE_FOREGROUND` | bool | フォアグラウンド有効フラグ | False | なし |
| `ENABLE_CHECK_ENERGY_SEND` | bool | 送信エネルギーチェック有効 | False | なし |
| `ENABLE_CHECK_ENERGY_RECEIVE` | bool | 受信エネルギーチェック有効 | False | なし |
| `SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT` | Dict[str, bool] | CTranslate2重み状態辞書 | {} | なし |
| `SELECTABLE_WHISPER_WEIGHT_TYPE_DICT` | Dict[str, bool] | Whisper重み状態辞書 | {} | なし |
| `SELECTABLE_TRANSLATION_ENGINE_STATUS` | Dict[str, bool] | 翻訳エンジン状態辞書 | {} | なし |
| `SELECTABLE_TRANSCRIPTION_ENGINE_STATUS` | Dict[str, bool] | 音声認識エンジン状態辞書 | {} | なし |
#### 永続化設定60項目以上
**メインウィンドウ設定**
- `SELECTED_TAB_NO`: 選択中のタブ番号
- `SELECTED_TRANSLATION_ENGINES`: タブごとの翻訳エンジン選択
- `SELECTED_YOUR_LANGUAGES`: タブごとの入力言語設定
- `SELECTED_TARGET_LANGUAGES`: タブごとのターゲット言語設定
- `SELECTED_TRANSCRIPTION_ENGINE`: 音声認識エンジン
- `CONVERT_MESSAGE_TO_ROMAJI`: ローマ字変換有効フラグ
- `CONVERT_MESSAGE_TO_HIRAGANA`: ひらがな変換有効フラグ
- `MAIN_WINDOW_SIDEBAR_COMPACT_MODE`: サイドバーコンパクトモード
- `SEND_MESSAGE_FORMAT_PARTS`: 送信メッセージフォーマット
- `RECEIVED_MESSAGE_FORMAT_PARTS`: 受信メッセージフォーマット
**UIウィンドウ設定**
- `TRANSPARENCY`: ウィンドウ透明度0-100
- `UI_SCALING`: UIスケーリング%
- `TEXTBOX_UI_SCALING`: テキストボックススケーリング(%
- `MESSAGE_BOX_RATIO`: メッセージボックス比率(即座保存)
- `SEND_MESSAGE_BUTTON_TYPE`: 送信ボタンタイプ
- `SHOW_RESEND_BUTTON`: 再送信ボタン表示フラグ
- `FONT_FAMILY`: フォントファミリー
- `UI_LANGUAGE`: UI言語
- `MAIN_WINDOW_GEOMETRY`: ウィンドウ位置・サイズ(即座保存)
**マイク設定**
- `AUTO_MIC_SELECT`: 自動マイク選択
- `SELECTED_MIC_HOST`: 選択されたマイクホスト
- `SELECTED_MIC_DEVICE`: 選択されたマイクデバイス
- `MIC_THRESHOLD`: マイクしきい値
- `MIC_AUTOMATIC_THRESHOLD`: 自動しきい値調整
- `MIC_RECORD_TIMEOUT`: 録音タイムアウト(秒)
- `MIC_PHRASE_TIMEOUT`: フレーズタイムアウト(秒)
- `MIC_MAX_PHRASES`: 最大フレーズ数
- `MIC_WORD_FILTER`: ワードフィルターリスト
- `MIC_AVG_LOGPROB`: 平均対数確率しきい値
- `MIC_NO_SPEECH_PROB`: 無音確率しきい値
**スピーカー設定**
- `AUTO_SPEAKER_SELECT`: 自動スピーカー選択
- `SELECTED_SPEAKER_DEVICE`: 選択されたスピーカーデバイス
- `SPEAKER_THRESHOLD`: スピーカーしきい値
- `SPEAKER_AUTOMATIC_THRESHOLD`: 自動しきい値調整
- `SPEAKER_RECORD_TIMEOUT`: 録音タイムアウト(秒)
- `SPEAKER_PHRASE_TIMEOUT`: フレーズタイムアウト(秒)
- `SPEAKER_MAX_PHRASES`: 最大フレーズ数
- `SPEAKER_AVG_LOGPROB`: 平均対数確率しきい値
- `SPEAKER_NO_SPEECH_PROB`: 無音確率しきい値
**モデル設定**
- `SELECTED_TRANSLATION_COMPUTE_DEVICE`: 翻訳計算デバイス
- `SELECTED_TRANSCRIPTION_COMPUTE_DEVICE`: 音声認識計算デバイス
- `CTRANSLATE2_WEIGHT_TYPE`: CTranslate2重みタイプ
- `SELECTED_TRANSLATION_COMPUTE_TYPE`: 翻訳計算タイプ
- `WHISPER_WEIGHT_TYPE`: Whisper重みタイプ
- `SELECTED_TRANSCRIPTION_COMPUTE_TYPE`: 音声認識計算タイプ
**通信設定**
- `OSC_IP_ADDRESS`: OSC IPアドレスデフォルト: "127.0.0.1"
- `OSC_PORT`: OSCポートデフォルト: 9000
- `AUTH_KEYS`: 認証キー辞書DeepL API等
- `WEBSOCKET_HOST`: WebSocketホスト
- `WEBSOCKET_PORT`: WebSocketポート
- `WEBSOCKET_SERVER`: WebSocketサーバー有効フラグ非永続化
**オーバーレイ設定**
- `OVERLAY_SMALL_LOG`: 小ログオーバーレイ有効
- `OVERLAY_SMALL_LOG_SETTINGS`: 小ログオーバーレイ設定(位置、回転、表示時間等)
- `OVERLAY_LARGE_LOG`: 大ログオーバーレイ有効
- `OVERLAY_LARGE_LOG_SETTINGS`: 大ログオーバーレイ設定
- `OVERLAY_SHOW_ONLY_TRANSLATED_MESSAGES`: 翻訳メッセージのみ表示
**その他設定**
- `HOTKEYS`: ホットキー設定辞書(即座保存)
- `PLUGINS_STATUS`: プラグイン状態リスト(即座保存)
- `USE_EXCLUDE_WORDS`: 除外ワード機能使用フラグ
- `AUTO_CLEAR_MESSAGE_BOX`: メッセージボックス自動クリア
- `SEND_ONLY_TRANSLATED_MESSAGES`: 翻訳メッセージのみ送信
- `SEND_MESSAGE_TO_VRC`: VRChatへメッセージ送信
- `SEND_RECEIVED_MESSAGE_TO_VRC`: 受信メッセージをVRChatへ送信
- `LOGGER_FEATURE`: ロガー機能有効
- `VRC_MIC_MUTE_SYNC`: VRChatマイクミュート同期
- `NOTIFICATION_VRC_SFX`: VRChat通知効果音
## 内部実装の詳細
### デバウンス保存の実装
```python
def saveConfig(self, key: str, value: Any, immediate_save: bool = False) -> None:
self._config_data[key] = value
# 既存のタイマーをキャンセル
if isinstance(self._timer, threading.Timer) and self._timer.is_alive():
self._timer.cancel()
if immediate_save:
self.saveConfigToFile()
else:
# 2秒後に保存するタイマーをセット
self._timer = threading.Timer(self._debounce_time, self.saveConfigToFile)
self._timer.daemon = True
self._timer.start()
```
### プロパティのバリデーション例
```python
@SELECTED_TAB_NO.setter
def SELECTED_TAB_NO(self, value):
if isinstance(value, str):
if value in self.SELECTABLE_TAB_NO_LIST:
self._SELECTED_TAB_NO = value
self.saveConfig(inspect.currentframe().f_code.co_name, value)
```
各setterは以下のパターンを実装:
1. 型チェック (`isinstance`)
2. 値の範囲・有効性チェック
3. 内部変数への代入
4. `saveConfig` 呼び出し(永続化対象の場合)
### メッセージフォーマット構造
```python
{
"message": {
"prefix": "", # メッセージ前置文字列
"suffix": "" # メッセージ後置文字列
},
"separator": "\n", # メッセージと翻訳の区切り
"translation": {
"prefix": "", # 翻訳前置文字列
"separator": "\n", # 複数翻訳の区切り
"suffix": "" # 翻訳後置文字列
},
"translation_first": False # 翻訳を先に表示するか
}
```
### オーバーレイ設定構造
```python
{
"x_pos": 0.0, # X座標
"y_pos": 0.0, # Y座標
"z_pos": 0.0, # Z座標
"x_rotation": 0.0, # X軸回転
"y_rotation": 0.0, # Y軸回転
"z_rotation": 0.0, # Z軸回転
"display_duration": 5, # 表示時間(秒)
"fadeout_duration": 2, # フェードアウト時間(秒)
"opacity": 1.0, # 不透明度0.0-1.0
"ui_scaling": 1.0, # UIスケーリング
"tracker": "HMD" # トラッカー ("HMD", "LeftHand", "RightHand")
}
```
## エラーハンドリング
### セーフガードインポート
```python
try:
from device_manager import device_manager
except Exception:
device_manager = None # フォールバック値
```
全ての外部モジュールインポートはtry-exceptでラップされており、インポート失敗時でも `Config` クラスは正常に動作する。
### 初期化エラー
```python
def __new__(cls):
if cls._instance is None:
cls._instance = super(Config, cls).__new__(cls)
try:
cls._instance.init_config()
except Exception:
errorLogging() # エラーをログに記録
try:
cls._instance.load_config()
except Exception:
errorLogging()
return cls._instance
```
初期化とロード処理はそれぞれ独立してエラーハンドリングされる。
### 設定ロード時のエラー
```python
for key, value in self._config_data.items():
try:
setattr(self, key, value)
except Exception:
errorLogging() # 個別設定の読み込み失敗は継続
```
JSONから読み込んだ設定のうち、不正な値があっても他の設定の読み込みは継続される。
## パフォーマンス考慮事項
1. **デバウンス保存**: 頻繁な設定変更時にI/Oを削減
2. **遅延初期化**: オプションモジュールは必要時のみロード
3. **シングルトン**: 設定オブジェクトの複製を防止
4. **デーモンスレッド**: タイマースレッドはメインスレッド終了時に自動終了
## セキュリティ考慮事項
1. **認証キー**: `AUTH_KEYS` に格納される外部APIキーは平文でJSON保存される
2. **パス検証**: IP アドレスは `isValidIpAddress` でバリデーション
3. **型安全性**: 全てのセッターで型チェック実施
## テスト推奨事項
### 単体テスト
```python
def test_config_singleton():
config1 = Config()
config2 = Config()
assert config1 is config2
def test_debounce_save():
config.UI_LANGUAGE = "ja"
time.sleep(1)
config.UI_LANGUAGE = "en"
# 2秒以内の変更は1回のみ保存される
time.sleep(2.5)
# ここで保存完了
```
### バリデーションテスト
```python
def test_invalid_tab_no():
config.SELECTED_TAB_NO = "invalid" # 無視される
assert config.SELECTED_TAB_NO != "invalid"
```
### オプション依存のテスト
```python
def test_missing_device_manager():
# device_manager が None でも動作すること
assert config.SELECTABLE_COMPUTE_DEVICE_LIST is not None
```
## マイグレーション
### 設定ファイルのバージョンアップ
`load_config()` は存在しないキーを無視し、`init_config()` のデフォルト値を使用する。新しいバージョンでキーが追加された場合:
1. 既存キーはJSONから読み込まれる
2. 新規キーは `init_config()` のデフォルト値が使用される
3. 次回保存時に全てのキーがJSON に書き込まれる
## 制限事項
1. **マルチプロセス**: シングルトンはプロセス単位。マルチプロセス環境では各プロセスが独立したインスタンスを持つ
2. **スレッドセーフティ**: プロパティアクセス自体はスレッドセーフではない(保存タイマーのみスレッド対応)
3. **循環参照**: `device_manager``config` 間の循環参照に注意
4. **JSON制限**: JSON にシリアライズ可能な型のみ保存可能
## ライセンス
プロジェクトのルートディレクトリの `LICENSE` ファイルを参照
## 関連ドキュメント
- `controller.md`: Controller クラスの設定使用方法
- `mainloop.md`: メインループでの設定参照
- `仕様書.md`: 全体仕様
- `設計書.md`: システム設計
## 変更履歴
### v3.3.0
- 現行バージョン
- WebSocket サーバー設定追加
- オーバーレイ設定の拡張

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,51 +0,0 @@
# システム図
以下はシステム構成の概要Mermaid シーケンス図とテキスト版の両方です。Mermaid がサポートされているビューアでは下のシーケンス図が描画されます。
```mermaid
sequenceDiagram
participant GUI as GUI (stdin/stdout)
participant Main as mainloop
participant Controller as Controller
participant Model as Model
participant Recorder as Recorder
participant Transcriber as Transcriber
participant Translator as Translator
participant Overlay as Overlay
participant OSC as OSC
participant WS as WebSocket
GUI->>Main: send JSON endpoint
Main->>Controller: dispatch
Controller->>Model: startMicTranscript(callback)
Recorder->>Transcriber: audio data
Transcriber->>Controller: result (text, language)
Controller->>Translator: getInputTranslate(text)
Translator-->>Controller: translations
Controller->>Overlay: updateOverlay(translation)
Controller->>OSC: sendMessage(osc_message)
Controller->>WS: websocketSendMessage(event)
Controller-->>GUI: run(status, endpoint, result)
```
## テキスト版(簡易)
Main process (`mainloop.py`)
- stdin -> JSON コマンド -> Main.receiver -> queue
- Main.handler -> Controller (コマンド実行)
- run(status, endpoint, result) -> stdout (GUI に通知)
Controller
- config (読み書き)
- model (起動/停止/アクション)
Model サブシステム
- device_manager (デバイス列挙/監視)
- transcription (recorder -> transcriber)
- translation (Translator)
- overlay (OverlayImage -> Overlay)
- osc (OSCHandler)
- websocket (WebSocketServer)
データフロー(代表): 録音 -> audio_queue -> AudioTranscriber -> Controller.micMessage -> Translator -> (OSC / Overlay / WebSocket / ログ)

346
src-python/docs/mainloop.md Normal file
View File

@@ -0,0 +1,346 @@
# mainloop.py 設計書
## 概要
`mainloop.py` は VRCT アプリケーションのバックエンドエントリーポイントであり、stdin/stdout を介したフロントエンドTauri/React UIとの通信を担当する。JSON ベースのリクエスト/レスポンスプロトコルを実装し、複数のワーカースレッドによる並列処理と排他制御を提供する。
## 主要コンポーネント
### 1. グローバル変数
#### `run_mapping` (dict)
フロントエンドへの通知用エンドポイントマッピング。Controllerが `run()` コールバックを通じてフロントエンドに状態変化を通知する際に使用。
**主要なエンドポイント:**
- `/run/enable_translation` - 翻訳機能の有効/無効状態
- `/run/transcription_mic_message` - マイク音声認識結果
- `/run/transcription_speaker_message` - スピーカー音声認識結果
- `/run/error_*` - 各種エラー通知
- `/run/initialization_complete` - 初期化完了通知
#### `mapping` (dict)
フロントエンドからのリクエストを処理する関数マッピング。各エンドポイントに対して:
- `status`: ロック状態True: 処理可能, False: ロック中)
- `variable`: 実行する Controller メソッド
**エンドポイント分類:**
- `/get/data/*` - 設定値の取得(初期化時に使用)
- `/set/data/*` - 設定値の更新
- `/set/enable/*` - 機能の有効化
- `/set/disable/*` - 機能の無効化
- `/run/*` - アクション実行(メッセージ送信、ダウンロード等)
#### `init_mapping` (dict)
初期化時に実行される `/get/data/*` エンドポイントのサブセット。アプリケーション起動時に全設定値をフロントエンドに送信するために使用。
### 2. Mainクラス
#### コンストラクタ `__init__(controller_instance, mapping_data, worker_count)`
**パラメータ:**
- `controller_instance`: Controller インスタンス
- `mapping_data`: エンドポイントマッピング辞書
- `worker_count`: ハンドラワーカースレッド数(デフォルト: 3
**初期化処理:**
1. リクエストキュー (`Queue[Tuple[str, Any]]`) の作成
2. 停止イベント (`Event`) の作成
3. エンドポイント別 Lock の生成:
- `/set/enable/xxx``/set/disable/xxx``/lock/set/xxx` に正規化
- 同一機能の有効化/無効化リクエストが競合しないよう排他制御
**正規化ロジックの例:**
```python
"/set/enable/translation" "/lock/set/translation"
"/set/disable/translation" "/lock/set/translation"
# 両方が同じロックを共有 → 排他的に実行される
```
#### `receiver()` メソッド
**責務:** stdin から JSON リクエストを読み取り、キューに投入
**処理フロー:**
1. `sys.stdin.readline()` でブロッキング読み取り
2. JSON パース (`json.loads()`)
3. エンドポイントとデータを抽出
4. データが存在する場合は Base64 デコード (`encodeBase64()`)
5. ログ出力 (`printLog()`)
6. キューに投入 `self.queue.put((endpoint, data))`
**エラー処理:**
- JSON パースエラー: ログ出力して継続
- EOF 到達: 0.1秒待機して再試行
- その他の例外: `errorLogging()` でトレースバック記録
**スレッド:** デーモンスレッド `main_receiver` として起動
#### `handler()` メソッド
**責務:** キューからリクエストを取り出し、適切なロックを取得して処理
**処理フロー:**
1. キューから `(endpoint, data)` を取得0.5秒タイムアウト)
2. エンドポイントを正規化キーに変換
3. 対応する Lock を取得試行(非ブロッキング)
- 取得成功 → 処理実行 → ロック解放
- 取得失敗 → 0.05秒待機して再キュー
4. `_call_handler(endpoint, data)` を呼び出し
5. レスポンスを stdout に出力 (`printResponse()`)
**排他制御の意義:**
- 例: 翻訳機能の有効化中に無効化リクエストが来た場合、無効化は待機
- 異なる機能のリクエストは並列実行可能
**再キューロジック:**
- status == 423 (Locked): 0.1秒待機して再キュー
- これにより、初期化中の設定変更リクエストが適切にリトライされる
**ワーカー数:** `worker_count` 個のスレッド `main_handler_0`, `main_handler_1`, ... として起動
#### `_call_handler(endpoint, data)` メソッド
**責務:** 実際のビジネスロジック実行
**処理フロー:**
1. `mapping` から対応するハンドラを取得
2. エンドポイントが存在しない → status 404
3. ハンドラの `status` が False → status 423 (Locked)
4. ハンドラの `variable` 関数を実行 → `response = handler["variable"](data)`
5. 0.2秒待機(処理安定化のため)
6. status と result を抽出して返却
**エラー処理:**
- 例外発生時: `errorLogging()` でトレースバック記録、status 500 を返却
#### `start()` / `stop(wait)` メソッド
**start():**
- `startReceiver()` - stdin 読み取りスレッド起動
- `startHandler()` - ハンドラワーカースレッド起動
**stop(wait):**
- `_stop_event.set()` - 全スレッドに停止シグナル送信
- 各スレッドを `join(timeout=remaining)` で待機(最大 `wait` 秒)
### 3. 初期化シーケンス
**`if __name__ == "__main__":` ブロック:**
1. `main_instance` 作成
2. `startReceiver()` - stdin リスニング開始
3. `startHandler()` - リクエスト処理開始
4. **Watchdog 設定:**
- `controller.setWatchdogCallback(main_instance.stop)`
- Watchdog がタイムアウトした場合にプロセス全体を停止
5. **Controller 初期化:**
- `controller.init()`
- Model の遅延初期化、デバイス列挙、ネットワーク接続チェック
- `init_mapping` のすべてのエンドポイントを実行して初期設定をフロントエンドに送信
6. **マッピングのアンロック:**
- すべての `mapping[key]["status"]` を True に設定
- これにより初期化中だった機能が利用可能になる
7. `main_instance.start()` - 実質的には何もしない(既に起動済み)
## 並列処理とスレッドセーフティ
### スレッド構成
| スレッド名 | 役割 | 生存期間 |
|-----------|------|---------|
| `main_receiver` | stdin からの JSON 読み取り | プロセス終了まで |
| `main_handler_0` ~ `main_handler_N` | リクエスト処理ワーカー | プロセス終了まで |
### 同期メカニズム
1. **キュー (`Queue`):**
- スレッドセーフな FIFO キュー
- receiver → handler への通信チャネル
2. **エンドポイント別 Lock (`dict[str, Lock]`):**
- 同一リソースへの競合アクセスを防止
- 正規化キーによる enable/disable ペアの統合
3. **停止イベント (`Event`):**
- グレースフルシャットダウン用のシグナル
### デッドロック回避
- **非ブロッキング Lock 取得:** `lock.acquire(blocking=False)`
- **失敗時の再キュー:** ロック取得失敗時は即座に諦めて再キュー
- **タイムアウト付きキュー取得:** `queue.get(timeout=0.5)` で無限待機を回避
## プロトコル仕様
### リクエストフォーマット (stdin)
```json
{
"endpoint": "/set/data/transparency",
"data": "ODU=" // Base64 encoded: "85"
}
```
**フィールド:**
- `endpoint`: 実行するエンドポイント(必須)
- `data`: パラメータオプション、Base64 エンコード)
### レスポンスフォーマット (stdout)
```json
{
"status": 200,
"endpoint": "/set/data/transparency",
"result": 85
}
```
**フィールド:**
- `status`: HTTP ステータスコード相当
- 200: 成功
- 400: バリデーションエラー
- 404: 無効なエンドポイント
- 423: ロック中(リトライされる)
- 500: 内部エラー
- `endpoint`: リクエストされたエンドポイント
- `result`: 処理結果(型はエンドポイントに依存)
### ログフォーマット (stdout)
```json
{
"status": 348, // 専用ステータスコード
"log": "setSelectedTabNo",
"data": "1"
}
```
## エラーハンドリング
### 1. JSON パースエラー
- **発生箇所:** `receiver()``json.loads()`
- **処理:** `errorLogging()` でトレースバック記録、リクエストをスキップ
### 2. ハンドラ実行エラー
- **発生箇所:** `_call_handler()``handler["variable"](data)`
- **処理:**
- `errorLogging()` でトレースバック記録
- status 500 と "Internal error" を返却
- プロセスは継続
### 3. JSON シリアライズエラー
- **発生箇所:** `printResponse()``json.dumps()`
- **処理:**
- エラーログに詳細を記録
- フォールバック JSON を出力status 500
- プロセスは継続
### 4. EOF (stdin 終了)
- **発生箇所:** `receiver()``readline()`
- **処理:** 0.1秒待機して再試行(フロントエンドの再起動待ち)
## パフォーマンス最適化
### 1. 複数ワーカースレッド
- デフォルト3スレッドで並列処理
- CPU バウンドな処理(翻訳、文字起こし)を効率化
### 2. 非ブロッキングロック
- ロック競合時に即座に再キュー
- スレッドのブロッキング時間を最小化
### 3. 処理安定化待機
- 各ハンドラ実行後に 0.2秒待機
- 連続リクエストによる競合状態を回避
## 制限事項
### 1. 初期化中の制限
- `mapping[key]["status"] = False` の間はリクエストが 423 でリトライされる
- 初期化完了まで最大数秒のレイテンシが発生
### 2. stdin の単方向性
- stdin → キュー → ハンドラの一方向フロー
- 複数のフロントエンドからの同時接続は非対応
### 3. シリアル実行の保証
- 同一エンドポイントのリクエストは排他的に実行されるが、
- 異なるエンドポイントは並列実行される可能性がある
- 依存関係のある操作は呼び出し側で順序制御が必要
## デバッグとトラブルシューティング
### ログファイル
| ファイル名 | 内容 |
|-----------|------|
| `process.log` | 全リクエスト/レスポンスの記録 |
| `error.log` | 例外トレースバック |
### デバッグ手法
1. **リクエストトレース:**
- `process.log` で endpoint と data を確認
- Base64 デコードは `base64.b64decode(data).decode('utf-8')` で手動実行
2. **ロック競合の検出:**
- 同一エンドポイントで status 423 が頻発する場合
- `_canonical_lock_key()` の正規化ロジックを確認
3. **パフォーマンス分析:**
- 各リクエストの処理時間は status 前後のタイムスタンプから算出
- worker_count を増やして並列度を調整
## 今後の拡張性
### 1. 双方向通信
- WebSocket への移行でリアルタイム通知を改善
- stdin/stdout は互換性のため維持
### 2. 動的ワーカー数調整
- キューの深さに応じてスレッド数を自動調整
- CPU 負荷に応じた適応的なスケーリング
### 3. 優先度キュー
- 重要なリクエスト(エラー通知等)を優先処理
- `queue.PriorityQueue` への移行
## 関連ファイル
- `controller.py` - ビジネスロジック実装
- `model.py` - 機能ファサード
- `utils.py` - ログとユーティリティ
- `config.py` - 設定管理
## コーディング規約
本ファイルは以下の規約に従う:
- PEP 8 スタイルガイド
- 型ヒント (`typing` モジュール)
- Docstring は Google スタイル
- エラーハンドリングは防御的に実装
## テストシナリオ
### 1. 基本動作テスト
```python
# stdin に JSON を送信
echo '{"endpoint": "/get/data/version", "data": null}' | python mainloop.py
# 期待される出力: {"status": 200, "endpoint": "/get/data/version", "result": "1.0.0"}
```
### 2. 並列リクエストテスト
- 複数の設定変更リクエストを同時送信
- すべてが正常に処理されることを確認
### 3. ロック競合テスト
- 翻訳の有効化と無効化を連続送信
- 両方が排他的に実行されることを確認
### 4. エラー回復テスト
- 不正なJSON、無効なエンドポイント、不正なデータを送信
- プロセスがクラッシュせずエラーレスポンスを返すことを確認
## まとめ
`mainloop.py` は VRCT の中核となる通信レイヤーであり、stdin/stdout を介したフロントエンドとの JSON ベースプロトコルを実装する。複数のワーカースレッドと細粒度のロックにより、高い並列性と排他制御を両立させている。初期化シーケンスとエラーハンドリングは堅牢に設計されており、プロセスの安定稼働を保証する。

1277
src-python/docs/model.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,212 +0,0 @@
# config.py クラス仕様書
目的: アプリケーションの全設定を集中管理するシングルトン `config`(クラス名: `Config`、インスタンス: `config`)。
特徴:
- JSON シリアライズ対象のプロパティには `@json_serializable('KEY_NAME')` デコレータが付いており、`load_config()` / `saveConfig()` によって `config.json` に永続化されます。
- プロパティは「読み取り専用 (Read Only)」と「読み書き (Read/Write)」に分類されます。読み書き可能なプロパティはバリデーション処理とともに setter が用意されています。
- 設定は内部的に `_config_data` に保持され、`saveConfig()` はデバウンス2秒でファイルへ書き込みます。即時書き込みオプションも可能ですsaveConfig(..., immediate_save=True))。
## 生成とライフサイクル
- `Config()` はシングルトン__new__ で単一インスタンスを生成)。
- `init_config()` でデフォルト値を初期化し、その後 `load_config()``config.json` を読み込んで既存値を適用します。
## 主要プロパティ一覧(型・デフォルト・説明)
注: 下は `config.py` の初期化ロジックに基づく抜粋です。`json_serializable` が付与されたキーは `config.json` に書き出されます。
- Read only
- `VERSION` (str) = "3.2.2"
- `PATH_LOCAL` (str) = フォロー実行ファイルのディレクトリか、ソースの __file__ のディレクトリ
- `PATH_CONFIG` (str) = PATH_LOCAL/config.json
- `PATH_LOGS` (str) = PATH_LOCAL/logs
- `GITHUB_URL`, `UPDATER_URL`, `BOOTH_URL`, `DOCUMENTS_URL`, `DEEPL_AUTH_KEY_PAGE_URL` (str)
- `MAX_MIC_THRESHOLD` (int) = 2000
- `MAX_SPEAKER_THRESHOLD` (int) = 4000
- `WATCHDOG_TIMEOUT` (int) = 60
- `WATCHDOG_INTERVAL` (int) = 20
- `SELECTABLE_*` 系: 各種選択肢のリスト/イテレータモデルの重みや言語、UI 言語等)。
- Read/Write主な項目
- `SEND_MESSAGE_FORMAT_PARTS` (dict) = デフォルトで message/translation/translation_first 等を含むフォーマット定義。json_serializable キー: 'SEND_MESSAGE_FORMAT_PARTS'
- `RECEIVED_MESSAGE_FORMAT_PARTS` (dict)
- `ENABLE_TRANSLATION` (bool) = False
- `ENABLE_TRANSCRIPTION_SEND` (bool) = False
- `ENABLE_TRANSCRIPTION_RECEIVE` (bool) = False
- `ENABLE_FOREGROUND` (bool) = False
- `ENABLE_CHECK_ENERGY_SEND` (bool) = False
- `ENABLE_CHECK_ENERGY_RECEIVE` (bool) = False
- `SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT` (dict) = {<weight_type>: False, ...}
- `SELECTABLE_WHISPER_WEIGHT_TYPE_DICT` (dict)
- `SELECTABLE_TRANSLATION_ENGINE_STATUS` (dict)
- `SELECTABLE_TRANSCRIPTION_ENGINE_STATUS` (dict)
- `SELECTED_TAB_NO` (str) = "1" (json_serializable: 'SELECTED_TAB_NO')
- `SELECTED_TRANSLATION_ENGINES` (dict) = tab毎に選択 ('CTranslate2' 等)
- `SELECTED_YOUR_LANGUAGES`, `SELECTED_TARGET_LANGUAGES` (dict) = 翻訳元/先の選択と有効フラグ
- `SELECTED_TRANSCRIPTION_ENGINE` (str) = 'Google'
- `CONVERT_MESSAGE_TO_ROMAJI` / `CONVERT_MESSAGE_TO_HIRAGANA` (bool)
- UI 設定: `TRANSPARENCY` (int), `UI_SCALING` (int), `TEXTBOX_UI_SCALING` (int), `MESSAGE_BOX_RATIO` (int)
- `SEND_MESSAGE_BUTTON_TYPE` (str) = 'show'(候補は SEND_MESSAGE_BUTTON_TYPE_LIST
- `SHOW_RESEND_BUTTON` (bool)
- `FONT_FAMILY` (str) = 'Yu Gothic UI'
- `UI_LANGUAGE` (str) = 'en'(候補は SELECTABLE_UI_LANGUAGE_LIST
- `MAIN_WINDOW_GEOMETRY` (dict) = {x_pos, y_pos, width, height}
- マイク/スピーカー関係: `AUTO_MIC_SELECT`, `SELECTED_MIC_HOST`, `SELECTED_MIC_DEVICE`, `MIC_THRESHOLD`, `MIC_AUTOMATIC_THRESHOLD`, `MIC_RECORD_TIMEOUT`, `MIC_PHRASE_TIMEOUT`, `MIC_MAX_PHRASES`, `MIC_WORD_FILTER`, `HOTKEYS`
- `PLUGINS_STATUS` (list)
- マイク転写確度閾値: `MIC_AVG_LOGPROB`, `MIC_NO_SPEECH_PROB`
- スピーカー関連(同様の項目): `AUTO_SPEAKER_SELECT`, `SELECTED_SPEAKER_DEVICE`, `SPEAKER_THRESHOLD`, ...
- `OSC_IP_ADDRESS` (str) = '127.0.0.1'
- `OSC_PORT` (int) = 9000
- `AUTH_KEYS` (dict) = {'DeepL_API': None}
- `USE_EXCLUDE_WORDS` (bool) = True
- 計算デバイス選択: `SELECTED_TRANSLATION_COMPUTE_DEVICE` / `SELECTED_TRANSCRIPTION_COMPUTE_DEVICE``getComputeDeviceList()` に基づくデバイス辞書)
- 重み/計算タイプ: `CTRANSLATE2_WEIGHT_TYPE`, `WHISPER_WEIGHT_TYPE`, `SELECTED_TRANSLATION_COMPUTE_TYPE`, `SELECTED_TRANSCRIPTION_COMPUTE_TYPE`
- オーバーレイ設定: `OVERLAY_SMALL_LOG`, `OVERLAY_SMALL_LOG_SETTINGS`, `OVERLAY_LARGE_LOG`, `OVERLAY_LARGE_LOG_SETTINGS`, `OVERLAY_SHOW_ONLY_TRANSLATED_MESSAGES`
- VRC/ログ/WebSocket: `SEND_MESSAGE_TO_VRC`, `SEND_RECEIVED_MESSAGE_TO_VRC`, `LOGGER_FEATURE`, `VRC_MIC_MUTE_SYNC`, `NOTIFICATION_VRC_SFX`, `WEBSOCKET_SERVER`, `WEBSOCKET_HOST`, `WEBSOCKET_PORT`
# config.py — 完全上書きドキュメント
目的: アプリケーションの全設定を集中管理するシングルトン `config`(クラス名: `Config`、インスタンス: `config`)。
特徴:
- JSON シリアライズ対象のプロパティには `@json_serializable('KEY_NAME')` デコレータが付いており、`load_config()` / `saveConfig()` によって `config.json` に永続化されます。
- プロパティは「読み取り専用 (Read Only)」と「読み書き (Read/Write)」に分類されます。読み書き可能なプロパティはバリデーション処理とともに setter が用意されています。
- 設定は内部的に `_config_data` に保持され、`saveConfig()` はデバウンス2秒でファイルへ書き込みます。即時書き込みオプションも可能ですsaveConfig(..., immediate_save=True))。
## 生成とライフサイクル
- `Config()` はシングルトン__new__ で単一インスタンスを生成)。
- `init_config()` でデフォルト値を初期化し、その後 `load_config()``config.json` を読み込んで既存値を適用します。
## 主要プロパティ一覧(型・デフォルト・説明)
注: 下は `config.py` の初期化ロジックに基づく抜粋です。`json_serializable` が付与されたキーは `config.json` に書き出されます。
- Read only
- `VERSION` (str) = "3.2.2"
- `PATH_LOCAL` (str) = フォロー実行ファイルのディレクトリか、ソースの __file__ のディレクトリ
- `PATH_CONFIG` (str) = PATH_LOCAL/config.json
- `PATH_LOGS` (str) = PATH_LOCAL/logs
- `GITHUB_URL`, `UPDATER_URL`, `BOOTH_URL`, `DOCUMENTS_URL`, `DEEPL_AUTH_KEY_PAGE_URL` (str)
- `MAX_MIC_THRESHOLD` (int) = 2000
- `MAX_SPEAKER_THRESHOLD` (int) = 4000
- `WATCHDOG_TIMEOUT` (int) = 60
- `WATCHDOG_INTERVAL` (int) = 20
- `SELECTABLE_*` 系: 各種選択肢のリスト/イテレータモデルの重みや言語、UI 言語等)。
- Read/Write主な項目
- `SEND_MESSAGE_FORMAT_PARTS` (dict) = デフォルトで message/translation/translation_first 等を含むフォーマット定義。json_serializable キー: 'SEND_MESSAGE_FORMAT_PARTS'
- `RECEIVED_MESSAGE_FORMAT_PARTS` (dict)
- `ENABLE_TRANSLATION` (bool) = False
- `ENABLE_TRANSCRIPTION_SEND` (bool) = False
- `ENABLE_TRANSCRIPTION_RECEIVE` (bool) = False
- `ENABLE_FOREGROUND` (bool) = False
- `ENABLE_CHECK_ENERGY_SEND` (bool) = False
- `ENABLE_CHECK_ENERGY_RECEIVE` (bool) = False
- `SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT` (dict) = {<weight_type>: False, ...}
- `SELECTABLE_WHISPER_WEIGHT_TYPE_DICT` (dict)
- `SELECTABLE_TRANSLATION_ENGINE_STATUS` (dict)
- `SELECTABLE_TRANSCRIPTION_ENGINE_STATUS` (dict)
- `SELECTED_TAB_NO` (str) = "1" (json_serializable: 'SELECTED_TAB_NO')
- `SELECTED_TRANSLATION_ENGINES` (dict) = tab毎に選択 ('CTranslate2' 等)
- `SELECTED_YOUR_LANGUAGES`, `SELECTED_TARGET_LANGUAGES` (dict) = 翻訳元/先の選択と有効フラグ
- `SELECTED_TRANSCRIPTION_ENGINE` (str) = 'Google'
- `CONVERT_MESSAGE_TO_ROMAJI` / `CONVERT_MESSAGE_TO_HIRAGANA` (bool)
- UI 設定: `TRANSPARENCY` (int), `UI_SCALING` (int), `TEXTBOX_UI_SCALING` (int), `MESSAGE_BOX_RATIO` (int)
- `SEND_MESSAGE_BUTTON_TYPE` (str) = 'show'(候補は SEND_MESSAGE_BUTTON_TYPE_LIST
- `SHOW_RESEND_BUTTON` (bool)
- `FONT_FAMILY` (str) = 'Yu Gothic UI'
- `UI_LANGUAGE` (str) = 'en'(候補は SELECTABLE_UI_LANGUAGE_LIST
- `MAIN_WINDOW_GEOMETRY` (dict) = {x_pos, y_pos, width, height}
- マイク/スピーカー関係: `AUTO_MIC_SELECT`, `SELECTED_MIC_HOST`, `SELECTED_MIC_DEVICE`, `MIC_THRESHOLD`, `MIC_AUTOMATIC_THRESHOLD`, `MIC_RECORD_TIMEOUT`, `MIC_PHRASE_TIMEOUT`, `MIC_MAX_PHRASES`, `MIC_WORD_FILTER`, `HOTKEYS`
- `PLUGINS_STATUS` (list)
- マイク転写確度閾値: `MIC_AVG_LOGPROB`, `MIC_NO_SPEECH_PROB`
- スピーカー関連(同様の項目): `AUTO_SPEAKER_SELECT`, `SELECTED_SPEAKER_DEVICE`, `SPEAKER_THRESHOLD`, ...
- `OSC_IP_ADDRESS` (str) = '127.0.0.1'
- `OSC_PORT` (int) = 9000
- `AUTH_KEYS` (dict) = {'DeepL_API': None}
- `USE_EXCLUDE_WORDS` (bool) = True
- 計算デバイス選択: `SELECTED_TRANSLATION_COMPUTE_DEVICE` / `SELECTED_TRANSCRIPTION_COMPUTE_DEVICE``getComputeDeviceList()` に基づくデバイス辞書)
- 重み/計算タイプ: `CTRANSLATE2_WEIGHT_TYPE`, `WHISPER_WEIGHT_TYPE`, `SELECTED_TRANSLATION_COMPUTE_TYPE`, `SELECTED_TRANSCRIPTION_COMPUTE_TYPE`
- オーバーレイ設定: `OVERLAY_SMALL_LOG`, `OVERLAY_SMALL_LOG_SETTINGS`, `OVERLAY_LARGE_LOG`, `OVERLAY_LARGE_LOG_SETTINGS`, `OVERLAY_SHOW_ONLY_TRANSLATED_MESSAGES`
- VRC/ログ/WebSocket: `SEND_MESSAGE_TO_VRC`, `SEND_RECEIVED_MESSAGE_TO_VRC`, `LOGGER_FEATURE`, `VRC_MIC_MUTE_SYNC`, `NOTIFICATION_VRC_SFX`, `WEBSOCKET_SERVER`, `WEBSOCKET_HOST`, `WEBSOCKET_PORT`
## セッタのバリデーション
- 多くの setter は型チェックと候補値チェック(リストや辞書のキー整合性)を行います。例:
- `SELECTED_MIC_DEVICE``device_manager.getMicDevices()` の一覧に存在する名前であること。
- `SELECTED_TRANSLATION_COMPUTE_TYPE``SELECTED_TRANSLATION_COMPUTE_DEVICE['compute_types']` に含まれる文字列であること。
- UI 関連の集合は `SELECTABLE_UI_LANGUAGE_LIST` などの一覧に従う。
## 永続化の詳細
- `load_config()``config.json` が存在し、かつ中身がある場合に読み込みを試み、ファイル中のキーを `setattr(self, key, value)` して既存の setter を利用して適用します。
- 読み込み後、`json_serializable` 指定された全キーを `_config_data` に書き戻し、ファイルを上書き(常に書く)。
## 使い方の例
以下は `config` を使った典型的なコード例です。
```python
from config import config
# 値の参照
print('App version:', config.VERSION)
print('Current UI language:', config.UI_LANGUAGE)
# 値の更新setter を通す)
config.UI_LANGUAGE = 'ja'
config.SEND_MESSAGE_TO_VRC = False
# 複雑な dict を設定する例(メッセージフォーマットを上書き)
config.SEND_MESSAGE_FORMAT_PARTS = {
'message': {'prefix': '[YOU] ', 'suffix': ''},
'separator': '\n',
'translation': {'prefix': '[TR] ', 'separator': '\n', 'suffix': ''},
'translation_first': True,
}
# 即時保存したい場合(即座に config.json を上書き)
config.saveConfig('CUSTOM_SAVE', {'foo': 'bar'}, immediate_save=True)
```
## エッジケース / 注意点
- `load_config()` はファイル値を setter 経由で当てはめるため、ファイルに古いキーや予期しない型があると setter によって無視されることがあります(例: 言語キーが不正の場合)。
- `saveConfig()` はデバウンスされるため、高頻度の設定変更では複数の変更がまとめて書き込まれます。即時書き込みが必要な操作(重要な鍵の更新など)は `immediate_save=True` を使ってください。
- `SELECTABLE_*` 系や `*_DICT` 系は初期化時に外部モジュール翻訳リソース、whisper_models、device_manager 等)から生成されます。これらが利用できない環境ではデフォルトが空になる可能性があります。
### 2025-10-13 の変更device_manager / config に関する挙動改善)
- `DeviceManager` のシングルトン生成時に軽量 `init()` を実行するようになりました。これにより、モジュールのインポート順序に依存して `config``SELECTED_*``NoDevice` のままになる問題が軽減されます(監視スレッドは自動起動しません)。
- `config.init_config()` はこれまで `device_manager._initialized` をチェックしていた箇所を見直し、`device_manager.getDefaultMicDevice()` / `getDefaultSpeakerDevice()` といったアクセサを利用して値を取得するように変更しました。アクセサは必要なら遅延初期化を行うため、`controller``config` のトップレベルインポート順に依存しません。
- 影響: 起動時に PyAudio 等の依存が利用可能であれば、起動中に実機デバイス名が `config` に反映される確率が高くなります。依存がない場合は従来どおり `NoDevice` にフォールバックします。
推奨運用:
- `controller.init()` でコールバック登録後、`mainloop` の起動シーケンスで `device_manager.startMonitoring()` を明示的に呼ぶと、起動後もデバイス変更がコールバック経由で確実に届きます(この呼び出しは任意です)。
## 推奨改善点(将来的なドキュメント/実装)
- 設定スキーマを JSON Schema で定義し、load 時の検証を明確化すると安全性が向上します。
- 設定変更イベントを発火する仕組みobserver パターンを導入すると、Controller/Model 側の再初期化処理をより明確に実装できます。
---
このファイルは `config.py` の実装に基づいて自動生成的に作成されたドキュメントoverwriteです。実装の微細な差分は `config.py` を参照してください。
## 詳細設計
目的: アプリケーションの全設定を保持するシングルトン `config`
ポイント:
- JSON シリアライズ可能な設定値には `@json_serializable` デコレータが付与され、save 操作でファイルへ書き出される。
- 多数のプロパティが定義され、読み取り専用 (Read Only) と 読み書き (Read/Write) が混在する。
- 設定項目の例:
- ENABLE_TRANSLATION, ENABLE_TRANSCRIPTION_SEND, ENABLE_TRANSCRIPTION_RECEIVE
- SELECTED_MIC_HOST, SELECTED_MIC_DEVICE, SELECTED_SPEAKER_DEVICE
- SELECTED_TRANSLATION_ENGINES, SELECTED_YOUR_LANGUAGES, SELECTED_TARGET_LANGUAGES
- PATH_LOCAL, PATH_LOGS, VERSION, GITHUB_URL, UPDATER_URL
- SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT / SELECTABLE_WHISPER_WEIGHT_TYPE_DICT
- COMPUTE 関連: SELECTABLE_COMPUTE_DEVICE_LIST, SELECTED_TRANSLATION_COMPUTE_DEVICE, SELECTED_TRANSCRIPTION_COMPUTE_DEVICE
設計上の契約:
- 全ての get/set は辞書形で status/result を返す Controller の呼び出しに合わせて変換される。
- 外部から設定を変更した際は必要に応じて Model/Controller による再初期化処理を呼ぶ。
検討事項:
- 現状は設定変更が即時反映されるが、一部操作は再初期化(モデルロード、デバイス再取得)を要求するため Controller 側で連携している。

View File

@@ -1,39 +0,0 @@
# config.py 変更参照ドキュメント
このファイルは `config.py` に対して行った最近のリファクタリング / 安全化についての参照資料です。
目的: import 時の副作用を抑止し、`device_manager` などの外部モジュールがない環境でも安全に `config` をインポートできるようにすること。
主な変更点
- import-time の初期化保護
- `Config.__new__` の中で `init_config()` / `load_config()` を呼び出しますが、これらを try/except で保護し、初期化に失敗しても例外を上位に伝播させずログ記録のみで処理を継続します。
- このため、アプリ起動環境に必須ではない外部依存が欠けている場合でも、`import config` によるクラッシュを防止します。
- 外部モジュールの呼び出しをガード
- `device_manager`、翻訳/文字起こし関連のモデル一覧 (`whisper_models`, `ctranslate2_weights`) などは import 時に直接呼び出さず、存在チェックtry/exceptを行って安全なデフォルト空リストや "NoDevice" など)にフォールバックします。
- これによりヘビーな依存Windows 固有パッケージや大きな ML ライブラリ)がない CI 環境や軽量実行環境での import が安定します。
- エラーロギング
- 初期化やデフォルト取得に失敗した場合は、例外を握りつぶすのではなく `utils.errorLogging()` を経由してエラーメッセージを残します。これにより問題の診断が容易になります。
- 設定デフォルト値の扱い
- `getDefaultMicDevice()` / `getDefaultSpeakerDevice()` などを呼ぶ箇所は try/except で保護され、失敗時には `"NoHost"` / `"NoDevice"` 等の安全な文字列で代替されます。
利用上の注意
- 既存のコードは `config` をインポートしただけで `device_manager` を起動することを想定している箇所があるかもしれません。今回のリファクタリングでは "import 時に副作用を起こさない" ことを優先しているため、もし明示的な初期化を必要とする場合は、呼び出し側で `device_manager.init()` を明示的に行ってください。
- もし `config` のロードで致命的な設定エラーが発生した場合でも、アプリは継続動作しますが、ログを確認して手動で修復することが必要になる場合があります。
ドキュメントの提案差分
- 既存 `docs/modules/config.md` の "生成とライフサイクル" セクションに次の一文を追加することを推奨します:
> 注意: `Config()` のインポートは副作用を起こさないよう保護されています。プラットフォーム依存のコンポーネント(例: `device_manager`)は明示的に初期化してください。
- `SELECTABLE_*` 系の説明に、起動環境に依存して空になる可能性があることを明示するパラグラフを追加してくださいCI 環境や headless 環境では空になる)。
---
作業済み: このファイルはワークスペースに `docs/modules/config_ref.md` として作成済みです。既存 `docs/modules/config.md` は上書きしていません。上書き/マージの希望があれば続けます。

View File

@@ -1,162 +0,0 @@
## Controller クラス仕様書
概要
- `Controller` はアプリケーションのコントロール層Facadeで、`model``device_manager`、および外部 UI / mainloop とを仲介します。
- UI からのコマンドを受け取り、`model` の開始/停止、設定の変更、ダウンロードの開始、各種フラグの切り替え、進捗通知(`run` コールバック経由)を行います。
- 多くのメソッドは JSON 系の応答オブジェクトを返します: {"status": int, "result": Any}。副作用で `self.run(status, run_mapping[key], payload)` を呼び出して UI に通知します。
### mainloop のマルチワーカー化とカノニカルロックについて (2025-10-13)
- `mainloop.Main` はデフォルトで複数(デフォルト 3のハンドラワーカースレッドを動かすようになりました。これにより、モデルロードなどの重い操作で他のリクエストが待たされることが少なくなります。
- `/set/enable/<feature>``/set/disable/<feature>` のように同一機能の ON/OFF を切り替えるエンドポイントは、内部的にカノニカルロックキー(例: `/lock/set/<feature>`)に正規化してロック取得されます。これにより、遅い disable の処理が後から来て最終状態を書き換えてしまうレースが防がれます。
初期化とランタイムフック
- __init__() -> None
- フィールド: `init_mapping: dict`, `run_mapping: dict`, `run: Callable`, `device_access_status: bool`
- `setInitMapping(init_mapping: dict)` / `setRunMapping(run_mapping: dict)` / `setRun(run: Callable)` で mainloop からマッピング・コールバックを注入されることを想定。
コールバック通知用メソッドUI への通知)
- connectedNetwork() / disconnectedNetwork() -> None
- enableAiModels() / disableAiModels() -> None
- updateMicHostList() / updateMicDeviceList() / updateSpeakerDeviceList() -> None
- updateConfigSettings() -> None
- これらは `self.run(status, run_mapping[key], payload)` を使って UI にイベントを送ります。
ダウンロード用ヘルパークラス
- class DownloadCTranslate2(run_mapping: dict, weight_type: str, run: Callable)
- progressBar(progress: float) -> None
- downloaded() -> None
- class DownloadWhisper(run_mapping: dict, weight_type: str, run: Callable)
- progressBar(progress: float) -> None
- downloaded() -> None
音声・翻訳イベントハンドラ
- micMessage(result: dict) -> None
- 引数: result: {"text": str|False, "language": str}
- 挙動: ワードフィルタ、繰り返し検出、翻訳(`model.getInputTranslate`、音声送信OSC・オーバーレイ更新・WebSocket ブロードキャスト等を行う。
- エラー: 翻訳中に VRAM OOM が起きた場合は model.detectVRAMError を使って検出し、翻訳機能を無効化して UI に 400 を通知。
- speakerMessage(result: dict) -> None
- 引数: result: {"text": str|False, "language": str}
- micMessage と同様だが、受信speaker側のロジックやオーバーレイの扱いが異なる。
- chatMessage(data: dict) -> dict
- 引数: {"id": Any, "message": str}
- 戻り値: {"status": int, "result": {"id":..., "original":..., "translations":[...]}}
- 挙動詳細:
- 翻訳処理は `model.getInputTranslate` を呼び出します。翻訳処理中に VRAM 関連の例外が発生した場合、`model.detectVRAMError` によって検出し、翻訳機能を自動で無効化します。
- VRAM エラー検出時は Controller は UI に対して 400 系の run イベントを発行する(例: `error_translation_chat_vram_overflow`, `enable_translation` で無効化通知)。
- エラー発生時の戻り値: 翻訳を行わずに基本情報を含む 200 応答を返すコードパスがあり、クライアント側でのハンドリングを想定しています。
設定取得/変更系メソッド(代表例)
- getVersion() -> {"status":200, "result": config.VERSION}
- getComputeMode() / getComputeDeviceList() / getSelectedTranslationComputeDevice() -> dict
- setSelectedTranslationComputeDevice(device: str) -> {"status":200, "result": device}
- getSelectableCtranslate2WeightTypeDict() -> dict
- setEnableTranslation() / setDisableTranslation() -> dict
- setEnableTranslation はモデルロード時に VRAM エラーを検知するロジックを内包している。
- 多くの setXXX / getXXX メソッドは config を直接操作して即時反映する。
自動デバイス選択
- applyAutoMicSelect() / applyAutoSpeakerSelect()
- `device_manager` にコールバックを登録して自動選択を有効化する。
トランスクリプション制御(スレッドで実行)
- startTranscriptionSendMessage() / stopTranscriptionSendMessage() / startThreadingTranscriptionSendMessage() / stopThreadingTranscriptionSendMessage()
- startTranscriptionReceiveMessage() / stopTranscriptionReceiveMessage() / startThreadingTranscriptionReceiveMessage() / stopThreadingTranscriptionReceiveMessage()
- 実際の処理は `model.startMicTranscript` / `model.startSpeakerTranscript` に委譲される。VRAM エラーは検出して UI に通知し、自動的に停止する処理あり。
閾値・チェック系
- startCheckMicEnergy() / stopCheckMicEnergy() / startThreadingCheckMicEnergy() / stopThreadingCheckMicEnergy()
- startCheckSpeakerEnergy() / stopCheckSpeakerEnergy() / startThreadingCheckSpeakerEnergy() / stopThreadingCheckSpeakerEnergy()
ダウンロード開始(非同期/同期)
- downloadCtranslate2Weight(data: str, asynchronous: bool=True) -> dict
- downloadWhisperWeight(data: str, asynchronous: bool=True) -> dict
- 非同期なら別スレッドでダウンロードを行い progressBar コールバックを経由して UI に進捗を返す。
Watchdog / WebSocket / OSC 周り
- startWatchdog() / feedWatchdog() / stopWatchdog()
- getWebSocketHost() / setWebSocketHost(data) -> dict
- setEnableWebSocketServer() / setDisableWebSocketServer()
- setOscIpAddress(data) / setOscPort(data)
- ネットワーク周りの設定は検証ロジックIP アドレス検証、サーバー利用可否のチェック)を含む。
ユーティリティ関数
- messageFormatter(format_type: str, translation: list, message: str) -> str
- OSC に送る文面のフォーマットを生成(設定に基づく)。
- replaceExclamationsWithRandom(text) -> (str, dict)
- restoreText(escaped_text, escape_dict) -> str
- removeExclamations(text) -> str
重要な戻り値規約
- 成功: {"status": 200, "result": ...}
- 失敗: {"status": 400, "result": {"message": str, "data": Any}}
- 多くのメソッドは UI への通知として `self.run(status, run_mapping[key], payload)` を行う。
エッジケース / エラー処理
- VRAM OOM 検出: モデル例外が上がると model.detectVRAMError(e) を呼び出し、VRAM エラーが検出された場合は関連機能を自動で無効化して UI に 400 を通知する。
- デバイスアクセスの競合: `device_access_status` による簡易ロックで、デバイス操作中は待機する。
- ネットワーク依存: DeepL 等の外部翻訳 API 利用可否は `model.authenticationTranslatorDeepLAuthKey` で検査し、無効時は選択肢を更新する。
呼び出し例Python から直接)
```python
from controller import Controller
ctrl = Controller()
# run コールバックの例: (status:int, event_name:str, payload:any)
def ui_run(status, event, payload):
print(status, event, payload)
ctrl.setRun(ui_run)
resp = ctrl.setEnableTranslation()
print(resp) # {'status':200, 'result': True}
data = {"id": 123, "message": "Hello"}
resp = ctrl.chatMessage(data)
print(resp)
```
シーケンス図(簡易: マイク入力 -> 翻訳 -> UI 通知)
```mermaid
sequenceDiagram
participant UI
participant Mainloop
participant Controller
participant Model
UI->>Mainloop: ユーザ操作 (send message)
Mainloop->>Controller: chatMessage(data)
Controller->>Model: getInputTranslate(message)
Model-->>Controller: translation
Controller->>Model: oscSendMessage(...)
Controller->>UI: run(200, run_mapping['transcription_send_mic_message'], payload)
```
次の作業
- `docs/api.md``mainloop.py` のマッピングに基づいて拡張し、各エンドポイントの request/response 例を追加してください。
参考: 実装詳細は `src-python/controller.py` を参照してください(メソッドごとに細かな条件分岐や run_mapping キーが存在します)。
# controller.py — 詳細設計
目的: UIまたは外部プロセスからの操作を受け、`config``model` を操作して副作用を生じさせるコマンド層。
主要クラス/関数:
- class Controller
- 属性:
- init_mapping: アプリ起動時の読み出し用マッピング(/get/data/*
- run_mapping: イベント通知先のエンドポイントマップrun 関数で使用)
- run: run(status, endpoint, result) を格納
- 主要メソッド:
- setEnableTranslation / setDisableTranslation: 翻訳機能の切替(モデル切替や VRAM エラー回復処理を含む)
- start/stop transcription/energy checks: Model の startMicTranscript 等を呼ぶ
- downloadCtranslate2Weight / downloadWhisperWeight: ダウンロードを非同期で開始し進捗を run 経由で通知
- micMessage / speakerMessage / chatMessage: 認識結果を受け、翻訳/OSC/Overlay/WebSocket/ログ記録を行う主要ハンドラ
- messageFormatter: OSC 用メッセージ整形
- 多数の get/set 系関数: config の各種設定を読み書きし status/result を返す
エラー/例外:
- VRAM 関連は特に注意し、検出時は該当機能を無効化してユーザーへ通知する。
API マッピング:
- `mainloop.py``mapping` と連携しており、多くの `/get/data/*` `/set/data/*` `/run/*` が Controller のメソッドにマッピングされる(詳細は docs/api.md を参照)。

View File

@@ -1,25 +0,0 @@
## Controller リファクタリングノート (2025-10-09)
概要:
このドキュメントは `controller.py` に適用した互換性修正と実装上の注意点をまとめた参照用メモです。既存の `controller.md` を直接上書きするのではなく、参照版として保存しています。
実施内容(要約):
- Model の lazy-init 対応に合わせ、`Controller.__init__()` 内で明示的に `model.init()` を呼び出す互換レイヤを追加しました。これにより、既存コードが import 時に model の属性へアクセスしていても安全に動作します。
- オーバーレイの存在チェックを安全に行うため、`_is_overlay_available()` ヘルパを導入しました。以前に直接参照していた `model.overlay.initialized` をこのヘルパで置換しています(合計 5 箇所を置換)。
- `micMessage` 内の翻訳周りで発生していたインデントの回帰を修正しましたtry/except ブロックの整合性を回復)。
- 未使用の `import copy` を削除しました。
- ドキュメント編集は非破壊を原則とし、既存ファイルの安全な上書きが困難な場合は参照版(このファイル)を作成する方針を採りました。
互換性と注意点:
- Controller は起動時に model を初期化するため、多くの通常の利用ケースで変更の影響はありません。
- ただし、外部のモジュールやテストコードが import 時に model の内部属性(例: `model.overlay``model.translator`)へ直接アクセスしている場合は、明示的に `model.init()` を呼ぶか、Controller を経由して初期化することを推奨します。
検証:
- 軽量なローカル検証を行い、`from controller import Controller; Controller()` の実行で初期化が成功することを確認しました。
今後の作業候補:
- 既存の `docs/modules/controller.md` とこの参照ドキュメントのマージ(必要であれば差分を反映して上書きを行う)。
- linter/mypy を通して型安全性の追加と残存する静的解析の問題を解消する。
- テスト: Controller の初期化・主要ハンドラmicMessage/chatMessageを対象にしたユニットテストを追加して、model.lazy-init による破壊的変更が再発しないことを保証する。
このファイルは自動生成ではなく、安全に変更履歴を残すための参照メモです。上書きを希望する場合はご指示ください。

View File

@@ -1,84 +0,0 @@
# device_manager.py — デバイス検出と監視overwrite
目的: システムのマイク/スピーカー(主に Windows の WASAPIを列挙し、変更を監視してコールバックで通知する `DeviceManager` シングルトンを提供します。
主要コンポーネント:
- class Client(MMNotificationClient)
- オーディオデバイスのシステムイベント(追加/削除/デフォルト変更)を受け取り、監視ループの再起動をトリガーします。
- class DeviceManager
- シングルトンインスタンス: `device_manager`
- 主要プロパティ:
- `mic_devices` (dict): {host_name: [device_info, ...]}
- `default_mic_device` (dict): {'host': {...}, 'device': {...}}
- `speaker_devices` (list): [device_info, ...]
- `default_speaker_device` (dict)
- 各種 prev_/update_flag_: 差分検出用
- callback 関連プロパティ: `callback_default_mic_device`, `callback_mic_device_list`, など多数
- 主要メソッド (抜粋):
- `update()` -> None: PyAudio を利用してホスト毎の入力デバイスとループバック(スピーカー)を列挙し内部状態を更新します。
- `checkUpdate()` -> bool: 前回値との差分を計算して変更フラグを返します。
- `monitoring()` -> None: pycaw/MMNotificationClient を使った長時間監視ループ。変化を検出すると各コールバックを呼び出す。
- `startMonitoring()` / `stopMonitoring()`
- `getMicDevices()` / `getDefaultMicDevice()` / `getSpeakerDevices()` / `getDefaultSpeakerDevice()`
- `forceUpdateAndSetMicDevices()` / `forceUpdateAndSetSpeakerDevices()`
コールバックAPI:
- `setCallbackMicDeviceList(callback)` — マイクデバイスリスト変更時に呼ばれる
- `setCallbackDefaultMicDevice(callback)` — デフォルトマイク変更時に呼ばれる
- `setCallbackProcessBeforeUpdateMicDevices(callback)` / `setCallbackProcessAfterUpdateMicDevices(callback)` — 更新前後のフック
例:
```python
from device_manager import device_manager
def on_default_mic(host_name, device_name):
print('Default mic changed:', host_name, device_name)
device_manager.setCallbackDefaultMicDevice(on_default_mic)
device_manager.forceUpdateAndSetMicDevices()
```
注意点:
- Windows 固有のモジュールPyAudio paWASAPI, pycawに依存します。クロスプラットフォーム対応が必要な場合は別実装が必要です。
- 監視スレッドは永続的に動作するため、アプリケーション終了時は `stopMonitoring()` を呼んで安全に停止してください。
変更点2025-10-13:
- `DeviceManager` のシングルトン生成時(`__new__`)に軽量な `init()` を実行するようになりました。これによりモジュールのインポート順に依存せず、最小限の内部構造が常に確立されます(※監視スレッドは自動で起動しません)。
- `init()` は監視スレッドを開始しませんが、PyAudio が利用可能な場合に限りベストエフォートで一度だけ `update()` を呼び、起動時に可能な限り実機デバイス情報を埋めるようになりました(例外は握り潰して安全性を維持)。
- アクセサ (`getDefaultMicDevice()` / `getDefaultSpeakerDevice()` など) は遅延初期化を行い、呼び出し時に `init()` が動いていない場合は安全に初期化されるようになりました。これにより `controller``config` がトップレベルインポートで互いに依存している状況でも、`config` に正しいデバイス情報が入るようになります。
推奨起動シーケンス:
- `controller.init()` でコールバック登録が完了した直後に、`mainloop` の起動シーケンス中で明示的に `device_manager.startMonitoring()` を呼ぶことを推奨します。これにより以降のデバイス変更がコールバックを通じて確実に届きます。なお、`startMonitoring()` は任意で、軽量にしたい場合は呼ばなくても構いません(ただし動的変化は検出されません)。
ドキュメントにおける重要な注意:
- この変更は "import-time に重大な副作用を持たせない" という方針を維持しつつ、インポート順の違いによる初期化漏れを解消するために行われています。`init()` は監視スレッドを開始しないため、インポートだけでスレッドが走ることはありません。
## 詳細設計
目的: ローカルの入力マイクと出力ループバックから抽出されたスピーカーデバイスを列挙し、変更を監視してコールバックで通知する。Windows の WASAPI 等に依存。
主要クラス/関数:
- class Client(MMNotificationClient)
- Audio デバイスの変更イベントを受けると `loop = False` にして監視ループを再起動させる設計。
- class DeviceManager
- シングルトン: `device_manager = DeviceManager()`
- 主要属性:
- mic_devices: {host: [device_info...]}
- default_mic_device: {host, device}
- speaker_devices: [device_info...]
- default_speaker_device: {device}
- 各種 prev_*, update_flag_*: 差分検出のために保持
- コールバック属性: callback_default_mic_device, callback_host_list など
- 主要メソッド:
- update(): PyAudio を使ってホストごとにデバイス列挙。Loopback デバイスを speaker_devices に集める。
- monitoring(): MMNotificationClient と組み合わせてデバイスの変化を検出し、コールバックを発行
- set/clear Callback 系: UI や Controller が登録して自動選択や再起動をトリガーできる
- forceUpdateAndSetMicDevices / forceUpdateAndSetSpeakerDevices: 即時更新とコールバック通知
注意点:
- Windows 固有の処理paWASAPI, pycawに依存する。
- デバイス取得はリソースに依存するので try/except で例外を吸収し errorLogging() を呼ぶ。

View File

@@ -1,93 +0,0 @@
# device_manager.py — デバイス検出と監視 (改訂版)
### 概要
`device_manager.py` はローカルのマイク(入力)とスピーカー(ループバックから抽出)を列挙し、デフォルトデバイスの変更やデバイスリストの変化を監視してコールバックで通知するユーティリティです。
設計上のポイント:
- Windows 固有の依存 (`comtypes`, `pyaudiowpatch` (PyAudio + WASAPI), `pycaw`) はオプショナルです。モジュールを import してもこれらが無ければ例外にならず、プレースホルダ値を返すようになっています。
- モジュールの import 時点では監視は開始されません。リソースやスレッドの副作用を避けるため、`init()``startMonitoring()` は呼び出し側で明示的に実行してください。
---
### 使い方(簡単な流れ)
1. モジュールをインポート
```py
from device_manager import device_manager
```
2. 初期化(内部状態のセットアップ)
```py
device_manager.init()
```
3. 監視の開始(バックグラウンドスレッド)
```py
device_manager.startMonitoring()
```
4. 停止(アプリ終了時など)
```py
device_manager.stopMonitoring()
```
---
### 主な API
- `device_manager.init()`
- internal state の初期化。import 後に必ず呼ぶ必要はないが、実機デバイスを取得する前に呼ぶことを推奨します。
- `device_manager.startMonitoring()` / `device_manager.stopMonitoring()`
- 監視の開始 / 停止。`startMonitoring()` はデーモンスレッドを作成します。`stopMonitoring()` は best-effort で join を試みます。
- `device_manager.getMicDevices()`
- ホストごとにグループ化された入力デバイスの辞書を返します。例: `{ 'Realtek': [ {index: 2, name: 'Microphone (Realtek)'} ] }`
- `device_manager.getDefaultMicDevice()` / `device_manager.getSpeakerDevices()` / `device_manager.getDefaultSpeakerDevice()`
- デフォルトデバイスやスピーカーループバックの情報を返します。
- `device_manager.forceUpdateAndSetMicDevices()` / `device_manager.forceUpdateAndSetSpeakerDevices()`
- 即時に update() を実行して対応するコールバックを呼びます。
---
### コールバック登録(例)
コールバックは例外を内部で捕捉してログを出すため、コールバック実装側でもエラーハンドリングしてください。
- `setCallbackDefaultMicDevice(callback)` — デフォルト入力が変わったときに `callback(host_name, device_name)` が呼ばれます。
- `setCallbackDefaultSpeakerDevice(callback)` — デフォルト出力が変わったときに `callback(device_name)` が呼ばれます。
- `setCallbackHostList(callback)` / `setCallbackMicDeviceList(callback)` / `setCallbackSpeakerDeviceList(callback)` — それぞれ list 変更時に `callback()` が呼ばれます。
- `setCallbackProcessBeforeUpdateMicDevices(callback)` / `setCallbackProcessAfterUpdateMicDevices(callback)` — 更新の前後に呼ばれるフックです。
簡単な例:
```py
from device_manager import device_manager
def on_default_mic(host, device):
print('default mic changed', host, device)
device_manager.init()
device_manager.setCallbackDefaultMicDevice(on_default_mic)
device_manager.startMonitoring()
# 後で停止
# device_manager.stopMonitoring()
```
---
### 注意点 / トラブルシュート
- Windows 固有の依存が無い場合、`getMicDevices()` などはデフォルトのプレースホルダ(`NoHost` / `NoDevice`)を返します。実機のデバイス検出や WASAPI によるループバック検出は Windows 環境でのみ保証されます。
- `startMonitoring()` は監視用のデーモンスレッドを作るため、アプリケーションの終了時には `stopMonitoring()` を呼ぶかプロセスを終了してください。`stopMonitoring()` は join を行いますが、失敗した場合でも致命的にならないよう best-effort 実装です。
- コールバック内部で例外が発生してもモジュール側で捕捉してログ出力します(`utils.errorLogging()`)。コールバック側で詳細なハンドリングやリトライが必要な場合は呼び出し側で行ってください。
---
### 実装メモ
- `monitoring()` は可能なら Windows の COM (pycaw / MMNotificationClient) を使ってイベント駆動で待ち受け、失敗時や非Windows 環境では PyAudio を使ったポーリング(定期的な update()) にフォールバックします。
- 外部ライブラリが原因の例外は内部で捕捉し、`errorLogging()` を呼んで記録する設計です。

View File

@@ -1,50 +0,0 @@
## mainloop モジュールsrc-python/mainloop.py
このドキュメントは `mainloop.py` の実装と、最近行ったリファクタの概要をまとめます。`mainloop` は標準入力から JSON を受け取り、`controller` のメソッドにルーティングして標準出力へ JSON で応答を返す小さなメインループです。
重要な変更点:
- 2025-10-09: `Main` クラスに `start()` / `stop()` を追加し、受信スレッドとハンドラスレッドのライフサイクル管理を明示化しました。`queue.get(timeout=...)` による安全なシャットダウンを可能にしています。
- 2025-10-13: ハンドラの振る舞いを改善しました(マルチワーカー化とロック正規化):
- マルチワーカー化: ハンドラ処理はデフォルトで複数ワーカー(例: 3 本で並列実行されます。これにより、1 つの重い処理が他のすべてのリクエストをブロックしてしまう問題を緩和します。
- ロック正規化: `/set/enable/<feature>``/set/disable/<feature>` のような on/off ペアは同一のロックキーに正規化され、同一機能の on と off が同時に別スレッドで実行されることを防ぎます。これにより、遅い方の処理結果が後から上書きして最終状態が意図しないものになる不具合を防止します。
クラス: Main
- __init__(controller_instance: Controller, mapping_data: dict, worker_count: int = 3) -> None
- `controller_instance`: `Controller` のインスタンス。
- `mapping_data`: `mainloop` 内で使用する `mapping`(エンドポイント -> ハンドラ情報)辞書。
- `worker_count`: ハンドラワーカー数(デフォルト 3。実行環境に応じて調整可能です。
- start() -> None
- 内部で `startReceiver()``startHandler()` を呼び、受信とハンドラのスレッド群を起動します。
- stop(wait: float = 2.0) -> None
- シャットダウンシグナルをセットし、スレッド終了を待ちます(デフォルト 2 秒)。
動作の重要ポイント
- キュー運用: 受信した JSON は内部キューに入れられ、ハンドラワーカーが順次取り出して処理します。`queue.get(timeout=...)` を使っているため CPU 負荷を抑えつつ安全に停止できます。
- 同期応答設計: 各エンドポイントは基本的に呼び出し元に同期的に結果を返します(`handler` が戻り値としてステータスと結果を返す)。今回の変更でもこの設計は維持されています。
- 同一機能直列化: `/set/enable/X``/set/disable/X` のような on/off ペアは内部で同一の "ロックキー" に正規化され、同時に両方が実行されることを防ぎます。これにより、enable と disable が競合して遅い方が勝つ問題が解消されます。
使い方(例):
```python
from mainloop import Main, mapping, controller
main_instance = Main(controller_instance=controller, mapping_data=mapping)
main_instance.start()
# 実行中に別スレッドや外部シグナルで停止させる
main_instance.stop()
```
確認手順(変更の検証):
1. バックエンドを起動しておく。
2. UIテストスクリプトから `/set/enable/translation``/set/disable/translation` を高速に交互送信する(数十〜数百ミリ秒間隔で連打)。
3. ログ(`printLog` 出力)を確認し、同一機能の複数実行が同時に走っていないこと、最終状態が遅い方に常に上書きされないことを確認する。
4. 必要に応じて `worker_count` を増減して挙動を確認するPC リソースに応じて 1〜6 程度を推奨)。
注意点と推奨事項:
- `worker_count` を増やすと他のエンドポイントの並列処理性は上がりますが、controller/model 側で共有リソースGPU メモリやデバイスハンドルなどへの同時アクセスが許可されていない場合は、controller 側で機能単位のロック(例: translation_lockを追加してください。
- このドキュメントの変更は `mainloop` の外側から見える挙動同期応答、ログ、ロックの方針を説明するものです。controller 内の処理自体は引き続き同期的に実行されます。必要があれば、enable 系の重い処理を非同期化して完了通知をイベントで返す設計UI 変更が必要)も検討してください。
変更履歴:
- 2025-10-09: start/stop ライフサイクル、タイムアウト付きキュー取得、エラー処理強化を追加。
- 2025-10-13: マルチワーカー化(デフォルト 3と enable/disable のロック正規化を実装。これにより同一機能の on/off の同時実行を防止し、UI からの高速トグルで最終状態が遅い方に上書きされる問題を修正しました。

View File

@@ -1,118 +0,0 @@
# model.py — クラスと主要メソッド
目的: アプリケーションの中核オーケストレータ。翻訳器 (Translator)、オーバーレイ、トランスクリプタ、OSC、WebSocket、Watchdog などのインスタンスを保持し、これらの起動/停止/操作を担います。`model``Model` のシングルトンインスタンスです。
主要クラスとシグネチャ:
- class threadFnc(Thread)
- __init__(self, fnc, end_fnc=None, daemon=True, *args, **kwargs)
- stop(self) -> None
- pause(self) -> None
- resume(self) -> None
- class Model
- __new__(cls) -> Model
- init(self) -> None
- checkTranslatorCTranslate2ModelWeight(self, weight_type: str) -> bool
- changeTranslatorCTranslate2Model(self) -> None
- downloadCTranslate2ModelWeight(self, weight_type, callback=None, end_callback=None) -> Any
- isLoadedCTranslate2Model(self) -> bool
- getListLanguageAndCountry(self) -> list
- getTranslate(self, translator_name, source_language, target_language, target_country, message) -> tuple
- getInputTranslate(self, message, source_language=None) -> (list, list)
- getOutputTranslate(self, message, source_language=None) -> (list, list)
- startMicTranscript(self, fnc) -> None
- stopMicTranscript(self) -> None
- startSpeakerTranscript(self, fnc: Optional[Callable[[dict], None]] = None) -> None
- stopSpeakerTranscript(self) -> None
- startWebSocketServer(self, host, port) -> None
- stopWebSocketServer(self) -> None
- websocketSendMessage(self, message_dict: dict) -> bool
変更点2025-10-09:
- startCheckMicEnergy(self, fnc: Optional[Callable[[float], None]] = None) -> None
- 説明: 進捗/エネルギー表示用のコールバックを受け取ります。fnc が None の場合は内部で no-op を使い、呼び出し前に callable チェックを行います。これにより呼び出し側が None を渡しても安全になりました。
- startCheckSpeakerEnergy(self, fnc: Optional[Callable[[float], None]] = None) -> None
- 説明: 同上fnc を Optional として受け取り、呼び出し時に callable を確認します)。内部では Queue を作成して録音データを受け取り、定期的にコールバックを呼びます。
- convertMessageToTransliteration(self, message: str, hiragana: bool = True, romaji: bool = True) -> list
- 説明: 以前は単一の文字列や別形を返す箇所がありましたが、現在は常にリスト(トークン単位の dict を要素とする listを返します。hiragana/romaji の両方が False の場合は空リストを返します。
- createOverlayImageLargeLog(self, message_type: str, message: Optional[str], your_language: Optional[str], translation: list, target_language: Optional[dict] = None) -> object
- 説明: `target_language` は辞書形式で渡される場合があり、内部で言語リストに正規化されますenabled な言語のみ抽出)。`message` / `your_language` は Optional となり、`None` を渡して翻訳のみのログを作ることが可能です。
使用例(簡易):
```python
from model import model
# 翻訳を呼び出す
translation, success = model.getTranslate('CTranslate2', 'Japanese', 'English', 'United States', 'こんにちは')
print(translation, success)
# マイク文字起こしの開始(コールバックで結果を受け取る)
def on_mic_transcript(result):
print('mic transcript:', result)
model.startMicTranscript(on_mic_transcript)
# WebSocket サーバー起動
model.startWebSocketServer('127.0.0.1', 2231)
```
注意点:
- `Model` は多くの外部リソースGPU、ファイル、ネットワークに依存するため、各操作は例外処理で保護されています。
- 大きなモデルのロードで VRAM OOM を検出する `detectVRAMError` を備え、Controller 側でのフォールバック処理に使われます。
## 詳細設計
### 2025-10-09 のリファクタリング要約
- 遅延初期化 (lazy-init): `Model` のコンストラクタで重い初期化を行わず、`model.init()` を明示的に呼ぶか、各メソッド先頭で呼ばれる `ensure_initialized()` によって必要時に初期化する設計に変更しました。これによりインポート時の副作用(外部環境依存の初期化)が抑止されます。
- `threadFnc` の堅牢化: スレッドユーティリティは args/kwargs をインスタンスで保持し、内部で発生する例外を捕捉して `utils.errorLogging()` に委ねるようになりました。これによりバックグラウンドスレッドが例外で終了するリスクを減らしています。
- `device_manager` 呼び出しのガード: `getListMicHost()` / `getListMicDevice()` / `getMicDefaultDevice()` / `getListSpeakerDevice()` など、`device_manager` を参照する箇所は try/except で保護され、失敗時は安全なデフォルト(空リストや `"NoDevice"`)を返すようになりました。
- WebSocket/Overlay/Watchdog 等の起動系メソッドは `ensure_initialized()` を先頭に呼ぶようになり、遅延初期化の恩恵を受けるようになっています。
これらの変更は非破壊で既存の API を維持することを目的としていますが、起動フローで確実にリソースを確保したい場合はアプリ起動時に `model.init()` を呼ぶことを推奨します。
目的: 各モデル(翻訳/転写/Overlay/Watchdog/OSC/WebSocket 等)のインスタンスを保持し、高レベルの操作を提供するファサード。
主要クラス/変数:
- class threadFnc(Thread)
- 説明: ループする関数をバックグラウンドで呼ぶヘルパ。pause/stop/end callback をサポート。
- class Model
- シングルトン: ファイル末で `model = Model()` として公開。
- 主な属性:
- translator (Translator)
- overlay (Overlay)
- overlay_image (OverlayImage)
- mic_audio_queue, mic_audio_recorder, mic_transcriber
- speaker_audio_queue, speaker_audio_recorder, speaker_transcriber
- watchdog (Watchdog)
- osc_handler (OSCHandler)
- websocket_server (WebSocketServer)
- 主なメソッド:
- start/stop logger, overlay, watchdog
- startMicTranscript / stopMicTranscript: 録音、transcriber の起動とキュー処理
- startSpeakerTranscript / stopSpeakerTranscript
- startCheckMicEnergy / stopCheckMicEnergy
- startCheckSpeakerEnergy / stopCheckSpeakerEnergy
- getTranslate / getInputTranslate / getOutputTranslate: Translator を利用する高レベル関数
- createOverlayImage* / updateOverlay* : OverlayImage と Overlay を結合して VR 表示を作成
- startWebSocketServer / stopWebSocketServer / websocketSendMessage
エラー処理:
- 音声認識や翻訳で VRAM エラーが発生した場合、detectVRAMError() で特殊な例外内容を検査し、Controller 経由で翻訳機能を OFF にする処理がある。
非同期/リソース:
- Recorder/Transcriber/Overlay/Watchdog/WebSocket はそれぞれ別スレッドで動作する。Model はそれらの開始/停止を管理する。
依存:
- models/translation, models/transcription, models/overlay, models/osc, models/websocket

View File

@@ -1,60 +0,0 @@
# model.py — クラス一覧と使用例
以下は `model.py` で提供される主要クラスのシグネチャ概要と、簡単な呼び出し例です。
## クラス / 主要シグネチャ
- class threadFnc(Thread)
- __init__(self, fnc: Callable, interval: float = 0.1, end_callback: Callable | None = None)
- start(self) -> None
- pause(self) -> None
- resume(self) -> None
- stop(self) -> None
- class Model
- startLogger(self) -> None
- stopLogger(self) -> None
- startOverlay(self) -> None
- shutdownOverlay(self) -> None
- startMicTranscript(self, callback: Callable[[dict], None]) -> None
- stopMicTranscript(self) -> None
- startSpeakerTranscript(self, callback: Callable[[dict], None]) -> None
- stopSpeakerTranscript(self) -> None
- startCheckMicEnergy(self, progress_callback: Callable[[int], None]) -> None
- stopCheckMicEnergy(self) -> None
- startCheckSpeakerEnergy(self, progress_callback: Callable[[int], None]) -> None
- stopCheckSpeakerEnergy(self) -> None
- startWebSocketServer(self, host: str, port: int) -> None
- stopWebSocketServer(self) -> None
- websocketSendMessage(self, message: dict) -> None
- getListMicHost(self) -> dict
- getListMicDevice(self) -> list
- getListSpeakerDevice(self) -> list
- getInputTranslate(self, text: str, source_language: str = None) -> tuple[list[str], list[bool]]
- getOutputTranslate(self, text: str, source_language: str = None) -> tuple[list[str], list[bool]]
- detectVRAMError(self, exception: Exception) -> tuple[bool, str]
## サンプル(呼び出し例)
以下は Model の簡単な呼び出し例です。
```python
from model import model
# マイク転写のコールバック例
def on_mic_result(result: dict):
# result の想定形: {"text": str|False, "language": str}
text = result.get("text")
language = result.get("language")
print('mic:', text, language)
# マイク転写を開始(別スレッドで動く)
model.startMicTranscript(on_mic_result)
# 一度だけ翻訳を呼ぶ
translation, success = model.getInputTranslate('Hello', source_language='English')
print('translation:', translation, 'success:', success)
# WebSocket 経由で外部クライアントへイベント送信
model.websocketSendMessage({'type': 'INFO', 'message': 'VRCT ready'})
```

View File

@@ -1,47 +0,0 @@
## OSC モジュール (models.osc)
このドキュメントは `models/osc/osc.py` の使い方と注意点を簡潔にまとめたものです。
### 概要
- `OSCHandler` クラスは OSC メッセージの送信 (/chatbox/input, /chatbox/typing 等) と、
ローカル環境では OSCQuery でエンドポイントを公開するための薄いラッパーを提供します。
### 依存関係
- `python-osc` — UDP クライアント/サーバ
- `tinyoscquery` — OSCQuery を利用する場合に必要(オプショナル)
### 使い方(例)
```python
from models.osc.osc import OSCHandler
handler = OSCHandler(ip_address="127.0.0.1", port=9000)
handler.setDictFilterAndTarget({
"/chatbox/input": lambda addr, *args: print(args),
})
handler.receiveOscParameters()
handler.sendTyping(True)
handler.sendMessage("Hello")
handler.oscServerStop()
```
### 注意点
- `tinyoscquery` がインストールされていない場合、OSCQuery 関連機能は無効になりますが、送信UDP クライアント)は動作します。
- サービスのアドバタイズ中に例外が発生した場合、内部でリトライします。
# models/osc — 詳細設計
目的: VRChat 等と OSC / OSCQuery 経由で値の取得やチャット送信を行う。
主要クラス/関数:
- class OSCHandler
- sendMessage(message: str, notification: bool=True): OSC で chatbox/input を送信
- sendTyping(flag: bool): chatbox/typing を送信
- receiveOscParameters(): OSCQuery を立て、指定したフィルタに対してローカルでサーバを実装してイベントを受ける
- getOSCParameterValue(address: str): OSCQuery を通じて現在値を問い合わせるuse tinyoscquery
注意点:
- `is_osc_query_enabled` が True のときに OSCQuery を使う127.0.0.1 や localhost の場合に True
- 受信ハンドラは dispatcher にマップしてコールバックを呼ぶ。
- ネットワーク環境や OSCQuery の可否により動作が変わるため例外処理が多く入っている。
依存: python-osc, tinyoscquery

View File

@@ -1,59 +0,0 @@
# overlay.py — OpenVR オーバーレイ管理
目的: OpenVR を使ったオーバーレイ表示(複数サイズ: small/largeを管理する `Overlay` クラスを提供します。
主要メソッド:
- __init__(self, settings_dict: dict)
- init(self) -> None
- startOverlay(self) -> None
- shutdownOverlay(self) -> None
- reStartOverlay(self) -> None
- updateImage(self, img: PIL.Image.Image, size: str) -> None
- updateOpacity(self, opacity: float, size: str, with_fade: bool = False) -> None
- updateUiScaling(self, ui_scaling: float, size: str) -> None
- updatePosition(self, x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation, tracker, size) -> None
- mainloop(self) -> None # アニメーション / フェード評価ループ
使用上の注意:
- OpenVR (SteamVR) が稼働していることが前提です。`checkSteamvrRunning()``vrmonitor.exe` の存在チェックを行います。
- 例外が発生した場合は `errorLogging()` を呼んでスタックトレースを残します。
短い使用例:
```py
from models.overlay.overlay_image import OverlayImage
from models.overlay.overlay import Overlay
from PIL import Image
settings = {
"small": {
"x_pos": 0.0, "y_pos": 0.0, "z_pos": 0.0,
"x_rotation": 0.0, "y_rotation": 0.0, "z_rotation": 0.0,
"display_duration": 5, "fadeout_duration": 2,
"opacity": 1.0, "ui_scaling": 1.0, "tracker": "HMD"
}
}
overlay_img = OverlayImage()
overlay = Overlay(settings)
overlay.startOverlay()
# wait until initialized
while not overlay.initialized:
time.sleep(0.5)
# push a simple blank image
overlay.updateImage(Image.new("RGBA", (256, 64), (255,255,255,255)), "small")
```
## モジュール構成(補足)
- overlay.py — OpenVR を使ったオーバーレイ管理。Overlay クラスは複数サイズsmall/largeを扱い、位置/回転/透明度/フェードを制御する。
- overlay_image.py — PIL を使ってオーバーレイに表示する画像を生成(テキストボックス、ログレイアウト、フォント管理)。
- overlay_utils.py — 行列演算や座標変換ユーティリティ。
注意点:
- OpenVRSteamVRに依存。SteamVR が動作していることが前提。
- フォントファイルは repo の fonts フォルダか、ランタイム内パスを探索して読み込む。
- 生成画像は RGBA バイト列に変換され `overlay.setOverlayRaw` で渡される。

View File

@@ -1,115 +0,0 @@
# overlay_image.py — 画像生成ユーティリティ
目的: `models.overlay.overlay_image.OverlayImage` の実装に基づき、オーバーレイ用のテキストボックス/ログ画像を PIL (Pillow) で生成するための仕様書です。
このドキュメントは実装に合わせて書かれており、主要な公開メソッドの振る舞い、引数、返り値、例外、使用例、注意点を含みます。
概要
------
- 提供クラス: `OverlayImage`
- 役割: 文字列(元文/翻訳)やメッセージタイプ(send/receive) を受け取り、Small/Large 向けの RGBA PIL.Image を生成する。
- 依存: Pillow (PIL)、フォントファイル群(`fonts/` ディレクトリまたは環境配下)
主要機能
--------
- テキストをラップして画像化する(行折り返しを含む)
- 複数テキストブロック(原文+複数の翻訳)を縦に連結して一つの画像にする
- 背景(角丸矩形)を合成して最終的な RGBA 画像を返す
- Small と Large で UI 設定(幅、高さ、フォントサイズ等)を切り替え
- フォント探索: 実行環境の `fonts/` 配下または相対パスからフォントを探し、見つからない場合は FileNotFoundError を投げる
公開 API要約
-----------------
- class OverlayImage(root_path: str | None = None)
- コンストラクタ引数
- root_path: フォント等のリソースのベースディレクトリ。None の場合は実装に合わせて repo の `fonts/` を候補パスとして探索する。
- OverlayImage.createOverlayImageSmallLog(message: str, your_language: str, translation: list | None = None, target_language: list | None = None) -> PIL.Image.Image
- 説明: Small ログ向け横長・1行〜複数行にテキストブロックを作成して結合し、角丸背景と合成して RGBA 画像を返す。
- 引数
- message: 表示する原文テキストNone を許容しない想定)
- your_language: 原文の言語キー(フォントマッピングに使用)
- translation: 翻訳テキストのリスト(省略可)
- target_language: 翻訳それぞれに対応する言語キーのリスト(省略可)
- 戻り値: PIL.Image.Image (RGBA)
- 例外: フォントが見つからない場合は FileNotFoundError を投げる可能性あり
- OverlayImage.createOverlayImageLargeLog(message_type: str, message: str | None = None, your_language: str | None = None, translation: list | None = None, target_language: list | None = None) -> PIL.Image.Image
- 説明: Large ログ(複数行 + ヘッダSend/Receiveや時刻向けに、複数ブロックを作成して縦結合し、背景を合成して返す。
- 引数
- message_type: 'send' または 'receive'UI 向けアンカー/色指定に使用)
- message: 表示する原文テキストNone 可。この場合翻訳のみを表示することもある)
- your_language: 原文の言語キー(フォント選定に使用)
- translation: 翻訳テキストのリスト(省略可)
- target_language: 翻訳それぞれに対応する言語キーのリスト(省略可)
- 戻り値: PIL.Image.Image (RGBA)
- 例外: フォント未発見などで FileNotFoundError を投げる可能性あり
内部で使われる補助メソッド(要旨)
---------------------------------
- concatenateImagesVertically(img1, img2, margin=0) -> Image
- addImageMargin(image, top, right, bottom, left, color) -> Image
- createTextboxSmallLog(...) -> Image
- createTextImageLargeLog(...) -> Image
- createTextboxLargeLog(...) -> Image
- getUiSizeSmallLog(), getUiColorSmallLog(), getUiSizeLargeLog(), getUiColorLargeLog()
フォントとローカライズ
-----------------------
- 実装は `LANGUAGES` マッピングを持ち、言語キーからフォントファイル名を決定します(例: "Japanese" -> "NotoSansJP-Regular.ttf")。
- フォントは `root_path` を基準に探索します。実行環境によりフォントファイルの場所が異なるため、実装は複数パスを順に試します。フォントが見つからない場合は FileNotFoundError を発生させる設計です。
描画と折り返しロジック(実装に基づく注意点)
--------------------------------------------
- テキスト幅を計算し、基準幅に収まるように文字数ベースで分割して折り返す単純なロジックを採用しています。厳密な単語単位折り返しではなく、文字数ベースの分割になります。
- Small/Large でフォントサイズや余白、角丸半径などを分けており、複数行のテキストブロックを縦結合することで最終画像を作ります。
使用例
------
Small ログ画像を作る例:
```python
from models.overlay.overlay_image import OverlayImage
overlay = OverlayImage()
img = overlay.createOverlayImageSmallLog(
message='こんにちは、世界!',
your_language='Japanese',
translation=['Hello, world!'],
target_language=['English']
)
img.save('overlay_small.png')
```
Large ログ(複数メッセージ履歴)を作る例:
```python
from models.overlay.overlay_image import OverlayImage
from datetime import datetime
overlay = OverlayImage()
img = overlay.createOverlayImageLargeLog(
message_type='send',
message='Hello from VRCT',
your_language='English',
translation=['こんにちは'],
target_language=['Japanese']
)
img.save('overlay_large.png')
```
実装上の注意と推奨事項
-----------------------
- 実行環境にフォントが存在することを確認してください(`fonts/` に主要フォントを置くのが簡単です)。
- Pillow (PIL) のバージョンに依存する描画 API を使っています。Pillow は v8〜最新程度で問題ありません。
- 長いテキストの折り返しは単純な文字幅分割ロジックです。より自然な折り返し(単語単位・ルビ考慮等)が必要なら実装拡張を推奨します。
- 生成画像は RGBA透過です。Overlay 側の API`overlay.setOverlayRaw` 相当)へ渡して使う前提です。
復元メモ
--------
このファイルは実装ファイル `models/overlay/overlay_image.py` を参照して復元しました。実装を変更した場合は本ドキュメントも同期して更新してください。
関連ファイル
-------------
- 実装: `models/overlay/overlay_image.py`
- ヘルパ: `models/overlay/overlay_utils.py`
- フォント: `fonts/` ディレクトリ

View File

@@ -1,126 +0,0 @@
## 文字起こしモジュール (models.transcription)
このドキュメントでは `models/transcription` に関する設計・セットアップ・使用例・テスト方針・トラブルシュートをまとめます。
### 概要
- `models/transcription` は音声入力をテキストに変換する機能を提供します。主に:
- `transcription_recorder.py` — マイクやスピーカからの音声取得ラッパー
- `transcription_transcriber.py` — 音声バッファを認識エンジンに渡して文字起こしを行うロジック
- `transcription_whisper.py` — faster-whisperWhisperModel周りのダウンロード/ロード補助
- `transcription_languages.py` — 各言語・国別のエンジン別コードマップ
### 最近の変更点
- 各モジュールに型注釈と docstring を追加しました。これによりメンテナンス性が向上します。
- `transcription_whisper.py` にダウンロード進捗コールバックを明記した実装を追加しました。
### 依存関係
主要な依存:
- `speech_recognition` — オーディオ録音と Google 音声認識のラッパー
- `pyaudiowpatch` — クロスプラットフォームのオーディオ設定
- `pydub` — 音声のチャンネル変換や処理
- `faster_whisper`(オプショナル)— ローカルで Whisper を使う場合
- `huggingface_hub`(オプショナル)— モデルアーティファクトのダウンロード
注意: `pydub``ffmpeg` が必要です。環境に ffmpeg が無いとワーニングが出ます。
推奨インストール(任意):
```powershell
pip install speechrecognition pyaudiowpatch pydub faster-whisper huggingface-hub
```
テストでは多くの外部依存をモックするため、全てをインストールする必要はありません。
### 初回セットアップ
1. 必要に応じて `ffmpeg` をインストールしてくださいpydub の動作に必要)。
2. Whisper ローカルモデルを使う場合、`transcription_whisper.downloadWhisperWeight(root, weight_type, callback, end_callback)` を呼んでモデルを取得します。
- `callback(progress: float)` は 0.0〜1.0 の進捗通知です。
- 例:
```python
from models.transcription import transcription_whisper as tw
tw.downloadWhisperWeight("./", "tiny", callback=lambda p: print(f"{p*100:.1f}%"), end_callback=lambda: print("done"))
```
### API 使用例
簡単な `AudioTranscriber` の使い方:
```python
from models.transcription.transcription_transcriber import AudioTranscriber
# source はライブラリが提供するオーディオソースオブジェクト
tr = AudioTranscriber(speaker=False, source=source, phrase_timeout=3, max_phrases=10, transcription_engine="Google")
# audio_queue は録音スレッドがプッシュするキュー
tr.transcribeAudioQueue(audio_queue, languages=["English"], countries=["United States"])
```
戻り値やエラー処理のルールについては各関数の docstring を参照してください。
### テスト方針
- `AudioTranscriber``Whisper` ラッパーはユニットテストでモック化して検証します。
- 推奨: `pytest``unittest.mock` を使い、以下のケースをカバーします:
- 正常系: Google/Whisper の成功パス(モックで期待テキストを返す)
- エッジ: 無音、低確信、複数言語
- フォールバック: Whisper が利用不可の場合のフォールバック動作
### トラブルシュート
- ffmpeg が見つからない: `pydub` がワーニングを出します。OS に合わせて ffmpeg をインストールしてください。
- Whisper のロード時に VRAM エラー: `getWhisperModel` は VRAM 不足を検出して `ValueError("VRAM_OUT_OF_MEMORY", message)` を投げます。デバイス設定や compute_type を調整してください。
- ハッシュ不一致やダウンロード失敗: キャッシュや weights ディレクトリを削除して再ダウンロードしてください。
### 変更履歴
- 2025-10-09: 型注釈と docstring を追加、ダウンロード/コールバック仕様を明記。
---
このドキュメントは簡潔な参照用です。さらに詳細な実行手順ログ収集方法、ffmpeg のインストール手順例など)が必要であれば追記します。
# transcription — 文字起こしモジュール
概要: マイク/スピーカー音声の録音と Whisper/Google などのエンジンを使った文字起こしを提供するモジュール群です。主なクラスは録音用の Recorder と `AudioTranscriber` です。
主要クラス/シグネチャ:
- SelectedMicEnergyAndAudioRecorder(device, energy_threshold, dynamic_energy_threshold, phrase_time_limit)
- SelectedSpeakerEnergyAndAudioRecorder(...)
- SelectedMicEnergyRecorder(device)
- SelectedSpeakerEnergyRecorder(device)
- AudioTranscriber(speaker: bool, source, phrase_timeout: int, max_phrases: int, transcription_engine: str, root: str, whisper_weight_type: str, device: str, device_index: int, compute_type: str)
- transcribeAudioQueue(queue, languages:list, countries:list, avg_logprob: float, no_speech_prob: float) -> bool
- getTranscript() -> dict
使用例:
```python
from models.transcription.transcription_recorder import SelectedMicEnergyAndAudioRecorder
from models.transcription.transcription_transcriber import AudioTranscriber
# 録音
rec = SelectedMicEnergyAndAudioRecorder(device, energy_threshold=300, dynamic_energy_threshold=False, phrase_time_limit=3)
queue = Queue()
rec.recordIntoQueue(queue, None)
# 文字起こし
transcriber = AudioTranscriber(speaker=False, source=rec.source, phrase_timeout=3, max_phrases=10, transcription_engine='Google', root='.', whisper_weight_type='base', device='cpu', device_index=0, compute_type='auto')
transcriber.transcribeAudioQueue(queue, ['Japanese'], ['Japan'], -0.8, 0.6)
print(transcriber.getTranscript())
```
注意点:
- Whisper のモデルロードは VRAM を消費します。`Model.detectVRAMError` のような検知と回復策が必要です。
- 録音は OS のデバイス依存のため `device_manager` でのデバイス取得と組み合わせて利用してください。
# models/transcription — 詳細設計
構成ファイル:
- transcription_recorder.py — 各デバイス向け Recorder クラス群Base, SelectedMic*, SelectedSpeaker*。speech_recognition をラップし、Audio / Energy をキューへ出す。
- transcription_transcriber.py — AudioTranscriber: Google Speech API または faster-whisper を使った音声→テキスト変換の実行ロジック。複数言語に対する最良候補選択と confidence に基づく選出。
- transcription_whisper.py — Whisperfaster-whisper重みのダウンロードとモデル生成のユーティリティ。
主要契約:
- Recorder は recordIntoQueue(audio_queue, energy_queue) を提供し、バックグラウンドで音声データをキューに流す。
- AudioTranscriber.transcribeAudioQueue(audio_queue, languages, countries, avg_logprob, no_speech_prob) -> bool
- audio_queue から音声を取り出し認識を試みる。結果は getTranscript() で取得する。常に True/False を返して呼び出し側がループ継続を制御。
VRAM エラー対策:
- Whisper のモデルロードで GPU メモリ不足が発生すると、ValueError("VRAM_OUT_OF_MEMORY", message) を投げる実装。Controller で捕捉して機能停止/通知する。
外部依存:
- speech_recognition, faster_whisper, pydub, numpy, torch

View File

@@ -1,113 +0,0 @@
## 翻訳モジュール (models.translation)
このドキュメントは `models/translation` 配下に対して行った最近の変更点、セットアップ手順、API の使い方、テスト方針、トラブルシュートをまとめたものです。
### 概要
- モジュールの責務: テキストの翻訳を行う高レベルの `Translator` クラス、言語コードのマッピング、CTranslate2 用の重み・トークナイザのダウンロード/検証ユーティリティを提供します。
- 変更点の狙い: 型注釈と docstring を追加し、`translation_utils.py` のダウンロード/検証ロジックをシンプルで堅牢な実装へ置換しました。これにより初回セットアップの手順が明確になります。
### 主な変更点(サマリ)
- `translation_translator.py`: 型注釈、docstring を追記。外部依存は存在するが、例外が発生してもモジュールが壊れないように保護されています。
- `translation_languages.py`: 言語コードマッピングの説明を追加。
- `translation_utils.py`: 重みファイルの検証SHA-256 ハッシュ照合、zip 展開、`transformers.AutoTokenizer` を使ったトークナイザ取得、ダウンロード進捗用のコールバックを備えた実装へ置換。
### インストール(依存関係)
必須ではないものが含まれます。開発・最小稼働に必要なパッケージはプロジェクト全体の要件に従ってください。
主に使うパッケージ:
- `requests` — ダウンロード処理
- `transformers` — トークナイザ取得AutoTokenizer
- `ctranslate2` — CTranslate2 を使う場合(ランタイムのみ、テストではモック推奨)
推奨インストール例(任意):
```powershell
pip install requests transformers ctranslate2
```
DeepL や `translators` といった外部 API ラッパーはオプショナルです。CI やローカルテストではモックして動作確認してください。
### 初回セットアップ / 重みの準備
`translation_utils.py` に含まれるユーティリティ関数:
- `checkCTranslate2Weight(root: str, weight_type: str = "small") -> bool`
- 指定した `root/weights/ctranslate2/<model_dir>` 以下に必要なファイルが存在し、既知のハッシュと一致するかをチェックします。
- `downloadCTranslate2Weight(root: str, weight_type: str = "small", callback: Optional[Callable[[float], None]] = None, end_callback: Optional[Callable[[], None]] = None) -> None`
- 重みを ZIP 形式でダウンロードして展開します。
- `callback(progress: float)` は 0.0〜1.0 の進捗通知に使えます。
- `end_callback()` は処理完了時に呼び出されます。
- `downloadCTranslate2Tokenizer(path: str, weight_type: str = "small") -> None`
- `transformers.AutoTokenizer.from_pretrained` を利用してトークナイザをダウンロード/キャッシュします(`cache_dir` に保存)。
呼び出し例(簡単):
```python
from models.translation import translation_utils as tu
# ルートディレクトリ(プロジェクトルートなど)
root = "."
if not tu.checkCTranslate2Weight(root, "small"):
tu.downloadCTranslate2Weight(root, "small", callback=lambda p: print(f"{p*100:.1f}%"))
tu.downloadCTranslate2Tokenizer(root, "small")
```
注意: 大きなモデル(`large`)はダウンロードに時間とディスク容量を要します。
### API 使用例 (`Translator` の簡易例)
以下は `Translator` の想定されるシンプルな使い方です(実装は `translation_translator.py` を参照してください)。
```python
from models.translation.translation_translator import Translator
tr = Translator()
result = tr.translate("Hello", src_lang="en", target_lang="ja")
if result:
print(result)
else:
print("翻訳に失敗しました")
```
戻り値とエラー: 既存のコードベースとの互換性を重視し、失敗時は False を返すケースがあります。API 呼び出し前に戻り値の型を確認してください。
### テスト方針
- 外部サービスDeepL、web 翻訳ラッパー、ctranslate2、transformersはユニットテストでモックします。
- 推奨: `pytest``unittest.mock` を使い、`Translator.translate` の成功パス・失敗パスを検証するテストを追加してください。
簡単なテスト設計:
- 正常系: ctranslate2 経由の翻訳が正しく呼ばれる(モックで期待レスポンスを返す)
- フォールバック系: ctranslate2 が利用できない場合に別の翻訳経路を辿る(モック)
### トラブルシュート
- `ModuleNotFoundError` (例: `sudachidict_full`) — transliteration/別モジュールで必要な辞書が無い場合。該当パッケージのインストールか、当該機能を無効にしてください。
- ハッシュ不一致 — ダウンロード済みファイルの破損が疑われます。該当ファイルを削除して再ダウンロードしてください。
- `transformers` のトークナイザが取得できない場合、ネットワークやキャッシュ先の権限を確認してください。
### 変更履歴
- 2025-10-09: 型注釈と docstring の追加、`translation_utils.py` を再実装してダウンロード/検証ロジックを整理。
---
このドキュメントは簡潔な参照用です。必要なら実行例やさらに詳細なトラブルシュート手順(コマンド出力例、ログの取り方など)を追加します。
# models/translation — 詳細設計
構成ファイル:
- translation_translator.py — Translator クラス。DeepL/API、Google、Bing、Papago、CTranslate2 を統一インターフェースで扱う。
- translation_utils.py — 重みファイルのダウンロード・検証ロジックCTranslate2 用)。
- translation_languages.py — 各エンジンの対応言語マップ。
Translator の契約:
- translate(translator_name, source_language, target_language, target_country, message) -> str|False
- 成功時は文字列、失敗または一時的エラーは False を返す。
- changeCTranslate2Model(path, model_type, device, device_index, compute_type)
- CTranslate2 の Translator オブジェクトと Tokenizer を初期化する。
フォールバック:
- Controller/Model 層で翻訳が失敗した場合に CTranslate2 にフォールバックする実装がある。
外部依存:
- ctranslate2, transformers, deeplオプション、translators任意
安全性:
- 翻訳 API キーDeepLは Translator.authenticationDeepLAuthKey で検証して保持。

View File

@@ -1,17 +0,0 @@
# models/transliteration — 詳細設計
目的: 日本語テキストの仮名読みを解析し、ひらがな/ローマ字Hepburnに変換する。
主要クラス/関数:
- class Transliterator
- analyze(text: str, use_macron: bool=False) -> List[dict]
- 入力: テキスト
- 出力: トークンのリスト。各要素は { orig, kana, hira, hepburn }
- split_kanji_okurigana(surface, reading_kana): 漢字+送り仮名を分割して kana を割り当てるロジックを持つ(詳細設計あり)
実装上のポイント:
- SudachiPy を使い形態素解析して読みを得る。
- Katakana を Hiragana に変換し、katakana_to_hepburn モジュールでローマ字化を行う。
- 文脈ルールを `transliteration_context_rules.apply_context_rules` で適用できる設計(ルールエンジン)。
依存: sudachipy

View File

@@ -1,132 +0,0 @@
## utils モジュールsrc-python/utils.py
このドキュメントは `src-python/utils.py` に対する最近のリファクタ内容、公開 API、利用上の注意点、テスト方法をまとめたものです。
### 概要
- `utils.py` はプロジェクト全体で使われる汎用ユーティリティ群を提供します。主な内容:
- ネットワーク接続チェック (`isConnectedNetwork`)
- ソケットの空きポート確認 (`isAvailableWebSocketServer`)
- IP アドレス検証 (`isValidIpAddress`)
- 計算デバイス一覧取得 (`getComputeDeviceList` / `getBestComputeType`)
- Base64 デコード (JSON) (`encodeBase64`)
- ロガー設定/ログ出力ヘルパー (`setupLogger`, `printLog`, `printResponse`, `errorLogging`)
### 今回のリファクタ(要点)
- Optional 依存へのフォールバック: `torch``ctranslate2` が存在しない環境でも動作するよう、import をガードし、安全なデフォルトを返す実装にしました。
- 型注釈と docstring を追加して可読性を向上させました。
- ログ設定の重複ハンドラ追加を防ぐチェックを導入しました。
- `encodeBase64` はデコード失敗時に例外を投げず空辞書を返すように(安全側)変更しました。
- `getComputeDeviceList` は GPU 情報取得で失敗しても CPU 情報を返すように例外保護を行いました。
### 重要な利用上の注意breaking/behavior changes
- Optional 依存
- `torch` が無い環境では GPU 情報は取得できません(`getComputeDeviceList` は CPU エントリのみ返します)。
- `ctranslate2``get_supported_compute_types` が無い場合は空リストを返します。
→ 環境に依存する挙動を想定して、呼び出し側は存在チェックやフォールバックを実装してください。
- `encodeBase64` の挙動
- 不正な base64/JSON を入力した場合、例外を投げず `{}` を返します。既存コードが例外を期待している場合は注意してください。
- `isAvailableWebSocketServer` の仕様
- 指定した host:port に対して bind が成功すれば True を返します(「使用中かどうか」を判定する用途と逆の意味合いになることがあるため注意)。
- ロギング
- `setupLogger` は同じログファイルに対するハンドラを重複して追加しません。`errorLogging()` はログ書き込みに失敗した場合でも最後に trace を stdout に出力するフォールバックがあります。
### API 使い方(短い例)
```python
from utils import getComputeDeviceList, encodeBase64, printResponse
devices = getComputeDeviceList()
print(devices)
obj = encodeBase64('eyAia2V5IjogInZhbHVlIiB9') # -> {'key': 'value'}
printResponse(200, '/health', {'status': 'ok'})
```
### テスト方針
- optional 依存の違いを扱うため、ユニットテストは `torch``ctranslate2` をモックして行うことを推奨します。
- 例: `getComputeDeviceList()` は GPU がない環境でも CPU のエントリを返すことを確認するテスト。
### トラブルシュート
- ログファイルの書き込みエラー: 権限やディスク容量を確認してください。`error.log``process.log` の存在と権限をチェックします。
- `getComputeDeviceList()` が空しか返さない場合、`torch` または `ctranslate2` のインストールを確認してください。
### 変更履歴
- 2025-10-09: 型注釈・docstring 追加、optional import ガード、ロギング堅牢化。
# utils.py — 関数一覧と使用例
目的: 共通ユーティリティログ、JSON 出力、ネットワーク/ポート検査、デバイス/計算タイプ列挙、バリデーション等)を提供します。
主要関数とシグネチャ:
- validateDictStructure(data: dict, structure: dict) -> bool
- isConnectedNetwork(url: str = "http://www.google.com", timeout: int = 3) -> bool
- isAvailableWebSocketServer(host: str, port: int) -> bool
- isValidIpAddress(ip_address: str) -> bool
- getComputeDeviceList() -> dict
- getBestComputeType(device: str, device_index: int) -> str
- encodeBase64(data: str) -> dict
- removeLog() -> None
- setupLogger(name, log_file, level=logging.INFO) -> logging.Logger
- printLog(log: str, data: Any = None) -> None
- printResponse(status: int, endpoint: str, result: Any = None) -> None
- errorLogging() -> None
使用例:
```python
from utils import printResponse, getComputeDeviceList, validateDictStructure
# JSON 形式で mainloop に応答を返す
printResponse(200, '/get/data/version', {'version': '3.2.2'})
# 利用可能な計算デバイス一覧を取得
devices = getComputeDeviceList()
print(devices)
# 辞書構造のバリデーション
data = {'a': 1, 'b': {'c': 'x'}}
structure = {'a': int, 'b': {'c': str}}
ok = validateDictStructure(data, structure)
print('valid:', ok)
```
注意点:
- `printResponse` は stdout に JSON を出力しつつログファイルにも書き込みます。大きなオブジェクトは json.dumps で失敗する可能性があるため、例外処理が含まれています。
# utils.py — 詳細設計
目的: 小さなユーティリティ関数群。ロギング、ネットワーク検査、型検証、計算デバイス列挙など。
主要関数/変数:
- validateDictStructure(data: dict, structure: dict) -> bool
- 説明: 辞書が期待される構造(キーセットと値の型/入れ子)に完全一致するか検証する。
- 入力: data検証対象, structure期待構造: 値が型または入れ子 dict
- 出力: bool
- 例外: 型不一致や欠落時は False を返す(例外は投げない)。
- isConnectedNetwork(url="http://www.google.com", timeout=3) -> bool
- 説明: 指定 URL に HTTP GET して接続可否を判定。requests を使用。
- isAvailableWebSocketServer(host: str, port: int) -> bool
- 説明: 指定ポートへ bind できるかを試し、使用中かを判別するTrue=利用可能)。
- isValidIpAddress(ip_address: str) -> bool
- 説明: ipaddress.ip_address で検証。
- getComputeDeviceList() -> dict
- 説明: CPU と CUDA利用可能ならを列挙し、各デバイスでサポートされる compute types を取得する。
- 依存: torch, ctranslate2.get_supported_compute_types
- getBestComputeType(device: str, device_index: int) -> str
- 説明: デバイス名に基づき優先 compute_type を選び、利用可能なものを返す。デフォルトは "float32"。
- setupLogger(name, log_file, level=logging.INFO) -> Logger
- 説明: RotatingFileHandler を使って UTF-8 ログを作る。10MB ローテーション。
- printLog / printResponse / errorLogging
- 説明: mainloop と通信するために標準出力へ JSON を flush するユーティリティ。内部で file ログへも書く。
注意点:
- ネットワーク検査やファイル生成で例外が発生した場合、errorLogging() を呼んでトレースを error.log に保存する。

View File

@@ -1,80 +0,0 @@
# models/watchdog — 詳細設計
目的: 外部Process 管理側)へ定期的に "生存" を知らせるために使う軽量ウォッチドッグ。
設計:
- class Watchdog(timeout: int = 60, interval: int = 20)
- feed(): 最終フィード時刻を更新
- setCallback(callback): タイムアウト時に呼ぶコールバックを登録zero-arg を想定)
- start(): 単一チェックを行い、`interval` 秒の sleep を行う(継続監視は呼び出し側でループまたはスレッド化)
注意:
- 現行実装は非常にシンプルで、長時間のブロッキングやスレッド運用の見直しが必要になり得る。
変更点(実装に入れた改善):
- コールバック属性を初期化しておくことで AttributeError を防止
- コールバック呼び出し内の例外はウォッチドッグ本体に影響を与えないよう try/except で保護
- メソッドに型注釈と docstring を追加
短い使用例(ポーリング方式):
```py
import time
from models.watchdog.watchdog import Watchdog
def on_timeout():
print('watchdog timed out')
wd = Watchdog(timeout=5, interval=1)
wd.setCallback(on_timeout)
# 別スレッドにせず、単純なループでポーリングする例
while True:
wd.start() # ここで timeout をチェックし、必要なら callback を呼ぶ
# アプリケーションの他処理...
time.sleep(0.5)
# 正常時に feed を呼ぶ例
# wd.feed()
```
使用例(スレッド化ヘルパを用意するアプローチ):
```py
import time
from threading import Thread, Event
from models.watchdog.watchdog import Watchdog
stop_event = Event()
def run_watchdog(wd: Watchdog, stop_event: Event):
# シンプルなバックグラウンド実行ループ(安全な停止用フラグ付き)
while not stop_event.is_set():
wd.start()
wd = Watchdog(timeout=10, interval=1)
wd.setCallback(lambda: print('timed out'))
thread = Thread(target=run_watchdog, args=(wd, stop_event), daemon=True)
thread.start()
# 正常動作時
wd.feed()
time.sleep(2)
# 停止する場合は stop_event.set() を呼ぶ
stop_event.set()
thread.join()
```
拡張案(将来の改善):
- `start_in_thread()` / `stop()` を Watchdog に組み込む(内部で Thread と Event を管理して安全に停止できるようにする)
- コールバックに引数を渡せるようにするcontext 情報、呼び出し回数など)
- asyncio と相互運用できるバージョンasync/await ベース)を用意する
- ロギング統合(標準 logging を使って状態変化を記録)
- 単発one-shot/繰り返しの動作モード指定
簡易テスト済み:
- 基本的なコールバックの有効無効挙動をローカルで確認済みfeed 後は呼ばれず、タイムアウト状態で呼ばれる)。
注意事項:
- フル自動化CI での運用)を行う場合は、スレッド起動・停止のテストを追加することを推奨します。

View File

@@ -1,18 +0,0 @@
# models/websocket — 詳細設計
目的: 外部クライアント(例えば第三者のアプリ)へ翻訳済みテキストやイベントをブロードキャストする軽量 WebSocket サーバー。
API:
- class WebSocketServer(host='127.0.0.1', port=8765)
- start(): 別スレッドで asyncio ループを生成しサーバを起動。
- stop(): サーバ停止、全クライアント切断。
- set_message_handler(handler): クライアントからのメッセージ受信時のコールバックを登録。handler(server, websocket, message)
- send(message): 非同期キューに積んで全クライアントへ送信(スレッドセーフ)。
- broadcast(message): asyncio を経由して即時ブロードキャスト。
実装上の工夫:
- サーバ本体は別スレッドで asyncio イベントループを run_forever している。
- 送信用に内部キュー `_send_queue` を持ち、_send_loop で順次送信する。これにより GUI 等から安全に send() を呼べる。
依存: websocketsasyncio

View File

@@ -1,125 +0,0 @@
# Run events payloads
このファイルは `controller.py` 内で `self.run(status, run_mapping["key"], payload)` として発行される全ての run イベントの鍵と、実際に渡されるペイロードの具体例を列挙します。
---
## 抽出済み run イベント一覧(正規化済み)
以下は controller.py の self.run 呼び出しを解析して抽出した run イベントです。名称は `mainloop.py``run_mapping` に合わせて正規化しています。
- connected_network (200)
- payload: true | false
- enable_ai_models (200)
- payload: true | false
- mic_host_list (200)
- payload: list[str]
- mic_device_list (200)
- payload: list[str]
- speaker_device_list (200)
- payload: list[str]
- initialization_complete (200)
- payload: dict mapping endpoint -> current value (constructed from init_mapping)
- selected_mic_device (200)
- payload: {"host": <host>, "device": <device>}
- selected_speaker_device (200)
- payload: string (device name)
- error_device (400)
- payload: {"message": <message>, "data": null}
- check_mic_volume (200)
- payload: numeric energy value (float)
- check_speaker_volume (200)
- payload: numeric energy value (float)
- download_progress_ctranslate2_weight (200)
- payload: {"weight_type": <str>, "progress": <float>}
- downloaded_ctranslate2_weight (200)
- payload: <weight_type:str>
- error_ctranslate2_weight (400)
- payload: {"message":"CTranslate2 weight download error","data": null}
- download_progress_whisper_weight (200)
- payload: {"weight_type": <str>, "progress": <float>}
- downloaded_whisper_weight (200)
- payload: <weight_type:str>
- error_whisper_weight (400)
- payload: {"message":"Whisper weight download error","data": null}
- word_filter (200)
- payload: {"message": "Detected by word filter: <matched_text>"}
- error_translation_engine (400)
- payload: {"message":"Translation engine limit error","data": null}
- error_translation_mic_vram_overflow (400)
- payload: {"message":"VRAM out of memory during translation of mic","data": <error_message:str>}
- error_translation_speaker_vram_overflow (400)
- payload: {"message":"VRAM out of memory during translation of speaker","data": <error_message:str>}
- error_translation_chat_vram_overflow (400)
- payload: {"message":"VRAM out of memory during translation of chat","data": <error_message:str>}
- enable_translation (400 or 200)
- payload example on OOM: {"message":"Translation disabled due to VRAM overflow","data": false}
- transcription_send_mic_message (200)
- payload: {
"original": {"message": <str>, "transliteration": <list|[]>},
"translations": [ {"message": <str>, "transliteration": <list|[]>}, ... ]
}
- transcription_receive_speaker_message (200)
- payload: same shape as transcription_send_mic_message
- software_update_info (200)
- payload: dict (e.g. {"has_update": true, "latest_version": "3.3.0"})
- selected_translation_compute_type (200)
- payload: string e.g. "auto" | "cpu" | "cuda:0"
- selected_transcription_compute_type (200)
- payload: string
- selected_translation_compute_device (200)
- payload: device descriptor (object) — `config.SELECTED_TRANSLATION_COMPUTE_DEVICE` の現在値。
- selected_translation_engines (200)
- payload: config.SELECTED_TRANSLATION_ENGINES (list/dict per tab)
- translation_engines (200)
- payload: list of selectable engines (e.g. ["CTranslate2"])
- initialization_progress (200)
- payload: integer stage (used values in code: 1..4)
- enable_osc_query (200)
- payload: {"data": true|false, "disabled_functions": [<str>...]}
- enable_transcription_receive (200)
- payload: boolean (true when transcription receive enabled)
- error_transcription_mic_vram_overflow (400)
- payload: {"message":"VRAM out of memory during mic transcription","data": <error_message:str>}
- error_transcription_speaker_vram_overflow (400)
- payload: {"message":"VRAM out of memory during speaker transcription","data": <error_message:str>}
---
注: 上記は controller.py の self.run 呼び出しを解析して作成した "実際に送られる" ペイロード例です。UI 側はこれらの形を期待してコーディングしてください。状況によっては model 層からの戻り値の具象型が変化するため、実装では型チェック/存在チェックを行ってください。

View File

@@ -1,43 +0,0 @@
# 実行手順と依存関係
対象 OS: Windows を想定device_manager は WASAPI / pycaw を使う)。
必須依存(概略):
- Python 3.10+ 推奨
- pip パッケージ:
- torch
- ctranslate2
- transformers
- requests
- pyaudiowpatch
- pycaw
- speech_recognition
- pydub
- websockets
- python-osc
- tinyoscquery
- sudachipy
- pillow
- flashtext
- faster_whisper (オプション: Whisper をローカルで使う場合)
- deepl / translators外部翻訳を使う場合
実行手順 (開発環境):
1. 仮想環境を作成し有効化
2. 必要パッケージをインストール
- requirements.txt を用意する場合はそこからインストール
3. `src-python` をワークディレクトリにして `python mainloop.py` を実行
注意点:
- Whisper / CTranslate2 の重みは初回にダウンロードする必要がある。Controller の downloadCtranslate2Weight / downloadWhisperWeight エンドポイントからトリガできる。
- OpenVR (SteamVR) を使う Overlay は SteamVR が動作している環境でのみ動作。
- Windows 固有: device_manager が pyaudiowpatch と pycaw に依存。Linux/Mac での互換性は保証されない。
ログ:
- process.log (標準動作ログ)
- error.log (トレースバック)
- models 用のロガーは `model.startLogger()` により PATH_LOGS 配下に日付付きファイルを作成する。
デバッグ:
- `utils.printLog``utils.printResponse` が stdout に JSON を出すため、GUI 側はそれをパースして UI 更新を行う。
- WebSocket を有効にすると外部クライアントに JSON をブロードキャストできる。

940
src-python/docs/utils.md Normal file
View File

@@ -0,0 +1,940 @@
# utils.py ドキュメント
## 概要
`utils.py` は VRCT アプリケーション全体で使用される汎用ユーティリティ関数とロギング機能を提供するモジュール。辞書構造の検証、ネットワーク接続確認、計算デバイス管理、Base64エンコーディング、構造化ログ出力など、複数のサブシステムで共有される基盤機能を集約している。
## 主要機能
- 辞書構造の厳密な検証
- ネットワーク接続状態の診断
- WebSocketサーバーのアドレス可用性チェック
- IPアドレスのバリデーション
- CUDA/CPU計算デバイスの検出と最適化
- 構造化ログ出力process.log, error.log
- Base64エンコード/デコード
- ログファイルのローテーション管理
## アーキテクチャ上の位置づけ
```
┌─────────────────┐
│ All Modules │ (controller, model, device_manager, etc.)
└────────┬────────┘
│ Import
┌────────▼────────┐
│ utils.py │ ◄── このファイル
└─────────────────┘
┌────────▼────────┐
│ External Deps │ (torch, ctranslate2, requests, ipaddress)
└─────────────────┘
```
全てのモジュールから参照される共通基盤として機能し、循環参照を避けるため他の内部モジュールへの依存を持たない。
## 依存関係
### 標準ライブラリ
```python
import base64
import json
import traceback
import logging
from logging.handlers import RotatingFileHandler
from typing import Any, List, Dict, Optional
```
### サードパーティライブラリ(オプション依存)
```python
import torch # GPU検出用インポート失敗時はNoneにフォールバック
from ctranslate2 import get_supported_compute_types # 計算タイプ取得用
import requests # ネットワーク接続確認用
import ipaddress # IPアドレス検証用
import socket # WebSocketサーバー可用性チェック用
```
**セーフガードインポート:**
```python
try:
import torch
except Exception:
torch = None # type: ignore
try:
from ctranslate2 import get_supported_compute_types
except Exception:
def get_supported_compute_types(device: str, device_index: int) -> List[str]:
return []
```
オプション依存が満たされない環境でもモジュールは正常にロード可能。
## 関数リファレンス
### 1. 辞書構造検証
#### `validateDictStructure(data: dict, structure: dict) -> bool`
**責務:** 辞書の構造と型が期待される仕様と完全に一致するかを検証
**アルゴリズム:**
1. 両方が辞書型であることを確認
2. キーの数と名前が完全一致するかチェック
3. 各キーの値について:
- 期待値が辞書の場合: 再帰的に検証(多重入れ子対応)
- 期待値が型オブジェクトの場合: `isinstance()` で型チェック
**引数:**
- `data` (dict): 検証対象の辞書
- `structure` (dict): 期待される構造定義
- 値には型str, int, bool等または入れ子の辞書を指定
**返り値:**
- `True`: 構造が完全に一致
- `False`: 不一致(キー不足、余分なキー、型不一致等)
**使用例:**
```python
# 単純な構造検証
data = {"name": "Alice", "age": 30}
structure = {"name": str, "age": int}
assert validateDictStructure(data, structure) is True
# 入れ子構造の検証
data = {
"user": {
"id": 123,
"profile": {"name": "Bob", "active": True}
}
}
structure = {
"user": {
"id": int,
"profile": {"name": str, "active": bool}
}
}
assert validateDictStructure(data, structure) is True
# 不一致の検出
data = {"name": "Alice", "extra_key": "value"}
structure = {"name": str, "age": int}
assert validateDictStructure(data, structure) is False # キーが不一致
```
**使用場面:**
- フロントエンドからのリクエストペイロード検証
- 設定ファイルのスキーマ検証
- API レスポンスの構造確認
---
### 2. ネットワーク診断
#### `isConnectedNetwork(url: str = "http://www.google.com", timeout: int = 3) -> bool`
**責務:** インターネット接続の可用性を高速チェック
**処理:**
1. 指定URLに HTTP GET リクエストを送信
2. `timeout` 秒以内に 200 OK レスポンスを受信したら接続あり
3. タイムアウトまたは例外発生時は接続なし
**引数:**
- `url` (str): 接続確認先URLデフォルト: Google
- `timeout` (int): タイムアウト時間(秒)
**返り値:**
- `True`: ネットワーク接続あり
- `False`: ネットワーク接続なし
**使用例:**
```python
if isConnectedNetwork():
# モデルウェイトをダウンロード
downloadModelWeights()
else:
# オフラインモードで動作
useLocalModels()
```
**注意事項:**
- ファイアウォールやプロキシ環境では正しく動作しない場合がある
- 初期化時の1回のみチェックを推奨頻繁な呼び出しは避ける
---
#### `isAvailableWebSocketServer(host: str, port: int) -> bool`
**責務:** 指定したホスト/ポートでWebSocketサーバーが起動可能かを確認
**処理:**
1. TCP ソケットを作成
2. `SO_REUSEADDR` オプションを設定
3. `bind()` を試行
4. 成功 → アドレス利用可能、失敗 → アドレス使用中
**引数:**
- `host` (str): バインドするIPアドレス
- `port` (int): バインドするポート番号
**返り値:**
- `True`: アドレスが利用可能
- `False`: アドレスが使用中
**使用例:**
```python
if isAvailableWebSocketServer("127.0.0.1", 8080):
startWebSocketServer("127.0.0.1", 8080)
else:
print("Port 8080 is already in use")
```
**注意事項:**
- `SO_REUSEADDR` により、TIME_WAIT 状態のアドレスも利用可能と判定される
- 管理者権限が必要なポート1024未満では失敗する場合がある
---
#### `isValidIpAddress(ip_address: str) -> bool`
**責務:** IPv4/IPv6アドレスの妥当性を検証
**処理:**
- `ipaddress.ip_address()` でパース
- 成功 → 有効なIPアドレス、失敗 → 無効
**引数:**
- `ip_address` (str): 検証対象のIPアドレス文字列
**返り値:**
- `True`: 有効なIPアドレス
- `False`: 無効なIPアドレス
**使用例:**
```python
assert isValidIpAddress("127.0.0.1") is True
assert isValidIpAddress("2001:db8::1") is True
assert isValidIpAddress("invalid") is False
```
**サポート形式:**
- IPv4: "192.168.1.1", "127.0.0.1"
- IPv6: "2001:db8::1", "fe80::1"
---
### 3. 計算デバイス管理
#### `getComputeDeviceList() -> List[Dict[str, Any]]`
**責務:** 利用可能な計算デバイスCPU/GPUとサポートされる計算タイプを列挙
**返り値構造:**
```python
[
{
"device": "cpu",
"device_index": 0,
"device_name": "cpu",
"compute_types": ["auto", "float32", "int8", ...]
},
{
"device": "cuda",
"device_index": 0,
"device_name": "NVIDIA GeForce RTX 3090",
"compute_types": ["auto", "int8_bfloat16", "int8_float16", ...]
},
...
]
```
**処理フロー:**
1. CPU デバイスを常に追加(最低限の計算環境を保証)
2. PyTorch と CUDA が利用可能な場合:
- 全GPUデバイスを列挙
- 各GPUの計算タイプを `get_supported_compute_types()` で取得
- GPU アーキテクチャに応じて計算タイプを制限:
- **GTX シリーズ**: `int8_bfloat16`, `bfloat16`, `float16`, `int8` を除外
- **RTX, Tesla, A100, Quadro**: 全計算タイプをサポート
- **その他**: `float32` のみ
**GPU別の計算タイプ制限:**
```python
if "GTX" in gpu_device_name:
unsupported_types = {"int8_bfloat16", "bfloat16", "float16", "int8"}
gpu_compute_types = [t for t in gpu_compute_types if t not in unsupported_types]
elif not any(keyword in gpu_device_name for keyword in ["RTX", "Tesla", "A100", "Quadro"]):
gpu_compute_types = ["float32"]
```
**使用例:**
```python
devices = getComputeDeviceList()
for device in devices:
print(f"{device['device_name']}: {', '.join(device['compute_types'])}")
# 出力例:
# cpu: auto, float32, int8
# NVIDIA GeForce RTX 3090: auto, int8_bfloat16, int8_float16, int8, bfloat16, float16, int8_float32, float32
```
**エラーハンドリング:**
- GPU検出中の例外は `errorLogging()` でログ記録し、CPU デバイスのみ返却
---
#### `getBestComputeType(device: str, device_index: int) -> str`
**責務:** デバイスアーキテクチャに最適な計算タイプを自動選択
**優先順位:**
```python
preferred_types = {
"default": [
"int8_bfloat16", # 最も効率的対応GPUのみ
"int8_float16", # 2番目に効率的
"int8", # 整数演算高速化
"bfloat16", # 混合精度
"float16", # 半精度浮動小数点
"int8_float32", # 互換性重視
"float32" # フォールバック
],
"GTX": ["float32"], # GTXシリーズは制限あり
"RTX": ["int8_bfloat16", "int8_float16", ...],
"Tesla": [...],
"A100": [...],
"Quadro": [...]
}
```
**処理フロー:**
1. `get_supported_compute_types()` で利用可能な計算タイプを取得
2. デバイス名に基づいて優先リストを選択
3. 優先順に計算タイプをチェックし、最初に利用可能なものを返却
4. 全て利用不可の場合は `"float32"` を返却(安全なフォールバック)
**引数:**
- `device` (str): "cpu" または "cuda"
- `device_index` (int): GPUデバイスのインデックスCPUの場合は0
**返り値:**
- 最適な計算タイプ文字列(例: "int8_bfloat16", "float32"
**使用例:**
```python
best_type = getBestComputeType("cuda", 0)
model.load_model(compute_type=best_type)
```
**計算タイプの特性:**
| 計算タイプ | メモリ使用量 | 速度 | 精度 | 対応GPU |
|----------|------------|------|------|--------|
| int8_bfloat16 | 最小 | 最速 | 高 | RTX 30xx以降 |
| int8_float16 | 最小 | 最速 | 高 | RTX 20xx以降 |
| int8 | 小 | 高速 | 中 | 多くのGPU |
| bfloat16 | 中 | 高速 | 高 | RTX 30xx以降 |
| float16 | 中 | 高速 | 高 | RTX 20xx以降 |
| float32 | 大 | 標準 | 最高 | 全GPU/CPU |
---
### 4. エンコーディング
#### `encodeBase64(data: str) -> Dict[str, Any]`
**責務:** Base64エンコードされたJSON文字列をデコードしてパース
**処理:**
1. Base64デコード
2. UTF-8文字列に変換
3. JSON パース
4. 失敗時は空の辞書を返却
**引数:**
- `data` (str): Base64エンコードされたJSON文字列
**返り値:**
- パース成功: JSON オブジェクト
- パース失敗: `{}`(空の辞書)
**使用例:**
```python
# エンコード例(参考)
import base64
import json
payload = {"message": "Hello", "id": 123}
encoded = base64.b64encode(json.dumps(payload).encode('utf-8')).decode('utf-8')
# デコード
decoded = encodeBase64(encoded)
assert decoded == {"message": "Hello", "id": 123}
```
**エラーハンドリング:**
- 不正なBase64文字列
- 不正なJSON形式
- 文字エンコーディングエラー
全て `errorLogging()` でログ記録し、空の辞書を返却。
**注意事項:**
- 関数名が `encodeBase64` だが、実際には**デコード**を行う(命名の歴史的経緯)
- セキュリティ: Base64は暗号化ではないため、機密情報の保護には使用しない
---
### 5. ロギング
#### `removeLog() -> None`
**責務:** プロセスログファイルprocess.logを初期化
**処理:**
- `process.log` を空の内容で上書き
- ファイルが存在しない場合は新規作成
**使用例:**
```python
# アプリケーション起動時にログをクリア
removeLog()
printLog("Application started")
```
**エラーハンドリング:**
- ファイル書き込み失敗時は `errorLogging()` でエラーログに記録
---
#### `setupLogger(name: str, log_file: str, level: int = logging.INFO) -> logging.Logger`
**責務:** ローテーション機能付きロガーインスタンスを生成
**設定:**
- **最大ログサイズ**: 10MB
- **バックアップ数**: 1最大2ファイル
- **ローテーション動作**: 10MB到達時に `.1` バックアップを作成し、新規ログを開始
- **エンコーディング**: UTF-8
- **遅延書き込み**: `delay=True`(最初の書き込み時にファイルを開く)
**引数:**
- `name` (str): ロガー名(例: "process", "error"
- `log_file` (str): ログファイルパス
- `level` (int): ログレベル(デフォルト: `logging.INFO`
**返り値:**
- 設定済み `logging.Logger` インスタンス
**ログフォーマット:**
```
%(asctime)s - %(name)s - %(levelname)s - %(message)s
```
**出力例:**
```
2025-10-13 14:30:45,123 - process - INFO - Application started
2025-10-13 14:30:46,456 - error - ERROR - Connection failed
```
**重複ハンドラー防止:**
```python
if not any(isinstance(h, RotatingFileHandler) and getattr(h, 'baseFilename', None) == getattr(file_handler, 'baseFilename', None) for h in logger.handlers):
logger.addHandler(file_handler)
```
同じファイルへの重複ハンドラー追加を防止し、複数回呼び出されても安全。
---
#### `printLog(log: str, data: Any = None) -> None`
**責務:** 構造化プロセスログの出力
**出力先:**
1. `process.log` ファイル
2. 標準出力JSON形式
**出力形式:**
```python
{
"status": 348, # プロセスログ専用ステータス
"log": "User action performed",
"data": "additional context"
}
```
**引数:**
- `log` (str): ログメッセージ
- `data` (Any): 追加のコンテキスト情報(オプション)
**使用例:**
```python
printLog("Model loading started", {"model_type": "whisper", "weight": "medium"})
# 出力stdout:
# {"status": 348, "log": "Model loading started", "data": "{'model_type': 'whisper', 'weight': 'medium'}"}
```
**実装の詳細:**
```python
global process_logger
if process_logger is None:
process_logger = setupLogger("process", "process.log", logging.INFO)
response = {
"status": 348,
"log": log,
"data": str(data),
}
process_logger.info(response)
serialized = json.dumps(response)
print(serialized, flush=True)
```
**注意事項:**
- `data``str()` で文字列化されるため、複雑なオブジェクトは読みにくくなる可能性がある
- `flush=True` により即座に出力(バッファリングを無効化)
---
#### `printResponse(status: int, endpoint: str, result: Any = None) -> None`
**責務:** 構造化APIレスポンスの出力
**出力先:**
1. `process.log` ファイル
2. 標準出力JSON形式
**出力形式:**
```python
{
"status": 200,
"endpoint": "/get/config/version",
"result": {"version": "3.3.0"}
}
```
**引数:**
- `status` (int): HTTPステータスコード風のステータス番号
- `endpoint` (str): エンドポイント識別子
- `result` (Any): レスポンスペイロード(オプション)
**使用例:**
```python
printResponse(200, "/set/config/language", {"language": "ja"})
printResponse(400, "/set/config/threshold", {"error": "Value out of range"})
```
**JSONシリアライズエラーハンドリング:**
```python
try:
serialized_response = json.dumps(response)
except Exception as e:
errorLogging() # 完全なトレースバックをログ
process_logger.error(f"Problematic response object: {response}")
process_logger.error(f"Exception during json.dumps: {e}")
# フォールバックエラーペイロード
error_json = json.dumps({
"status": 500,
"endpoint": endpoint,
"result": {"error": "Failed to serialize response", "details": str(e)},
})
print(error_json, flush=True)
else:
print(serialized_response, flush=True)
```
**シリアライズ不可能なオブジェクトの例:**
- `datetime` オブジェクト
- カスタムクラスインスタンス
- 循環参照を持つ辞書
**対策:**
- `result` を構築する際に JSON シリアライズ可能な型のみ使用
- 必要に応じて `str()` や専用のシリアライザーで変換
---
#### `errorLogging() -> None`
**責務:** 現在の例外トレースバックをエラーログに記録
**処理:**
1. `error.log` ファイルにトレースバックを出力
2. ロガー初期化失敗時は標準出力にフォールバック
**使用例:**
```python
try:
risky_operation()
except Exception:
errorLogging() # トレースバックをerror.logに記録
# 必要に応じて追加処理
```
**出力例error.log:**
```
2025-10-13 14:35:12,789 - error - ERROR - Traceback (most recent call last):
File "model.py", line 123, in loadModel
model.load()
File "ctranslate2/model.py", line 456, in load
raise RuntimeError("CUDA out of memory")
RuntimeError: CUDA out of memory
```
**注意事項:**
- **例外コンテキスト内でのみ呼び出し可能**`traceback.format_exc()` を使用)
- 例外をキャッチせずに呼び出すと空のトレースバックが記録される
**ベストプラクティス:**
```python
try:
dangerous_function()
except SpecificException as e:
errorLogging() # 詳細をログ
# ユーザーフレンドリーなエラー処理
printResponse(400, endpoint, {"error": "Operation failed"})
except Exception:
errorLogging() # 予期しないエラーもログ
raise # 上位へ伝播
```
---
## グローバル変数
### `process_logger: Optional[logging.Logger] = None`
プロセスログ用のグローバルロガーインスタンス。初回 `printLog()` または `printResponse()` 呼び出し時に初期化される。
### `error_logger: Optional[logging.Logger] = None`
エラーログ用のグローバルロガーインスタンス。初回 `errorLogging()` 呼び出し時に初期化される。
**遅延初期化の理由:**
- モジュールインポート時のオーバーヘッド削減
- ファイルシステムへの不要なアクセスを回避
---
## エラーハンドリング戦略
### 1. 防御的プログラミング
全てのユーティリティ関数は例外を内部で処理し、呼び出し元に例外を伝播しない:
```python
def isConnectedNetwork(url="http://www.google.com", timeout=3) -> bool:
try:
response = requests.get(url, timeout=timeout)
return response.status_code == 200
except requests.RequestException:
return False # 例外をキャッチして安全な値を返却
```
### 2. フォールバック値
- `encodeBase64()`: パース失敗時は `{}`
- `getComputeDeviceList()`: GPU検出失敗時はCPUのみ
- `getBestComputeType()`: 全て失敗時は `"float32"`
### 3. ログ記録
全てのエラーは `errorLogging()` でトレースバックを記録し、デバッグを容易にする。
---
## パフォーマンス考慮事項
### 1. ネットワーク接続チェック
`isConnectedNetwork()` はブロッキング操作最大3秒のため、起動時の1回のみ実行を推奨:
```python
# 良い例
if isConnectedNetwork():
downloadModels()
# 悪い例UI フリーズの原因)
while True:
if isConnectedNetwork(): # 毎回3秒待機
processData()
```
### 2. ログローテーション
10MB のログファイルローテーションにより、ディスク容量を制御最大20MB
### 3. グローバルロガーの遅延初期化
ロガーは初回使用時に初期化されるため、インポート時のオーバーヘッドを最小化。
---
## 使用パターン
### パターン1: ネットワーク依存機能の初期化
```python
def initialize_online_features():
if not isConnectedNetwork():
printLog("Offline mode: skipping model download")
return
printLog("Online mode: downloading models")
downloadModels()
```
### パターン2: デバイス自動選択
```python
devices = getComputeDeviceList()
if len(devices) > 1:
# GPU利用可能
best_device = devices[1] # 最初のGPU
best_type = getBestComputeType(best_device["device"], best_device["device_index"])
printLog(f"Using GPU: {best_device['device_name']}", {"compute_type": best_type})
else:
# CPUのみ
printLog("No GPU detected, using CPU")
best_type = "float32"
```
### パターン3: 構造化リクエスト検証
```python
def handle_request(payload):
expected_structure = {
"action": str,
"data": {
"id": int,
"value": str
}
}
if not validateDictStructure(payload, expected_structure):
printResponse(400, "/handle_request", {"error": "Invalid request structure"})
return
# 処理続行
printLog("Valid request received", payload)
```
### パターン4: WebSocketサーバー起動
```python
def start_websocket(host, port):
if not isValidIpAddress(host):
printResponse(400, "/websocket/start", {"error": "Invalid IP address"})
return
if not isAvailableWebSocketServer(host, port):
printResponse(400, "/websocket/start", {"error": f"Port {port} is in use"})
return
# サーバー起動
printLog(f"Starting WebSocket server", {"host": host, "port": port})
startServer(host, port)
```
---
## テスト推奨事項
### 単体テスト例
**辞書構造検証:**
```python
def test_validate_dict_structure_simple():
data = {"name": "Alice", "age": 30}
structure = {"name": str, "age": int}
assert validateDictStructure(data, structure) is True
def test_validate_dict_structure_nested():
data = {"user": {"id": 1, "active": True}}
structure = {"user": {"id": int, "active": bool}}
assert validateDictStructure(data, structure) is True
def test_validate_dict_structure_invalid():
data = {"name": "Alice"}
structure = {"name": str, "age": int} # 'age'キーが不足
assert validateDictStructure(data, structure) is False
```
**ネットワーク診断:**
```python
def test_network_connection():
# 実際のネットワーク接続をテスト
result = isConnectedNetwork()
assert isinstance(result, bool)
def test_network_timeout():
# タイムアウト動作を確認
result = isConnectedNetwork(url="http://192.0.2.1", timeout=1)
assert result is False
```
**計算デバイス:**
```python
def test_get_compute_device_list():
devices = getComputeDeviceList()
assert len(devices) >= 1 # 最低限CPUが含まれる
assert devices[0]["device"] == "cpu"
def test_get_best_compute_type():
compute_type = getBestComputeType("cpu", 0)
assert compute_type in ["float32", "int8"]
```
**ロギング:**
```python
def test_print_log(capsys):
printLog("Test message", {"key": "value"})
captured = capsys.readouterr()
output = json.loads(captured.out)
assert output["status"] == 348
assert output["log"] == "Test message"
def test_print_response(capsys):
printResponse(200, "/test", {"result": "success"})
captured = capsys.readouterr()
output = json.loads(captured.out)
assert output["status"] == 200
assert output["endpoint"] == "/test"
```
---
## セキュリティ考慮事項
### 1. IPアドレス検証
`isValidIpAddress()` はフォーマット検証のみで、プライベートアドレス範囲のチェックは行わない:
```python
# セキュリティを強化する場合
import ipaddress
def is_public_ip(ip_str):
if not isValidIpAddress(ip_str):
return False
ip = ipaddress.ip_address(ip_str)
return not (ip.is_private or ip.is_loopback or ip.is_reserved)
```
### 2. Base64デコード
`encodeBase64()` は入力検証を行わないため、信頼できないソースからのデータには注意:
```python
# 安全な使用例
if source_is_trusted:
data = encodeBase64(base64_string)
else:
# 追加の検証を実施
pass
```
### 3. ログファイルへの機密情報記録
ログに機密情報API キー、パスワード等)が含まれないよう注意:
```python
# 悪い例
printLog("API key loaded", api_key)
# 良い例
printLog("API key loaded", "***REDACTED***")
```
---
## 制限事項
1. **プラットフォーム依存性:**
- GPU検出は CUDA 環境でのみ動作ROCm/Metal非対応
2. **ネットワークチェックの制限:**
- ファイアウォール、プロキシ環境で誤判定の可能性
- IPv6専用環境での動作は未検証
3. **ログファイルのスレッドセーフティ:**
- `RotatingFileHandler` は基本的にスレッドセーフだが、高負荷時のローテーション中にログ損失の可能性
4. **計算タイプの最適化:**
- `getBestComputeType()` の優先順位は一般的な推奨値であり、特定のモデルやタスクでは最適でない場合がある
---
## 依存モジュールとの関係
### controller.py
- デバイス管理の設定変更時にデバイスリスト取得
- エラー時のログ記録
- ネットワーク接続確認
### model.py
- 計算デバイスとタイプの決定
- エラー時のトレースバック記録
### config.py
- 起動時のネットワーク接続確認
- 計算デバイスリストの提供
### mainloop.py
- リクエスト/レスポンスの構造化ログ出力
- エラー時のトレースバック記録
---
## 今後の拡張性
### 1. 非同期ネットワークチェック
```python
import asyncio
import aiohttp
async def isConnectedNetworkAsync(url="http://www.google.com", timeout=3) -> bool:
try:
async with aiohttp.ClientSession() as session:
async with session.get(url, timeout=aiohttp.ClientTimeout(total=timeout)) as response:
return response.status == 200
except Exception:
return False
```
### 2. 構造化ログの拡張
```python
def printStructuredLog(level: str, message: str, context: dict = None):
"""
より詳細な構造化ログ出力
- timestamp
- level
- message
- context (key-value pairs)
- stack trace (error時)
"""
pass
```
### 3. メトリクス収集
```python
def recordMetric(metric_name: str, value: float, tags: dict = None):
"""
パフォーマンスメトリクスの記録
- function execution time
- memory usage
- GPU utilization
"""
pass
```
---
## 関連ドキュメント
- `controller.md`: Controller での utils 関数使用例
- `config.md`: Config での計算デバイス管理
- `model.md`: Model でのエラーハンドリング
- `コーディングルール.md`: ロギングとエラーハンドリングの規約
---
## ライセンス
プロジェクトのルートディレクトリの `LICENSE` ファイルを参照
---
## まとめ
`utils.py` は VRCT プロジェクトの基盤インフラストラクチャとして、以下の重要な責務を担う:
1. **安全性**: 全ての関数が例外を内部処理し、安全なフォールバック値を提供
2. **可観測性**: 構造化ログとローテーション機能により、問題の診断を容易化
3. **互換性**: オプション依存のセーフガードにより、様々な環境で動作
4. **最適化**: GPU アーキテクチャに応じた計算タイプの自動選択
5. **検証**: 辞書構造、IPアドレス、ネットワーク接続の厳密なバリデーション
全てのサブシステムから依存される中核モジュールとして、高い信頼性と保守性を維持している。

View File

@@ -0,0 +1,58 @@
# 仕様書
概要
- プロジェクト名: VRCT (VR Chat Translator)
- 目的: マイク入力とスピーカー出力をリアルタイムに文字起こし・翻訳し、VR オーバーレイや OSC/WebSocket 経由で外部に送出するバックエンドロジック。
- 言語: Python
対象ユーザー
- VR 環境でリアルタイム翻訳・文字起こしを利用したいエンドユーザー
- フロントエンドGUIや VR クライアントOSCと連携するアプリケーション開発者
主要機能(機能要件)
1. 音声の取り込み・文字起こし
- マイク(送信)およびスピーカー(受信)から音声を取得し、ローカル Whisperfaster-whisperまたは外部サービスによりテキスト化する。
- 音声エネルギー(音量)監視を行い、閾値ベースで検出する。
2. 翻訳
- DeepL / DeepL API / 各クラウド翻訳 / ローカル CTranslate2 モデルの複数バックエンドをサポート。
- 複数出力言語への一括翻訳、翻訳エンジンのフォールバックCTranslate2 など)。
- 翻訳モデルのダウンロードと管理機能。
3. 表示・通知
- OpenVR オーバーレイsmall/large用の画像生成と更新。
- OSC による VR へのメッセージ送信typing/通知等)。
- WebSocket サーバーを介した外部クライアントへの JSON ブロードキャスト。
4. 入出力インターフェース
- stdin ラインベースの JSON コマンド受信mainloop が実装)。
- stdout に対して構造化された JSON レスポンスを出力printResponse/printLog
5. 設定・永続化
- JSON ベースの設定ファイルを使用(`config.py` による読み書きとデバウンス保存)。
6. ロギングと監視
- プロセスログprocess.logとエラーログerror.logをローテーションで管理。
- ウォッチドッグ機構で定期的に死活チェック・コールバック。
非機能要件
- プラットフォーム: 主に WindowsAudio 周りは WASAPI を利用)を想定。クロスプラットフォームでの import 安全性を考慮。
- 可用性: 外部依存PyAudio, CUDA, ctranslate2 等)が無い環境でも安全にインポートでき、機能劣化しつつ動作する。
- パフォーマンス: ローカルモデル利用時は GPU を利用して計算性能を確保。compute type 選択ロジックを実装。
- セキュリティ: 外部への API キーDeepL など)は設定で扱い、コード上では平文保持を避ける(設定ファイルに保存)。
運用フロー
- 起動: stdin でコマンドを受け付ける mainloop を実行。必要な初期化は遅延実行lazy initを採用。
- モデル重ダウンロード: CTranslate2/Whisper 重みは `weights/` 配下にダウンロードし、チェックサム等で整合性確認。
- 障害時: 例外は utils.errorLogging() でトレースを error.log に出力。重要機能はフォールバック実装。
インターフェース(抜粋)
- stdin(JSON): {"endpoint": "/set/..." | "/get/..." | "/run/...", "data": <base64(JSON)|any>}
- stdout(JSON): 標準化されたレスポンスを printResponse/printLog が出力status, endpoint, result など)。
依存関係(オプション含む)
- 必須(実装時想定): requests, packaging, flashtext, pillow, pyaudiowpatch, speech_recognition
- ローカル推奨: faster-whisper, ctranslate2, torchGPU 利用時)
- Windows 固有(音声ループバック): pycaw, comtypes
参考: 実装上の安全設計として optional な import は try/except でガードしており、存在しない依存があっても import 時にクラッシュしない。

View File

@@ -0,0 +1,57 @@
# 設計書
概要
- 本設計書はアプリケーションのアーキテクチャ、主要コンポーネント、並列化モデル、エラー処理ポリシー、設定の保存方針を記述する。
アーキテクチャ概要
- 層構造
- mainloop: stdin ベースのコマンド受け取り、ワーカー(複数スレッド)で実行。
- controller: GUI/フロントエンドからの操作とモデルの仲介。`Controller` がビジネスロジックを実行。
- model: 実際の機能翻訳、文字起こし、オーバーレイ、OSC、WebSocket、デバイス管理を提供するファサード的シングルトン。
- models/*: 翻訳、文字起こしなどのドメイン別実装Translator, AudioTranscriber, Overlay, WebSocketServer ...)。
- device_manager: 音声デバイス検出・監視Windows の場合は WASAPI/pycaw を利用)。
- utils: 共通ユーティリティロギング、ネットワークチェック、compute device 列挙など)。
初期化ポリシー
- 重い初期化GPU モデルロード、OpenVR 初期化など)は import 時に行わず、`model.init()` か要求時の `ensure_initialized()` にて遅延実行。
- `DeviceManager` は import 時に軽量な init を行い、監視スレッドは `startMonitoring()` で開始する。
並列化・同期モデル
- mainloop.Main は 1 つの受信スレッドstdin 읽取り)と N 個のハンドラワーカースレッドを持つ。
- 各リクエストはキューに入れられ、handler() により処理される。
- 有効/無効の切替(/set/enable/**, /set/disable/**)は同一リソースを競合しないよう正規化キーで Lock を割り当てる。
- モデル内部では threadFncThread ラッパ)で周期的な送信処理や監視処理を実装。
- Audio 録音や文字起こしは専用の Queue を用い、ProducerRecorderと ConsumerAudioTranscriberを分離。
エラー処理
- すべての外周呼び出しは try/except で保護し、`utils.errorLogging()` によってトレースバックを error.log に出力する。
- JSON シリアライズに失敗した場合はフォールバック JSON を stdout に出力してプロセスを止めない。
- VRAM 関連のエラーは model.detectVRAMError() で判定し、該当する機能(翻訳等)を無効化してユーザーに通知する。
設定管理
- `config.py` が単一の Config シングルトンを持ち、変更はデバウンスして JSON ファイルへ保存。
- GUI からの操作は Controller が受け取り、Config を更新する。
ログ
- `utils.setupLogger` によりローテートファイルハンドラを使ったログを実装process.log / error.log
- stdout には構造化ログを出力してフロントエンドと通信する。
インターフェース一覧(抜粋)
- STDIN/STDOUT プロトコル: mainloop の JSON 入出力(詳細は `mainloop.py` の mapping を参照)
- OSC: `models.osc.OSCHandler` が OSC 送受信と OSCQuery を管理
- WebSocket: `models.websocket.WebSocketServer` がクライアント管理とメッセージブロードキャストを担う
スレッド図(要点)
- main_thread: メインstdin 読み取り、キュー投入)
- handler_threads: キューから取り出し処理
- device_manager.th_monitoring: デバイス監視
- model.mic_print_transcript / speaker_print_transcript: 音声 -> 翻訳結果送出のループ
- websocket_server_thread: WebSocket サーバの asyncio ループを別スレッドで実行
拡張性・互換性設計
- 依存性は try/except でガードして optional 機能として扱う(例: faster-whisper が無くても import は成功する)。
- 翻訳エンジンは backend 名で抽象化され、Translator クラスにより統一インターフェースを提供。
運用上の考慮
- 大きなモデルファイルWhisper, CTranslate2をダウンロードする仕組みを持ち、進捗を GUI に報告する。
- GPU 計算タイプは utils.getBestComputeType で選択し、不適切な設定を検出した場合はフォールバック。

View File

@@ -0,0 +1,66 @@
# 詳細設計書
この文書は主要クラス・関数の詳細、データ構造、例外ケース、スレッドの振る舞いを記載する。
目次
- Model
- Controller
- Main (mainloop)
- DeviceManager
- Utils
- モデルの重みダウンロードと整合性
## Model
- シングルトン: `model = Model()`
- 遅延初期化: `init()``ensure_initialized()` を備え、init は重いリソースOverlay, Translator, Watchdog, OSC ハンドラ等)を構築する。
- 主な責務
- 翻訳/文字起こし関連の起動停止ラッパ
- Overlay/OSChandler/WebSocket の操作
- キーワード検出flashtextと重複検出
- VRAM エラー検出とフォールバック
- 重要属性(抜粋)
- `translator` : Translator インスタンス
- `overlay` / `overlay_image` : Overlay 系
- `mic_*`, `speaker_*` : 録音、トランスクリプタ、energy recorder
- `watchdog` : Watchdog
- `osc_handler`, `websocket_server`
- スレッド制御
- threadFnc を用いて周期処理を回す。stop/pause/resume が可能。
## Controller
- GUI からの要求を受け、Model を操作して結果を run() コールバックへ返す。
- 各種設定変更 (/set/ や /get/ エンドポイント) を実装。
- 翻訳/文字起こし/オーバーレイ連携ロジックを持ち、メッセージ整形messageFormatterや OSC の送信を行う。
- ダウンロード作業は別スレッドで行い、進捗を run_mapping を通して通知。
## Main (mainloop.Main)
- stdin を readline() で受け取り JSON を parse、endpoint と data をキューへ投入。
- worker_count 個の handler スレッドが queue を取り出し `_call_handler` を実行。
- endpoint ロック正規化: `/set/enable/...``/set/disable/...` は同じ正規化キー `/lock/set/...` を共有して排他制御。
- エラーレスポンスの標準化と再試行ロジックstatus==423 は再キュー化)。
## DeviceManager
- シングルトン。初期化は軽量で、`init()` により内部構造をセット、実デバイスは `update()` で取得。
- Windows 環境では COM イベント (pycaw/MMNotificationClient) を用いた検出か、PyAudio によるポーリングでデバイス一覧を構成。
- コールバック設計: 変更検出時に Controller のコールバックを呼び出して UI 更新を促す。
## Utils
- `validateDictStructure(data, structure)` : JSON 構造検証。
- `getComputeDeviceList()` / `getBestComputeType()` : CPU/CUDA を列挙し、推奨 compute_type を返す。
- `setupLogger()` / `printLog()` / `printResponse()` / `errorLogging()` : ログ、標準出力の整形、エラー記録。
- ネットワーク/ソケット/IP アドレス検査ユーティリティ。
## モデル重みダウンロード
- `models.translation.translation_utils``models.transcription.transcription_whisper` にダウンロード/チェック関数があり、チェックサムやファイル存在を検証する。
- GUI からの要求は Controller により非同期スレッドで実行され、進捗コールバックが run_mapping を介してフロントエンドに渡る。
## エッジケース / 例外処理
- 外部 API のレート制限や認証エラーは呼び出し元に 400 系のレスポンスを返し、必要であればフォールバック実装CTranslate2 への切替)を行う。
- 大きなモデル実行時の VRAM エラーは検出し、当該機能を無効化してユーザへ通知する。
- 音声デバイスが存在しない場合は NoDevice を返し、UI 側で扱う。
## テスト観点
- メッセージ受信/送信のエンドツーエンド: stdin -> handler -> Controller -> Model -> printResponse の流れ。
- デバイス挙動: DeviceManager.update() がデバイス一覧を取得できるかPyAudio 経由)。
- モデルダウンロード: ダウンロード成功・失敗、チェックサム検証。
- ログ/エラー: errorLogging() による例外トレースが error.log に記録されるか。