From d1aef28c7a892f749fcf35174f21d46a3d9cf3d4 Mon Sep 17 00:00:00 2001 From: misyaguziya <53165965+misyaguziya@users.noreply.github.com> Date: Tue, 14 Oct 2025 07:28:03 +0900 Subject: [PATCH] Add comprehensive detailed design documents --- src-python/docs/details/backend_test.md | 95 ++ src-python/docs/details/config.md | 392 +++++++ src-python/docs/details/controller.md | 349 ++++++ src-python/docs/details/mainloop.md | 275 +++++ src-python/docs/details/model.md | 292 ++++++ src-python/docs/details/osc.md | 602 +++++++++++ src-python/docs/details/overlay.md | 754 +++++++++++++ .../docs/details/transcription_languages.md | 229 ++++ .../docs/details/transcription_recorder.md | 325 ++++++ .../docs/details/transcription_transcriber.md | 325 ++++++ .../docs/details/transcription_whisper.md | 373 +++++++ .../docs/details/translation_languages.md | 342 ++++++ .../docs/details/translation_translator.md | 406 +++++++ src-python/docs/details/translation_utils.md | 438 ++++++++ .../details/transliteration_context_rules.md | 397 +++++++ .../transliteration_kana_to_hepburn.md | 465 ++++++++ .../details/transliteration_transliterator.md | 659 ++++++++++++ src-python/docs/details/utils.md | 213 ++++ src-python/docs/details/watchdog.md | 670 ++++++++++++ src-python/docs/details/websocket_server.md | 989 ++++++++++++++++++ 20 files changed, 8590 insertions(+) create mode 100644 src-python/docs/details/backend_test.md create mode 100644 src-python/docs/details/config.md create mode 100644 src-python/docs/details/controller.md create mode 100644 src-python/docs/details/mainloop.md create mode 100644 src-python/docs/details/model.md create mode 100644 src-python/docs/details/osc.md create mode 100644 src-python/docs/details/overlay.md create mode 100644 src-python/docs/details/transcription_languages.md create mode 100644 src-python/docs/details/transcription_recorder.md create mode 100644 src-python/docs/details/transcription_transcriber.md create mode 100644 src-python/docs/details/transcription_whisper.md create mode 100644 src-python/docs/details/translation_languages.md create mode 100644 src-python/docs/details/translation_translator.md create mode 100644 src-python/docs/details/translation_utils.md create mode 100644 src-python/docs/details/transliteration_context_rules.md create mode 100644 src-python/docs/details/transliteration_kana_to_hepburn.md create mode 100644 src-python/docs/details/transliteration_transliterator.md create mode 100644 src-python/docs/details/utils.md create mode 100644 src-python/docs/details/watchdog.md create mode 100644 src-python/docs/details/websocket_server.md diff --git a/src-python/docs/details/backend_test.md b/src-python/docs/details/backend_test.md new file mode 100644 index 00000000..af0413c0 --- /dev/null +++ b/src-python/docs/details/backend_test.md @@ -0,0 +1,95 @@ +# backend_test.py - APIエンドポイントテストモジュール + +## 概要 +VRCTアプリケーションのAPIエンドポイントを包括的にテストするためのモジュールです。メインループの各種機能をランダムアクセスでテストし、システムの安定性と堅牢性を検証します。 + +## 主要機能 + +### Color クラス +- ANSIエスケープシーケンスを使用したコンソール出力色彩管理 +- テスト結果の視覚的表示(成功・失敗・スキップ等) + +### TestMainloop クラス +- APIエンドポイントの包括的テスト実行 +- ランダムアクセステスト +- テスト結果の記録・分析 +- VRCTメインループとの統合テスト + +## 主要メソッド + +### テスト実行メソッド +- `test_endpoints_on_off_all()`: ON/OFF系エンドポイントの全テスト +- `test_set_data_endpoints_all()`: データ設定系エンドポイントの全テスト +- `test_run_endpoints_all()`: 実行系エンドポイントの全テスト +- `test_endpoints_all_random()`: 全エンドポイントのランダムアクセステスト + +### 特定機能テスト +- `test_translate_all_language_pairs()`: 全言語ペアでの翻訳テスト +- `test_endpoints_on_off_continuous()`: ON/OFF連続切り替えテスト +- `test_endpoints_specific_random()`: 特定エンドポイントのランダムテスト + +### 結果分析 +- `generate_summary()`: テスト結果のサマリー生成 +- `record_test_result()`: テスト結果の記録 + +## 使用方法 + +### 基本的な使い方 +```python +# テストインスタンスを作成 +test = TestMainloop() + +# 各種テストを実行 +test.test_endpoints_on_off_all() +test.test_set_data_endpoints_all() +test.test_run_endpoints_all() + +# テスト結果のサマリー表示 +test.generate_summary() +``` + +### ランダムテストの実行 +```python +# 全エンドポイントのランダムアクセステスト +test.test_endpoints_all_random() + +# 特定エンドポイントのランダムテスト +test.test_endpoints_specific_random() +``` + +## 依存関係 +- `mainloop`: VRCTメインループモジュール +- `random`: ランダムテストデータ生成 +- `time`: テスト間隔制御 + +## テスト対象エンドポイント + +### 制御系 +- `/set/enable/*`: 機能有効化 +- `/set/disable/*`: 機能無効化 + +### データ設定系 +- `/set/data/*`: 各種設定データの更新 + +### 実行系 +- `/run/*`: 各種機能の実行 + +### データ削除系 +- `/delete/data/*`: データの削除 + +## 注意事項 +- テスト実行前に`config.json`を削除して初期化 +- 重いAIモデルを使用するテストは実行時間に注意 +- ランダムテストは指定回数(デフォルト1000-10000回)実行される +- テスト終了時は自動的にすべての機能を無効化する + +## エラーハンドリング +- 各テストは独立して実行され、一つの失敗が全体に影響しない +- 期待されるステータスコードと実際の結果を比較 +- VRAM不足等のリソースエラーも適切にハンドリング + +## テスト結果の分類 +- **PASS**: 期待されるステータスコードと一致 +- **ERROR**: 期待されるステータスコードと不一致 +- **SKIP**: テスト実行不可(401ステータス) +- **Invalid**: 無効なエンドポイント(404ステータス) \ No newline at end of file diff --git a/src-python/docs/details/config.md b/src-python/docs/details/config.md new file mode 100644 index 00000000..fd619d77 --- /dev/null +++ b/src-python/docs/details/config.md @@ -0,0 +1,392 @@ +# config.py - 設定管理モジュール + +## 概要 + +VRCTアプリケーションの全設定を一元管理するモジュールです。シングルトンパターンを採用し、アプリケーション全体で統一された設定アクセスを提供します。JSON設定ファイルの読み書き、設定の永続化、デバウンス機能付き保存機能を提供します。 + +## 主要機能 + +### シングルトン設計 +- アプリケーション全体で単一の設定インスタンス +- スレッドセーフな設定アクセス +- 遅延初期化による軽量インポート + +### 設定永続化 +- JSON形式での設定ファイル管理 +- デバウンス機能付き自動保存 +- 設定変更の即座反映 + +### 動的設定管理 +- 実行時設定変更対応 +- デバイス情報の動的取得 +- 言語・エンジン設定の自動更新 + +### 型安全な設定アクセス +- プロパティベースのアクセス制御 +- 読み取り専用・読み書き可能設定の分離 +- デコレータによるシリアライゼーション管理 + +## クラス構造 + +### Config クラス +```python +class Config: + _instance = None # シングルトンインスタンス + _config_data: Dict[str, Any] # 設定データ + _timer: Optional[threading.Timer] # デバウンスタイマー + _debounce_time: int = 2 # デバウンス時間(秒) +``` + +## 設定カテゴリ + +### 読み取り専用設定 + +```python +@property +def VERSION(self) -> str +``` +- アプリケーションバージョン + +```python +@property +def PATH_LOCAL(self) -> str +``` +- ローカルディレクトリパス + +```python +@property +def PATH_CONFIG(self) -> str +``` +- 設定ファイルパス + +### UI・表示設定 + +```python +@property +def UI_LANGUAGE(self) -> str +``` +- UIの表示言語 + +```python +@property +def TRANSPARENCY(self) -> int +``` +- ウィンドウの透明度(0-100) + +```python +@property +def UI_SCALING(self) -> int +``` +- UIのスケーリング(50-200%) + +```python +@property +def FONT_FAMILY(self) -> str +``` +- 使用フォントファミリー + +### 翻訳設定 + +```python +@property +def ENABLE_TRANSLATION(self) -> bool +``` +- 翻訳機能の有効・無効 + +```python +@property +def SELECTED_TRANSLATION_ENGINES(self) -> Dict[str, str] +``` +- 選択されている翻訳エンジン + +```python +@property +def SELECTED_YOUR_LANGUAGES(self) -> Dict[str, Dict[str, Any]] +``` +- 送信言語設定 + +```python +@property +def SELECTED_TARGET_LANGUAGES(self) -> Dict[str, Dict[str, Any]] +``` +- 受信言語設定 + +### 音声認識設定 + +```python +@property +def ENABLE_TRANSCRIPTION_SEND(self) -> bool +``` +- 送信音声認識の有効・無効 + +```python +@property +def SELECTED_TRANSCRIPTION_ENGINE(self) -> str +``` +- 音声認識エンジン + +```python +@property +def SELECTED_MIC_DEVICE(self) -> str +``` +- 選択されたマイクデバイス + +```python +@property +def MIC_THRESHOLD(self) -> int +``` +- マイク音声しきい値 + +```python +@property +def MIC_RECORD_TIMEOUT(self) -> int +``` +- マイク録音タイムアウト(秒) + +### VR設定 + +```python +@property +def OVERLAY_SMALL_LOG(self) -> bool +``` +- 小型ログオーバーレイの有効・無効 + +```python +@property +def OVERLAY_SMALL_LOG_SETTINGS(self) -> Dict[str, Any] +``` +- 小型オーバーレイの詳細設定 + +```python +@property +def OVERLAY_LARGE_LOG_SETTINGS(self) -> Dict[str, Any] +``` +- 大型オーバーレイの詳細設定 + +### 通信設定 + +```python +@property +def OSC_IP_ADDRESS(self) -> str +``` +- OSC通信IPアドレス + +```python +@property +def OSC_PORT(self) -> int +``` +- OSC通信ポート + +```python +@property +def WEBSOCKET_HOST(self) -> str +``` +- WebSocketサーバーホスト + +```python +@property +def WEBSOCKET_PORT(self) -> int +``` +- WebSocketサーバーポート + +### 計算デバイス設定 + +```python +@property +def SELECTED_TRANSLATION_COMPUTE_DEVICE(self) -> Dict[str, Any] +``` +- 翻訳用計算デバイス + +```python +@property +def SELECTED_TRANSCRIPTION_COMPUTE_DEVICE(self) -> Dict[str, Any] +``` +- 音声認識用計算デバイス + +## 主要メソッド + +### 設定保存 + +```python +saveConfig(key: str, value: Any, immediate_save: bool = False) -> None +``` +- 設定値の保存(デバウンス付き) +- immediate_save=Trueで即座保存 + +```python +saveConfigToFile() -> None +``` +- 設定ファイルへの直接保存 + +### 初期化・設定読み込み + +```python +init_config() -> None +``` +- 設定の初期化 +- デフォルト値の設定 + +```python +load_config() -> None +``` +- 設定ファイルからの読み込み +- 存在しない場合はデフォルト設定を作成 + +## デコレータ機能 + +### @json_serializable +```python +@json_serializable("setting_name") +@property +def SETTING_NAME(self) -> Any: +``` +- 設定のJSONシリアライゼーション対象指定 +- 自動的にconfig.jsonに保存される設定を定義 + +## 使用方法 + +### 基本的な使い方 + +```python +from config import config + +# 設定値の取得 +version = config.VERSION +ui_language = config.UI_LANGUAGE +translation_enabled = config.ENABLE_TRANSLATION + +# 設定値の変更 +config.UI_LANGUAGE = "ja" +config.TRANSPARENCY = 80 +config.MIC_THRESHOLD = 1500 +``` + +### 複雑な設定の変更 + +```python +# 翻訳エンジンの設定 +engines = config.SELECTED_TRANSLATION_ENGINES +engines["1"] = "DeepL" +config.SELECTED_TRANSLATION_ENGINES = engines + +# オーバーレイ設定の変更 +overlay_settings = config.OVERLAY_SMALL_LOG_SETTINGS +overlay_settings["x_pos"] = 0.5 +overlay_settings["opacity"] = 0.8 +config.OVERLAY_SMALL_LOG_SETTINGS = overlay_settings +``` + +### 即座保存 + +```python +# 重要な設定変更時の即座保存 +config.saveConfig("ENABLE_TRANSLATION", True, immediate_save=True) +``` + +## 設定ファイル形式 + +設定は`config.json`ファイルにJSON形式で保存されます: + +```json +{ + "UI_LANGUAGE": "ja", + "TRANSPARENCY": 85, + "UI_SCALING": 100, + "ENABLE_TRANSLATION": true, + "SELECTED_TRANSLATION_ENGINES": { + "1": "DeepL", + "2": "Google", + "3": "CTranslate2" + }, + "OVERLAY_SMALL_LOG_SETTINGS": { + "x_pos": 0.0, + "y_pos": -0.4, + "z_pos": 1.0, + "opacity": 1.0, + "ui_scaling": 1.0, + "display_duration": 5, + "fadeout_duration": 1 + } +} +``` + +## デフォルト設定 + +### UI設定 +- UI言語: "en"(英語) +- 透明度: 85% +- UIスケーリング: 100% +- フォント: "Noto Sans JP" + +### 翻訳設定 +- 翻訳機能: 無効 +- デフォルトエンジン: "Google" +- 送信言語: English(US) +- 受信言語: 日本語 + +### 音声認識設定 +- 送信音声認識: 無効 +- 受信音声認識: 無効 +- 音声認識エンジン: "Google" +- マイクしきい値: 300 + +### VR設定 +- 小型オーバーレイ: 無効 +- 大型オーバーレイ: 無効 +- オーバーレイ位置: HMD正面 + +### 通信設定 +- OSC IP: "127.0.0.1" +- OSC ポート: 9000 +- WebSocket ホスト: "127.0.0.1" +- WebSocket ポート: 8765 + +## 依存関係 + +### 必須依存関係 +- `json`: 設定ファイルのシリアライゼーション +- `threading`: デバウンス機能 +- `typing`: 型注釈 + +### オプション依存関係 +- `device_manager`: デバイス情報取得 +- `torch`: CUDA計算デバイス情報 +- 各種モデルモジュール: 言語・エンジン情報 + +## エラーハンドリング + +- 設定ファイル読み込みエラーの適切な処理 +- 不正な設定値の検証・補正 +- オプション依存関係の欠如に対するフォールバック +- ファイル書き込みエラーの処理 + +## パフォーマンス特性 + +### デバウンス機能 +- 設定変更から2秒後に自動保存 +- 連続する変更の統合 +- I/O負荷の軽減 + +### 遅延初期化 +- 重い依存関係の遅延読み込み +- インポート時間の短縮 + +### メモリ効率 +- 設定データのシングルトン管理 +- 不要な複製の防止 + +## 注意事項 + +- 設定変更は即座にメモリに反映される +- ファイル保存はデバウンス機能により遅延される +- 重要な設定はimmediate_save=Trueを使用 +- オプション依存関係の欠如時はデフォルト値を使用 +- 不正な設定値は自動的に補正される +- 設定ファイルが破損した場合は新規作成される + +## セキュリティ考慮事項 + +- 設定ファイルの適切な権限管理 +- 外部入力値の検証 +- APIキー等の機密情報の適切な取り扱い +- パスインジェクション攻撃の防止 \ No newline at end of file diff --git a/src-python/docs/details/controller.md b/src-python/docs/details/controller.md new file mode 100644 index 00000000..ca1fefda --- /dev/null +++ b/src-python/docs/details/controller.md @@ -0,0 +1,349 @@ +# controller.py - VRCTコントローラーモジュール + +## 概要 + +VRCTアプリケーションのビジネスロジックを制御するコントローラークラスです。UI層とモデル層の間に位置し、ユーザーの入力を適切な処理に変換し、結果を UI に返す役割を担います。全ての機能制御、設定管理、状態管理を一元的に行います。 + +## 主要機能 + +### 機能制御 +- 翻訳機能の有効化・無効化 +- 音声認識機能の制御 +- VRオーバーレイの管理 +- WebSocketサーバーの制御 + +### 設定管理 +- アプリケーション設定の取得・更新 +- デバイス設定の管理 +- 言語・エンジン設定の制御 + +### 状態管理 +- システム状態の監視 +- エラー状態の管理 +- 初期化プロセスの制御 + +### 通信制御 +- OSC通信の管理 +- WebSocket通信の制御 +- 外部アプリケーション連携 + +## クラス構造 + +### Controller クラス +```python +class Controller: + def __init__(self) -> None +``` + +中核となるコントローラークラス + +### 内部ヘルパークラス + +#### DownloadCTranslate2 クラス +```python +class DownloadCTranslate2: + def progressBar(self, progress) -> None + def downloaded(self) -> None +``` +- 翻訳モデルのダウンロード進捗管理 + +#### DownloadWhisper クラス +```python +class DownloadWhisper: + def progressBar(self, progress) -> None + def downloaded(self) -> None +``` +- 音声認識モデルのダウンロード進捗管理 + +## 主要メソッド + +### 初期化・設定 + +```python +init() -> None +``` +- コントローラーの初期化 +- 各コンポーネントの起動 +- 初期設定の適用 + +```python +setInitMapping(init_mapping: dict) -> None +setRunMapping(run_mapping: dict) -> None +setRun(run: Callable) -> None +``` +- エンドポイント・コールバック設定 + +### 翻訳機能制御 + +```python +setEnableTranslation(data) -> dict +setDisableTranslation(data) -> dict +``` +- 翻訳機能の有効化・無効化 + +```python +setSelectedTranslationEngines(data) -> dict +getSelectedTranslationEngines(data) -> dict +``` +- 翻訳エンジンの選択・取得 + +```python +setSelectedYourLanguages(data) -> dict +setSelectedTargetLanguages(data) -> dict +``` +- 送信・受信言語の設定 + +```python +sendMessageBox(data) -> dict +``` +- メッセージの翻訳・送信処理 + +### 音声認識機能制御 + +```python +setEnableTranscriptionSend(data) -> dict +setEnableTranscriptionReceive(data) -> dict +``` +- 音声認識機能の有効化 + +```python +setSelectedTranscriptionEngine(data) -> dict +getSelectedTranscriptionEngine(data) -> dict +``` +- 音声認識エンジンの選択・取得 + +```python +setSelectedMicDevice(data) -> dict +setSelectedSpeakerDevice(data) -> dict +``` +- 音声デバイスの選択 + +```python +setMicThreshold(data) -> dict +setSpeakerThreshold(data) -> dict +``` +- 音声しきい値の設定 + +### VRオーバーレイ制御 + +```python +setEnableOverlaySmallLog(data) -> dict +setEnableOverlayLargeLog(data) -> dict +``` +- VRオーバーレイの有効化 + +```python +setOverlaySmallLogSettings(data) -> dict +setOverlayLargeLogSettings(data) -> dict +``` +- オーバーレイ設定の更新 + +### WebSocket制御 + +```python +setEnableWebSocketServer(data) -> dict +setDisableWebSocketServer(data) -> dict +``` +- WebSocketサーバーの制御 + +```python +setWebSocketHost(data) -> dict +setWebSocketPort(data) -> dict +``` +- WebSocket接続設定 + +### システム管理 + +```python +updateSoftware(data) -> dict +updateCudaSoftware(data) -> dict +``` +- ソフトウェアアップデート + +```python +downloadCtranslate2Weight(data) -> dict +downloadWhisperWeight(data) -> dict +``` +- AIモデルのダウンロード + +```python +feedWatchdog(data) -> dict +``` +- ウォッチドッグの生存シグナル送信 + +## 使用方法 + +### 基本的な使い方 + +```python +from controller import Controller + +# コントローラーの初期化 +controller = Controller() +controller.init() + +# 翻訳機能の有効化 +result = controller.setEnableTranslation(None) +print(f"翻訳機能: {result}") + +# メッセージ送信 +message_data = {"id": "123", "message": "Hello World"} +result = controller.sendMessageBox(message_data) +``` + +### エンドポイント設定 + +```python +# マッピング設定 +mapping = { + "/set/enable/translation": controller.setEnableTranslation, + "/get/data/version": controller.getVersion, +} + +# 実行関数の設定 +def run_callback(status, endpoint, result): + print(f"Status: {status}, Endpoint: {endpoint}, Result: {result}") + +controller.setRun(run_callback) +``` + +### 音声認識の設定 + +```python +# マイクデバイスの選択 +host_data = "DirectSound" +result = controller.setSelectedMicHost(host_data) + +device_data = "マイク (USB Audio Device)" +result = controller.setSelectedMicDevice(device_data) + +# 音声認識の開始 +result = controller.setEnableTranscriptionSend(None) +``` + +## レスポンス形式 + +全てのメソッドは統一されたレスポンス形式を返します: + +```python +{ + "status": int, # HTTPステータスコード(200, 400, 500等) + "result": any # 処理結果(成功時)または エラーメッセージ(失敗時) +} +``` + +### 成功レスポンス例 +```python +{ + "status": 200, + "result": "翻訳機能が有効化されました" +} +``` + +### エラーレスポンス例 +```python +{ + "status": 400, + "result": "Invalid device selection" +} +``` + +## 状態管理 + +### システム状態 +- 各機能の有効・無効状態 +- デバイスの接続状態 +- ネットワーク接続状態 + +### エラー状態 +- デバイスエラー +- 翻訳エンジンエラー +- VRAMオーバーフローエラー + +### 初期化状態 +- 段階的な初期化プロセス +- 依存関係の解決状態 + +## イベント処理 + +### 音声認識イベント + +```python +micMessage(result: dict) -> None +``` +- マイク音声認識結果の処理 +- 翻訳・フィルタリング・送信 + +```python +speakerMessage(result: dict) -> None +``` +- スピーカー音声認識結果の処理 + +### ダウンロードイベント +- 進捗通知 +- 完了通知 +- エラー通知 + +### デバイス変更イベント +- マイク・スピーカーの選択変更 +- 計算デバイスの変更 + +## 依存関係 + +### 直接依存 +- `config`: 設定管理 +- `model`: コアモデル機能 +- `device_manager`: デバイス管理 +- `utils`: ユーティリティ機能 + +### 間接依存 +- 各種モデルモジュール(翻訳、音声認識等) +- VRオーバーレイモジュール +- 通信モジュール + +## エラーハンドリング + +### VRAM不足エラー +- 自動的にCTranslate2への切り替え +- ユーザーへの適切な通知 + +### デバイスエラー +- デバイス接続状態の監視 +- 自動復旧機能 + +### ネットワークエラー +- 接続状態の定期確認 +- オフライン機能への切り替え + +### 設定エラー +- 設定値の妥当性チェック +- デフォルト値への復帰 + +## パフォーマンス考慮事項 + +### 遅延初期化 +- 必要な時点での機能初期化 +- メモリ使用量の最適化 + +### 非同期処理 +- バックグラウンドでの重い処理 +- UI の応答性維持 + +### キャッシュ機能 +- 設定値のキャッシュ +- 翻訳結果のキャッシュ + +## 注意事項 + +- すべてのメソッドは例外安全である +- 設定変更は即座に config に反映される +- 重い処理は別スレッドで実行される +- VR機能は適切な環境でのみ動作する +- ネットワーク機能はオフライン時に制限される + +## セキュリティ考慮事項 + +- 外部入力の適切な検証 +- APIキーの安全な管理 +- ファイルアクセスの制限 +- ネットワーク通信の暗号化(該当する場合) \ No newline at end of file diff --git a/src-python/docs/details/mainloop.md b/src-python/docs/details/mainloop.md new file mode 100644 index 00000000..22a086ed --- /dev/null +++ b/src-python/docs/details/mainloop.md @@ -0,0 +1,275 @@ +# mainloop.py - VRCTメインループモジュール + +## 概要 + +VRCTアプリケーションのメインイベントループを管理するモジュールです。標準入力からのJSONリクエストを処理し、適切なコントローラーメソッドを呼び出してレスポンスを返す、アプリケーションの中枢的な役割を担います。 + +## 主要機能 + +### リクエスト処理システム +- JSON形式の標準入力からのリクエスト受信 +- エンドポイントベースのルーティング +- 非同期・並列処理対応 + +### エンドポイント管理 +- RESTライクなエンドポイント構造 +- 機能別のエンドポイント分類 +- 排他制御によるスレッドセーフティ + +### 初期化システム +- アプリケーション設定の初期化 +- コンポーネント間の依存関係解決 +- 段階的な機能有効化 + +## クラス構造 + +### Main クラス +```python +class Main: + def __init__(self, controller_instance: Controller, mapping_data: dict, worker_count: int = 3) +``` + +- メインループの制御 +- ワーカースレッドプール管理 +- エンドポイント排他制御 + +## エンドポイント分類 + +### 機能制御系 +``` +/set/enable/* - 各機能の有効化 +/set/disable/* - 各機能の無効化 +``` + +### データ操作系 +``` +/get/data/* - 設定データの取得 +/set/data/* - 設定データの更新 +/delete/data/* - データの削除 +``` + +### 実行系 +``` +/run/* - 各種処理の実行 +``` + +## 主要エンドポイント + +### 翻訳機能 +- `/set/enable/translation`: 翻訳機能の有効化 +- `/set/disable/translation`: 翻訳機能の無効化 +- `/set/data/selected_translation_engines`: 翻訳エンジンの選択 +- `/run/send_message_box`: メッセージ送信 + +### 音声認識機能 +- `/set/enable/transcription_send`: 送信音声認識の有効化 +- `/set/enable/transcription_receive`: 受信音声認識の有効化 +- `/set/data/selected_transcription_engine`: 音声認識エンジン選択 + +### VR機能 +- `/set/data/overlay_small_log_settings`: 小型オーバーレイ設定 +- `/set/data/overlay_large_log_settings`: 大型オーバーレイ設定 + +### WebSocket機能 +- `/set/enable/websocket_server`: WebSocketサーバー有効化 +- `/set/data/websocket_host`: サーバーホスト設定 +- `/set/data/websocket_port`: サーバーポート設定 + +### システム管理 +- `/run/update_software`: ソフトウェアアップデート +- `/run/download_ctranslate2_weight`: 翻訳モデルダウンロード +- `/run/download_whisper_weight`: 音声認識モデルダウンロード + +## 主要メソッド + +### リクエスト処理 + +```python +receiver() -> None +``` +- 標準入力からのJSONリクエスト受信 +- パースエラーの適切な処理 + +```python +handleRequest(endpoint: str, data: Any = None) -> tuple +``` +- エンドポイント処理の実行 +- ステータスコードと結果の返却 + +```python +handler() -> None +``` +- ワーカースレッドのメイン処理 +- キューからのリクエスト取得・処理 + +### スレッド管理 + +```python +startReceiver() -> None +``` +- レシーバースレッドの起動 + +```python +startHandler() -> None +``` +- ハンドラースレッドプールの起動 + +```python +start() -> None +``` +- 全スレッドの起動 + +```python +stop(wait: float = 2.0) -> None +``` +- 全スレッドの安全な停止 + +## 使用方法 + +### 基本的な使い方 + +```python +from mainloop import main_instance + +# メインループの開始 +main_instance.start() + +# ウォッチドッグコールバックの設定 +main_instance.controller.setWatchdogCallback(main_instance.stop) + +# コントローラーの初期化 +main_instance.controller.init() +``` + +### 直接リクエスト処理 + +```python +# エンドポイントの直接呼び出し +result, status = main_instance.handleRequest("/get/data/version", None) +print(f"バージョン: {result}") + +# 翻訳機能の有効化 +result, status = main_instance.handleRequest("/set/enable/translation", None) +``` + +### 標準入力からの処理 + +```json +{ + "endpoint": "/run/send_message_box", + "data": "eyJpZCI6ICIxMjMiLCAibWVzc2FnZSI6ICJIZWxsbyBXb3JsZCJ9" +} +``` + +## リクエスト形式 + +### 入力形式 +```json +{ + "endpoint": "string", // 必須:処理対象のエンドポイント + "data": "string|null" // オプション:Base64エンコード済みデータ +} +``` + +### 出力形式 +```json +{ + "status": 200, // HTTPステータスコード + "endpoint": "string", // 処理されたエンドポイント + "result": "any" // 処理結果 +} +``` + +## ステータスコード + +- `200`: 成功 +- `400`: 不正なリクエスト +- `404`: 存在しないエンドポイント +- `423`: ロック中(機能が無効化されている) +- `500`: 内部エラー + +## 排他制御 + +### ロック機能 +- enable/disableペアは同一ロックキーを共有 +- 同一機能の同時実行を防止 +- デッドロックを回避する設計 + +### ロックキー正規化 +```python +/set/enable/translation -> /lock/set/translation +/set/disable/translation -> /lock/set/translation +``` + +## 初期化プロセス + +### 段階的初期化 +1. コントローラーの初期化 +2. デバイスマネージャーの初期化 +3. モデルの初期化 +4. 各機能の段階的有効化 + +### 初期化mapping +- `/get/data/*`エンドポイントから初期化設定を自動抽出 +- システム起動時の設定復元 + +## ログ機能 + +### プロセスログ +- 全リクエスト・レスポンスの記録 +- JSON形式での構造化ログ + +### エラーログ +- 例外の詳細記録 +- スタックトレースの保存 + +## 依存関係 + +### 直接依存 +- `controller`: ビジネスロジック制御 +- `utils`: ユーティリティ機能(ログ、エンコード等) + +### 間接依存 +- `config`: 設定管理 +- `model`: コアモデル機能 +- `device_manager`: デバイス管理 + +## 設定項目 + +### ワーカー数 +```python +DEFAULT_WORKER_COUNT = 3 # 並列処理スレッド数 +``` + +### タイムアウト +- キュー待機タイムアウト: 0.5秒 +- スレッド停止待機: 2.0秒 +- 処理安定化待機: 0.2秒 + +## エラーハンドリング + +- JSONパースエラーの適切な処理 +- エンドポイント実行エラーのキャッチ +- スレッドセーフなエラーログ記録 +- グレースフルシャットダウン + +## パフォーマンス特性 + +### スループット +- 複数ワーカーによる並列処理 +- ノンブロッキングI/O + +### レイテンシ +- キューイング遅延の最小化 +- 排他制御による一時的な遅延あり + +### メモリ使用量 +- リクエストキューのサイズ制限なし(要注意) +- スレッドプールによる固定オーバーヘッド + +## 注意事項 + +- 標準入力をブロッキングで読み取るため、パイプ経由での使用を想定 +- エンドポイント名の大文字小文字は区別される +- Base64データは自動的にデコードされる +- 長時間のブロッキング処理は他のリクエストに影響する可能性 \ No newline at end of file diff --git a/src-python/docs/details/model.md b/src-python/docs/details/model.md new file mode 100644 index 00000000..9880730c --- /dev/null +++ b/src-python/docs/details/model.md @@ -0,0 +1,292 @@ +# model.py - VRCTコアモデルクラス + +## 概要 + +VRCTアプリケーションの中核となるModelクラスを定義するモジュールです。音声認識、翻訳、VRオーバーレイ、OSC通信、WebSocketサーバーなどの主要機能を統合管理し、システム全体の動作を制御します。 + +## 主要機能 + +### シングルトンパターン +- アプリケーション全体で単一のModelインスタンスを保証 +- 遅延初期化による軽量なインポート + +### 音声認識機能 +- マイク音声のリアルタイム文字起こし +- スピーカー出力の音声認識 +- エネルギーレベル監視 +- 複数言語対応 + +### 翻訳機能 +- 複数の翻訳エンジン対応(DeepL、Google、CTranslate2等) +- 言語自動検出 +- バッチ翻訳処理 + +### VRオーバーレイ +- OpenVR統合 +- 小型・大型ログオーバーレイ +- 動的配置・透明度制御 + +### OSC通信 +- VRChatとのOSC通信 +- タイピング状態の同期 +- ミュート状態の監視 + +### WebSocketサーバー +- 外部アプリケーションとの通信 +- リアルタイムメッセージ配信 + +## クラス構造 + +### threadFnc クラス +```python +class threadFnc(Thread): + def __init__(self, fnc, end_fnc=None, daemon: bool = True, *args, **kwargs) +``` + +- 関数を繰り返し実行するスレッドラッパー +- 一時停止・再開機能 +- エラー保護機能 + +### Model クラス +```python +class Model: + def __new__(cls) # シングルトンパターン + def init(self) # 重い初期化処理 + def ensure_initialized(self) # 遅延初期化 +``` + +## 主要メソッド + +### 初期化・管理 + +```python +init() -> None +``` +- 全コンポーネントの初期化 +- 重い処理のため明示的に呼び出し + +```python +ensure_initialized() -> None +``` +- 必要時の自動初期化 +- 安全な遅延初期化 + +### 翻訳機能 + +```python +getInputTranslate(message, source_language=None) -> Tuple[List[str], List[bool]] +``` +- 入力メッセージの多言語翻訳 +- 成功フラグも同時に返却 + +```python +getOutputTranslate(message, source_language=None) -> Tuple[List[str], List[bool]] +``` +- 出力メッセージの翻訳(逆方向) + +```python +authenticationTranslatorDeepLAuthKey(auth_key) -> bool +``` +- DeepL APIキーの認証 + +### 音声認識機能 + +```python +startMicTranscript(fnc: Callable) -> None +``` +- マイク音声認識の開始 +- コールバック関数で結果を通知 + +```python +startSpeakerTranscript(fnc: Callable) -> None +``` +- スピーカー音声認識の開始 + +```python +pauseMicTranscript() -> None +resumeMicTranscript() -> None +``` +- 音声認識の一時停止・再開 + +```python +startCheckMicEnergy(fnc: Callable) -> None +startCheckSpeakerEnergy(fnc: Callable) -> None +``` +- 音声エネルギーレベルの監視 + +### VRオーバーレイ機能 + +```python +createOverlayImageSmallLog(message, your_language, translation, target_language) -> Image +``` +- 小型ログオーバーレイ画像の生成 + +```python +createOverlayImageLargeLog(message_type, message, your_language, translation, target_language) -> Image +``` +- 大型ログオーバーレイ画像の生成 + +```python +updateOverlaySmallLogSettings() -> None +updateOverlayLargeLogSettings() -> None +``` +- オーバーレイ設定の更新 + +### OSC通信機能 + +```python +oscSendMessage(message: str) -> None +``` +- VRChatへのメッセージ送信 + +```python +oscStartSendTyping() -> None +oscStopSendTyping() -> None +``` +- タイピング状態の通知 + +```python +setMuteSelfStatus() -> None +``` +- VRChatミュート状態の取得 + +### WebSocket機能 + +```python +startWebSocketServer(host: str, port: int) -> None +``` +- WebSocketサーバーの起動 + +```python +websocketSendMessage(message_dict: dict) -> bool +``` +- 全クライアントへのメッセージ送信 + +```python +checkWebSocketServerAlive() -> bool +``` +- サーバー稼働状態の確認 + +### ファイルダウンロード機能 + +```python +downloadCTranslate2ModelWeight(weight_type, callback=None, end_callback=None) +``` +- 翻訳モデルのダウンロード + +```python +downloadWhisperModelWeight(weight_type, callback=None, end_callback=None) +``` +- 音声認識モデルのダウンロード + +### ウォッチドッグ機能 + +```python +startWatchdog() -> None +feedWatchdog() -> None +setWatchdogCallback(callback: Callable) -> None +``` +- システム監視とタイムアウト処理 + +## 使用方法 + +### 基本的な使い方 + +```python +from model import model + +# 明示的な初期化(推奨) +model.init() + +# または自動初期化 +model.ensure_initialized() + +# 翻訳機能の使用 +translations, success_flags = model.getInputTranslate("Hello World") + +# 音声認識の開始 +def on_transcript_result(result): + print(f"認識結果: {result}") + +model.startMicTranscript(on_transcript_result) +``` + +### VRオーバーレイの使用 + +```python +# オーバーレイの開始 +model.startOverlay() + +# 画像の作成と更新 +img = model.createOverlayImageSmallLog( + message="Hello", + your_language="English", + translation=["こんにちは"], + target_language={"1": {"language": "Japanese", "enable": True}} +) +model.updateOverlaySmallLog(img) +``` + +### WebSocketサーバーの使用 + +```python +# サーバー起動 +model.startWebSocketServer("127.0.0.1", 8765) + +# メッセージ送信 +message = {"type": "translation", "text": "Hello", "translation": "こんにちは"} +success = model.websocketSendMessage(message) +``` + +## 依存関係 + +### 必須モジュール +- `controller`: アプリケーション制御 +- `config`: 設定管理 +- `device_manager`: デバイス管理 + +### 音声・翻訳関連 +- `models.transcription.*`: 音声認識 +- `models.translation.*`: 翻訳機能 +- `models.transliteration.*`: 音写変換 + +### VR・通信関連 +- `models.overlay.*`: VRオーバーレイ +- `models.osc.*`: OSC通信 +- `models.websocket.*`: WebSocket通信 + +### ユーティリティ +- `models.watchdog.*`: 監視機能 +- `utils`: 共通ユーティリティ +- `flashtext`: キーワードフィルタリング + +## 設定依存関係 + +多くの機能がconfigモジュールの設定に依存: + +- 音声認識設定(しきい値、タイムアウト等) +- 翻訳設定(エンジン選択、言語設定等) +- VR設定(オーバーレイ位置、透明度等) +- OSC設定(IPアドレス、ポート等) + +## エラーハンドリング + +- 初期化エラーの適切な処理 +- VRAM不足エラーの検出と対応 +- ネットワークエラーの回復機能 +- スレッドセーフティの保証 + +## 注意事項 + +- 重い初期化処理のため、明示的な初期化を推奨 +- OpenVR環境が必要(VRオーバーレイ使用時) +- CUDA環境推奨(高速な音声認識・翻訳) +- WebSocketサーバーは非同期で動作 +- 音声デバイスのアクセス権限が必要 + +## パフォーマンス考慮事項 + +- 遅延初期化によるメモリ使用量の最適化 +- スレッドプールによる並行処理 +- モデルの重複読み込み防止 +- キューイングによる非同期処理 \ No newline at end of file diff --git a/src-python/docs/details/osc.md b/src-python/docs/details/osc.md new file mode 100644 index 00000000..72ffefa3 --- /dev/null +++ b/src-python/docs/details/osc.md @@ -0,0 +1,602 @@ +# osc.py - OSC通信・OSCQueryプロトコル管理 + +## 概要 + +VRChatとの高度なOSC(Open Sound Control)通信を管理する包括的なシステムです。基本的なOSCメッセージ送信に加え、OSCQueryプロトコルによる双方向通信、パラメータ監視、自動サービス発見機能を提供します。 + +## 主要機能 + +### OSC通信機能 +- VRChatチャットボックスへのメッセージ送信 +- タイピング状態の制御 +- パラメータ値の動的取得 + +### OSCQuery対応 +- 自動サービス発見・接続 +- リアルタイムパラメータ監視 +- 双方向エンドポイント公開 + +### 堅牢性機能 +- 防御的プログラミング設計 +- 欠損ライブラリの優雅な処理 +- 自動エラー復旧機構 + +## クラス構造 + +### OSCHandler クラス + +```python +class OSCHandler: + def __init__(self, ip_address: str = "127.0.0.1", port: int = 9000) -> None: + self.is_osc_query_enabled: bool + self.osc_ip_address: str + self.osc_port: int + self.udp_client: udp_client.SimpleUDPClient + self.osc_server: Optional[osc_server.ThreadingOSCUDPServer] + self.osc_query_service: Optional[OSCQueryService] + self.browser: Optional[OSCQueryBrowser] +``` + +OSC通信の中核管理クラス + +#### 属性 +- **is_osc_query_enabled**: OSCQuery機能の有効性フラグ +- **osc_ip_address**: 送信先IPアドレス +- **osc_port**: UDP通信ポート +- **udp_client**: OSC送信クライアント +- **osc_server**: ローカルOSCサーバー +- **osc_query_service**: OSCQueryサービスインスタンス +- **browser**: OSCQueryブラウザー + +## 主要メソッド + +### メッセージ送信 + +```python +def sendMessage(self, message: str = "", notification: bool = True) -> None +``` + +VRChatチャットボックスにメッセージを送信 + +#### パラメータ +- **message**: 送信するテキストメッセージ +- **notification**: 通知フラグ(音・表示の有無) + +```python +def sendTyping(self, flag: bool = False) -> None +``` + +タイピング状態をVRChatに送信 + +#### パラメータ +- **flag**: タイピング中フラグ + +### パラメータ監視 + +```python +def getOSCParameterMuteSelf() -> Optional[bool] +``` + +VRChatのMuteSelfパラメータ値を取得 + +#### 戻り値 +- **Optional[bool]**: ミュート状態(取得失敗時はNone) + +```python +def getOSCParameterValue(self, address: str) -> Any +``` + +任意のOSCパラメータ値を取得 + +#### パラメータ +- **address**: OSCアドレス(例:"/avatar/parameters/MuteSelf") + +#### 戻り値 +- **Any**: パラメータ値(取得失敗時はNone) + +### 設定変更 + +```python +def setOscIpAddress(self, ip_address: str) -> None +``` + +送信先IPアドレスを変更し、サービスを再初期化 + +#### パラメータ +- **ip_address**: 新しいIPアドレス + +```python +def setOscPort(self, port: int) -> None +``` + +送信ポートを変更し、サービスを再初期化 + +#### パラメータ +- **port**: 新しいUDPポート番号 + +## 使用方法 + +### 基本的なメッセージ送信 + +```python +from models.osc.osc import OSCHandler + +# OSCハンドラーの初期化 +osc = OSCHandler(ip_address="127.0.0.1", port=9000) + +# チャットボックスにメッセージを送信 +osc.sendMessage("こんにちは、VRChat!", notification=True) + +# タイピング状態の制御 +osc.sendTyping(True) # タイピング開始 +# ... 実際のタイピング処理 ... +osc.sendTyping(False) # タイピング終了 + +# 再度メッセージ送信 +osc.sendMessage("翻訳完了しました", notification=False) +``` + +### リモートVRChatへの接続 + +```python +# リモートVRChatインスタンスへの接続 +remote_osc = OSCHandler(ip_address="192.168.1.100", port=9000) + +# OSCQuery機能は自動的に無効化される +print(f"OSCQuery有効: {remote_osc.getIsOscQueryEnabled()}") # False + +# 基本的なメッセージ送信は利用可能 +remote_osc.sendMessage("リモートからの翻訳結果", notification=True) +``` + +### パラメータ監視(ローカル接続時のみ) + +```python +# ローカル接続でのパラメータ監視 +local_osc = OSCHandler(ip_address="127.0.0.1", port=9000) + +if local_osc.getIsOscQueryEnabled(): + # MuteSelfパラメータの監視 + mute_status = local_osc.getOSCParameterMuteSelf() + + if mute_status is not None: + if mute_status: + print("ユーザーはミュート中です") + else: + print("ユーザーはミュート解除中です") + else: + print("MuteSelfパラメータの取得に失敗しました") + + # カスタムパラメータの監視 + custom_value = local_osc.getOSCParameterValue("/avatar/parameters/CustomParam") + if custom_value is not None: + print(f"カスタムパラメータ値: {custom_value}") +``` + +### 双方向OSC通信の設定 + +```python +def handle_mute_change(address, *args): + """ミュート状態変更のハンドラー""" + print(f"ミュート状態が変更されました: {args}") + +def handle_typing_change(address, *args): + """タイピング状態変更のハンドラー""" + print(f"タイピング状態: {args}") + +def handle_chatbox_input(address, *args): + """チャットボックス入力のハンドラー""" + print(f"チャットボックス入力: {args}") + +# OSCパラメータハンドラーの設定 +osc_handlers = { + "/avatar/parameters/MuteSelf": handle_mute_change, + "/chatbox/typing": handle_typing_change, + "/chatbox/input": handle_chatbox_input +} + +osc = OSCHandler() +osc.setDictFilterAndTarget(osc_handlers) + +# OSCサーバー開始(OSCQuery自動公開) +osc.receiveOscParameters() + +print("OSC受信サーバーが開始されました") +print("VRChatからのパラメータ変更を監視中...") + +# メッセージ送信テスト +import time +time.sleep(2) +osc.sendMessage("双方向通信テスト", notification=True) + +# 長時間実行 +time.sleep(30) + +# クリーンアップ +osc.oscServerStop() +``` + +### 動的設定変更 + +```python +# 実行時のIP・ポート変更 +osc = OSCHandler(ip_address="127.0.0.1", port=9000) + +# 初期設定でローカル接続 +osc.sendMessage("ローカル接続テスト") + +print("リモート接続に切り替え中...") +osc.setOscIpAddress("192.168.1.150") # 自動的にOSCQueryが無効化 +osc.sendMessage("リモート接続テスト") + +print("ポート変更...") +osc.setOscPort(9001) +osc.sendMessage("新しいポートでのテスト") + +print("ローカル接続に戻る...") +osc.setOscIpAddress("127.0.0.1") # OSCQueryが再度有効化 +osc.sendMessage("ローカル接続復帰テスト") +``` + +## OSCQuery詳細機能 + +### 自動サービス発見 + +```python +class VRChatMonitor: + """VRChatサービス監視クラス""" + + def __init__(self): + self.osc = OSCHandler() + self.monitoring = False + + def start_monitoring(self): + """VRChatパラメータの継続監視開始""" + + if not self.osc.getIsOscQueryEnabled(): + print("OSCQuery機能が無効です(ローカル接続のみサポート)") + return + + # OSCハンドラー設定 + handlers = { + "/avatar/parameters/MuteSelf": self.on_mute_change, + "/avatar/parameters/Voice": self.on_voice_change, + "/avatar/parameters/Viseme": self.on_viseme_change, + "/avatar/parameters/GestureLeft": self.on_gesture_left, + "/avatar/parameters/GestureRight": self.on_gesture_right + } + + self.osc.setDictFilterAndTarget(handlers) + self.osc.receiveOscParameters() + + self.monitoring = True + print("VRChatパラメータ監視を開始しました") + + def on_mute_change(self, address, *args): + print(f"ミュート状態変更: {args[0] if args else 'Unknown'}") + + def on_voice_change(self, address, *args): + print(f"音声レベル: {args[0] if args else 'Unknown'}") + + def on_viseme_change(self, address, *args): + print(f"口形変化: {args[0] if args else 'Unknown'}") + + def on_gesture_left(self, address, *args): + print(f"左手ジェスチャー: {args[0] if args else 'Unknown'}") + + def on_gesture_right(self, address, *args): + print(f"右手ジェスチャー: {args[0] if args else 'Unknown'}") + + def stop_monitoring(self): + """監視停止""" + self.osc.oscServerStop() + self.monitoring = False + print("VRChatパラメータ監視を停止しました") + +# 使用例 +monitor = VRChatMonitor() +monitor.start_monitoring() + +# 監視中に他の処理を実行 +time.sleep(60) # 1分間監視 + +monitor.stop_monitoring() +``` + +### リアルタイムパラメータ追跡 + +```python +class ParameterTracker: + """パラメータ値の追跡・履歴管理""" + + def __init__(self, osc_handler): + self.osc = osc_handler + self.parameter_history = {} + self.tracking_active = False + + def track_parameter(self, address, interval=0.1): + """指定されたパラメータを定期監視""" + + import threading + + def monitoring_loop(): + while self.tracking_active: + try: + value = self.osc.getOSCParameterValue(address) + if value is not None: + timestamp = time.time() + + if address not in self.parameter_history: + self.parameter_history[address] = [] + + # 値が変更された場合のみ記録 + if (not self.parameter_history[address] or + self.parameter_history[address][-1][1] != value): + + self.parameter_history[address].append((timestamp, value)) + print(f"{address}: {value} (時刻: {timestamp:.2f})") + + # 履歴サイズ制限(最新100件まで) + if len(self.parameter_history[address]) > 100: + self.parameter_history[address] = self.parameter_history[address][-100:] + + time.sleep(interval) + + except Exception as e: + print(f"パラメータ追跡エラー: {e}") + time.sleep(interval) + + self.tracking_active = True + thread = threading.Thread(target=monitoring_loop, daemon=True) + thread.start() + + def stop_tracking(self): + """追跡停止""" + self.tracking_active = False + + def get_parameter_history(self, address): + """パラメータの履歴取得""" + return self.parameter_history.get(address, []) + + def get_latest_value(self, address): + """最新パラメータ値取得""" + history = self.get_parameter_history(address) + return history[-1][1] if history else None + +# 使用例 +osc = OSCHandler() +tracker = ParameterTracker(osc) + +# MuteSelfパラメータの追跡開始 +tracker.track_parameter("/avatar/parameters/MuteSelf", interval=0.5) + +# しばらく監視 +time.sleep(30) + +# 結果確認 +mute_history = tracker.get_parameter_history("/avatar/parameters/MuteSelf") +print(f"MuteSelf変更履歴: {len(mute_history)}件") + +for timestamp, value in mute_history[-5:]: # 最新5件表示 + print(f" {time.ctime(timestamp)}: {value}") + +tracker.stop_tracking() +``` + +## エラーハンドリング・復旧機構 + +### 堅牢な接続管理 + +```python +class RobustOSCHandler: + """堅牢性を高めたOSCハンドラー""" + + def __init__(self, ip_address="127.0.0.1", port=9000): + self.osc = OSCHandler(ip_address, port) + self.connection_retries = 3 + self.retry_delay = 1.0 + + def safe_send_message(self, message, notification=True, max_retries=None): + """安全なメッセージ送信(リトライ機構付き)""" + + retries = max_retries or self.connection_retries + + for attempt in range(retries): + try: + self.osc.sendMessage(message, notification) + return True + + except Exception as e: + print(f"送信試行 {attempt + 1}/{retries} 失敗: {e}") + + if attempt < retries - 1: + time.sleep(self.retry_delay * (attempt + 1)) # 指数バックオフ + + # 接続再初期化を試行 + try: + self.osc.udp_client = udp_client.SimpleUDPClient( + self.osc.osc_ip_address, + self.osc.osc_port + ) + except Exception as reconnect_error: + print(f"再接続失敗: {reconnect_error}") + + print(f"メッセージ送信に失敗しました: '{message}'") + return False + + def safe_get_parameter(self, address, timeout=5.0): + """安全なパラメータ取得(タイムアウト付き)""" + + if not self.osc.getIsOscQueryEnabled(): + return None + + import threading + import queue + + result_queue = queue.Queue() + + def parameter_getter(): + try: + value = self.osc.getOSCParameterValue(address) + result_queue.put(value) + except Exception as e: + result_queue.put(e) + + # タイムアウト付きでパラメータ取得 + thread = threading.Thread(target=parameter_getter, daemon=True) + thread.start() + + try: + result = result_queue.get(timeout=timeout) + if isinstance(result, Exception): + raise result + return result + + except queue.Empty: + print(f"パラメータ取得タイムアウト: {address}") + return None + +# 使用例 +robust_osc = RobustOSCHandler() + +# 堅牢な送信 +success = robust_osc.safe_send_message("堅牢性テスト", notification=True) +print(f"送信成功: {success}") + +# 安全なパラメータ取得 +mute_value = robust_osc.safe_get_parameter("/avatar/parameters/MuteSelf", timeout=3.0) +print(f"MuteSelf値: {mute_value}") +``` + +## パフォーマンス最適化 + +### 効率的な通信管理 + +```python +class OptimizedOSCHandler: + """パフォーマンス最適化OSCハンドラー""" + + def __init__(self, ip_address="127.0.0.1", port=9000): + self.osc = OSCHandler(ip_address, port) + self.message_queue = [] + self.batch_size = 10 + self.batch_interval = 0.1 + self.last_batch_time = 0 + + def queue_message(self, message, notification=True): + """メッセージをキューに追加(バッチ送信用)""" + + self.message_queue.append((message, notification)) + + # バッチサイズまたは時間間隔でフラッシュ + current_time = time.time() + + if (len(self.message_queue) >= self.batch_size or + current_time - self.last_batch_time >= self.batch_interval): + self.flush_messages() + + def flush_messages(self): + """キューされたメッセージを一括送信""" + + if not self.message_queue: + return + + # 最新のメッセージのみ送信(重複排除) + if len(self.message_queue) > 1: + # 最後のメッセージを優先 + last_message, last_notification = self.message_queue[-1] + self.osc.sendMessage(last_message, last_notification) + else: + message, notification = self.message_queue[0] + self.osc.sendMessage(message, notification) + + # キューをクリア + self.message_queue.clear() + self.last_batch_time = time.time() + + def send_immediate(self, message, notification=True): + """即座にメッセージ送信(キューをバイパス)""" + self.flush_messages() # 既存キューを先にフラッシュ + self.osc.sendMessage(message, notification) + +# 使用例 +optimized_osc = OptimizedOSCHandler() + +# 複数のメッセージを効率的に送信 +for i in range(20): + optimized_osc.queue_message(f"バッチメッセージ {i}") + time.sleep(0.05) # 短い間隔 + +# 残りのメッセージをフラッシュ +optimized_osc.flush_messages() + +# 即座に送信が必要な重要メッセージ +optimized_osc.send_immediate("緊急メッセージ", notification=True) +``` + +## 依存関係・要件 + +### 必須依存関係 +- `pythonosc`: 基本OSC通信ライブラリ +- `threading`: 並行処理制御 +- `time`: 時間管理機能 + +### オプション依存関係 +- `tinyoscquery`: OSCQuery機能(ローカル接続時のみ) +- `utils`: エラーログ機能(フォールバック処理あり) + +### システム要件 +```python +# 最小システム要件 +requirements = { + "python_version": "3.7+", + "network": "UDP通信対応", + "vrchat_version": "OSCサポート版(2022年8月以降)", + "local_ports": "空きUDP/TCPポート(OSCQuery使用時)" +} + +# 推奨環境 +recommended = { + "network_latency": "< 10ms(ローカル接続)", + "cpu_usage": "OSCQuery使用時は追加CPU負荷", + "memory": "tinyoscquery使用時は追加メモリ" +} +``` + +## 注意事項・制限 + +### OSCQuery制限 +- ローカルホスト(127.0.0.1/localhost)接続時のみ利用可能 +- tinyoscqueryライブラリが必要 +- ファイアウォール設定によっては動作しない可能性 + +### 通信制限 +- UDPプロトコルのため送達保証なし +- VRChatのOSC受信制限(レート制限あり) +- ネットワーク環境による遅延・パケット loss + +### プラットフォーム依存 +```python +# 既知の制限事項 +limitations = { + "windows": "Windowsファイアウォールの設定が必要な場合あり", + "macos": "セキュリティ設定によるポート制限の可能性", + "linux": "一部のLinuxディストリビューションでの互換性問題", + "vrchat_platform": "PC版VRChatのみOSCサポート" +} +``` + +## 関連モジュール + +- `config.py`: OSC設定管理 +- `controller.py`: OSC機能制御インターフェース +- `model.py`: OSC機能統合 +- `utils.py`: エラーログ・ネットワークユーティリティ + +## 将来の改善点 + +- より高度なOSCQueryパラメータ監視 +- カスタムOSCプロトコル拡張 +- パフォーマンス監視・分析機能 +- 自動再接続・復旧機構の改善 +- VRChatアバター固有パラメータ対応 \ No newline at end of file diff --git a/src-python/docs/details/overlay.md b/src-python/docs/details/overlay.md new file mode 100644 index 00000000..e28382a0 --- /dev/null +++ b/src-python/docs/details/overlay.md @@ -0,0 +1,754 @@ +# overlay - VRオーバーレイ統合システム + +## 概要 + +VRChat向けのOpenVRオーバーレイシステムです。翻訳結果や字幕をVR空間内に表示する機能を提供し、HMD・コントローラー追跡、フェード効果、多言語フォント対応を統合的に管理します。 + +## 主要コンポーネント + +### overlay.py - メインオーバーレイ管理 +- OpenVRオーバーレイの生成・配置・制御 +- HMD・左手・右手への追跡設定 +- フェードイン・フェードアウト効果 + +### overlay_image.py - 画像生成・描画 +- 多言語対応テキスト画像生成 +- メッセージログ・履歴表示 +- フォント・レイアウト管理 + +### overlay_utils.py - 数学的変換ユーティリティ +- 3D座標変換行列計算 +- オイラー角・回転行列変換 +- 同次座標系変換 + +## クラス構造 + +### Overlay クラス (overlay.py) + +```python +class Overlay: + def __init__(self, settings_dict: Dict[str, Dict[str, Any]]) -> None: + self.system: Optional[Any] = None # OpenVRシステム + self.overlay: Optional[Any] = None # オーバーレイインターface + self.handle: Dict[str, Any] = {} # サイズ別ハンドル + self.settings: Dict[str, Dict[str, Any]] # サイズ別設定 + self.lastUpdate: Dict[str, float] = {} # 最終更新時刻 + self.fadeRatio: Dict[str, float] = {} # フェード比率 +``` + +VRオーバーレイの総合管理クラス + +#### 主要機能 +- OpenVRの初期化・管理 +- 複数サイズオーバーレイの同時管理 +- リアルタイムフェード効果処理 +- SteamVR接続状態監視 + +### OverlayImage クラス (overlay_image.py) + +```python +class OverlayImage: + LANGUAGES = { + "Default": "NotoSansJP-Regular.ttf", + "Japanese": "NotoSansJP-Regular.ttf", + "Korean": "NotoSansKR-Regular.ttf", + "Chinese Simplified": "NotoSansSC-Regular.ttf", + "Chinese Traditional": "NotoSansTC-Regular.ttf" + } + + def __init__(self, root_path: Optional[str] = None) -> None: + self.message_log: List[dict] = [] + self.root_path: str +``` + +テキスト画像生成・多言語フォント管理クラス + +#### 主要機能 +- 多言語フォント自動選択 +- メッセージ履歴管理 +- 動的画像生成・合成 +- UI要素のサイズ計算 + +## 主要メソッド + +### Overlay クラス + +#### 初期化・制御 + +```python +def startOverlay(self) -> None +``` + +オーバーレイシステム開始 + +```python +def shutdownOverlay(self) -> None +``` + +オーバーレイシステム終了・リソース解放 + +```python +def reStartOverlay(self) -> None +``` + +オーバーレイシステム再起動 + +#### 表示制御 + +```python +def showOverlay(self, image: Image, size: str) -> None +``` + +画像をオーバーレイに表示 + +#### パラメータ +- **image**: 表示するPIL画像 +- **size**: オーバーレイサイズ識別子 + +```python +def setOpacity(self, opacity: float, size: str) -> None +``` + +オーバーレイ透明度設定 + +#### パラメータ +- **opacity**: 透明度(0.0-1.0) +- **size**: 対象サイズ + +```python +def setTrackedDeviceRelative(self, tracker: str, size: str) -> None +``` + +追跡デバイスへのオーバーレイ配置 + +#### パラメータ +- **tracker**: 追跡デバイス("HMD", "LeftHand", "RightHand") +- **size**: オーバーレイサイズ + +### OverlayImage クラス + +#### 画像生成 + +```python +def createOverlayImage(self, message: str, language: str, ui_size: dict, + ui_settings: dict, message_log_settings: dict) -> Image +``` + +オーバーレイ用画像の生成 + +#### パラメータ +- **message**: 表示メッセージ +- **language**: 言語設定 +- **ui_size**: UIサイズ設定 +- **ui_settings**: UI表示設定 +- **message_log_settings**: ログ表示設定 + +#### 戻り値 +- **Image**: 生成されたPIL画像 + +#### 履歴管理 + +```python +def addMessageLog(self, message: str, timestamp: datetime) -> None +``` + +メッセージログに新規追加 + +#### パラメータ +- **message**: 追加するメッセージ +- **timestamp**: タイムスタンプ + +```python +def clearMessageLog(self) -> None +``` + +メッセージログのクリア + +## 使用方法 + +### 基本的なオーバーレイ表示 + +```python +from models.overlay.overlay import Overlay +from models.overlay.overlay_image import OverlayImage +from PIL import Image + +# オーバーレイ設定 +settings = { + "small": { + "width": 0.3, + "height": 0.1, + "x_pos": 0.0, + "y_pos": -0.2, + "z_pos": 1.0, + "opacity": 0.8, + "display_duration": 3.0, + "fadeout_duration": 1.0 + }, + "large": { + "width": 0.5, + "height": 0.2, + "x_pos": 0.0, + "y_pos": -0.3, + "z_pos": 1.2, + "opacity": 0.9, + "display_duration": 5.0, + "fadeout_duration": 1.5 + } +} + +# オーバーレイシステム初期化 +overlay_system = Overlay(settings) +overlay_image = OverlayImage() + +# システム開始 +overlay_system.startOverlay() + +# 翻訳結果の表示 +translation_text = "Hello, world! / こんにちは、世界!" + +# 画像生成設定 +ui_size = OverlayImage.getUiSizeSmallLog() +ui_settings = { + "font_size": 20, + "text_color": (255, 255, 255, 255), + "background_color": (0, 0, 0, 180) +} +message_log_settings = { + "enabled": True, + "max_lines": 5 +} + +# 画像生成・表示 +overlay_img = overlay_image.createOverlayImage( + message=translation_text, + language="Japanese", + ui_size=ui_size, + ui_settings=ui_settings, + message_log_settings=message_log_settings +) + +# オーバーレイに表示 +overlay_system.showOverlay(overlay_img, "small") + +# システム終了 +import time +time.sleep(10) +overlay_system.shutdownOverlay() +``` + +### HMD・コントローラー追跡設定 + +```python +# HMDに固定表示 +overlay_system.setTrackedDeviceRelative("HMD", "large") + +# 左手コントローラーに追従 +overlay_system.setTrackedDeviceRelative("LeftHand", "small") + +# 右手コントローラーに追従 +overlay_system.setTrackedDeviceRelative("RightHand", "small") + +# 位置・回転の微調整(設定変更) +overlay_system.settings["small"]["x_pos"] = 0.1 +overlay_system.settings["small"]["y_pos"] = -0.1 +overlay_system.settings["small"]["z_pos"] = 0.8 +overlay_system.settings["small"]["x_rotation"] = -30.0 +overlay_system.settings["small"]["y_rotation"] = 15.0 + +# 設定を適用 +overlay_system.setTrackedDeviceRelative("LeftHand", "small") +``` + +### フェード効果制御 + +```python +# フェード効果設定 +overlay_system.updateDisplayDuration(4.0, "large") # 4秒表示 +overlay_system.updateFadeoutDuration(2.0, "large") # 2秒でフェードアウト + +# 即座に透明度変更 +overlay_system.setOpacity(0.5, "large") # 50%透明度 + +# フェード効果を無効にして固定表示 +overlay_system.settings["small"]["fadeout_duration"] = 0 +overlay_system.setOpacity(1.0, "small") # 完全不透明で固定 +``` + +### 多言語対応表示 + +```python +# 日本語表示 +japanese_text = "これは日本語のテストです" +jp_image = overlay_image.createOverlayImage( + message=japanese_text, + language="Japanese", + ui_size=ui_size, + ui_settings=ui_settings, + message_log_settings=message_log_settings +) +overlay_system.showOverlay(jp_image, "large") + +# 韓国語表示 +korean_text = "이것은 한국어 테스트입니다" +kr_image = overlay_image.createOverlayImage( + message=korean_text, + language="Korean", + ui_size=ui_size, + ui_settings=ui_settings, + message_log_settings=message_log_settings +) +overlay_system.showOverlay(kr_image, "small") + +# 中国語(簡体字)表示 +chinese_text = "这是中文测试" +cn_image = overlay_image.createOverlayImage( + message=chinese_text, + language="Chinese Simplified", + ui_size=ui_size, + ui_settings=ui_settings, + message_log_settings=message_log_settings +) +overlay_system.showOverlay(cn_image, "large") +``` + +### メッセージログ機能 + +```python +from datetime import datetime + +# メッセージログの追加 +overlay_image.addMessageLog("最初のメッセージ", datetime.now()) +overlay_image.addMessageLog("翻訳結果: Hello -> こんにちは", datetime.now()) +overlay_image.addMessageLog("音声認識: こんにちは", datetime.now()) + +# ログ表示設定 +log_settings = { + "enabled": True, + "max_lines": 3, # 最大3行表示 + "show_timestamp": True, # タイムスタンプ表示 + "font_size": 16, + "text_color": (200, 200, 200, 255) +} + +# ログ付きオーバーレイ画像生成 +logged_image = overlay_image.createOverlayImage( + message="新しいメッセージ", + language="Japanese", + ui_size=ui_size, + ui_settings=ui_settings, + message_log_settings=log_settings +) + +overlay_system.showOverlay(logged_image, "large") + +# ログクリア +overlay_image.clearMessageLog() +``` + +## 座標系・変換システム + +### 基本座標設定 + +```python +# HMD基準座標(頭部固定表示) +def getHMDBaseMatrix() -> np.ndarray: + x_pos = 0.0 # 左右位置 + y_pos = -0.4 # 上下位置(下方向) + z_pos = 1.0 # 前後位置(前方向) + x_rotation = 0.0 # X軸回転 + y_rotation = 0.0 # Y軸回転 + z_rotation = 0.0 # Z軸回転 + +# 左手コントローラー基準座標 +def getLeftHandBaseMatrix() -> np.ndarray: + x_pos = 0.3 # 右側にオフセット + y_pos = 0.1 # 上方向にオフセット + z_pos = -0.31 # 手前にオフセット + x_rotation = -65.0 # 下向きに傾斜 + y_rotation = 165.0 # Y軸回転 + z_rotation = 115.0 # Z軸回転 + +# 右手コントローラー基準座標 +def getRightHandBaseMatrix() -> np.ndarray: + x_pos = -0.3 # 左側にオフセット + y_rotation = -165.0 # 左手と対称 + z_rotation = -115.0 # 左手と対称 +``` + +### 変換行列計算 (overlay_utils.py) + +```python +import numpy as np +from models.overlay.overlay_utils import * + +# 移動変換 +translation = (0.1, -0.2, 0.5) # x, y, z移動 +translation_matrix = calcTranslationMatrix(translation) + +# 回転変換(各軸独立) +x_rotation_matrix = calcRotationMatrixX(30.0) # X軸30度回転 +y_rotation_matrix = calcRotationMatrixY(45.0) # Y軸45度回転 +z_rotation_matrix = calcRotationMatrixZ(60.0) # Z軸60度回転 + +# オイラー角から回転行列生成 +euler_angles = (30.0, 45.0, 60.0) # X, Y, Z軸回転角度 +rotation_matrix = euler_to_rotation_matrix(euler_angles) + +# 基本行列への変換適用 +base_matrix = getHMDBaseMatrix() +translation = (0.05, -0.1, 0.2) +rotation = (10.0, -5.0, 0.0) +transformed_matrix = transform_matrix(base_matrix, translation, rotation) + +# 3x4行列を4x4同次座標に変換 +homogeneous_matrix = toHomogeneous(transformed_matrix) +``` + +### カスタム配置設定 + +```python +# カスタム位置でのオーバーレイ配置 +def createCustomOverlay(overlay_system, custom_pos, custom_rot, size): + """カスタム位置・回転でのオーバーレイ設定""" + + # 設定を動的に変更 + overlay_system.settings[size]["x_pos"] = custom_pos[0] + overlay_system.settings[size]["y_pos"] = custom_pos[1] + overlay_system.settings[size]["z_pos"] = custom_pos[2] + overlay_system.settings[size]["x_rotation"] = custom_rot[0] + overlay_system.settings[size]["y_rotation"] = custom_rot[1] + overlay_system.settings[size]["z_rotation"] = custom_rot[2] + + # 追跡デバイス設定を再適用 + overlay_system.setTrackedDeviceRelative("HMD", size) + +# 使用例:カスタム配置 +custom_position = (0.2, -0.3, 0.8) # やや右下前方 +custom_rotation = (-15.0, 10.0, 5.0) # 軽く傾斜 +createCustomOverlay(overlay_system, custom_position, custom_rotation, "large") +``` + +## 高度な機能 + +### 動的サイズ・レイアウト管理 + +```python +class AdaptiveOverlayManager: + """適応的オーバーレイ管理クラス""" + + def __init__(self, base_overlay_system, base_overlay_image): + self.overlay = base_overlay_system + self.image_gen = base_overlay_image + self.current_layout = "compact" + + def adaptLayoutToContent(self, message, language): + """コンテンツに応じたレイアウト自動調整""" + + # メッセージ長に応じてサイズ決定 + if len(message) < 50: + layout = "compact" + size_key = "small" + elif len(message) < 150: + layout = "standard" + size_key = "medium" + else: + layout = "expanded" + size_key = "large" + + # 言語に応じたフォントサイズ調整 + if language in ["Chinese Simplified", "Chinese Traditional"]: + font_scale = 1.1 # 中国語は少し大きめ + elif language == "Korean": + font_scale = 1.05 # 韓国語は微調整 + else: + font_scale = 1.0 # 日本語・その他 + + # UI設定の動的生成 + ui_size = self.getAdaptiveUiSize(layout) + ui_settings = { + "font_size": int(18 * font_scale), + "line_height": int(24 * font_scale), + "text_color": (255, 255, 255, 255), + "background_color": (0, 0, 0, 200), + "border_width": 2, + "border_color": (100, 150, 255, 255) + } + + return ui_size, ui_settings, size_key + + def getAdaptiveUiSize(self, layout): + """レイアウトに応じたUIサイズ取得""" + + layouts = { + "compact": { + "width": 400, + "height": 100, + "margin": 10, + "padding": 8 + }, + "standard": { + "width": 600, + "height": 150, + "margin": 15, + "padding": 12 + }, + "expanded": { + "width": 800, + "height": 200, + "margin": 20, + "padding": 16 + } + } + + return layouts.get(layout, layouts["standard"]) + +# 使用例 +adaptive_manager = AdaptiveOverlayManager(overlay_system, overlay_image) + +messages = [ + ("Hello!", "English"), + ("これは中程度の長さのメッセージです。翻訳結果を表示します。", "Japanese"), + ("这是一个很长的消息,用来测试自适应布局功能。当消息内容很长时,系统会自动选择更大的显示区域,并调整字体大小以确保良好的可读性。", "Chinese Simplified") +] + +for message, language in messages: + # 自動レイアウト調整 + ui_size, ui_settings, size_key = adaptive_manager.adaptLayoutToContent(message, language) + + # 画像生成・表示 + adaptive_image = overlay_image.createOverlayImage( + message=message, + language=language, + ui_size=ui_size, + ui_settings=ui_settings, + message_log_settings={"enabled": True, "max_lines": 3} + ) + + overlay_system.showOverlay(adaptive_image, size_key) + time.sleep(3) +``` + +### パフォーマンス監視・最適化 + +```python +class OverlayPerformanceMonitor: + """オーバーレイパフォーマンス監視クラス""" + + def __init__(self, overlay_system): + self.overlay = overlay_system + self.frame_times = [] + self.update_counts = {} + + def monitorFrameRate(self, duration=10.0): + """フレームレート監視""" + + start_time = time.monotonic() + frame_count = 0 + + while time.monotonic() - start_time < duration: + frame_start = time.monotonic() + + # フレーム処理(空の処理) + time.sleep(1/90) # 90Hz目標 + + frame_end = time.monotonic() + self.frame_times.append(frame_end - frame_start) + frame_count += 1 + + # 統計計算 + avg_frame_time = sum(self.frame_times) / len(self.frame_times) + avg_fps = 1.0 / avg_frame_time if avg_frame_time > 0 else 0 + + print(f"平均フレーム時間: {avg_frame_time*1000:.2f}ms") + print(f"平均FPS: {avg_fps:.1f}") + print(f"総フレーム数: {frame_count}") + + return avg_fps + + def optimizeSettings(self, target_fps=60): + """パフォーマンス目標に基づく設定最適化""" + + current_fps = self.monitorFrameRate(5.0) + + if current_fps < target_fps * 0.8: + print("パフォーマンス不足。設定を軽量化します...") + + # フェード処理間隔を延長 + for size in self.overlay.settings: + self.overlay.settings[size]["fadeout_duration"] *= 1.5 + + # 更新頻度を下げる + # (mainloopの sleep_time 調整は overlay.py 内で実装) + + elif current_fps > target_fps * 1.2: + print("パフォーマンスに余裕があります。品質を向上します...") + + # より滑らかなフェード + for size in self.overlay.settings: + self.overlay.settings[size]["fadeout_duration"] *= 0.8 + +# 使用例 +performance_monitor = OverlayPerformanceMonitor(overlay_system) +performance_monitor.monitorFrameRate(10.0) +performance_monitor.optimizeSettings(target_fps=60) +``` + +## エラーハンドリング・復旧 + +### 堅牢な接続管理 + +```python +class RobustOverlaySystem: + """堅牢性を高めたオーバーレイシステム""" + + def __init__(self, settings_dict): + self.base_overlay = Overlay(settings_dict) + self.connection_retries = 3 + self.auto_reconnect = True + + def safeStartOverlay(self, max_retries=None): + """安全なオーバーレイ開始(リトライ機構付き)""" + + retries = max_retries or self.connection_retries + + for attempt in range(retries): + try: + # SteamVR接続確認 + if not self.base_overlay.checkSteamvrRunning(): + print("SteamVRが起動していません。待機中...") + time.sleep(5) + continue + + # オーバーレイ開始 + self.base_overlay.startOverlay() + + # 初期化完了まで待機 + timeout = 10.0 + start_time = time.monotonic() + + while not self.base_overlay.initialized and time.monotonic() - start_time < timeout: + time.sleep(0.1) + + if self.base_overlay.initialized: + print("オーバーレイシステム開始完了") + return True + else: + print(f"初期化タイムアウト(試行 {attempt + 1}/{retries})") + + except Exception as e: + print(f"オーバーレイ開始エラー(試行 {attempt + 1}/{retries}): {e}") + + # 既存システムのクリーンアップ + try: + self.base_overlay.shutdownOverlay() + except Exception: + pass + + if attempt < retries - 1: + time.sleep(2 ** attempt) # 指数バックオフ + + print("オーバーレイシステムの開始に失敗しました") + return False + + def monitorConnection(self): + """接続監視・自動復旧""" + + while self.auto_reconnect: + try: + if self.base_overlay.initialized and not self.base_overlay.checkActive(): + print("OpenVR接続が切断されました。再接続を試行します...") + + self.base_overlay.shutdownOverlay() + time.sleep(2) + + if self.safeStartOverlay(): + print("オーバーレイシステムが復旧しました") + else: + print("復旧に失敗しました") + + time.sleep(1) + + except Exception as e: + print(f"接続監視エラー: {e}") + time.sleep(5) + +# 使用例 +robust_overlay = RobustOverlaySystem(settings) + +# 安全な開始 +if robust_overlay.safeStartOverlay(): + # 接続監視開始(別スレッド) + import threading + monitor_thread = threading.Thread(target=robust_overlay.monitorConnection, daemon=True) + monitor_thread.start() + + # 通常の操作 + overlay_img = overlay_image.createOverlayImage(...) + robust_overlay.base_overlay.showOverlay(overlay_img, "small") +``` + +## 依存関係・システム要件 + +### 必須依存関係 +- `openvr`: OpenVR Python バインディング +- `numpy`: 数値計算・行列演算 +- `PIL (Pillow)`: 画像処理・生成 +- `psutil`: プロセス監視 + +### システム要件 +```python +system_requirements = { + "steamvr": "SteamVR環境必須", + "openvr_runtime": "OpenVR Runtime", + "vr_headset": "対応VRヘッドセット(Oculus, Vive, Index等)", + "graphics": "VR対応GPU", + "python": "Python 3.7以上" +} + +performance_requirements = { + "cpu": "VR処理に十分なCPU性能", + "memory": "追加メモリ使用量 ~100-500MB", + "disk_space": "フォントファイル用容量 ~50MB" +} +``` + +### オプション依存関係 +- `utils.errorLogging`: エラーログ機能(フォールバック処理あり) + +## 注意事項・制限 + +### VR環境制限 +- SteamVRが起動していない場合は動作不可 +- VRヘッドセットが接続されていない場合は制限あり +- OpenVRドライバーの互換性に依存 + +### パフォーマンス制限 +- リアルタイム描画処理によるCPU・GPU負荷 +- フォントレンダリングによるメモリ使用量 +- 高解像度VRディスプレイでの描画負荷 + +### プラットフォーム制限 +```python +platform_limitations = { + "windows": "主要サポートプラットフォーム", + "linux": "SteamVR Linux版での制限あり", + "macos": "SteamVR macOS版サポート終了により制限", + "mobile_vr": "OpenVR非対応のため利用不可" +} +``` + +## 関連モジュール + +- `config.py`: オーバーレイ設定管理 +- `controller.py`: オーバーレイ制御インターフェース +- `model.py`: オーバーレイ機能統合 +- `utils.py`: エラーログ・ユーティリティ + +## 将来の改善点 + +- よりリッチなUI要素対応 +- アニメーション・エフェクト機能 +- カスタムフォント・テーマシステム +- パフォーマンス監視・自動最適化 +- 他のVRプラットフォーム対応検討 \ No newline at end of file diff --git a/src-python/docs/details/transcription_languages.md b/src-python/docs/details/transcription_languages.md new file mode 100644 index 00000000..1425593a --- /dev/null +++ b/src-python/docs/details/transcription_languages.md @@ -0,0 +1,229 @@ +# transcription_languages.py - 音声認識言語マッピング + +## 概要 + +音声認識エンジンが対応する言語コードのマッピングテーブルを提供するモジュールです。異なる音声認識エンジンの言語コード仕様の差異を吸収し、統一的なインターフェースを提供します。 + +## 主要機能 + +### 言語マッピングテーブル +- 表示用言語名から各エンジン固有の言語コードへの変換 +- 国・地域固有の言語バリエーション対応 +- 複数音声認識エンジンの統一的な言語管理 + +### 対応エンジン +- Google Speech Recognition +- OpenAI Whisper(faster-whisper) +- その他の音声認識エンジン + +## データ構造 + +### transcription_lang +```python +transcription_lang: Dict[str, List[Dict[str, str]]] +``` + +言語とその地域バリエーションのマッピング + +```python +transcription_lang = { + "English": [ + {"country": "United States", "google_language_code": "en-US"}, + {"country": "United Kingdom", "google_language_code": "en-GB"}, + {"country": "Australia", "google_language_code": "en-AU"} + ], + "Japanese": [ + {"country": "Japan", "google_language_code": "ja-JP"} + ], + "Korean": [ + {"country": "South Korea", "google_language_code": "ko-KR"} + ] +} +``` + +## 使用方法 + +### 基本的な言語コード取得 + +```python +from models.transcription.transcription_languages import transcription_lang + +# 日本語の言語コード取得 +japanese_codes = transcription_lang.get("Japanese", []) +if japanese_codes: + code = japanese_codes[0]["google_language_code"] # "ja-JP" + +# 英語の地域別言語コード取得 +english_codes = transcription_lang.get("English", []) +for region in english_codes: + print(f"{region['country']}: {region['google_language_code']}") +``` + +### 利用可能言語の一覧取得 + +```python +# 対応言語の一覧 +supported_languages = list(transcription_lang.keys()) +print(f"対応言語: {supported_languages}") + +# 言語と国の組み合わせ一覧 +language_country_pairs = [] +for lang, countries in transcription_lang.items(): + for country_data in countries: + language_country_pairs.append({ + "language": lang, + "country": country_data["country"], + "code": country_data["google_language_code"] + }) +``` + +### 翻訳システムとの連携 + +```python +# 翻訳システムで対応している言語の確認 +from models.translation.translation_languages import translation_lang + +transcription_langs = list(transcription_lang.keys()) +translation_langs = [] +for engine in translation_lang.keys(): + translation_langs.extend(translation_lang[engine]["source"].keys()) + +# 音声認識と翻訳の両方で対応している言語 +supported_langs = list(filter(lambda x: x in transcription_langs, translation_langs)) +``` + +## 主要対応言語 + +### 西欧言語 +- **English**: US, UK, Australia, Canada, India, South Africa +- **Spanish**: Spain, Mexico, Argentina, Colombia +- **French**: France, Canada, Belgium +- **German**: Germany, Austria, Switzerland +- **Italian**: Italy +- **Portuguese**: Brazil, Portugal + +### アジア言語 +- **Japanese**: Japan +- **Korean**: South Korea +- **Chinese**: China (Simplified), Taiwan (Traditional), Hong Kong +- **Thai**: Thailand +- **Vietnamese**: Vietnam + +### その他の言語 +- **Russian**: Russia +- **Arabic**: Saudi Arabia, UAE, Egypt +- **Hindi**: India +- **Dutch**: Netherlands +- **Swedish**: Sweden +- **Norwegian**: Norway + +## エンジン別言語コード形式 + +### Google Speech Recognition +- RFC 5646準拠の言語タグ形式 +- 例: "ja-JP", "en-US", "zh-CN" + +### OpenAI Whisper +- ISO 639-1言語コード(2文字) +- 例: "ja", "en", "zh" + +### その他のエンジン +- エンジン固有の形式に対応 +- マッピングテーブルによる変換 + +## 地域対応 + +### 同一言語の地域別対応 +```python +# 英語の地域バリエーション +"English": [ + {"country": "United States", "google_language_code": "en-US"}, + {"country": "United Kingdom", "google_language_code": "en-GB"}, + {"country": "Australia", "google_language_code": "en-AU"}, + {"country": "Canada", "google_language_code": "en-CA"}, + {"country": "India", "google_language_code": "en-IN"} +] +``` + +### 方言・変種対応 +```python +# 中国語の簡体字・繁体字対応 +"Chinese Simplified": [ + {"country": "China", "google_language_code": "zh-CN"} +], +"Chinese Traditional": [ + {"country": "Taiwan", "google_language_code": "zh-TW"}, + {"country": "Hong Kong", "google_language_code": "zh-HK"} +] +``` + +## 統合利用 + +### VRCTでの利用例 + +```python +def get_supported_transcription_languages(): + """音声認識対応言語の取得""" + languages = [] + for language, countries in transcription_lang.items(): + for country_data in countries: + languages.append({ + "language": language, + "country": country_data["country"], + "display_name": f"{language} ({country_data['country']})", + "code": country_data["google_language_code"] + }) + return languages +``` + +### エラーハンドリング + +```python +def get_language_code(language: str, country: str = None) -> str: + """安全な言語コード取得""" + try: + countries = transcription_lang.get(language, []) + if not countries: + return "en-US" # フォールバック + + if country: + for country_data in countries: + if country_data["country"] == country: + return country_data["google_language_code"] + + # 国指定なしまたは見つからない場合は最初の項目を返す + return countries[0]["google_language_code"] + except (KeyError, IndexError): + return "en-US" # エラー時のフォールバック +``` + +## 拡張性 + +### 新言語の追加 +```python +# 新しい言語の追加例 +transcription_lang["Turkish"] = [ + {"country": "Turkey", "google_language_code": "tr-TR"} +] +``` + +### 新エンジンへの対応 +```python +# 新しいエンジンのコードフィールドを追加 +transcription_lang["English"][0]["azure_language_code"] = "en-US" +transcription_lang["English"][0]["aws_language_code"] = "en-US" +``` + +## 注意事項 + +- 言語コードは各エンジンの仕様に依存 +- 新しいエンジン追加時は対応コードの追加が必要 +- 地域固有の音声認識精度差に注意 +- エンジンによってサポート言語が異なる場合がある + +## 関連モジュール + +- `transcription_transcriber.py`: 音声認識エンジン本体 +- `translation_languages.py`: 翻訳エンジン言語マッピング +- `config.py`: 言語設定管理 +- `controller.py`: 言語選択UI制御 \ No newline at end of file diff --git a/src-python/docs/details/transcription_recorder.md b/src-python/docs/details/transcription_recorder.md new file mode 100644 index 00000000..1ac6dddc --- /dev/null +++ b/src-python/docs/details/transcription_recorder.md @@ -0,0 +1,325 @@ +# transcription_recorder.py - 音声録音インターフェース + +## 概要 + +音声認識システムの入力となる音声データを録音するレコーダークラス群です。マイクとスピーカー出力の両方をサポートし、エネルギーレベル監視機能とともに音声データをキューに送信します。pyaudiowpatchライブラリを使用してWindowsの音声システムと統合します。 + +## 主要機能 + +### 音声録音機能 +- マイクからの音声録音 +- スピーカー出力の録音(ループバック) +- リアルタイム音声データキューイング + +### エネルギー監視 +- 音声エネルギーレベルの監視 +- 動的しきい値調整 +- 無音検出 + +### デバイス対応 +- 複数音声デバイスの対応 +- デバイス固有設定の管理 +- 自動デバイス選択 + +## クラス構造 + +### BaseRecorder クラス +```python +class BaseRecorder: + def __init__(self, source: Any, energy_threshold: int, dynamic_energy_threshold: bool, record_timeout: int) +``` + +基底レコーダークラス - 共通機能を提供 + +### SelectedMicRecorder クラス +```python +class SelectedMicRecorder(BaseRecorder): + def __init__(self, device: dict, energy_threshold: int, dynamic_energy_threshold: bool, record_timeout: int) +``` + +選択されたマイクデバイスからの録音 + +### SelectedSpeakerRecorder クラス +```python +class SelectedSpeakerRecorder(BaseRecorder): + def __init__(self, device: dict, energy_threshold: int, dynamic_energy_threshold: bool, record_timeout: int) +``` + +選択されたスピーカーデバイスからの録音(ループバック) + +### エネルギー監視クラス群 + +#### BaseEnergyRecorder クラス +```python +class BaseEnergyRecorder: + def __init__(self, source: Any) +``` + +エネルギーレベル監視の基底クラス + +#### SelectedMicEnergyRecorder クラス +```python +class SelectedMicEnergyRecorder(BaseEnergyRecorder): + def __init__(self, device: dict) +``` + +マイクエネルギーレベルの監視 + +#### SelectedSpeakerEnergyRecorder クラス +```python +class SelectedSpeakerEnergyRecorder(BaseEnergyRecorder): + def __init__(self, device: dict) +``` + +スピーカーエネルギーレベルの監視 + +### 統合録音クラス群 + +#### BaseEnergyAndAudioRecorder クラス +```python +class BaseEnergyAndAudioRecorder: + def __init__(self, source: Any, energy_threshold: int, dynamic_energy_threshold: bool, + phrase_time_limit: int, phrase_timeout: int, record_timeout: int) +``` + +音声録音とエネルギー監視を統合 + +#### SelectedMicEnergyAndAudioRecorder クラス +```python +class SelectedMicEnergyAndAudioRecorder(BaseEnergyAndAudioRecorder): + def __init__(self, device: dict, energy_threshold: int, dynamic_energy_threshold: bool, + phrase_time_limit: int, phrase_timeout: int = 1, record_timeout: int = 5) +``` + +マイクの音声録音とエネルギー監視を統合 + +#### SelectedSpeakerEnergyAndAudioRecorder クラス +```python +class SelectedSpeakerEnergyAndAudioRecorder(BaseEnergyAndAudioRecorder): + def __init__(self, device: dict, energy_threshold: int, dynamic_energy_threshold: bool, + phrase_time_limit: int, phrase_timeout: int = 1, record_timeout: int = 5) +``` + +スピーカーの音声録音とエネルギー監視を統合 + +## 主要メソッド + +### 録音制御 + +```python +adjustForNoise() -> None +``` +- 環境ノイズに合わせたしきい値調整 +- 録音開始前の較正 + +```python +recordIntoQueue(audio_queue: Queue) -> None +``` +- 音声データの継続的キューイング +- バックグラウンドスレッドでの実行 + +```python +pause() -> None +resume() -> None +stop() -> None +``` +- 録音の一時停止・再開・停止制御 + +### エネルギー監視 + +```python +recordIntoQueue(energy_queue: Queue) -> None +``` +- エネルギーレベルのキューイング +- リアルタイム監視データの提供 + +## 使用方法 + +### 基本的なマイク録音 + +```python +from queue import Queue +from models.transcription.transcription_recorder import SelectedMicRecorder + +# デバイス設定 +mic_device = { + "name": "マイク (USB Audio Device)", + "index": 0, + "channels": 1, + "sample_rate": 16000 +} + +# 録音設定 +energy_threshold = 300 +dynamic_threshold = True +record_timeout = 5 + +# レコーダー初期化 +recorder = SelectedMicRecorder( + device=mic_device, + energy_threshold=energy_threshold, + dynamic_energy_threshold=dynamic_threshold, + record_timeout=record_timeout +) + +# 音声キューの作成 +audio_queue = Queue() + +# 録音開始 +recorder.adjustForNoise() # ノイズ調整 +recorder.recordIntoQueue(audio_queue) + +# 音声データの取得 +while True: + if not audio_queue.empty(): + audio_data = audio_queue.get() + print(f"音声データ受信: {len(audio_data)} bytes") +``` + +### スピーカー録音(ループバック) + +```python +from models.transcription.transcription_recorder import SelectedSpeakerRecorder + +# スピーカーデバイス設定 +speaker_device = { + "name": "スピーカー (USB Audio Device)", + "index": 1, + "channels": 2, + "sample_rate": 44100 +} + +# スピーカーレコーダー +recorder = SelectedSpeakerRecorder( + device=speaker_device, + energy_threshold=500, + dynamic_energy_threshold=False, + record_timeout=3 +) + +audio_queue = Queue() +recorder.recordIntoQueue(audio_queue) +``` + +### エネルギー監視 + +```python +from models.transcription.transcription_recorder import SelectedMicEnergyRecorder + +# エネルギー監視のみ +energy_recorder = SelectedMicEnergyRecorder(mic_device) +energy_queue = Queue() + +energy_recorder.recordIntoQueue(energy_queue) + +# エネルギーレベルの取得 +while True: + if not energy_queue.empty(): + energy_level = energy_queue.get() + print(f"エネルギーレベル: {energy_level}") +``` + +### 統合録音(音声+エネルギー) + +```python +from models.transcription.transcription_recorder import SelectedMicEnergyAndAudioRecorder + +# 統合レコーダー +integrated_recorder = SelectedMicEnergyAndAudioRecorder( + device=mic_device, + energy_threshold=300, + dynamic_energy_threshold=True, + phrase_time_limit=5, # フレーズ制限時間 + phrase_timeout=1, # フレーズタイムアウト + record_timeout=5 # 録音タイムアウト +) + +audio_queue = Queue() +energy_queue = Queue() + +# 両方のキューに同時出力 +integrated_recorder.recordIntoQueue(audio_queue, energy_queue) +``` + +## 設定パラメータ + +### しきい値設定 +- **energy_threshold**: 音声検出のエネルギーしきい値 +- **dynamic_energy_threshold**: 動的しきい値調整の有効・無効 + +### タイムアウト設定 +- **record_timeout**: 録音継続時間の上限 +- **phrase_timeout**: フレーズ間の無音許容時間 +- **phrase_time_limit**: 単一フレーズの最大長 + +### デバイス設定 +- **name**: デバイス名 +- **index**: デバイスインデックス +- **channels**: チャンネル数(1=モノラル、2=ステレオ) +- **sample_rate**: サンプリングレート(Hz) + +## デバイス対応 + +### マイクデバイス +- USB マイク +- 内蔵マイク +- Bluetooth マイク +- 仮想マイクデバイス + +### スピーカーデバイス(ループバック) +- USB スピーカー/ヘッドフォン +- 内蔵スピーカー +- Bluetooth スピーカー +- 仮想音声デバイス + +## エラーハンドリング + +### デバイスエラー +- デバイス接続失敗の検出 +- 適切なエラーメッセージの提供 + +### 音声フォーマットエラー +- 非対応フォーマットの検出 +- 自動フォーマット変換 + +### メモリエラー +- キューオーバーフローの防止 +- メモリ使用量の最適化 + +## パフォーマンス特性 + +### レイテンシ +- 低レイテンシ録音(~10ms) +- リアルタイム処理最適化 + +### スループット +- 連続録音対応 +- 高サンプリングレート対応 + +### メモリ使用量 +- 効率的なバッファ管理 +- キューサイズの最適化 + +## 依存関係 + +### 必須依存関係 +- `speech_recognition`: 音声認識ライブラリ +- `pyaudiowpatch`: Windows音声システム統合 +- `queue`: データキューイング + +### オプション依存関係 +- `datetime`: タイムスタンプ機能 + +## 注意事項 + +- Windows専用(pyaudiowpatchによる制限) +- 適切な音声デバイスドライバーが必要 +- 排他制御による同時デバイスアクセス制限 +- 高サンプリングレート使用時のCPU使用率上昇 + +## 関連モジュール + +- `transcription_transcriber.py`: 音声認識エンジン +- `device_manager.py`: デバイス管理 +- `config.py`: 録音設定管理 +- `model.py`: 録音制御統合 \ No newline at end of file diff --git a/src-python/docs/details/transcription_transcriber.md b/src-python/docs/details/transcription_transcriber.md new file mode 100644 index 00000000..db5111f7 --- /dev/null +++ b/src-python/docs/details/transcription_transcriber.md @@ -0,0 +1,325 @@ +# transcription_transcriber.py - 音声文字起こしエンジン + +## 概要 + +音声データを文字テキストに変換する音声認識エンジンのメインクラスです。Google Speech RecognitionとOpenAI Whisper(faster-whisper)の両方をサポートし、オンライン・オフラインの音声認識を統合的に管理します。キューベースの非同期処理により、リアルタイム音声認識を実現します。 + +## 主要機能 + +### 音声認識エンジン +- Google Speech Recognition(オンライン) +- OpenAI Whisper(faster-whisper、オフライン) +- エンジン自動切り替え機能 + +### リアルタイム処理 +- 音声キューからの継続的データ処理 +- 非同期音声認識処理 +- 結果の即座通知 + +### 多言語対応 +- 複数言語の同時認識 +- 地域固有言語コードの対応 +- 自動言語検出 + +### 音声品質制御 +- 音声品質フィルタリング +- ノイズ除去機能 +- 信頼度スコア評価 + +## クラス構造 + +### AudioTranscriber クラス +```python +class AudioTranscriber: + def __init__(self, speaker: bool, source: Any, phrase_timeout: int, max_phrases: int, + transcription_engine: str, root: Optional[str] = None, + whisper_weight_type: Optional[str] = None, device: str = "cpu", + device_index: int = 0, compute_type: str = "auto") +``` + +音声認識の中核クラス + +#### 初期化パラメータ +- **speaker**: スピーカー音声かマイク音声か +- **source**: 音声ソース +- **phrase_timeout**: フレーズタイムアウト(秒) +- **max_phrases**: 最大フレーズ数 +- **transcription_engine**: 認識エンジン("Google"/"Whisper") +- **whisper_weight_type**: Whisperモデル種類 +- **device**: 計算デバイス("cpu"/"cuda") +- **device_index**: デバイスインデックス +- **compute_type**: 計算精度タイプ + +## 主要メソッド + +### 音声認識処理 + +```python +transcribeAudioQueue(audio_queue: Queue, languages: List[str], countries: List[str], + avg_logprob: float = -0.8, no_speech_prob: float = 0.6) -> bool +``` + +音声キューからの継続的音声認識 + +#### パラメータ +- **audio_queue**: 音声データキュー +- **languages**: 認識対象言語リスト +- **countries**: 地域コードリスト +- **avg_logprob**: Whisper平均対数確率しきい値 +- **no_speech_prob**: Whisper無音判定しきい値 + +### 結果管理 + +```python +getTranscript() -> dict +``` + +最新の認識結果を取得 + +```python +updateTranscript(result: dict) -> None +``` + +認識結果の更新と通知 + +```python +clearTranscriptData() -> None +``` + +認識データのクリア + +### 音声データ処理 + +```python +processMicData() -> AudioData +``` + +マイク音声データの前処理 + +```python +processSpeakerData() -> AudioData +``` + +スピーカー音声データの前処理 + +## 使用方法 + +### 基本的な音声認識 + +```python +from queue import Queue +from models.transcription.transcription_transcriber import AudioTranscriber + +# 音声認識の初期化 +transcriber = AudioTranscriber( + speaker=False, # マイク音声 + source=mic_source, # 音声ソース + phrase_timeout=3, # 3秒のフレーズタイムアウト + max_phrases=10, # 最大10フレーズ + transcription_engine="Google", # Google音声認識 + device="cpu" +) + +# 音声キューの準備 +audio_queue = Queue() + +# 認識対象言語の設定 +languages = ["Japanese", "English"] +countries = ["Japan", "United States"] + +# 音声認識の実行 +def transcription_loop(): + while True: + success = transcriber.transcribeAudioQueue( + audio_queue, languages, countries + ) + if success: + result = transcriber.getTranscript() + print(f"認識結果: {result['text']}") + print(f"言語: {result['language']}") + +# バックグラウンドで実行 +import threading +thread = threading.Thread(target=transcription_loop) +thread.daemon = True +thread.start() +``` + +### Whisperエンジンの使用 + +```python +# Whisper音声認識の初期化 +whisper_transcriber = AudioTranscriber( + speaker=True, # スピーカー音声 + source=speaker_source, + phrase_timeout=5, + max_phrases=5, + transcription_engine="Whisper", + whisper_weight_type="base", # Whisperモデル + device="cuda", # CUDA使用 + device_index=0, + compute_type="float16" # 半精度浮動小数点 +) + +# Whisper固有パラメータでの認識 +success = whisper_transcriber.transcribeAudioQueue( + audio_queue, languages, countries, + avg_logprob=-0.5, # より厳しい品質しきい値 + no_speech_prob=0.4 # より敏感な無音検出 +) +``` + +### コールバック処理 + +```python +def on_transcription_result(result): + """認識結果のコールバック処理""" + if result["text"]: + print(f"認識成功: {result['text']}") + print(f"言語: {result['language']}") + print(f"信頼度: {result.get('confidence', 'N/A')}") + else: + print("音声認識失敗") + +# 結果通知の設定 +transcriber.transcript_changed_event.set() # イベント設定 +``` + +### エラーハンドリング付きの使用 + +```python +def safe_transcription(transcriber, audio_queue, languages, countries): + """安全な音声認識処理""" + try: + success = transcriber.transcribeAudioQueue( + audio_queue, languages, countries + ) + + if success: + result = transcriber.getTranscript() + return result + else: + return {"text": False, "language": None, "error": "認識失敗"} + + except Exception as e: + print(f"音声認識エラー: {e}") + return {"text": False, "language": None, "error": str(e)} +``` + +## 認識エンジン比較 + +### Google Speech Recognition + +#### 利点 +- 高い認識精度 +- 多言語対応 +- リアルタイム処理 +- ノイズ耐性 + +#### 制限 +- インターネット接続必須 +- API制限 +- プライバシー懸念 +- レイテンシ + +### OpenAI Whisper(faster-whisper) + +#### 利点 +- オフライン動作 +- プライバシー保護 +- 高精度 +- 多言語対応 + +#### 制限 +- 初回起動時間 +- メモリ使用量 +- CUDA推奨 +- モデルファイル必要 + +## 設定パラメータ + +### フレーズ制御 +- **phrase_timeout**: フレーズ間無音時間(秒) +- **max_phrases**: バッファ内最大フレーズ数 + +### Whisper品質設定 +- **avg_logprob**: 平均対数確率しきい値(-1.0〜0.0) +- **no_speech_prob**: 無音判定しきい値(0.0〜1.0) + +### 計算設定 +- **device**: "cpu" または "cuda" +- **compute_type**: "float32", "float16", "int8" など + +## 音声データフォーマット + +### 入力形式 +- サンプリングレート: 16kHz推奨 +- ビット深度: 16bit +- チャンネル: モノラル推奨 +- フォーマット: WAV、FLAC等 + +### 処理フロー +1. 音声キューからデータ取得 +2. 音声フォーマット正規化 +3. 音声認識エンジン実行 +4. 結果の後処理・フィルタリング +5. 最終結果の通知 + +## パフォーマンス最適化 + +### メモリ管理 +- 音声バッファの適切なサイズ設定 +- 不要な音声データの早期解放 +- Whisperモデルのメモリ効率化 + +### 計算最適化 +- CUDA使用による高速化 +- 適切な計算精度選択 +- バッチ処理の活用 + +### レイテンシ削減 +- 音声バッファサイズの最適化 +- エンジン切り替えの高速化 +- キャッシュ機能の活用 + +## エラーハンドリング + +### ネットワークエラー +- Google API接続失敗の検出 +- 自動Whisperエンジン切り替え + +### 音声品質エラー +- 低品質音声の検出・フィルタリング +- ノイズレベル監視 + +### リソースエラー +- VRAM不足の検出 +- メモリ不足時の対応 + +## 依存関係 + +### 必須依存関係 +- `speech_recognition`: Google音声認識 +- `faster_whisper`: Whisper音声認識 +- `pyaudiowpatch`: 音声入力 +- `pydub`: 音声処理 + +### オプション依存関係 +- `torch`: CUDA計算 +- `utils`: エラーログ機能 + +## 注意事項 + +- Google APIは使用制限あり +- Whisperは初回起動に時間要 +- CUDA使用時はVRAM消費に注意 +- 音声品質が認識精度に大きく影響 +- 多言語認識時は処理負荷増加 + +## 関連モジュール + +- `transcription_recorder.py`: 音声録音 +- `transcription_whisper.py`: Whisperモデル管理 +- `transcription_languages.py`: 言語コード管理 +- `config.py`: 認識設定管理 +- `model.py`: 音声認識統合制御 \ No newline at end of file diff --git a/src-python/docs/details/transcription_whisper.md b/src-python/docs/details/transcription_whisper.md new file mode 100644 index 00000000..70dcedc9 --- /dev/null +++ b/src-python/docs/details/transcription_whisper.md @@ -0,0 +1,373 @@ +# transcription_whisper.py - Whisperモデル管理 + +## 概要 + +OpenAI Whisper(faster-whisper)モデルのダウンロード、検証、読み込みを管理するユーティリティモジュールです。複数のモデルサイズをサポートし、Hugging Face Hubからの自動ダウンロード機能とファイル整合性チェック機能を提供します。 + +## 主要機能 + +### モデル管理 +- 複数Whisperモデルサイズの対応 +- Hugging Face Hubからの自動ダウンロード +- モデルファイルの整合性検証 + +### ダウンロード機能 +- 進捗表示付きダウンロード +- レジューム対応 +- エラーハンドリング + +### モデル読み込み +- 効率的なモデル初期化 +- CUDA対応 +- 計算タイプ最適化 + +## サポートモデル + +### 利用可能なモデル +```python +_MODELS = { + "tiny": "Systran/faster-whisper-tiny", # ~39MB + "base": "Systran/faster-whisper-base", # ~74MB + "small": "Systran/faster-whisper-small", # ~244MB + "medium": "Systran/faster-whisper-medium", # ~769MB + "large-v1": "Systran/faster-whisper-large-v1", # ~1.5GB + "large-v2": "Systran/faster-whisper-large-v2", # ~1.5GB + "large-v3": "Systran/faster-whisper-large-v3", # ~1.5GB + "large-v3-turbo-int8": "Zoont/faster-whisper-large-v3-turbo-int8-ct2", # ~794MB + "large-v3-turbo": "deepdml/faster-whisper-large-v3-turbo-ct2" # ~1.58GB +} +``` + +### モデル特性比較 + +#### tiny +- **サイズ**: ~39MB +- **精度**: 低 +- **速度**: 最高速 +- **用途**: リアルタイム処理、リソース制限環境 + +#### base +- **サイズ**: ~74MB +- **精度**: 中程度 +- **速度**: 高速 +- **用途**: 一般的な用途、バランス重視 + +#### small +- **サイズ**: ~244MB +- **精度**: 良好 +- **速度**: 中程度 +- **用途**: 品質重視、モバイル環境 + +#### medium +- **サイズ**: ~769MB +- **精度**: 高 +- **速度**: やや低速 +- **用途**: 高品質認識、デスクトップ環境 + +#### large系 +- **サイズ**: ~1.5GB +- **精度**: 最高 +- **速度**: 低速 +- **用途**: 最高品質、サーバー環境 + +## 主要関数 + +### ファイルダウンロード + +```python +downloadFile(url: str, path: str, func: Optional[Callable[[float], None]] = None) -> None +``` + +ファイルのストリームダウンロード + +#### パラメータ +- **url**: ダウンロードURL +- **path**: 保存先パス +- **func**: 進捗コールバック関数 + +### モデル検証 + +```python +checkWhisperWeight(root: str, weight_type: str) -> bool +``` + +Whisperモデルの利用可能性確認 + +#### パラメータ +- **root**: アプリケーションルートパス +- **weight_type**: モデルタイプ("tiny", "base"等) + +#### 戻り値 +- **bool**: モデルが利用可能かどうか + +### モデルダウンロード + +```python +downloadWhisperWeight(root: str, weight_type: str, + callback: Optional[Callable[[float], None]] = None, + end_callback: Optional[Callable[[], None]] = None) -> None +``` + +Whisperモデルのダウンロード + +#### パラメータ +- **root**: アプリケーションルートパス +- **weight_type**: ダウンロードするモデルタイプ +- **callback**: 進捗コールバック +- **end_callback**: 完了コールバック + +### モデル読み込み + +```python +getWhisperModel(root: str, weight_type: str, device: str = "cpu", + device_index: int = 0, compute_type: str = "auto") -> WhisperModel +``` + +Whisperモデルの初期化 + +#### パラメータ +- **root**: アプリケーションルートパス +- **weight_type**: 使用するモデルタイプ +- **device**: 計算デバイス("cpu"/"cuda") +- **device_index**: デバイスインデックス +- **compute_type**: 計算精度タイプ + +#### 戻り値 +- **WhisperModel**: 初期化されたWhisperモデルインスタンス + +## 使用方法 + +### モデルの確認とダウンロード + +```python +from models.transcription.transcription_whisper import checkWhisperWeight, downloadWhisperWeight + +root_path = "." +model_type = "base" + +# モデルの利用可能性確認 +if not checkWhisperWeight(root_path, model_type): + print(f"{model_type}モデルが見つかりません。ダウンロードします...") + + # 進捗コールバック + def progress_callback(progress): + print(f"ダウンロード進捗: {progress:.1%}") + + # 完了コールバック + def completion_callback(): + print("ダウンロード完了!") + + # モデルダウンロード + downloadWhisperWeight( + root=root_path, + weight_type=model_type, + callback=progress_callback, + end_callback=completion_callback + ) +else: + print(f"{model_type}モデルは利用可能です") +``` + +### モデルの読み込みと使用 + +```python +from models.transcription.transcription_whisper import getWhisperModel + +# CPUでのモデル読み込み +model = getWhisperModel( + root=".", + weight_type="base", + device="cpu" +) + +# CUDAでのモデル読み込み(GPU使用) +gpu_model = getWhisperModel( + root=".", + weight_type="small", + device="cuda", + device_index=0, + compute_type="float16" # 半精度で高速化 +) + +# 音声認識の実行 +audio_file = "audio.wav" +segments, info = model.transcribe(audio_file, language="ja") + +for segment in segments: + print(f"{segment.start:.1f}s - {segment.end:.1f}s: {segment.text}") +``` + +### エラーハンドリング付きの使用 + +```python +def safe_model_loading(root, weight_type, device="cpu"): + """安全なモデル読み込み""" + try: + # モデル存在確認 + if not checkWhisperWeight(root, weight_type): + print(f"モデル {weight_type} をダウンロード中...") + downloadWhisperWeight(root, weight_type) + + # モデル読み込み + model = getWhisperModel(root, weight_type, device) + return model + + except Exception as e: + print(f"モデル読み込みエラー: {e}") + # フォールバック: より小さなモデルを試す + if weight_type != "tiny": + return safe_model_loading(root, "tiny", device) + return None +``` + +### 進捗表示付きダウンロード + +```python +import sys + +def download_with_progress(root, weight_type): + """進捗表示付きダウンロード""" + def show_progress(progress): + bar_length = 40 + filled_length = int(bar_length * progress) + bar = '█' * filled_length + '-' * (bar_length - filled_length) + sys.stdout.write(f'\r[{bar}] {progress:.1%}') + sys.stdout.flush() + + def download_complete(): + print("\nダウンロード完了!") + + print(f"Whisper {weight_type} モデルをダウンロード中...") + downloadWhisperWeight(root, weight_type, show_progress, download_complete) +``` + +## ディレクトリ構造 + +### モデルファイル配置 +``` +root/ +└── weights/ + └── whisper/ + ├── tiny/ + │ ├── config.json + │ ├── preprocessor_config.json + │ ├── model.bin + │ ├── tokenizer.json + │ └── vocabulary.txt + ├── base/ + └── small/ +``` + +### 必要ファイル +```python +_FILENAMES = [ + "config.json", # モデル設定 + "preprocessor_config.json", # 前処理設定 + "model.bin", # モデルウェイト + "tokenizer.json", # トークナイザー + "vocabulary.txt", # 語彙ファイル + "vocabulary.json" # 語彙ファイル(JSON形式) +] +``` + +## パフォーマンス考慮事項 + +### メモリ使用量 +- **tiny**: ~100MB RAM +- **base**: ~200MB RAM +- **small**: ~500MB RAM +- **medium**: ~1.5GB RAM +- **large**: ~3GB RAM + +### VRAM使用量(CUDA使用時) +- **tiny**: ~200MB VRAM +- **base**: ~300MB VRAM +- **small**: ~600MB VRAM +- **medium**: ~1.8GB VRAM +- **large**: ~3.5GB VRAM + +### 処理速度(目安) +- **tiny**: リアルタイム処理可能 +- **base**: 1x-2x リアルタイム +- **small**: 0.5x-1x リアルタイム +- **medium**: 0.2x-0.5x リアルタイム +- **large**: 0.1x-0.3x リアルタイム + +## 計算タイプ設定 + +### 利用可能な計算タイプ +- **float32**: 最高精度、低速 +- **float16**: 高精度、中速(CUDA推奨) +- **int8**: 中精度、高速 +- **int8_float16**: 混合精度、バランス + +### 推奨設定 +```python +# CPU使用時 +compute_type = "int8" # 速度重視 + +# CUDA使用時(RTX以上) +compute_type = "float16" # 精度と速度のバランス + +# CUDA使用時(VRAM制限) +compute_type = "int8_float16" # メモリ効率重視 +``` + +## エラーハンドリング + +### ダウンロードエラー +- ネットワーク接続失敗 +- ディスク容量不足 +- 権限不足 + +### モデル読み込みエラー +- VRAM不足 +- 破損したモデルファイル +- 非対応デバイス + +### 対応策 +```python +def robust_model_loading(root, preferred_type="base"): + """堅牢なモデル読み込み""" + model_priority = ["tiny", "base", "small", "medium"] + + # 優先モデルを先頭に配置 + if preferred_type in model_priority: + model_priority.remove(preferred_type) + model_priority.insert(0, preferred_type) + + for model_type in model_priority: + try: + if checkWhisperWeight(root, model_type): + return getWhisperModel(root, model_type) + except Exception as e: + print(f"{model_type} モデル読み込み失敗: {e}") + continue + + raise RuntimeError("利用可能なWhisperモデルがありません") +``` + +## 依存関係 + +### 必須依存関係 +- `faster_whisper`: Whisperエンジン +- `requests`: ファイルダウンロード +- `utils`: ユーティリティ機能 + +### オプション依存関係 +- `torch`: CUDA計算(GPU使用時) + +## 注意事項 + +- 初回モデル読み込み時はダウンロードに時間がかかる +- 大きなモデルほど高精度だが、メモリとVRAMを大量消費 +- CUDAを使用する場合は適切なGPUドライバーが必要 +- モデルファイルの整合性チェックが重要 +- ネットワーク環境によってダウンロード時間が大きく変動 + +## 関連モジュール + +- `transcription_transcriber.py`: Whisper音声認識エンジン +- `config.py`: Whisperモデル設定管理 +- `utils.py`: 計算デバイス管理 +- `model.py`: Whisper統合制御 \ No newline at end of file diff --git a/src-python/docs/details/translation_languages.md b/src-python/docs/details/translation_languages.md new file mode 100644 index 00000000..9943e3e6 --- /dev/null +++ b/src-python/docs/details/translation_languages.md @@ -0,0 +1,342 @@ +# translation_languages.py - 翻訳言語マッピング + +## 概要 + +翻訳エンジンが対応する言語コードのマッピングテーブルを提供するモジュールです。複数の翻訳エンジン(DeepL、Google、Bing、Papago等)の言語コード仕様の差異を吸収し、統一的な翻訳言語管理を実現します。 + +## 主要機能 + +### 多エンジン対応 +- DeepL(無料版・API版) +- Google Translate +- Microsoft Translator(Bing) +- Papago Translator +- その他のWeb翻訳サービス + +### 言語コード統合管理 +- 各エンジン固有の言語コード形式を統一 +- 送信元(source)と送信先(target)言語の分離管理 +- 地域固有言語バリエーションの対応 + +## データ構造 + +### translation_lang +```python +translation_lang: Dict[str, Dict[str, Dict[str, str]]] = { + "エンジン名": { + "source": {"言語名": "言語コード", ...}, + "target": {"言語名": "言語コード", ...} + } +} +``` + +### DeepL翻訳エンジン(無料版) + +```python +translation_lang["DeepL"] = { + "source": { + "Arabic": "ar", "Bulgarian": "bg", "Czech": "cs", "Danish": "da", + "German": "de", "Greek": "el", "English": "en", "Spanish": "es", + "Estonian": "et", "Finnish": "fi", "French": "fr", "Irish": "ga", + "Croatian": "hr", "Hungarian": "hu", "Indonesian": "id", + "Icelandic": "is", "Italian": "it", "Japanese": "ja", + "Korean": "ko", "Lithuanian": "lt", "Latvian": "lv", + "Maltese": "mt", "Bokmal": "nb", "Dutch": "nl", + "Norwegian": "no", "Polish": "pl", "Portuguese": "pt", + "Romanian": "ro", "Russian": "ru", "Slovak": "sk", + "Slovenian": "sl", "Swedish": "sv", "Turkish": "tr", + "Ukrainian": "uk", "Chinese Simplified": "zh", + "Chinese Traditional": "zh" + }, + "target": {/* 同じマッピング */} +} +``` + +### DeepL API(有料版) + +```python +translation_lang["DeepL_API"] = { + "source": {/* 基本的にDeepLと同様 */}, + "target": { + "Japanese": "ja", + "English American": "en-US", # 地域別対応 + "English British": "en-GB", + "Portuguese Brazilian": "pt-BR", # ブラジル・ポルトガル語 + "Portuguese European": "pt-PT", # ヨーロッパ・ポルトガル語 + "Chinese Simplified": "zh", + "Chinese Traditional": "zh" + /* その他の言語 */ + } +} +``` + +## 主要対応言語 + +### 西欧言語 +- **English**: 英語(米国・英国バリエーション) +- **German**: ドイツ語 +- **French**: フランス語 +- **Spanish**: スペイン語 +- **Italian**: イタリア語 +- **Portuguese**: ポルトガル語(ブラジル・欧州) +- **Dutch**: オランダ語 +- **Swedish**: スウェーデン語 +- **Norwegian**: ノルウェー語 + +### 東欧・スラブ言語 +- **Russian**: ロシア語 +- **Polish**: ポーランド語 +- **Czech**: チェコ語 +- **Slovak**: スロバキア語 +- **Ukrainian**: ウクライナ語 +- **Bulgarian**: ブルガリア語 +- **Croatian**: クロアチア語 +- **Slovenian**: スロベニア語 + +### アジア言語 +- **Japanese**: 日本語 +- **Korean**: 韓国語 +- **Chinese Simplified**: 中国語(簡体字) +- **Chinese Traditional**: 中国語(繁体字) +- **Indonesian**: インドネシア語 + +### その他の言語 +- **Arabic**: アラビア語 +- **Turkish**: トルコ語 +- **Finnish**: フィンランド語 +- **Estonian**: エストニア語 +- **Latvian**: ラトビア語 +- **Lithuanian**: リトアニア語 +- **Maltese**: マルタ語 +- **Irish**: アイルランド語 + +## 使用方法 + +### 基本的な言語コード取得 + +```python +from models.translation.translation_languages import translation_lang + +# DeepLで日本語から英語への翻訳 +deepl_source = translation_lang["DeepL"]["source"]["Japanese"] # "ja" +deepl_target = translation_lang["DeepL"]["target"]["English"] # "en" + +# DeepL APIで地域固有の英語指定 +deepl_api_target = translation_lang["DeepL_API"]["target"]["English American"] # "en-US" +``` + +### 対応言語の確認 + +```python +def get_supported_languages(engine_name): + """指定エンジンの対応言語一覧取得""" + if engine_name in translation_lang: + engine_data = translation_lang[engine_name] + source_langs = list(engine_data["source"].keys()) + target_langs = list(engine_data["target"].keys()) + return { + "source": source_langs, + "target": target_langs, + "common": list(set(source_langs) & set(target_langs)) + } + return None + +# 使用例 +deepl_langs = get_supported_languages("DeepL") +print(f"DeepL対応言語数: {len(deepl_langs['common'])}") +``` + +### 言語コード変換 + +```python +def convert_language_code(language_name, from_engine, to_engine, direction="source"): + """エンジン間での言語コード変換""" + try: + # 元エンジンから言語名を確認 + from_codes = translation_lang[from_engine][direction] + to_codes = translation_lang[to_engine][direction] + + if language_name in from_codes and language_name in to_codes: + return to_codes[language_name] + return None + except KeyError: + return None + +# 使用例:DeepLからGoogle Translateへの変換 +google_code = convert_language_code("Japanese", "DeepL", "Google", "target") +``` + +### 翻訳システムでの統合利用 + +```python +class TranslationLanguageManager: + """翻訳言語管理クラス""" + + @staticmethod + def get_language_code(engine, language, direction="target"): + """安全な言語コード取得""" + try: + return translation_lang[engine][direction][language] + except KeyError: + return None + + @staticmethod + def is_language_supported(engine, language, direction="target"): + """言語サポート確認""" + try: + return language in translation_lang[engine][direction] + except KeyError: + return False + + @staticmethod + def get_compatible_engines(source_lang, target_lang): + """両言語をサポートするエンジン一覧""" + compatible = [] + for engine in translation_lang: + source_supported = TranslationLanguageManager.is_language_supported( + engine, source_lang, "source" + ) + target_supported = TranslationLanguageManager.is_language_supported( + engine, target_lang, "target" + ) + if source_supported and target_supported: + compatible.append(engine) + return compatible + +# 使用例 +manager = TranslationLanguageManager() + +# 日本語→英語をサポートするエンジン +engines = manager.get_compatible_engines("Japanese", "English") +print(f"対応エンジン: {engines}") + +# 特定エンジンでの言語コード取得 +ja_code = manager.get_language_code("DeepL", "Japanese", "source") +en_code = manager.get_language_code("DeepL", "English", "target") +``` + +## エンジン別特徴 + +### DeepL(無料版) +- **強み**: 高精度、自然な翻訳 +- **制限**: 月間使用量制限、API制限 +- **対応**: 26言語 + +### DeepL API(有料版) +- **強み**: DeepLの高精度、地域別言語対応 +- **制限**: 従量課金 +- **対応**: 地域固有言語バリエーション + +### Google Translate +- **強み**: 多言語対応、高速 +- **制限**: API制限、精度のばらつき +- **対応**: 100+言語 + +### Microsoft Translator +- **強み**: リアルタイム翻訳、音声対応 +- **制限**: APIキー必要 +- **対応**: 70+言語 + +## 地域バリエーション対応 + +### 英語の地域別対応 +```python +# DeepL APIでの英語バリエーション +"English American": "en-US", # アメリカ英語 +"English British": "en-GB", # イギリス英語 +``` + +### ポルトガル語の地域別対応 +```python +# ブラジル・ポルトガル語とヨーロッパ・ポルトガル語 +"Portuguese Brazilian": "pt-BR", +"Portuguese European": "pt-PT", +``` + +### 中国語の文字体系対応 +```python +# 簡体字・繁体字の区別 +"Chinese Simplified": "zh", # 簡体字(中国本土) +"Chinese Traditional": "zh", # 繁体字(台湾・香港) +``` + +## 拡張性 + +### 新エンジンの追加 +```python +# 新しい翻訳エンジンの追加例 +translation_lang["NewEngine"] = { + "source": { + "Japanese": "jp", + "English": "en", + "Korean": "kr" + }, + "target": { + "Japanese": "jp", + "English": "en", + "Korean": "kr" + } +} +``` + +### 新言語の追加 +```python +# 既存エンジンへの新言語追加 +translation_lang["DeepL"]["source"]["Hindi"] = "hi" +translation_lang["DeepL"]["target"]["Hindi"] = "hi" +``` + +## エラーハンドリング + +### 安全な言語コード取得 +```python +def safe_get_language_code(engine, language, direction="target", fallback="en"): + """フォールバック機能付き言語コード取得""" + try: + return translation_lang[engine][direction][language] + except KeyError: + # フォールバック言語を返す + try: + return translation_lang[engine][direction].get("English", fallback) + except KeyError: + return fallback +``` + +### 言語サポート検証 +```python +def validate_translation_pair(engine, source_lang, target_lang): + """翻訳ペアの有効性検証""" + try: + engine_data = translation_lang[engine] + source_supported = source_lang in engine_data["source"] + target_supported = target_lang in engine_data["target"] + + return { + "valid": source_supported and target_supported, + "source_supported": source_supported, + "target_supported": target_supported + } + except KeyError: + return { + "valid": False, + "source_supported": False, + "target_supported": False, + "error": f"Unknown engine: {engine}" + } +``` + +## 注意事項 + +- エンジンによって言語コード形式が異なる +- 地域バリエーションはエンジンにより対応状況が異なる +- 新しい言語追加時は全エンジンでの対応状況を確認 +- API制限や課金体系はエンジンごとに異なる +- 一部の言語ペアは翻訳精度に差がある場合がある + +## 関連モジュール + +- `translation_translator.py`: 翻訳エンジン本体 +- `translation_utils.py`: 翻訳ユーティリティ +- `transcription_languages.py`: 音声認識言語マッピング +- `config.py`: 翻訳言語設定管理 +- `controller.py`: 言語選択UI制御 \ No newline at end of file diff --git a/src-python/docs/details/translation_translator.md b/src-python/docs/details/translation_translator.md new file mode 100644 index 00000000..970385ae --- /dev/null +++ b/src-python/docs/details/translation_translator.md @@ -0,0 +1,406 @@ +# translation_translator.py - 翻訳エンジン統合クラス + +## 概要 + +複数の翻訳エンジンを統合管理する高レベル翻訳インターフェースです。DeepL、Google、Bing、Papago、CTranslate2などの多様な翻訳サービスを統一的に扱い、エラー時の自動フォールバック機能と認証管理を提供します。 + +## 主要機能 + +### 多エンジン統合 +- DeepL(無料版・API版) +- Google Translate(Webスクレイピング) +- Microsoft Translator(Bing) +- Papago Translator +- CTranslate2(ローカル翻訳) + +### 統一インターフェース +- エンジン依存を隠蔽した単一の翻訳メソッド +- 自動エラーハンドリング・フォールバック +- 認証情報の統合管理 + +### オフライン翻訳対応 +- CTranslate2による完全オフライン翻訳 +- 複数モデルサイズ(small/large)対応 +- CUDA高速化サポート + +## クラス構造 + +### Translator クラス +```python +class Translator: + def __init__(self) -> None: + self.deepl_client: Optional[DeepLClient] = None + self.ctranslate2_translator: Any = None + self.ctranslate2_tokenizer: Any = None + self.is_loaded_ctranslate2_model: bool = False + self.is_changed_translator_parameters: bool = False + self.is_enable_translators: bool = ENABLE_TRANSLATORS +``` + +翻訳機能の中核クラス + +#### 属性 +- **deepl_client**: DeepL APIクライアント +- **ctranslate2_translator**: ローカル翻訳モデル +- **ctranslate2_tokenizer**: CTranslate2トークナイザー +- **is_loaded_ctranslate2_model**: ローカルモデル読み込み状態 +- **is_enable_translators**: Web翻訳サービス利用可能フラグ + +## 主要メソッド + +### 翻訳実行 + +```python +translate(translator_name: str, source_language: str, target_language: str, + target_country: str, message: str) -> Any +``` + +統一翻訳インターフェース + +#### パラメータ +- **translator_name**: 翻訳エンジン名("DeepL", "Google", "CTranslate2"等) +- **source_language**: 送信元言語 +- **target_language**: 送信先言語 +- **target_country**: 送信先国・地域 +- **message**: 翻訳対象テキスト + +#### 戻り値 +- **str**: 翻訳結果(成功時) +- **False**: 翻訳失敗時 + +### DeepL認証管理 + +```python +authenticationDeepLAuthKey(authkey: str) -> bool +``` + +DeepL APIキーの認証と設定 + +#### パラメータ +- **authkey**: DeepL APIキー + +#### 戻り値 +- **bool**: 認証成功可否 + +### CTranslate2管理 + +```python +changeCTranslate2Model(path: str, model_type: str, device: str = "cpu", + device_index: int = 0, compute_type: str = "auto") -> None +``` + +ローカル翻訳モデルの読み込み・変更 + +#### パラメータ +- **path**: モデルファイルのベースパス +- **model_type**: モデルサイズ("small"/"large") +- **device**: 計算デバイス("cpu"/"cuda") +- **device_index**: デバイスインデックス +- **compute_type**: 計算精度タイプ + +### 状態管理 + +```python +isLoadedCTranslate2Model() -> bool +``` + +CTranslate2モデルの読み込み状態確認 + +```python +isChangedTranslatorParameters() -> bool +setChangedTranslatorParameters(is_changed: bool) -> None +``` + +翻訳設定変更フラグの管理 + +## 使用方法 + +### 基本的な翻訳 + +```python +from models.translation.translation_translator import Translator + +# 翻訳器の初期化 +translator = Translator() + +# Google翻訳の使用 +result = translator.translate( + translator_name="Google", + source_language="Japanese", + target_language="English", + target_country="United States", + message="こんにちは、世界!" +) + +if result != False: + print(f"翻訳結果: {result}") # "Hello, world!" +else: + print("翻訳に失敗しました") +``` + +### DeepL API使用 + +```python +# DeepL APIキーの設定 +api_key = "your-deepl-api-key" +auth_success = translator.authenticationDeepLAuthKey(api_key) + +if auth_success: + print("DeepL API認証成功") + + # DeepL APIで翻訳 + result = translator.translate( + translator_name="DeepL_API", + source_language="English", + target_language="Japanese", + target_country="Japan", + message="Hello, world!" + ) + print(f"DeepL翻訳: {result}") +else: + print("DeepL API認証失敗") +``` + +### ローカル翻訳(CTranslate2)の使用 + +```python +# ローカルモデルの読み込み +translator.changeCTranslate2Model( + path=".", # アプリケーションルート + model_type="small", # smallモデル使用 + device="cuda", # GPU使用 + device_index=0, + compute_type="float16" # 半精度で高速化 +) + +# モデル読み込み確認 +if translator.isLoadedCTranslate2Model(): + print("CTranslate2モデル読み込み完了") + + # ローカル翻訳実行 + result = translator.translate( + translator_name="CTranslate2", + source_language="Japanese", + target_language="English", + target_country="United States", + message="機械翻訳のテストです" + ) + print(f"ローカル翻訳: {result}") +else: + print("CTranslate2モデル読み込み失敗") +``` + +### エラーハンドリング付きの翻訳 + +```python +def safe_translate(translator, message, source_lang="Japanese", target_lang="English"): + """安全な翻訳処理""" + # 翻訳エンジンの優先順位 + engines = ["DeepL_API", "DeepL", "Google", "CTranslate2"] + + for engine in engines: + try: + result = translator.translate( + translator_name=engine, + source_language=source_lang, + target_language=target_lang, + target_country="United States", + message=message + ) + + if result != False: + print(f"{engine}で翻訳成功: {result}") + return result + else: + print(f"{engine}翻訳失敗、次のエンジンを試行") + + except Exception as e: + print(f"{engine}でエラー: {e}") + continue + + print("全ての翻訳エンジンで失敗") + return None + +# 使用例 +result = safe_translate(translator, "こんにちは") +``` + +### 翻訳設定の管理 + +```python +# 設定変更フラグの確認 +if translator.isChangedTranslatorParameters(): + print("翻訳設定が変更されています") + + # 設定変更の適用(例:モデル再読み込み) + translator.changeCTranslate2Model(".", "small", "cpu") + + # フラグのリセット + translator.setChangedTranslatorParameters(False) +``` + +## 翻訳エンジン比較 + +### DeepL API(有料) +- **精度**: 最高レベル +- **速度**: 高速 +- **制限**: API使用料、月間制限 +- **対応**: 26言語、地域別対応 + +### DeepL(無料) +- **精度**: 高品質 +- **速度**: 中程度 +- **制限**: 月間使用量制限、文字数制限 +- **対応**: 26言語 + +### Google Translate +- **精度**: 良好 +- **速度**: 高速 +- **制限**: アクセス頻度制限 +- **対応**: 100+言語 + +### CTranslate2(ローカル) +- **精度**: 中〜高(モデル依存) +- **速度**: 高速(GPU使用時) +- **制限**: なし(オフライン) +- **対応**: 主要言語ペア + +### その他(Bing, Papago等) +- **精度**: 中程度 +- **速度**: 中程度 +- **制限**: サービス依存 +- **対応**: サービス固有 + +## CTranslate2詳細 + +### 対応モデル +```python +ctranslate2_weights = { + "small": { + "url": "m2m100_418m.zip", + "directory_name": "m2m100_418m", + "tokenizer": "facebook/m2m100_418M" + }, + "large": { + "url": "m2m100_12b.zip", + "directory_name": "m2m100_12b", + "tokenizer": "facebook/m2m100_1.2b" + } +} +``` + +### パフォーマンス特性 + +#### small モデル +- **サイズ**: ~400MB +- **メモリ**: ~1GB RAM +- **VRAM**: ~500MB(CUDA使用時) +- **速度**: 高速 +- **精度**: 良好 + +#### large モデル +- **サイズ**: ~4.8GB +- **メモリ**: ~6GB RAM +- **VRAM**: ~3GB(CUDA使用時) +- **速度**: 中程度 +- **精度**: 高品質 + +### 計算タイプ設定 +```python +# CPU使用時 +compute_type = "int8" # 速度重視 + +# CUDA使用時 +compute_type = "float16" # バランス重視 +compute_type = "int8_float16" # メモリ効率重視 +``` + +## エラーハンドリング + +### ネットワークエラー +- 接続タイムアウト +- API制限超過 +- サービス一時停止 + +### 認証エラー +- 無効なAPIキー +- 期限切れアカウント +- 使用量上限到達 + +### モデルエラー +- ファイル破損 +- VRAM不足 +- 非対応言語ペア + +### 対応策 +```python +def robust_translation(translator, message, source_lang, target_lang): + """堅牢な翻訳処理""" + # オンライン翻訳を先に試行 + online_engines = ["DeepL_API", "DeepL", "Google"] + + for engine in online_engines: + try: + result = translator.translate(engine, source_lang, target_lang, "", message) + if result != False: + return result + except Exception as e: + print(f"{engine}エラー: {e}") + continue + + # オンライン翻訳が全て失敗した場合、ローカル翻訳にフォールバック + try: + if not translator.isLoadedCTranslate2Model(): + translator.changeCTranslate2Model(".", "small", "cpu") + + result = translator.translate("CTranslate2", source_lang, target_lang, "", message) + if result != False: + return result + except Exception as e: + print(f"ローカル翻訳エラー: {e}") + + return "翻訳に失敗しました" +``` + +## 依存関係 + +### 必須依存関係 +- `translation_languages`: 言語コード管理 +- `translation_utils`: CTranslate2ユーティリティ +- `utils`: エラーログ、計算デバイス管理 + +### オプション依存関係 +- `deepl`: DeepL APIライブラリ +- `translators`: Web翻訳サービスライブラリ +- `ctranslate2`: ローカル翻訳エンジン +- `transformers`: トークナイザー + +## 設定要件 + +### 環境変数 +- `DEEPL_AUTH_KEY`: DeepL APIキー(オプション) + +### ファイル配置 +``` +root/ +└── weights/ + └── ctranslate2/ + ├── m2m100_418m/ # smallモデル + └── m2m100_12b/ # largeモデル +``` + +## 注意事項 + +- Web翻訳サービスは利用制限に注意 +- CTranslate2の初回読み込みは時間がかかる +- GPU使用時はVRAM消費量に注意 +- API認証情報の適切な管理が必要 +- 長文翻訳時は分割処理を推奨 + +## 関連モジュール + +- `translation_languages.py`: 言語コードマッピング +- `translation_utils.py`: CTranslate2ユーティリティ +- `config.py`: 翻訳設定管理 +- `model.py`: 翻訳機能統合 +- `controller.py`: 翻訳制御インターフェース \ No newline at end of file diff --git a/src-python/docs/details/translation_utils.md b/src-python/docs/details/translation_utils.md new file mode 100644 index 00000000..9eab4d2b --- /dev/null +++ b/src-python/docs/details/translation_utils.md @@ -0,0 +1,438 @@ +# translation_utils.py - CTranslate2モデル管理ユーティリティ + +## 概要 + +CTranslate2によるローカル機械翻訳モデルの自動ダウンロード、展開、管理を行うユーティリティモジュールです。複数のモデルサイズ(small/large)とプラットフォーム(CPU/CUDA)に対応し、モデルファイルの完全性チェックと自動修復機能を提供します。 + +## 主要機能 + +### モデル自動管理 +- CTranslate2モデルの自動ダウンロード +- ZIP形式モデルの展開・配置 +- モデルファイルの完全性検証 +- 破損モデルの自動再取得 + +### マルチプラットフォーム対応 +- CPU版・CUDA版の両対応 +- 複数モデルサイズの管理 +- プラットフォーム別最適化 + +## 定数・設定 + +### モデル定義 + +```python +# CTranslate2重みファイル情報 +ctranslate2_weights = { + "small": { + "url": "m2m100_418m.zip", + "directory_name": "m2m100_418m", + "tokenizer": "facebook/m2m100_418M" + }, + "large": { + "url": "m2m100_12b.zip", + "directory_name": "m2m100_12b", + "tokenizer": "facebook/m2m100_1.2b" + } +} +``` + +### 設定パラメータ +- **BASE_WEIGHTS_URL**: モデル配布ベースURL +- **LOCAL_WEIGHTS_DIR**: ローカル保存ディレクトリ +- **CHUNK_SIZE**: ダウンロード時のチャンクサイズ + +## 主要機能 + +### モデルダウンロード + +```python +def downloadCTranslate2Model(model_type: str, device: str = "cpu") -> bool: + """CTranslate2モデルの自動ダウンロード""" +``` + +指定されたモデルタイプとデバイス用のモデルをダウンロード + +#### パラメータ +- **model_type**: モデルサイズ("small"/"large") +- **device**: 計算デバイス("cpu"/"cuda") + +#### 戻り値 +- **bool**: ダウンロード成功可否 + +### モデル存在確認 + +```python +def checkCTranslate2ModelExists(model_type: str, device: str = "cpu") -> bool: + """モデルファイルの存在確認""" +``` + +指定されたモデルがローカルに存在するかチェック + +#### パラメータ +- **model_type**: 確認対象モデルタイプ +- **device**: 対象デバイス + +#### 戻り値 +- **bool**: モデル存在可否 + +### モデル完全性検証 + +```python +def validateCTranslate2Model(model_type: str, device: str = "cpu") -> bool: + """モデルファイルの完全性検証""" +``` + +ダウンロード済みモデルの整合性を確認 + +#### パラメータ +- **model_type**: 検証対象モデル +- **device**: 対象デバイス + +#### 戻り値 +- **bool**: モデル正常性 + +## 使用方法 + +### 基本的なモデル管理 + +```python +from models.translation.translation_utils import * + +# smallモデル(CPU版)のダウンロード確認 +if not checkCTranslate2ModelExists("small", "cpu"): + print("smallモデルが見つかりません。ダウンロード中...") + success = downloadCTranslate2Model("small", "cpu") + + if success: + print("ダウンロード完了") + else: + print("ダウンロード失敗") +else: + print("smallモデルは既に存在します") +``` + +### GPU用モデルの準備 + +```python +# CUDA版largeモデルのセットアップ +model_type = "large" +device = "cuda" + +# 既存モデルの確認 +if checkCTranslate2ModelExists(model_type, device): + # モデルの完全性検証 + if validateCTranslate2Model(model_type, device): + print(f"{model_type}モデル({device}版)準備完了") + else: + print("モデルが破損しています。再ダウンロード中...") + # 破損モデルの再取得 + downloadCTranslate2Model(model_type, device) +else: + # 新規ダウンロード + print(f"{model_type}モデル({device}版)をダウンロード中...") + downloadCTranslate2Model(model_type, device) +``` + +### 自動モデル管理システム + +```python +def ensureModelReady(model_type="small", device="cpu", max_retries=3): + """モデルの準備を保証する関数""" + + for attempt in range(max_retries): + print(f"モデル準備 試行 {attempt + 1}/{max_retries}") + + # モデル存在確認 + if not checkCTranslate2ModelExists(model_type, device): + print("モデルが見つかりません。ダウンロード中...") + if not downloadCTranslate2Model(model_type, device): + print(f"ダウンロード失敗(試行 {attempt + 1})") + continue + + # モデル完全性確認 + if validateCTranslate2Model(model_type, device): + print("モデル準備完了") + return True + else: + print("モデルが破損しています。再取得中...") + # 破損ファイルの削除(実装依存) + # remove_corrupted_model(model_type, device) + continue + + print("モデル準備に失敗しました") + return False + +# 使用例 +if ensureModelReady("small", "cpu"): + print("翻訳システム初期化可能") +else: + print("翻訳システム初期化失敗") +``` + +### 複数モデルの一括管理 + +```python +def setupAllModels(): + """全モデルの一括セットアップ""" + + models = [ + ("small", "cpu"), + ("small", "cuda"), + ("large", "cpu"), + ("large", "cuda") + ] + + results = {} + + for model_type, device in models: + print(f"\n=== {model_type}モデル({device}版)セットアップ ===") + + # デバイス利用可能性チェック(CUDA版の場合) + if device == "cuda" and not torch.cuda.is_available(): + print("CUDA環境が利用できません。スキップします。") + results[(model_type, device)] = False + continue + + # モデル準備 + success = ensureModelReady(model_type, device) + results[(model_type, device)] = success + + if success: + print(f"✓ {model_type}({device}版)準備完了") + else: + print(f"✗ {model_type}({device}版)準備失敗") + + # 結果サマリー + print("\n=== セットアップ結果 ===") + for (model_type, device), success in results.items(): + status = "成功" if success else "失敗" + print(f"{model_type}({device}版): {status}") + + return results + +# 全モデルセットアップの実行 +setupAllModels() +``` + +## モデル仕様 + +### smallモデル(m2m100_418m) + +```python +model_info = { + "name": "m2m100_418m", + "size": "~400MB", + "parameters": "418M", + "languages": "100言語", + "tokenizer": "facebook/m2m100_418M", + "memory_requirements": { + "cpu": "~1GB RAM", + "cuda": "~500MB VRAM" + }, + "performance": { + "speed": "高速", + "quality": "良好" + } +} +``` + +#### 特徴 +- 高速処理に適している +- メモリ使用量が少ない +- リアルタイム翻訳に最適 +- 100言語ペア対応 + +### largeモデル(m2m100_12b) + +```python +model_info = { + "name": "m2m100_12b", + "size": "~4.8GB", + "parameters": "1.2B", + "languages": "100言語", + "tokenizer": "facebook/m2m100_1.2b", + "memory_requirements": { + "cpu": "~6GB RAM", + "cuda": "~3GB VRAM" + }, + "performance": { + "speed": "中程度", + "quality": "高品質" + } +} +``` + +#### 特徴 +- 高品質翻訳が可能 +- 大容量メモリが必要 +- バッチ処理に適している +- 複雑な文章に対応 + +## ファイル構造 + +### ディレクトリレイアウト +``` +weights/ +└── ctranslate2/ + ├── m2m100_418m/ # smallモデル(CPU版) + │ ├── model.bin + │ ├── vocabulary.txt + │ ├── config.json + │ └── shared_vocabulary.txt + ├── m2m100_418m_cuda/ # smallモデル(CUDA版) + │ └── [同様のファイル構成] + ├── m2m100_12b/ # largeモデル(CPU版) + │ └── [同様のファイル構成] + └── m2m100_12b_cuda/ # largeモデル(CUDA版) + └── [同様のファイル構成] +``` + +### 必須ファイル +- `model.bin`: 変換済みモデルウェイト +- `vocabulary.txt`: 語彙ファイル +- `config.json`: モデル設定ファイル +- `shared_vocabulary.txt`: 共有語彙ファイル + +## ダウンロード処理 + +### ネットワーク処理 + +```python +def downloadWithProgress(url: str, destination: str) -> bool: + """進捗表示付きダウンロード""" + try: + response = requests.get(url, stream=True) + response.raise_for_status() + + total_size = int(response.headers.get('content-length', 0)) + + with open(destination, 'wb') as file: + downloaded = 0 + for chunk in response.iter_content(chunk_size=CHUNK_SIZE): + if chunk: + file.write(chunk) + downloaded += len(chunk) + + # 進捗表示 + if total_size > 0: + progress = (downloaded / total_size) * 100 + print(f"\rダウンロード進捗: {progress:.1f}%", end="") + + print(f"\nダウンロード完了: {destination}") + return True + + except Exception as e: + print(f"\nダウンロードエラー: {e}") + return False +``` + +### 展開処理 + +```python +def extractZipModel(zip_path: str, extract_to: str) -> bool: + """ZIPファイルの展開""" + try: + with zipfile.ZipFile(zip_path, 'r') as zip_ref: + # 展開先ディレクトリの作成 + os.makedirs(extract_to, exist_ok=True) + + # ファイル展開 + zip_ref.extractall(extract_to) + + print(f"展開完了: {extract_to}") + + # 元のZIPファイルを削除(オプション) + os.remove(zip_path) + print(f"一時ファイル削除: {zip_path}") + + return True + + except Exception as e: + print(f"展開エラー: {e}") + return False +``` + +## エラーハンドリング + +### ネットワークエラー +- 接続タイムアウト +- ダウンロード中断 +- サーバーエラー + +### ファイルシステムエラー +- 容量不足 +- 権限エラー +- ファイル破損 + +### リトライ機構 + +```python +def downloadWithRetry(url: str, destination: str, max_retries: int = 3) -> bool: + """リトライ付きダウンロード""" + + for attempt in range(max_retries): + print(f"ダウンロード試行 {attempt + 1}/{max_retries}") + + try: + if downloadWithProgress(url, destination): + return True + except Exception as e: + print(f"試行 {attempt + 1} 失敗: {e}") + + # 一時ファイルの清理 + if os.path.exists(destination): + os.remove(destination) + + # 最後の試行でない場合は少し待機 + if attempt < max_retries - 1: + time.sleep(2 ** attempt) # 指数バックオフ + + print("全ての試行が失敗しました") + return False +``` + +## パフォーマンス最適化 + +### ダウンロード最適化 +- チャンク単位での分割ダウンロード +- 進捗表示による体験向上 +- 自動リトライによる信頼性確保 + +### ストレージ最適化 +- 一時ファイルの自動削除 +- 重複ファイルの検出・排除 +- 容量効率的なファイル管理 + +### メモリ最適化 +- ストリーミングダウンロード +- 大容量ファイル対応 +- メモリ使用量の制御 + +## 依存関係 + +### 必須依存関係 +- `requests`: HTTPダウンロード +- `zipfile`: アーカイブ展開 +- `os`: ファイルシステム操作 +- `pathlib`: パス操作 + +### オプション依存関係 +- `tqdm`: 進捗バー表示(実装による) +- `hashlib`: ファイル整合性検証(実装による) + +## 注意事項 + +- 初回ダウンロードは時間がかかる(モデルサイズ依存) +- 十分なストレージ容量を確保 +- ネットワーク環境によってダウンロード速度が変動 +- CUDA版は対応GPU環境が必要 +- モデルファイルのバックアップ推奨 + +## 関連モジュール + +- `translation_translator.py`: モデル利用クラス +- `translation_languages.py`: 言語コード管理 +- `config.py`: 設定管理 +- `utils.py`: 共通ユーティリティ +- `device_manager.py`: デバイス管理 \ No newline at end of file diff --git a/src-python/docs/details/transliteration_context_rules.md b/src-python/docs/details/transliteration_context_rules.md new file mode 100644 index 00000000..f98531c8 --- /dev/null +++ b/src-python/docs/details/transliteration_context_rules.md @@ -0,0 +1,397 @@ +# transliteration_context_rules.py - 文脈的転写ルールエンジン + +## 概要 + +トークン化された結果に対して文脈依存の転写ルールを適用するコンパクトなルールエンジンです。隣接するトークンの情報に基づいて読み(かな)を動的に修正し、より自然で正確な転写を実現します。 + +## 主要機能 + +### 文脈依存転写 +- 隣接トークン情報を利用した読み修正 +- 優先度ベースのルール適用順序 +- 正規表現・完全一致の両方に対応 + +### ルールエンジン +- 埋め込み型ルール定義(外部JSONファイル不要) +- 前方・後方の隣接トークン検査対応 +- インプレース変更による効率的処理 + +### 動的読み変更 +- 文脈に応じたかな読みの書き換え +- ひらがな・ヘボン式の自動クリア +- 呼び出し元での再計算トリガー + +## ルール定義構造 + +### DEFAULT_RULES + +```python +DEFAULT_RULES = { + "rules": [ + { + "name": "nan_next_tdna", # ルール名 + "target": "何", # 対象文字 + "match_mode": "equals", # マッチモード + "direction": "next", # 検査方向 + "kana_set": ["タ", "チ", "ツ"...], # 条件文字セット + "on_true": {"kana": "ナン"}, # 条件真時のアクション + "on_false": {"kana": "ナニ"} # 条件偽時のアクション + } + ] +} +``` + +### ルール要素 + +#### 基本設定 +- **name**: ルールの識別名 +- **target**: 適用対象となる文字・文字列 +- **priority**: 適用優先度(高い順に処理) +- **match_mode**: マッチングモード("equals"/"regex") + +#### 条件設定 +- **direction**: 隣接トークン検査方向("next"/"prev") +- **kana_set**: 条件判定用の文字セット +- **pattern**: 正規表現パターン(regex時) + +#### アクション設定 +- **on_true**: 条件成立時のアクション +- **on_false**: 条件不成立時のアクション +- **kana**: 設定する新しいかな読み + +## 主要関数 + +### apply_context_rules + +```python +def apply_context_rules(results: List[Dict[str, Any]], use_macron: bool = False) -> List[Dict[str, Any]] +``` + +文脈ルールをトークンリストに適用 + +#### パラメータ +- **results**: `Transliterator.split_kanji_okurigana`で生成されたトークン辞書のリスト +- **use_macron**: 互換性のためのパラメータ(ルール処理では未使用) + +#### 戻り値 +- **List[Dict[str, Any]]**: 修正されたトークンリスト(インプレース変更も実施) + +#### 必須キー +各トークン辞書は以下のキーを含む必要があります: +- **orig**: 元の文字・文字列 +- **kana**: かな読み +- **hira**: ひらがな表記 +- **hepburn**: ヘボン式ローマ字 + +## 使用方法 + +### 基本的な文脈ルール適用 + +```python +from models.transliteration.transliteration_context_rules import apply_context_rules + +# トークン化された結果(例) +results = [ + {"orig": "何", "kana": "ナニ", "hira": "なに", "hepburn": "nani"}, + {"orig": "度", "kana": "ド", "hira": "ど", "hepburn": "do"}, + {"orig": "も", "kana": "モ", "hira": "も", "hepburn": "mo"} +] + +# 文脈ルールの適用 +modified_results = apply_context_rules(results) + +# 結果確認 +for token in modified_results: + print(f"{token['orig']}: {token['kana']} -> {token['hira']} ({token['hepburn']})") + +# 期待される出力(「何度」の場合): +# 何: ナン -> (再計算必要) (再計算必要) +# 度: ド -> ど (do) +# も: モ -> も (mo) +``` + +### カスタムルールでの処理 + +```python +# 独自ルール定義の例 +custom_rules = { + "rules": [ + { + "name": "custom_rule_example", + "target": "今", + "match_mode": "equals", + "direction": "next", + "kana_set": ["バ", "ビ", "ブ", "ベ", "ボ"], + "priority": 100, + "on_true": {"kana": "イマ"}, + "on_false": {"kana": "コン"} + } + ] +} + +# 注意:現在の実装では DEFAULT_RULES が固定使用されています +# カスタムルールを使用するには関数の拡張が必要です +``` + +### 正規表現マッチングの例 + +```python +# 正規表現ルールの定義例 +regex_rule = { + "name": "kanji_pattern_rule", + "match_mode": "regex", + "pattern": r"^[一-龯]$", # 任意の漢字1文字 + "direction": "next", + "kana_set": ["ア", "イ", "ウ", "エ", "オ"], + "priority": 50, + "on_true": {"kana": "特殊読み"}, + "on_false": {"kana": "通常読み"} +} +``` + +### 転写パイプラインでの統合 + +```python +def complete_transliteration_pipeline(text): + """完全な転写パイプライン""" + + # 1. 初期分割・転写 + transliterator = Transliterator() + tokens = transliterator.split_kanji_okurigana(text) + + # 2. 文脈ルール適用 + tokens = apply_context_rules(tokens) + + # 3. 修正されたトークンの再計算 + for token in tokens: + if token.get("kana") and not token.get("hira"): + # ひらがな・ヘボン式の再計算 + token["hira"] = katakana_to_hiragana(token["kana"]) + token["hepburn"] = hiragana_to_hepburn(token["hira"]) + + return tokens + +# 使用例 +text = "何度でも挑戦する" +result = complete_transliteration_pipeline(text) + +for token in result: + print(f"{token['orig']} -> {token['kana']} -> {token['hira']} -> {token['hepburn']}") +``` + +## ルール処理ロジック + +### 処理フロー + +1. **ルール準備** + - 優先度の降順でソート + - 正規表現の事前コンパイル + +2. **トークン走査** + - 各トークンに対してルールを順次適用 + - 空の`orig`を持つトークンはスキップ + +3. **マッチング判定** + - `equals`: 完全一致判定 + - `regex`: 正規表現マッチ判定 + +4. **隣接トークン検査** + - `direction`に基づく隣接トークン特定 + - 空のトークンをスキップして有効トークンを検索 + +5. **条件評価** + - 隣接トークンの`kana`の先頭文字チェック + - `kana_set`との一致判定 + +6. **アクション実行** + - 条件に応じて`on_true`/`on_false`を選択 + - `kana`の書き換えと`hira`/`hepburn`のクリア + +### アルゴリズム詳細 + +```python +def process_token_with_rules(token_index, tokens, rules): + """単一トークンのルール処理アルゴリズム""" + + token = tokens[token_index] + orig = token.get("orig", "") + + # 空トークンはスキップ + if not orig: + return + + for rule in rules: # 優先度順 + # マッチング判定 + if not matches_rule(orig, rule): + continue + + # 隣接トークン検索 + neighbor = find_neighbor_token(token_index, tokens, rule["direction"]) + + if neighbor: + # 条件評価 + condition = evaluate_condition(neighbor, rule["kana_set"]) + + # アクション実行 + action = rule["on_true"] if condition else rule["on_false"] + apply_action(token, action) + + # 最初にマッチしたルールで処理終了 + break + +def find_neighbor_token(current_index, tokens, direction): + """隣接する有効トークンを検索""" + + if direction == "next": + for i in range(current_index + 1, len(tokens)): + if tokens[i].get("orig"): + return tokens[i] + elif direction == "prev": + for i in range(current_index - 1, -1, -1): + if tokens[i].get("orig"): + return tokens[i] + + return None +``` + +## 具体的なルール例 + +### 「何」の読み分けルール + +```python +{ + "name": "nan_next_tdna", + "target": "何", + "match_mode": "equals", + "direction": "next", + "kana_set": ["タ", "チ", "ツ", "テ", "ト", "ダ", "ヂ", "ヅ", "デ", "ド", "ナ", "ニ", "ヌ", "ネ", "ノ"], + "on_true": {"kana": "ナン"}, + "on_false": {"kana": "ナニ"} +} +``` + +#### 動作例 + +```python +# 「何度」の場合 +tokens = [ + {"orig": "何", "kana": "ナニ"}, # 初期状態 + {"orig": "度", "kana": "ド"} # 次のトークン +] + +# ルール適用後 +tokens = [ + {"orig": "何", "kana": "ナン"}, # 「ド」が kana_set に含まれるため "ナン" に変更 + {"orig": "度", "kana": "ド"} +] + +# 「何回」の場合 +tokens = [ + {"orig": "何", "kana": "ナニ"}, # 初期状態 + {"orig": "回", "kana": "カイ"} # 次のトークン +] + +# ルール適用後 +tokens = [ + {"orig": "何", "kana": "ナニ"}, # 「カイ」が kana_set に含まれないため "ナニ" のまま + {"orig": "回", "kana": "カイ"} +] +``` + +## エラーハンドリング + +### 正規表現コンパイルエラー +```python +# 不正な正規表現の安全な処理 +for rule in rules: + if rule.get("match_mode") == "regex" and rule.get("pattern"): + try: + rule["_re"] = re.compile(rule["pattern"]) + except Exception as e: + print(f"正規表現コンパイルエラー: {rule['pattern']} - {e}") + rule["_re"] = None # 無効化 +``` + +### 不正なトークン構造 +```python +# 必須キーの存在確認 +def validate_token(token): + """トークンの妥当性検証""" + required_keys = ["orig", "kana", "hira", "hepburn"] + + for key in required_keys: + if key not in token: + print(f"警告: トークンに必須キー '{key}' が不足") + token[key] = "" # デフォルト値を設定 + + return token +``` + +## パフォーマンス考慮事項 + +### 効率的な処理 +- インプレース変更によるメモリ効率 +- 優先度ソートによる早期終了 +- 正規表現の事前コンパイル + +### スケーラビリティ +- 大量トークンでの線形処理時間 +- ルール数の増加に対する適切な対応 +- キャッシュ機能の追加可能性 + +## 拡張可能性 + +### ルール形式の拡張 +```python +# より複雑なルール例(将来的な拡張) +complex_rule = { + "name": "multi_condition_rule", + "target": "言", + "conditions": [ + {"direction": "prev", "kana_set": ["オ", "コ"]}, + {"direction": "next", "kana_set": ["ハ", "バ"]} + ], + "operator": "AND", # or "OR" + "actions": { + "all_true": {"kana": "ゴン"}, + "any_true": {"kana": "ゲン"}, + "all_false": {"kana": "イ"} + } +} +``` + +### 動的ルール追加 +```python +def add_runtime_rule(new_rule): + """実行時ルール追加(拡張版)""" + # ルールの検証 + if validate_rule_format(new_rule): + DEFAULT_RULES["rules"].append(new_rule) + return True + return False +``` + +## 依存関係 + +### 必須依存関係 +- `typing`: 型ヒント +- `re`: 正規表現処理 + +### 関連モジュール +- `transliteration_transliterator.py`: メイン転写クラス +- `transliteration_kana_to_hepburn.py`: かな→ヘボン式変換 + +## 注意事項 + +- ルール適用後は`hira`と`hepburn`が空文字列になるため、呼び出し元での再計算が必要 +- 現在のルールは日本語に特化している +- ルール適用順序は優先度に依存するため、適切な設定が重要 +- 正規表現ルールはパフォーマンスに影響する可能性がある + +## 将来の改善点 + +- 外部ルールファイルの読み込み対応 +- より複雑な条件式のサポート +- ルール適用ログ・デバッグ機能 +- 言語別ルールセットの対応 +- パフォーマンス最適化とキャッシュ機能 \ No newline at end of file diff --git a/src-python/docs/details/transliteration_kana_to_hepburn.md b/src-python/docs/details/transliteration_kana_to_hepburn.md new file mode 100644 index 00000000..39e6d042 --- /dev/null +++ b/src-python/docs/details/transliteration_kana_to_hepburn.md @@ -0,0 +1,465 @@ +# transliteration_kana_to_hepburn.py - カタカナ→ヘボン式変換 + +## 概要 + +カタカナ文字列を標準的なヘボン式ローマ字に変換するモジュールです。マクロン(長音記号)対応、外来語音の変換、促音・撥音処理など、日本語のローマ字表記に必要な機能を包括的に提供します。 + +## 主要機能 + +### 標準的なヘボン式変換 +- カタカナ文字の基本ローマ字変換 +- マクロン(ā ī ū ē ō)による長音表現 +- 連続母音表記の選択的対応 + +### 特殊音処理 +- 促音(ッ)の適切な子音重複処理 +- 撥音(ン)のm/n使い分け +- 長音符(ー)の前母音延長処理 + +### 外来語対応 +- シェ(she)、チェ(che)等の組み合わせ +- ヴ音(vu, va, vi, ve, vo)の変換 +- ファ行(fa, fi, fe, fo)の処理 + +## 主要関数 + +### katakana_to_hepburn + +```python +def katakana_to_hepburn(kata: str, use_macron: bool = True) -> str +``` + +カタカナ文字列をヘボン式ローマ字に変換 + +#### パラメータ +- **kata**: 変換対象のカタカナ文字列 +- **use_macron**: マクロン使用フラグ(True=ā ī ū ē ō、False=aa ii uu ee oo) + +#### 戻り値 +- **str**: ヘボン式ローマ字文字列(小文字) + +## 使用方法 + +### 基本的な変換 + +```python +from models.transliteration.transliteration_kana_to_hepburn import katakana_to_hepburn + +# 基本的なカタカナ変換 +result1 = katakana_to_hepburn("カタカナ") +print(result1) # "katakana" + +# 長音のマクロン表記 +result2 = katakana_to_hepburn("コンピューター", use_macron=True) +print(result2) # "konpyūtā" + +# 長音の連続母音表記 +result3 = katakana_to_hepburn("コンピューター", use_macron=False) +print(result3) # "konpyuutaa" +``` + +### 特殊音の処理 + +```python +# 促音(ッ)の処理 +result1 = katakana_to_hepburn("キャッチ") +print(result1) # "kyatchi" + +result2 = katakana_to_hepburn("マッチャ") +print(result2) # "matcha" + +# 撥音(ン)の処理 +result3 = katakana_to_hepburn("ホンバン") # ホン+バン +print(result3) # "homban" (n→m変換) + +result4 = katakana_to_hepburn("ホンテン") # ホン+テン +print(result4) # "honten" (nのまま) +``` + +### 外来語音の変換 + +```python +# 外来語特殊音 +result1 = katakana_to_hepburn("シェア") +print(result1) # "shea" + +result2 = katakana_to_hepburn("チェック") +print(result2) # "chekku" + +result3 = katakana_to_hepburn("ジェット") +print(result3) # "jetto" + +# ヴ音の処理 +result4 = katakana_to_hepburn("ヴァイオリン") +print(result4) # "vaiorin" + +result5 = katakana_to_hepburn("ヴィーナス") +print(result5) # "vīnasu" + +# ファ行の処理 +result6 = katakana_to_hepburn("ファイル") +print(result6) # "fairu" + +result7 = katakana_to_hepburn("フィルム") +print(result7) # "firumu" +``` + +### 長音処理の詳細 + +```python +# 長音符(ー)の処理 +result1 = katakana_to_hepburn("スーパー", use_macron=True) +print(result1) # "sūpā" + +result2 = katakana_to_hepburn("パーティー", use_macron=True) +print(result2) # "pātī" + +# ou → ō の変換(東京型) +result3 = katakana_to_hepburn("トウキョウ", use_macron=True) +print(result3) # "tōkyō" + +# 連続母音表記との比較 +result4 = katakana_to_hepburn("トウキョウ", use_macron=False) +print(result4) # "toukyou" +``` + +### 複雑な組み合わせ + +```python +# 拗音(ゃゅょ)の組み合わせ +test_cases = [ + ("キャンプ", "kyanpu"), + ("シュート", "shūto"), + ("チョコレート", "chokorēto"), + ("ギュウニュウ", "gyūnyū"), + ("リュックサック", "ryukkusakku"), + ("ピョンピョン", "pyonpyon") +] + +for kata, expected in test_cases: + result = katakana_to_hepburn(kata) + print(f"{kata} -> {result}") + # キャンプ -> kyanpu + # シュート -> shūto + # チョコレート -> chokorēto + # ギュウニュウ -> gyūnyū + # リュックサック -> ryukkusakku + # ピョンピョン -> pyonpyon +``` + +## 変換ルール詳細 + +### 基本音対応表 + +```python +base_mapping = { + # 清音 + 'ア':'a', 'イ':'i', 'ウ':'u', 'エ':'e', 'オ':'o', + 'カ':'ka', 'キ':'ki', 'ク':'ku', 'ケ':'ke', 'コ':'ko', + 'サ':'sa', 'シ':'shi', 'ス':'su', 'セ':'se', 'ソ':'so', + 'タ':'ta', 'チ':'chi', 'ツ':'tsu', 'テ':'te', 'ト':'to', + 'ナ':'na', 'ニ':'ni', 'ヌ':'nu', 'ネ':'ne', 'ノ':'no', + 'ハ':'ha', 'ヒ':'hi', 'フ':'fu', 'ヘ':'he', 'ホ':'ho', + 'マ':'ma', 'ミ':'mi', 'ム':'mu', 'メ':'me', 'モ':'mo', + 'ヤ':'ya', 'ユ':'yu', 'ヨ':'yo', + 'ラ':'ra', 'リ':'ri', 'ル':'ru', 'レ':'re', 'ロ':'ro', + 'ワ':'wa', 'ヲ':'wo', 'ン':'n', + + # 濁音・半濁音 + 'ガ':'ga', 'ギ':'gi', 'グ':'gu', 'ゲ':'ge', 'ゴ':'go', + 'ザ':'za', 'ジ':'ji', 'ズ':'zu', 'ゼ':'ze', 'ゾ':'zo', + 'ダ':'da', 'ヂ':'ji', 'ヅ':'zu', 'デ':'de', 'ド':'do', + 'バ':'ba', 'ビ':'bi', 'ブ':'bu', 'ベ':'be', 'ボ':'bo', + 'パ':'pa', 'ピ':'pi', 'プ':'pu', 'ペ':'pe', 'ポ':'po', + + # 特殊音 + 'ヴ':'vu' +} +``` + +### 拗音組み合わせ + +```python +digraphs_mapping = { + # キャ行 + ('キ','ャ'):'kya', ('キ','ュ'):'kyu', ('キ','ョ'):'kyo', + ('ギ','ャ'):'gya', ('ギ','ュ'):'gyu', ('ギ','ョ'):'gyo', + + # シャ行 + ('シ','ャ'):'sha', ('シ','ュ'):'shu', ('シ','ョ'):'sho', + ('ジ','ャ'):'ja', ('ジ','ュ'):'ju', ('ジ','ョ'):'jo', + + # チャ行 + ('チ','ャ'):'cha', ('チ','ュ'):'chu', ('チ','ョ'):'cho', + + # その他の拗音 + ('ニ','ャ'):'nya', ('ヒ','ャ'):'hya', ('ビ','ャ'):'bya', + ('ピ','ャ'):'pya', ('ミ','ャ'):'mya', ('リ','ャ'):'rya', + + # 外来語音(ファ行等) + ('フ','ァ'):'fa', ('フ','ィ'):'fi', ('フ','ェ'):'fe', ('フ','ォ'):'fo', + ('シ','ェ'):'she', ('チ','ェ'):'che', ('テ','ィ'):'ti', + ('ツ','ァ'):'tsa', ('ツ','ィ'):'tsi', ('ツ','ェ'):'tse', ('ツ','ォ'):'tso', + + # ヴ音組み合わせ + ('ヴ','ァ'):'va', ('ヴ','ィ'):'vi', ('ヴ','ェ'):'ve', + ('ヴ','ォ'):'vo', ('ヴ','ュ'):'vyu' +} +``` + +### マクロン変換規則 + +```python +macron_rules = { + 'aa': 'ā', # カア → kā + 'ii': 'ī', # キイ → kī + 'uu': 'ū', # クウ → kū + 'ee': 'ē', # ケエ → kē + 'oo': 'ō', # コオ → kō + 'ou': 'ō' # コウ → kō(東京型長音) +} +``` + +## 特殊処理アルゴリズム + +### 促音(ッ)処理 + +```python +def handle_sokuon(current_pos, kata_string, result_list): + """促音の処理アルゴリズム""" + + # 次の音を確認 + if current_pos + 1 < len(kata_string): + next_kana = kata_string[current_pos + 1] + + # 次の音のローマ字を取得 + next_roman = get_next_roman(next_kana, kata_string[current_pos + 1:]) + + # 子音部分を抽出して重複 + consonant = extract_initial_consonant(next_roman) + if consonant: + result_list.append(consonant[0]) # 先頭子音を重複 + + # 促音自体は消費 + return current_pos + 1 + +# 例: +# マッチャ -> ma + tcha (ッ -> t重複) -> matcha +# キャッチ -> kya + tchi (ッ -> t重複) -> kyatchi +``` + +### 撥音(ン)処理 + +```python +def handle_hatsuon(roman_string): + """撥音のm/n使い分け処理""" + + # n の後に b/p/m が続く場合は m に変換 + import re + + # パターン: n + [bmp] -> m + [bmp] + result = re.sub(r'n(?=[bmp])', 'm', roman_string) + + return result + +# 例: +# ホンバン -> honban -> homban +# サンポ -> sanpo -> sampo +# コンマ -> konma -> komma +# but: ホンテン -> honten (変更なし) +``` + +### 長音符(ー)処理 + +```python +def handle_choonpu(roman_list): + """長音符の前母音延長処理""" + + result = [] + i = 0 + + while i < len(roman_list): + if roman_list[i] == '-': # 長音符マーカー + if i > 0: + prev_char = result[-1] # 直前の文字 + if prev_char in 'aiueo': + # 前が母音なら重複(後でマクロン処理) + result.append(prev_char) + # else: 子音の場合は無視 + else: + result.append(roman_list[i]) + i += 1 + + return result + +# 例: +# スー -> su + - -> suu -> sū (マクロン処理後) +# パーティー -> pa + - + ti + - -> paatii -> pātī +``` + +## 実装例・テストケース + +### 基本テストセット + +```python +def run_basic_tests(): + """基本変換テストセット""" + + test_cases = [ + # 基本音 + ("アイウエオ", "aiueo"), + ("カキクケコ", "kakikukeko"), + ("サシスセソ", "sashisuseso"), + + # 濁音・半濁音 + ("ガギグゲゴ", "gagigugego"), + ("ザジズゼゾ", "zajizuzezo"), + ("バビブベボ", "babibubebo"), + ("パピプペポ", "papipupepo"), + + # 特殊音 + ("シャシュショ", "shashusho"), + ("チャチュチョ", "chachucho"), + ("ジャジュジョ", "jajujo"), + + # 促音 + ("アッパ", "appa"), + ("イッキ", "ikki"), + ("エッサ", "essa"), + + # 撥音 + ("アンパン", "ampan"), + ("コンマ", "komma"), + ("ホンテン", "honten") + ] + + for kata, expected in test_cases: + result = katakana_to_hepburn(kata) + assert result == expected, f"Failed: {kata} -> {result} (expected {expected})" + print(f"✓ {kata} -> {result}") + +run_basic_tests() +``` + +### 外来語テストセット + +```python +def run_foreign_word_tests(): + """外来語変換テストセット""" + + foreign_tests = [ + # ファ行 + ("ファイル", "fairu"), + ("フィルム", "firumu"), + ("フェイス", "feisu"), + ("フォント", "fonto"), + + # シェ・チェ + ("シェア", "shea"), + ("シェル", "sheru"), + ("チェック", "chekku"), + ("チェイン", "chein"), + + # ヴ音 + ("ヴァイオリン", "vaiorin"), + ("ヴィーナス", "vīnasu"), + ("ヴェール", "vēru"), + ("ヴォーカル", "vōkaru"), + + # ティ・トゥ・ドゥ + ("ティー", "tī"), + ("パーティー", "pātī"), + ("トゥー", "tū"), + ("ドゥー", "dū") + ] + + for kata, expected in foreign_tests: + result = katakana_to_hepburn(kata, use_macron=True) + print(f"✓ {kata} -> {result}") + # 実際のexpectedとの比較は実装依存 + +run_foreign_word_tests() +``` + +### 長音テストセット + +```python +def run_long_vowel_tests(): + """長音処理テストセット""" + + long_vowel_tests = [ + # マクロンあり + ("コーヒー", "kōhī", True), + ("スーパー", "sūpā", True), + ("パーティー", "pātī", True), + ("トウキョウ", "tōkyō", True), # ou -> ō + + # マクロンなし + ("コーヒー", "koohii", False), + ("スーパー", "suupaa", False), + ("パーティー", "paatii", False), + ("トウキョウ", "toukyou", False) + ] + + for kata, expected, use_macron in long_vowel_tests: + result = katakana_to_hepburn(kata, use_macron=use_macron) + print(f"✓ {kata} -> {result} (macron={use_macron})") + +run_long_vowel_tests() +``` + +## パフォーマンス考慮事項 + +### 効率的な処理 +- 単一パス処理による高速変換 +- 正規表現の最小限使用 +- 辞書ルックアップの最適化 + +### メモリ効率 +- 文字列連結の最適化 +- 不要な中間オブジェクトの削減 +- 大量テキスト処理への対応 + +## 制限事項・注意点 + +### 変換精度の制限 +- 文脈に依存する読み分けは非対応 +- 固有名詞の特殊読みは非対応 +- 方言・古語の特殊音は非対応 + +### ヘボン式の範囲 +- 標準的なヘボン式に準拠 +- 一部の外来語音は近似変換 +- 撥音の文脈依存ルールは簡略化 + +### 入力制限 +```python +# 適切な入力例 +good_inputs = ["カタカナ", "シャープ", "コンピューター"] + +# 問題のある入力例 +problematic_inputs = [ + "ひらがな", # ひらがな混在(処理されるがそのまま) + "English", # 英字混在(処理されるがそのまま) + "123数字", # 数字混在(処理されるがそのまま) + "", # 空文字列(空文字列を返す) +] + +# 混在入力の処理例 +mixed_result = katakana_to_hepburn("カタカナと英語English") +print(mixed_result) # "katakanaと英語english" +``` + +## 関連モジュール + +- `transliteration_transliterator.py`: メイン転写クラス +- `transliteration_context_rules.py`: 文脈依存ルール +- 外部のひらがな↔カタカナ変換モジュール(必要に応じて) + +## 将来の改善点 + +- 更なる外来語音への対応 +- 文脈依存の読み分け機能 +- パフォーマンス最適化 +- より詳細なヘボン式バリエーション対応 +- 音韻変化ルールの追加 \ No newline at end of file diff --git a/src-python/docs/details/transliteration_transliterator.md b/src-python/docs/details/transliteration_transliterator.md new file mode 100644 index 00000000..73dad537 --- /dev/null +++ b/src-python/docs/details/transliteration_transliterator.md @@ -0,0 +1,659 @@ +# transliteration_transliterator.py - 総合音写・転写システム + +## 概要 + +SudachiPyを利用した日本語のローマ字転写システムのメインクラスです。形態素解析、漢字・送り仮名の分離、文脈依存ルールの適用、ヘボン式変換を統合し、高精度な日本語ローマ字化を提供します。 + +## 主要機能 + +### 統合転写システム +- SudachiPyによる高精度形態素解析 +- 漢字・送り仮名の自動分離処理 +- 文脈依存読み変更ルールの適用 + +### 多層変換処理 +- カタカナ読み取得・分配 +- ひらがな自動変換 +- ヘボン式ローマ字生成 + +### 並行処理対応 +- スレッドセーフなトークナイザー利用 +- ロック機構による安全な並行実行 +- 高負荷環境での安定動作 + +## クラス構造 + +### Transliterator クラス +```python +class Transliterator: + def __init__(self) -> None: + self.tokenizer_obj: tokenizer.Tokenizer + self.mode: tokenizer.Tokenizer.SplitMode + self._tokenizer_lock: threading.Lock +``` + +日本語転写処理の中核クラス + +#### 属性 +- **tokenizer_obj**: SudachiPyトークナイザーインスタンス +- **mode**: 分割モード(SplitMode.C = 最長一致) +- **_tokenizer_lock**: 並行アクセス制御用ミューテックス + +## 主要メソッド + +### analyze + +```python +def analyze(self, text: str, use_macron: bool = False) -> List[Dict[str, Any]] +``` + +テキストを解析して転写情報を生成 + +#### パラメータ +- **text**: 解析対象の日本語テキスト +- **use_macron**: マクロン使用フラグ(長音表記方式) + +#### 戻り値 +- **List[Dict[str, Any]]**: トークン転写情報のリスト + +#### 出力辞書構造 +```python +{ + "orig": str, # 元の文字・文字列 + "kana": str, # カタカナ読み + "hira": str, # ひらがな読み + "hepburn": str # ヘボン式ローマ字 +} +``` + +### split_kanji_okurigana (静的メソッド) + +```python +@staticmethod +def split_kanji_okurigana(surface: str, reading_kana: str, use_macron: bool = True) -> List[Dict[str, str]] +``` + +単語の表層形と読みを漢字・送り仮名ブロックに分割 + +#### パラメータ +- **surface**: 表層形(漢字+ひらがな混在可能) +- **reading_kana**: 全体のカタカナ読み +- **use_macron**: ヘボン式変換でのマクロン使用 + +#### 戻り値 +- **List[Dict[str, str]]**: 分割された部分の転写情報 + +## 補助メソッド + +### is_kanji (静的メソッド) + +```python +@staticmethod +def is_kanji(ch: str) -> bool +``` + +文字が漢字かどうかを判定 + +#### パラメータ +- **ch**: 判定対象文字 + +#### 戻り値 +- **bool**: 漢字判定結果 + +### kata_to_hira (静的メソッド) + +```python +@staticmethod +def kata_to_hira(text: str) -> str +``` + +カタカナをひらがなに変換 + +#### パラメータ +- **text**: 変換対象のカタカナテキスト + +#### 戻り値 +- **str**: ひらがな変換結果 + +## 使用方法 + +### 基本的な転写処理 + +```python +from models.transliteration.transliteration_transliterator import Transliterator + +# 転写システムの初期化 +transliterator = Transliterator() + +# 基本的な文章の転写 +text = "向こうへ行く" +results = transliterator.analyze(text) + +for token in results: + print(f"{token['orig']} -> {token['kana']} -> {token['hira']} -> {token['hepburn']}") + +# 期待される出力例: +# 向こう -> ムコウ -> むこう -> mukou +# へ -> ヘ -> へ -> he +# 行く -> イク -> いく -> iku +``` + +### マクロン使用の長音処理 + +```python +# マクロンを使用した長音表記 +text = "東京に行く" +results_macron = transliterator.analyze(text, use_macron=True) +results_normal = transliterator.analyze(text, use_macron=False) + +print("=== マクロンあり ===") +for token in results_macron: + print(f"{token['orig']} -> {token['hepburn']}") + +print("=== マクロンなし ===") +for token in results_normal: + print(f"{token['orig']} -> {token['hepburn']}") + +# 期待される出力: +# === マクロンあり === +# 東京 -> tōkyō +# に -> ni +# 行く -> iku + +# === マクロンなし === +# 東京 -> toukyou +# に -> ni +# 行く -> iku +``` + +### 複雑な文章の処理 + +```python +# 漢字・ひらがな・カタカナ・英語混在文の処理 +complex_text = "パーティーで美しい花を見る" +results = transliterator.analyze(complex_text, use_macron=True) + +for token in results: + print(f"原文: '{token['orig']}'") + print(f" カナ: {token['kana']}") + print(f" ひら: {token['hira']}") + print(f" ローマ: {token['hepburn']}") + print() + +# 期待される出力: +# 原文: 'パーティー' +# カナ: パーティー +# ひら: ぱーてぃー +# ローマ: pātī +# +# 原文: 'で' +# カナ: デ +# ひら: で +# ローマ: de +# +# 原文: '美しい' +# カナ: ウツクシイ +# ひら: うつくしい +# ローマ: utsukushii +``` + +### 文脈依存ルールの効果確認 + +```python +# 文脈に依存する読み変更の例(「何」の読み分け) +test_cases = [ + "何が好き?", # 何 -> ナニ (後続が「ガ」) + "何度も挑戦", # 何 -> ナン (後続が「ド」) + "何色ありますか?" # 何 -> ナニ (後続が「イ」) +] + +for text in test_cases: + results = transliterator.analyze(text) + + print(f"入力: {text}") + + # 「何」トークンを探して読みを確認 + for token in results: + if token['orig'] == '何': + print(f"「何」の読み: {token['kana']} -> {token['hepburn']}") + break + print() + +# 期待される出力: +# 入力: 何が好き? +# 「何」の読み: ナニ -> nani +# +# 入力: 何度も挑戦 +# 「何」の読み: ナン -> nan +# +# 入力: 何色ありますか? +# 「何」の読み: ナニ -> nani +``` + +### 特殊文字・記号の処理 + +```python +# 記号・英数字混在テキストの処理 +mixed_text = "ID:12345、URL:https://example.com" +results = transliterator.analyze(mixed_text) + +for token in results: + print(f"'{token['orig']}' -> '{token['hepburn']}'") + +# 期待される出力: +# 'ID' -> 'ID' # 英字はそのまま +# ':' -> ':' # 記号はそのまま +# '12345' -> '12345' # 数字はそのまま +# '、' -> '、' # 区切り記号はそのまま +# 'URL' -> 'URL' # 英字はそのまま +``` + +## 内部処理フロー + +### 解析処理パイプライン + +```python +def analyze_pipeline_explained(self, text): + """転写処理パイプラインの詳細説明""" + + # 1. SudachiPy形態素解析 + with self._tokenizer_lock: + tokens = self.tokenizer_obj.tokenize(text, self.mode) + + results = [] + + # 2. 各トークンの処理 + for token in tokens: + surface = token.surface() # 表層形 + reading = token.reading_form() # 読み(カタカナ) + pos = token.part_of_speech() # 品詞情報 + + # 3. 記号・空白の特別処理 + if pos and pos[0] in ["記号", "補助記号", "空白"]: + reading = surface # 記号は表層形をそのまま使用 + + # 4. 表層形と読みが同じ場合(ひらがな・記号等) + if surface == reading: + results.append({ + "orig": surface, + "kana": reading, + "hira": surface, # そのまま + "hepburn": surface # そのまま + }) + continue + + # 5. 単一文字の処理 + if len(surface) == 1: + results.append({ + "orig": surface, + "kana": reading, + "hira": self.kata_to_hira(reading), + "hepburn": katakana_to_hepburn(reading, use_macron) + }) + else: + # 6. 複数文字の漢字・送り仮名分離 + parts = self.split_kanji_okurigana(surface, reading, use_macron) + results.extend(parts) + + # 7. 文脈依存ルールの適用 + try: + results = apply_context_rules(results, use_macron) or results + except Exception: + pass # ルール適用失敗時は元の結果を使用 + + # 8. ルール適用後の再計算 + for entry in results: + kana = entry.get("kana", "") + if kana: + entry["hira"] = self.kata_to_hira(kana) + entry["hepburn"] = katakana_to_hepburn(kana, use_macron) + + return results +``` + +### 漢字・送り仮名分離アルゴリズム + +```python +def split_algorithm_explained(surface, reading_kana): + """分離アルゴリズムの詳細説明""" + + # 1. 表層形のブロック分割 + blocks = [] + current_block = "" + prev_is_kanji = None + + for char in surface: + is_kanji = Transliterator.is_kanji(char) + + if prev_is_kanji is None or is_kanji == prev_is_kanji: + # 同じタイプの文字は同じブロックに + current_block += char + else: + # タイプが変わったら新しいブロック + blocks.append((prev_is_kanji, current_block)) + current_block = char + + prev_is_kanji = is_kanji + + if current_block: + blocks.append((prev_is_kanji, current_block)) + + # 例: "向こう" -> [(True, "向"), (False, "こう")] + # "行く" -> [(True, "行"), (False, "く")] + + # 2. 読みの分配 + kana_len = len(reading_kana) + + # 初期割当: 各ブロックの文字数に比例 + allocations = [len(block_text) for _, block_text in blocks] + allocated_total = sum(allocations) + remaining = kana_len - allocated_total + + # 3. 余った読みの分配(漢字ブロック優先) + if remaining > 0: + # まず漢字ブロックに分配 + for i, (is_kanji, _) in enumerate(blocks): + if remaining <= 0: + break + if is_kanji: + allocations[i] += 1 + remaining -= 1 + + # まだ余りがある場合は左から順に分配 + i = 0 + while remaining > 0 and len(blocks) > 0: + allocations[i] += 1 + remaining -= 1 + i = (i + 1) % len(blocks) + + # 4. 読みが不足している場合は右から削減 + if remaining < 0: + need_to_remove = -remaining + i = len(blocks) - 1 + + while need_to_remove > 0 and i >= 0: + can_remove = max(0, allocations[i] - 1) + remove_amount = min(can_remove, need_to_remove) + allocations[i] -= remove_amount + need_to_remove -= remove_amount + i -= 1 + + # 5. 最終的な読み分配 + pos = 0 + result = [] + + for (is_kanji, block_text), allocation in zip(blocks, allocations): + block_reading = reading_kana[pos:pos + allocation] + pos += allocation + + result.append({ + "orig": block_text, + "kana": block_reading, + "hira": Transliterator.kata_to_hira(block_reading), + "hepburn": katakana_to_hepburn(block_reading, use_macron) + }) + + return result +``` + +## 並行処理・スレッドセーフティ + +### ロック機構 + +```python +class ThreadSafeUsage: + """スレッドセーフな使用例""" + + def __init__(self): + self.transliterator = Transliterator() + + def process_texts_concurrently(self, texts): + """複数テキストの並行処理""" + import concurrent.futures + + def process_single(text): + return self.transliterator.analyze(text) + + with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor: + # 内部のロック機構により安全に並行実行 + futures = [executor.submit(process_single, text) for text in texts] + results = [f.result() for f in futures] + + return results + +# 使用例 +processor = ThreadSafeUsage() +texts = ["東京に行く", "大阪で食事", "名古屋を観光", "福岡に宿泊"] +results = processor.process_texts_concurrently(texts) + +for i, result in enumerate(results): + print(f"テキスト{i+1}: {texts[i]}") + for token in result: + print(f" {token['orig']} -> {token['hepburn']}") +``` + +### パフォーマンス考慮事項 + +```python +# 大量テキスト処理のベストプラクティス +def efficient_batch_processing(texts, batch_size=100): + """効率的なバッチ処理""" + + transliterator = Transliterator() + results = [] + + for i in range(0, len(texts), batch_size): + batch = texts[i:i + batch_size] + + batch_results = [] + for text in batch: + # 各テキストを個別に処理(ロック制御あり) + result = transliterator.analyze(text) + batch_results.append(result) + + results.extend(batch_results) + + # バッチ間で少し休憩(メモリ管理) + if len(results) % 1000 == 0: + print(f"処理済み: {len(results)} テキスト") + + return results +``` + +## エラーハンドリング + +### 例外処理 + +```python +def safe_analyze(text): + """安全な解析処理""" + + transliterator = Transliterator() + + try: + results = transliterator.analyze(text) + return results, None + + except RuntimeError as e: + if "Already borrowed" in str(e): + # SudachiPyの並行アクセスエラー + print("並行アクセスエラーが発生しました。リトライします。") + return None, "RETRY_NEEDED" + else: + print(f"実行時エラー: {e}") + return None, "RUNTIME_ERROR" + + except Exception as e: + print(f"予期しないエラー: {e}") + return None, "UNKNOWN_ERROR" + +# 使用例(リトライ機構付き) +def analyze_with_retry(text, max_retries=3): + """リトライ機構付き解析""" + + for attempt in range(max_retries): + results, error = safe_analyze(text) + + if results is not None: + return results + + if error == "RETRY_NEEDED": + print(f"リトライ {attempt + 1}/{max_retries}") + import time + time.sleep(0.1 * (attempt + 1)) # 指数バックオフ + continue + else: + break + + # 全てのリトライが失敗した場合のフォールバック + print("解析に失敗しました。フォールバック処理を実行します。") + return [{"orig": text, "kana": text, "hira": text, "hepburn": text}] +``` + +## 設定・カスタマイズ + +### SudachiPy設定 + +```python +# カスタムSudachiPy設定での初期化 +class CustomTransliterator(Transliterator): + def __init__(self, dict_type="full", split_mode="C"): + """カスタム設定での初期化""" + + # 辞書タイプの選択 + dict_types = { + "small": dictionary.Dictionary.create(dict_type="small"), + "core": dictionary.Dictionary.create(dict_type="core"), + "full": dictionary.Dictionary.create(dict_type="full") + } + + self.tokenizer_obj = dict_types.get(dict_type, dict_types["full"]) + + # 分割モードの選択 + split_modes = { + "A": tokenizer.Tokenizer.SplitMode.A, # 短い単位 + "B": tokenizer.Tokenizer.SplitMode.B, # 中間単位 + "C": tokenizer.Tokenizer.SplitMode.C # 長い単位(デフォルト) + } + + self.mode = split_modes.get(split_mode, split_modes["C"]) + self._tokenizer_lock = threading.Lock() + +# 使用例 +# 短い単位での分割を使用 +small_unit_transliterator = CustomTransliterator(dict_type="core", split_mode="A") + +text = "取り敢えず検索してみる" +results = small_unit_transliterator.analyze(text) + +for token in results: + print(f"{token['orig']} -> {token['hepburn']}") +``` + +## テスト・デバッグ + +### 包括的テストセット + +```python +def run_comprehensive_tests(): + """包括的な機能テスト""" + + transliterator = Transliterator() + + test_cases = [ + # 基本的な文章 + ("向こうへ行く", "向こう", "ムコウ"), + ("美しい花", "美しい", "ウツクシイ"), + + # 文脈依存 + ("何度も", "何", "ナン"), + ("何が", "何", "ナニ"), + + # 外来語 + ("パーティー", "パーティー", "パーティー"), + ("コンピューター", "コンピューター", "コンピューター"), + + # 漢字・送り仮名 + ("取り敢えず", "取り", "トリ"), + ("見知らぬ", "見知ら", "ミシラ"), + + # 記号・英数字 + ("ID:12345", "ID", "ID"), + ("SessionIDを取得", "SessionID", "SessionID") + ] + + for text, target_orig, expected_kana in test_cases: + results = transliterator.analyze(text) + + # 対象トークンを検索 + target_token = None + for token in results: + if token['orig'] == target_orig: + target_token = token + break + + if target_token: + actual_kana = target_token['kana'] + status = "✓" if actual_kana == expected_kana else "✗" + print(f"{status} {text}: {target_orig} -> {actual_kana} (期待値: {expected_kana})") + else: + print(f"✗ {text}: トークン '{target_orig}' が見つかりません") + +run_comprehensive_tests() +``` + +## 依存関係 + +### 必須依存関係 +- `sudachipy`: 形態素解析エンジン +- `threading`: 並行制御 +- `typing`: 型ヒント + +### 内部モジュール依存 +- `transliteration_kana_to_hepburn`: ヘボン式変換 +- `transliteration_context_rules`: 文脈依存ルール + +### システム要件 +- Python 3.7以上 +- SudachiPy辞書ファイル(自動ダウンロード) +- 十分なメモリ(辞書読み込み用) + +## 注意事項・制限 + +### 処理精度の制限 +- 形態素解析結果に依存 +- 未知語・固有名詞は読み推定 +- 文脈によっては不正確な分割 + +### パフォーマンス制限 +- 初回実行時の辞書読み込み時間 +- 大量テキスト処理時のメモリ使用量 +- 並行アクセス時のロック待機 + +### 出力形式の制限 +```python +# 現在サポートしていない機能 +unsupported_features = [ + "アクセント記号(音調)", + "方言・古語の特殊読み", + "人名・地名の特殊読み", + "外国語の音写(中国語・韓国語等)", + "カスタム読み辞書", + "品詞情報の出力" +] +``` + +## 関連モジュール + +- `transliteration_kana_to_hepburn.py`: ヘボン式変換処理 +- `transliteration_context_rules.py`: 文脈依存ルール適用 +- `config.py`: システム設定管理 +- `utils.py`: ユーティリティ関数 + +## 将来の改善点 + +- カスタム読み辞書対応 +- より高精度な文脈解析 +- 他言語音写システムとの統合 +- リアルタイム処理最適化 +- 分散処理対応 \ No newline at end of file diff --git a/src-python/docs/details/utils.md b/src-python/docs/details/utils.md new file mode 100644 index 00000000..e924bb3a --- /dev/null +++ b/src-python/docs/details/utils.md @@ -0,0 +1,213 @@ +# utils.py - ユーティリティ関数モジュール + +## 概要 + +VRCTアプリケーション全体で使用される汎用的なユーティリティ関数を提供するモジュールです。データ検証、ネットワーク接続確認、計算デバイス管理、ログ機能などの共通機能を集約しています。 + +## 主要機能 + +### データ検証機能 + +- 辞書構造の型安全な検証 +- IPアドレス形式の検証 +- WebSocketサーバーの可用性確認 + +### システム情報取得 + +- 利用可能な計算デバイス(CPU/CUDA)の一覧取得 +- 最適な計算タイプの自動選択 +- デバイス固有の制約対応 + +### ログ機能 + +- 構造化ログの出力 +- ローテーションログファイル管理 +- エラーログとプロセスログの分離 + +### ネットワーク機能 + +- インターネット接続状態の確認 +- Base64エンコード/デコード処理 + +## 主要関数 + +### データ検証 + +```python +validateDictStructure(data: dict, structure: dict) -> bool +``` + +- 辞書とその期待される構造が完全に一致するかを判別 +- 入れ子構造にも対応 +- 型安全性を保証 + +### ネットワーク関連 + +```python +isConnectedNetwork(url="http://www.google.com", timeout=3) -> bool +``` + +- 指定URLへの接続可能性をチェック +- タイムアウト設定可能 + +```python +isAvailableWebSocketServer(host: str, port: int) -> bool +``` + +- WebSocketサーバーのバインド可能性を確認 + +```python +isValidIpAddress(ip_address: str) -> bool +``` + +- IPv4/IPv6アドレスの有効性を検証 + +### 計算デバイス管理 + +```python +getComputeDeviceList() -> List[Dict[str, Any]] +``` + +- 利用可能なCPU/CUDA計算デバイスの一覧を取得 +- 各デバイスの計算タイプを含む詳細情報を提供 + +```python +getBestComputeType(device: str, device_index: int) -> str +``` + +- デバイスに最適な計算タイプを自動選択 +- GPU固有の制約を考慮(GTX、RTX、Tesla、A100、Quadro等) + +### ログ機能 + +```python +setupLogger(name: str, log_file: str, level: int = logging.INFO) -> logging.Logger +``` + +- ローテーション機能付きログの設定 +- 10MBサイズでのローテーション +- UTF-8エンコード対応 + +```python +printLog(log: str, data: Any = None) -> None +``` + +- 構造化プロセスログの出力 +- JSON形式での標準出力 + +```python +printResponse(status: int, endpoint: str, result: Any = None) -> None +``` + +- APIレスポンスの構造化出力 +- シリアライゼーションエラーの安全な処理 + +```python +errorLogging() -> None +``` + +- 例外トレースバックのログ記録 +- フォールバック機能付き + +### その他のユーティリティ + +```python +encodeBase64(data: str) -> Dict[str, Any] +``` + +- Base64エンコード済みJSON文字列のデコード +- エラー処理付き + +```python +removeLog() -> None +``` + +- プロセスログファイルの初期化 + +## 使用方法 + +### 基本的な使い方 + +```python +from utils import validateDictStructure, isConnectedNetwork, printLog + +# 辞書構造の検証 +expected_structure = {"name": str, "age": int} +data = {"name": "test", "age": 25} +is_valid = validateDictStructure(data, expected_structure) + +# ネットワーク接続確認 +is_connected = isConnectedNetwork() + +# ログ出力 +printLog("処理開始", {"user_id": 123}) +``` + +### 計算デバイス管理 + +```python +from utils import getComputeDeviceList, getBestComputeType + +# 利用可能デバイス一覧 +devices = getComputeDeviceList() + +# 最適な計算タイプ選択 +compute_type = getBestComputeType("cuda", 0) +``` + +### ログ設定 + +```python +from utils import setupLogger, errorLogging + +# カスタムログの設定 +logger = setupLogger("my_module", "my_module.log") +logger.info("処理完了") + +# エラーログ記録 +try: + # 何らかの処理 + pass +except Exception: + errorLogging() +``` + +## 依存関係 + +### 必須依存関係 + +- `json`: JSON処理 +- `logging`: ログ機能 +- `requests`: HTTP通信 +- `ipaddress`: IPアドレス検証 +- `socket`: ソケット通信 + +### オプション依存関係 + +- `torch`: CUDA計算デバイス情報取得 +- `ctranslate2`: 計算タイプ情報取得 + +## デバイス別計算タイプ制約 + +### GTXシリーズ +- サポート: `float32`のみ +- 理由: 古いアーキテクチャによる制約 + +### RTX/Tesla/A100/Quadroシリーズ +- サポート: フル機能 +- 優先順位: `int8_bfloat16` > `int8_float16` > `int8` > `bfloat16` > `float16` > `int8_float32` > `float32` + +### CPU +- サポート: 全計算タイプ(ハードウェア依存) + +## エラーハンドリング + +- すべての関数は例外安全性を考慮 +- オプション依存関係の欠如に対する適切なフォールバック +- ログ機能は多段階のフェールセーフ機構を持つ + +## 注意事項 + +- 計算デバイス情報取得は初回実行時にやや時間がかかる場合がある +- ログローテーションは10MBサイズで自動実行 +- ネットワーク接続確認はデフォルト3秒のタイムアウト設定 \ No newline at end of file diff --git a/src-python/docs/details/watchdog.md b/src-python/docs/details/watchdog.md new file mode 100644 index 00000000..e574b882 --- /dev/null +++ b/src-python/docs/details/watchdog.md @@ -0,0 +1,670 @@ +# watchdog.py - 軽量監視システム + +## 概要 + +タイムアウトベースの軽量監視(ウォッチドッグ)システムです。定期的な"餌やり"(feed)により正常動作を確認し、指定時間内に餌やりがない場合にコールバック関数を実行する単純で効果的な監視機構を提供します。 + +## 主要機能 + +### タイムアウト監視 +- 最後の餌やり時刻からの経過時間監視 +- 設定可能なタイムアウト閾値 +- タイムアウト時の自動コールバック実行 + +### 柔軟な実行モード +- 単発チェック(手動呼び出し) +- バックグラウンドスレッド実行 +- カスタムチェック間隔設定 + +### 防御的設計 +- コールバック例外の隔離処理 +- スレッドセーフな制御機構 +- 適切なリソース管理 + +## クラス構造 + +### Watchdog クラス + +```python +class Watchdog: + def __init__(self, timeout: int = 60, interval: int = 20) -> None: + self.timeout: int # タイムアウト秒数 + self.interval: int # チェック間隔秒数 + self.last_feed_time: float # 最後の餌やり時刻 + self.callback: Optional[Callable] # タイムアウト時コールバック + self._thread: Optional[Thread] # バックグラウンドスレッド + self._stop_event: Optional[Event] # 停止イベント +``` + +軽量ウォッチドッグの中核クラス + +#### パラメータ +- **timeout**: 餌やりなしでタイムアウトするまでの秒数 +- **interval**: 監視チェックの推奨間隔秒数 + +## 主要メソッド + +### 基本制御 + +```python +def feed(self) -> None +``` + +ウォッチドッグに餌やりを行い、タイマーをリセット + +```python +def setCallback(self, callback: Callable[[], None]) -> None +``` + +タイムアウト時に実行するコールバック関数を設定 + +#### パラメータ +- **callback**: 引数なしの呼び出し可能オブジェクト + +### 監視実行 + +```python +def start(self) -> None +``` + +単発のウォッチドッグチェックを実行し、間隔秒数だけスリープ + +```python +def start_in_thread(self, daemon: bool = True) -> None +``` + +バックグラウンドスレッドでウォッチドッグを開始 + +#### パラメータ +- **daemon**: デーモンスレッドとして実行するかのフラグ + +```python +def stop(self, timeout: Optional[float] = None) -> None +``` + +バックグラウンドスレッドを停止 + +#### パラメータ +- **timeout**: スレッド終了待機のタイムアウト秒数 + +## 使用方法 + +### 基本的な監視システム + +```python +from models.watchdog.watchdog import Watchdog +import time + +def on_timeout(): + """タイムアウト時の処理""" + print("警告: システムの応答がありません!") + # ログ出力、アラート送信、復旧処理等 + +# ウォッチドッグの初期化 +watchdog = Watchdog(timeout=30, interval=10) # 30秒でタイムアウト、10秒間隔 +watchdog.setCallback(on_timeout) + +# バックグラウンドで監視開始 +watchdog.start_in_thread(daemon=True) + +# メインプロセスのシミュレーション +for i in range(10): + print(f"処理中... {i}") + + # 正常な処理では定期的に餌やり + if i % 3 == 0: # 3回に1回餌やり + watchdog.feed() + print("ウォッチドッグに餌やりしました") + + time.sleep(5) + +# 監視停止 +watchdog.stop() +``` + +### 手動チェックモード + +```python +# 手動でウォッチドッグをチェック +def manual_monitoring_example(): + watchdog = Watchdog(timeout=60, interval=5) + + def system_failure_handler(): + print("システム障害を検出しました") + # 復旧処理、通知等 + + watchdog.setCallback(system_failure_handler) + + # メインループ内で定期チェック + while True: + # 何らかの重要な処理 + process_critical_work() + + # 処理が正常なら餌やり + if is_system_healthy(): + watchdog.feed() + + # 監視チェック実行(5秒間隔でスリープ) + watchdog.start() + +def process_critical_work(): + """重要な処理のシミュレーション""" + time.sleep(2) + +def is_system_healthy(): + """システム正常性チェックのシミュレーション""" + import random + return random.random() > 0.2 # 80%の確率で正常 + +# manual_monitoring_example() +``` + +### プロセス監視システム + +```python +class ProcessMonitor: + """外部プロセス監視システム""" + + def __init__(self, process_name, check_interval=30): + self.process_name = process_name + self.watchdog = Watchdog(timeout=60, interval=check_interval) + self.watchdog.setCallback(self.on_process_timeout) + self.monitoring = False + + def on_process_timeout(self): + """プロセス応答タイムアウト時の処理""" + print(f"警告: プロセス {self.process_name} が応答しません") + + # プロセス存在確認 + if self.is_process_running(): + print("プロセスは実行中ですが応答なし。再起動を試行します。") + self.restart_process() + else: + print("プロセスが停止しています。再起動します。") + self.start_process() + + def is_process_running(self): + """プロセス実行状態確認""" + import psutil + for proc in psutil.process_iter(['name']): + if proc.info['name'] == self.process_name: + return True + return False + + def start_process(self): + """プロセス起動""" + print(f"プロセス {self.process_name} を起動中...") + # 実際の起動処理 + + def restart_process(self): + """プロセス再起動""" + print(f"プロセス {self.process_name} を再起動中...") + # 実際の再起動処理 + + def feed_watchdog(self): + """外部から呼び出される餌やりメソッド""" + self.watchdog.feed() + + def start_monitoring(self): + """監視開始""" + self.monitoring = True + self.watchdog.start_in_thread(daemon=True) + print(f"プロセス {self.process_name} の監視を開始しました") + + def stop_monitoring(self): + """監視停止""" + self.monitoring = False + self.watchdog.stop() + print(f"プロセス {self.process_name} の監視を停止しました") + +# 使用例 +vrchat_monitor = ProcessMonitor("VRChat.exe", check_interval=15) +vrchat_monitor.start_monitoring() + +# VRChatプロセスが正常に動作している時の餌やり +# (実際にはVRChatからのOSC通信等で判定) +for _ in range(20): + time.sleep(10) + + if vrchat_monitor.is_process_running(): + vrchat_monitor.feed_watchdog() + print("VRChat正常動作確認") + +vrchat_monitor.stop_monitoring() +``` + +### ネットワーク監視システム + +```python +class NetworkWatchdog: + """ネットワーク接続監視""" + + def __init__(self, target_host="8.8.8.8", timeout=45): + self.target_host = target_host + self.watchdog = Watchdog(timeout=timeout, interval=15) + self.watchdog.setCallback(self.on_network_timeout) + self.last_ping_success = True + + def on_network_timeout(self): + """ネットワークタイムアウト処理""" + print("ネットワーク接続に問題があります") + + # 複数ホストでの確認 + test_hosts = ["8.8.8.8", "1.1.1.1", "google.com"] + + for host in test_hosts: + if self.ping_host(host): + print(f"{host} への接続は正常です") + self.watchdog.feed() # 1つでも成功したら復旧とみなす + return + + print("すべてのホストへの接続に失敗。ネットワーク設定を確認してください。") + self.handle_network_failure() + + def ping_host(self, host): + """ホストへのping確認""" + import subprocess + import platform + + # プラットフォームに応じたpingコマンド + if platform.system().lower() == "windows": + cmd = ["ping", "-n", "1", "-w", "3000", host] + else: + cmd = ["ping", "-c", "1", "-W", "3", host] + + try: + result = subprocess.run(cmd, capture_output=True, timeout=5) + return result.returncode == 0 + except (subprocess.TimeoutExpired, Exception): + return False + + def handle_network_failure(self): + """ネットワーク障害時の処理""" + print("ネットワーク障害対応処理を実行中...") + # DNS設定リセット、ネットワークアダプター再起動等 + + def check_network_continuously(self): + """継続的なネットワーク監視""" + self.watchdog.start_in_thread(daemon=True) + + while True: + if self.ping_host(self.target_host): + if not self.last_ping_success: + print("ネットワーク接続が復旧しました") + + self.watchdog.feed() + self.last_ping_success = True + else: + if self.last_ping_success: + print("ネットワーク接続に問題が発生しました") + + self.last_ping_success = False + + time.sleep(10) + +# 使用例 +network_monitor = NetworkWatchdog(target_host="google.com", timeout=60) + +# バックグラウンドでネットワーク監視 +import threading +monitor_thread = threading.Thread(target=network_monitor.check_network_continuously, daemon=True) +monitor_thread.start() + +# メインプログラムの実行 +print("ネットワーク監視システム開始...") +time.sleep(120) # 2分間監視 + +network_monitor.watchdog.stop() +``` + +### システムリソース監視 + +```python +class SystemResourceWatchdog: + """システムリソース監視""" + + def __init__(self): + self.cpu_watchdog = Watchdog(timeout=300, interval=30) # CPU使用率監視 + self.memory_watchdog = Watchdog(timeout=180, interval=20) # メモリ監視 + + self.cpu_watchdog.setCallback(self.on_cpu_overload) + self.memory_watchdog.setCallback(self.on_memory_pressure) + + self.cpu_threshold = 80.0 # CPU使用率閾値(%) + self.memory_threshold = 85.0 # メモリ使用率閾値(%) + + def on_cpu_overload(self): + """CPU過負荷時の処理""" + print("警告: CPU使用率が長時間高い状態です") + self.optimize_cpu_usage() + + def on_memory_pressure(self): + """メモリ圧迫時の処理""" + print("警告: メモリ使用率が危険なレベルです") + self.free_memory() + + def get_cpu_usage(self): + """CPU使用率取得""" + import psutil + return psutil.cpu_percent(interval=1) + + def get_memory_usage(self): + """メモリ使用率取得""" + import psutil + return psutil.virtual_memory().percent + + def optimize_cpu_usage(self): + """CPU使用率最適化""" + print("CPU最適化処理を実行中...") + # 低優先度プロセスの特定・制限 + # 不要なバックグラウンドタスクの停止等 + + def free_memory(self): + """メモリ解放処理""" + print("メモリ解放処理を実行中...") + # ガベージコレクション実行 + import gc + gc.collect() + # キャッシュクリア等 + + def monitor_resources(self): + """リソース監視メインループ""" + self.cpu_watchdog.start_in_thread(daemon=True) + self.memory_watchdog.start_in_thread(daemon=True) + + while True: + # CPU使用率チェック + cpu_usage = self.get_cpu_usage() + if cpu_usage < self.cpu_threshold: + self.cpu_watchdog.feed() + + # メモリ使用率チェック + memory_usage = self.get_memory_usage() + if memory_usage < self.memory_threshold: + self.memory_watchdog.feed() + + print(f"CPU: {cpu_usage:.1f}%, メモリ: {memory_usage:.1f}%") + time.sleep(5) + + def stop_monitoring(self): + """監視停止""" + self.cpu_watchdog.stop() + self.memory_watchdog.stop() + +# 使用例 +resource_monitor = SystemResourceWatchdog() + +# リソース監視開始 +import threading +resource_thread = threading.Thread(target=resource_monitor.monitor_resources, daemon=True) +resource_thread.start() + +# しばらく監視 +time.sleep(60) + +resource_monitor.stop_monitoring() +``` + +## 高度な使用パターン + +### 多段階監視システム + +```python +class MultilevelWatchdog: + """多段階警告レベル対応ウォッチドッグ""" + + def __init__(self): + # 異なるタイムアウトレベル + self.warning_watchdog = Watchdog(timeout=30, interval=10) # 警告レベル + self.critical_watchdog = Watchdog(timeout=60, interval=15) # 危険レベル + self.emergency_watchdog = Watchdog(timeout=120, interval=20) # 緊急レベル + + # 各レベルのコールバック設定 + self.warning_watchdog.setCallback(self.on_warning) + self.critical_watchdog.setCallback(self.on_critical) + self.emergency_watchdog.setCallback(self.on_emergency) + + self.alert_level = "normal" + + def on_warning(self): + """警告レベルのコールバック""" + self.alert_level = "warning" + print("⚠️ 警告: システムの応答が遅くなっています") + # 軽度の対応処理 + + def on_critical(self): + """危険レベルのコールバック""" + self.alert_level = "critical" + print("🔴 危険: システムの重大な問題を検出") + # 中程度の復旧処理 + + def on_emergency(self): + """緊急レベルのコールバック""" + self.alert_level = "emergency" + print("🚨 緊急: システムが完全に応答停止") + # 強制復旧・再起動処理 + + def feed_all(self): + """すべてのウォッチドッグに餌やり""" + self.warning_watchdog.feed() + self.critical_watchdog.feed() + self.emergency_watchdog.feed() + + if self.alert_level != "normal": + print("✅ システム復旧確認") + self.alert_level = "normal" + + def start_monitoring(self): + """多段階監視開始""" + self.warning_watchdog.start_in_thread(daemon=True) + self.critical_watchdog.start_in_thread(daemon=True) + self.emergency_watchdog.start_in_thread(daemon=True) + + def stop_monitoring(self): + """監視停止""" + self.warning_watchdog.stop() + self.critical_watchdog.stop() + self.emergency_watchdog.stop() + +# 使用例 +multilevel_monitor = MultilevelWatchdog() +multilevel_monitor.start_monitoring() + +# 正常時は定期餌やり、異常時は餌やり停止で段階的警告 +for i in range(30): + time.sleep(5) + + # 時々餌やりを忘れるシミュレーション + if i % 7 != 0: # 7回に1回は餌やりしない + multilevel_monitor.feed_all() + + print(f"現在の警告レベル: {multilevel_monitor.alert_level}") + +multilevel_monitor.stop_monitoring() +``` + +## エラーハンドリング・防御機構 + +### 例外安全性 + +```python +def safe_callback_example(): + """安全なコールバック実装例""" + + def potentially_failing_callback(): + """例外を起こす可能性があるコールバック""" + print("重要な処理を実行中...") + + # 何らかの理由で例外が発生 + raise RuntimeError("処理中にエラーが発生しました") + + # ウォッチドッグは例外を隔離するため、システム全体は継続動作 + watchdog = Watchdog(timeout=10, interval=5) + watchdog.setCallback(potentially_failing_callback) + + # エラーが発生してもウォッチドッグ自体は停止しない + watchdog.start_in_thread(daemon=True) + + # 通常の処理継続 + for i in range(5): + time.sleep(3) + watchdog.feed() + print(f"メイン処理継続中: {i}") + + watchdog.stop() + print("ウォッチドッグは例外に関係なく正常に停止しました") + +safe_callback_example() +``` + +### 堅牢なスレッド制御 + +```python +class RobustWatchdog: + """より堅牢なウォッチドッグ実装""" + + def __init__(self, timeout=60, interval=20): + self.base_watchdog = Watchdog(timeout, interval) + self.restart_count = 0 + self.max_restarts = 3 + + def robust_callback(self): + """自動復旧機能付きコールバック""" + try: + print(f"問題検出(再起動回数: {self.restart_count}/{self.max_restarts})") + + if self.restart_count < self.max_restarts: + self.attempt_recovery() + self.restart_count += 1 + else: + print("最大再起動回数に達しました。管理者に連絡してください。") + self.emergency_shutdown() + + except Exception as e: + print(f"復旧処理中にエラー: {e}") + + def attempt_recovery(self): + """復旧処理の試行""" + print("自動復旧処理を実行中...") + time.sleep(2) # 復旧処理のシミュレーション + + # 復旧成功時はカウンターリセット + if self.check_system_health(): + self.restart_count = 0 + self.base_watchdog.feed() + print("復旧成功") + else: + print("復旧失敗") + + def check_system_health(self): + """システム正常性確認""" + # 実際のヘルスチェックロジック + import random + return random.random() > 0.3 # 70%成功率 + + def emergency_shutdown(self): + """緊急停止処理""" + print("緊急停止処理を実行します") + # 安全な停止処理 + + def start_robust_monitoring(self): + """堅牢監視開始""" + self.base_watchdog.setCallback(self.robust_callback) + self.base_watchdog.start_in_thread(daemon=True) + + def feed(self): + """餌やり(成功時はカウンターリセット)""" + self.base_watchdog.feed() + self.restart_count = 0 + + def stop(self): + """監視停止""" + self.base_watchdog.stop() + +# 使用例 +robust_monitor = RobustWatchdog(timeout=20, interval=8) +robust_monitor.start_robust_monitoring() + +# 不安定なシステムのシミュレーション +for i in range(15): + time.sleep(3) + + # 時々システム異常をシミュレート + if random.random() > 0.7: # 30%の確率で異常 + print("システム異常発生...") + # 餌やりしない + else: + robust_monitor.feed() + print("システム正常動作") + +robust_monitor.stop() +``` + +## 性能・リソース考慮事項 + +### 軽量設計の特徴 +- 最小限のメモリフットプリント +- 効率的なスレッド利用 +- CPU使用量の最適化 + +### 推奨設定値 +```python +# 用途別推奨設定 +usage_patterns = { + "realtime_monitoring": { + "timeout": 10, # 10秒 + "interval": 2 # 2秒間隔 + }, + "service_monitoring": { + "timeout": 60, # 1分 + "interval": 15 # 15秒間隔 + }, + "batch_processing": { + "timeout": 300, # 5分 + "interval": 60 # 1分間隔 + }, + "background_tasks": { + "timeout": 1800, # 30分 + "interval": 300 # 5分間隔 + } +} +``` + +## 依存関係・要件 + +### 必須依存関係 +- `threading`: スレッド制御 +- `time`: 時刻管理 +- 標準ライブラリのみ(外部依存なし) + +### システム要件 +- Python 3.7以上 +- マルチスレッド対応OS +- 最小限のシステムリソース + +## 注意事項・制限 + +### 設計上の制限 +- 単純なタイムアウトベース監視のみ +- 複雑な条件判定は非対応 +- ネットワーク監視等は上位層で実装 + +### 使用上の注意 +- コールバック関数は軽量に保つ +- 長時間ブロックする処理は避ける +- 適切なタイムアウト値の設定が重要 + +## 関連モジュール + +- `threading`: スレッド管理 +- `config.py`: 監視設定管理 +- `utils.py`: エラーログ・ユーティリティ +- `controller.py`: 監視制御インターフェース + +## 将来の改善点 + +- より複雑な監視条件のサポート +- 監視統計・メトリクス収集機能 +- 設定可能な復旧戦略 +- 分散監視システムとの連携 +- Webインターフェースでの監視状態表示 \ No newline at end of file diff --git a/src-python/docs/details/websocket_server.md b/src-python/docs/details/websocket_server.md new file mode 100644 index 00000000..2c1d18e9 --- /dev/null +++ b/src-python/docs/details/websocket_server.md @@ -0,0 +1,989 @@ +# websocket_server.py - WebSocket通信サーバー + +## 概要 + +非同期WebSocket通信を提供する包括的なサーバーシステムです。クライアント接続管理、メッセージ配信、外部スレッドからの安全な操作を統合し、VRCTアプリケーションとWebフロントエンド間のリアルタイム通信を実現します。 + +## 主要機能 + +### 非同期WebSocket通信 +- asyncio/websockets による高性能WebSocketサーバー +- 複数クライアント同時接続対応 +- 自動接続・切断管理 + +### メッセージング機能 +- リアルタイムメッセージ受信処理 +- 全クライアントへのブロードキャスト配信 +- カスタムメッセージハンドラー対応 + +### スレッド間通信 +- GUI等の外部スレッドからの安全なメッセージ送信 +- 非同期キューによる効率的な通信制御 +- スレッドセーフな操作保証 + +## クラス構造 + +### WebSocketServer クラス + +```python +class WebSocketServer: + def __init__(self, host: str='127.0.0.1', port: int=8765): + self.host: str # サーバーホスト + self.port: int # サーバーポート + self.clients: Set[WebSocketServerProtocol] # 接続クライアント集合 + self._message_handler: Optional[Callable] # メッセージハンドラー + self._loop: Optional[asyncio.AbstractEventLoop] # イベントループ + self._server: Optional[websockets.serve] # WebSocketサーバー + self._thread: Optional[threading.Thread] # サーバースレッド + self._send_queue: Optional[asyncio.Queue] # 送信キュー + self.is_running: bool # 動作状態フラグ +``` + +WebSocket通信の中核管理クラス + +## 主要メソッド + +### サーバー制御 + +```python +def start_server(self) -> None +``` + +WebSocketサーバーを開始(バックグラウンドスレッド) + +```python +def stop_server(self) -> None +``` + +WebSocketサーバーを停止・リソース解放 + +### メッセージハンドリング + +```python +def set_message_handler(self, handler: Callable[['WebSocketServer', WebSocketServerProtocol, str], None]) -> None +``` + +クライアントからのメッセージ受信時コールバック設定 + +#### パラメータ +- **handler**: メッセージハンドラー関数 `(server, websocket, message) -> None` + +### メッセージ送信 + +```python +def send(self, message: str) -> None +``` + +外部スレッドから安全にメッセージを全クライアントに送信 + +#### パラメータ +- **message**: 送信するメッセージ文字列 + +```python +def broadcast(self, message: str) -> None +``` + +非同期的に全クライアントにメッセージをブロードキャスト + +#### パラメータ +- **message**: ブロードキャストするメッセージ + +## 使用方法 + +### 基本的なWebSocketサーバー + +```python +from models.websocket.websocket_server import WebSocketServer +import time +import json + +# メッセージハンドラーの定義 +def on_message_received(server, websocket, message): + """クライアントからのメッセージ処理""" + print(f"クライアントからメッセージ受信: {message}") + + try: + # JSONメッセージの解析 + data = json.loads(message) + + if data.get('type') == 'translation_request': + # 翻訳要求の処理 + handle_translation_request(server, data) + elif data.get('type') == 'config_update': + # 設定更新の処理 + handle_config_update(server, data) + else: + # エコーバック + response = { + 'type': 'echo', + 'original_message': data, + 'timestamp': time.time() + } + server.broadcast(json.dumps(response)) + + except json.JSONDecodeError: + # テキストメッセージの場合 + response = f"受信しました: {message}" + server.broadcast(response) + +def handle_translation_request(server, data): + """翻訳要求の処理""" + text = data.get('text', '') + target_lang = data.get('target_language', 'English') + + # 実際の翻訳処理(ここではモック) + translated_text = f"[{target_lang}] {text}" + + response = { + 'type': 'translation_result', + 'original': text, + 'translated': translated_text, + 'target_language': target_lang + } + + server.broadcast(json.dumps(response)) + +def handle_config_update(server, data): + """設定更新の処理""" + config_key = data.get('key') + config_value = data.get('value') + + print(f"設定更新: {config_key} = {config_value}") + + response = { + 'type': 'config_updated', + 'key': config_key, + 'value': config_value, + 'status': 'success' + } + + server.broadcast(json.dumps(response)) + +# WebSocketサーバーの起動 +ws_server = WebSocketServer(host='127.0.0.1', port=8765) +ws_server.set_message_handler(on_message_received) +ws_server.start_server() + +print("WebSocketサーバーが起動しました: ws://127.0.0.1:8765") + +# 定期的なステータス送信 +for i in range(10): + status_message = { + 'type': 'status', + 'server_time': time.time(), + 'uptime': i * 5, + 'connected_clients': len(ws_server.clients) + } + + ws_server.send(json.dumps(status_message)) + time.sleep(5) + +# サーバー停止 +ws_server.stop_server() +``` + +### VRCTアプリケーション統合 + +```python +class VRCTWebSocketInterface: + """VRCT用WebSocketインターフェース""" + + def __init__(self, controller, port=8765): + self.controller = controller # VRCTコントローラー + self.ws_server = WebSocketServer(host='127.0.0.1', port=port) + self.ws_server.set_message_handler(self.handle_web_message) + + def handle_web_message(self, server, websocket, message): + """Webクライアントからのメッセージ処理""" + try: + data = json.loads(message) + command = data.get('command') + + if command == 'get_config': + self.send_config(server) + elif command == 'set_config': + self.update_config(server, data) + elif command == 'start_translation': + self.start_translation_service(server, data) + elif command == 'stop_translation': + self.stop_translation_service(server) + elif command == 'get_status': + self.send_status(server) + elif command == 'translate_text': + self.translate_text(server, data) + else: + self.send_error(server, f"未知のコマンド: {command}") + + except Exception as e: + self.send_error(server, f"メッセージ処理エラー: {e}") + + def send_config(self, server): + """設定情報をWebクライアントに送信""" + config_data = { + 'type': 'config', + 'data': { + 'source_language': self.controller.config.source_language, + 'target_language': self.controller.config.target_language, + 'translation_engine': self.controller.config.translation_engine, + 'osc_enabled': self.controller.config.osc_enabled, + 'overlay_enabled': self.controller.config.overlay_enabled + } + } + server.broadcast(json.dumps(config_data)) + + def update_config(self, server, data): + """設定更新""" + config_updates = data.get('config', {}) + + for key, value in config_updates.items(): + if hasattr(self.controller.config, key): + setattr(self.controller.config, key, value) + print(f"設定更新: {key} = {value}") + + # 更新確認を送信 + response = { + 'type': 'config_updated', + 'status': 'success', + 'updated_keys': list(config_updates.keys()) + } + server.broadcast(json.dumps(response)) + + def start_translation_service(self, server, data): + """翻訳サービス開始""" + try: + self.controller.start_translation() + + response = { + 'type': 'service_status', + 'service': 'translation', + 'status': 'started', + 'message': '翻訳サービスが開始されました' + } + server.broadcast(json.dumps(response)) + + except Exception as e: + self.send_error(server, f"翻訳サービス開始エラー: {e}") + + def stop_translation_service(self, server): + """翻訳サービス停止""" + try: + self.controller.stop_translation() + + response = { + 'type': 'service_status', + 'service': 'translation', + 'status': 'stopped', + 'message': '翻訳サービスが停止されました' + } + server.broadcast(json.dumps(response)) + + except Exception as e: + self.send_error(server, f"翻訳サービス停止エラー: {e}") + + def send_status(self, server): + """システム状態送信""" + status_data = { + 'type': 'system_status', + 'data': { + 'translation_active': self.controller.is_translation_active(), + 'osc_connected': self.controller.is_osc_connected(), + 'overlay_active': self.controller.is_overlay_active(), + 'connected_clients': len(server.clients), + 'uptime': self.controller.get_uptime(), + 'memory_usage': self.controller.get_memory_usage() + } + } + server.broadcast(json.dumps(status_data)) + + def translate_text(self, server, data): + """即座翻訳実行""" + text = data.get('text', '') + source_lang = data.get('source_language') + target_lang = data.get('target_language') + + try: + # 翻訳実行 + result = self.controller.translate_text( + text, source_lang, target_lang + ) + + response = { + 'type': 'translation_result', + 'original': text, + 'translated': result, + 'source_language': source_lang, + 'target_language': target_lang, + 'timestamp': time.time() + } + server.broadcast(json.dumps(response)) + + except Exception as e: + self.send_error(server, f"翻訳エラー: {e}") + + def send_error(self, server, error_message): + """エラーメッセージ送信""" + error_data = { + 'type': 'error', + 'message': error_message, + 'timestamp': time.time() + } + server.broadcast(json.dumps(error_data)) + + def start(self): + """WebSocketインターフェース開始""" + self.ws_server.start_server() + print(f"VRCT WebSocketインターフェース開始: ws://127.0.0.1:{self.ws_server.port}") + + def stop(self): + """WebSocketインターフェース停止""" + self.ws_server.stop_server() + print("VRCT WebSocketインターフェース停止") + + def notify_translation_result(self, original, translated, source_lang, target_lang): + """翻訳結果の通知(VRCTコントローラーから呼び出し)""" + notification = { + 'type': 'live_translation', + 'original': original, + 'translated': translated, + 'source_language': source_lang, + 'target_language': target_lang, + 'timestamp': time.time() + } + self.ws_server.send(json.dumps(notification)) + +# 使用例(VRCTアプリケーション内) +# vrct_ws_interface = VRCTWebSocketInterface(controller) +# vrct_ws_interface.start() +``` + +### リアルタイム監視ダッシュボード + +```python +class MonitoringDashboard: + """リアルタイム監視ダッシュボード""" + + def __init__(self, system_components, port=8766): + self.components = system_components + self.ws_server = WebSocketServer(host='127.0.0.1', port=port) + self.ws_server.set_message_handler(self.handle_dashboard_message) + self.monitoring_active = False + + def handle_dashboard_message(self, server, websocket, message): + """ダッシュボードからのメッセージ処理""" + try: + data = json.loads(message) + action = data.get('action') + + if action == 'start_monitoring': + self.start_monitoring(server) + elif action == 'stop_monitoring': + self.stop_monitoring(server) + elif action == 'get_metrics': + self.send_metrics(server) + elif action == 'get_logs': + self.send_logs(server, data.get('limit', 100)) + + except Exception as e: + self.send_dashboard_error(server, str(e)) + + def start_monitoring(self, server): + """監視開始""" + if not self.monitoring_active: + self.monitoring_active = True + + # 監視スレッド開始 + import threading + monitor_thread = threading.Thread( + target=self.monitoring_loop, + args=(server,), + daemon=True + ) + monitor_thread.start() + + response = { + 'type': 'monitoring_status', + 'status': 'started' + } + server.broadcast(json.dumps(response)) + + def stop_monitoring(self, server): + """監視停止""" + self.monitoring_active = False + + response = { + 'type': 'monitoring_status', + 'status': 'stopped' + } + server.broadcast(json.dumps(response)) + + def monitoring_loop(self, server): + """リアルタイム監視ループ""" + while self.monitoring_active: + try: + # システムメトリクス収集 + metrics = self.collect_metrics() + + # ダッシュボードに送信 + dashboard_data = { + 'type': 'live_metrics', + 'metrics': metrics, + 'timestamp': time.time() + } + server.broadcast(json.dumps(dashboard_data)) + + time.sleep(2) # 2秒間隔で更新 + + except Exception as e: + print(f"監視ループエラー: {e}") + time.sleep(5) + + def collect_metrics(self): + """システムメトリクス収集""" + import psutil + + metrics = { + 'system': { + 'cpu_percent': psutil.cpu_percent(), + 'memory_percent': psutil.virtual_memory().percent, + 'disk_usage': psutil.disk_usage('/').percent + }, + 'network': { + 'bytes_sent': psutil.net_io_counters().bytes_sent, + 'bytes_recv': psutil.net_io_counters().bytes_recv + }, + 'vrct': { + 'translation_count': self.components.get('translation_count', 0), + 'osc_messages_sent': self.components.get('osc_count', 0), + 'overlay_updates': self.components.get('overlay_count', 0), + 'active_connections': len(self.ws_server.clients) + } + } + + return metrics + + def send_metrics(self, server): + """メトリクス送信""" + metrics = self.collect_metrics() + + response = { + 'type': 'metrics_snapshot', + 'metrics': metrics, + 'timestamp': time.time() + } + server.broadcast(json.dumps(response)) + + def send_logs(self, server, limit): + """ログ送信""" + # ログファイルから最新のログを取得(実装例) + logs = self.get_recent_logs(limit) + + response = { + 'type': 'log_data', + 'logs': logs, + 'count': len(logs) + } + server.broadcast(json.dumps(response)) + + def get_recent_logs(self, limit): + """最新ログ取得""" + # 実際のログファイル読み込み処理 + mock_logs = [ + {'level': 'INFO', 'message': 'システム開始', 'timestamp': time.time() - 60}, + {'level': 'DEBUG', 'message': '翻訳処理完了', 'timestamp': time.time() - 30}, + {'level': 'WARNING', 'message': 'メモリ使用量増加', 'timestamp': time.time() - 10} + ] + return mock_logs[-limit:] + + def send_dashboard_error(self, server, error_message): + """ダッシュボードエラー送信""" + error_data = { + 'type': 'dashboard_error', + 'message': error_message, + 'timestamp': time.time() + } + server.broadcast(json.dumps(error_data)) + + def start_dashboard(self): + """ダッシュボード開始""" + self.ws_server.start_server() + print(f"監視ダッシュボード開始: ws://127.0.0.1:{self.ws_server.port}") + + def stop_dashboard(self): + """ダッシュボード停止""" + self.monitoring_active = False + self.ws_server.stop_server() + +# 使用例 +system_components = { + 'translation_count': 150, + 'osc_count': 75, + 'overlay_count': 200 +} + +dashboard = MonitoringDashboard(system_components) +dashboard.start_dashboard() + +# しばらく実行 +time.sleep(60) + +dashboard.stop_dashboard() +``` + +### 高度なメッセージルーティング + +```python +class WebSocketRouter: + """WebSocketメッセージルーティングシステム""" + + def __init__(self, port=8767): + self.ws_server = WebSocketServer(host='127.0.0.1', port=port) + self.ws_server.set_message_handler(self.route_message) + self.routes = {} + self.middleware = [] + self.client_subscriptions = {} + + def add_route(self, message_type, handler): + """メッセージタイプに対するハンドラー登録""" + self.routes[message_type] = handler + + def add_middleware(self, middleware_func): + """ミドルウェア追加""" + self.middleware.append(middleware_func) + + def route_message(self, server, websocket, message): + """メッセージルーティング処理""" + try: + # JSON解析 + data = json.loads(message) + message_type = data.get('type') + + # ミドルウェア実行 + for middleware in self.middleware: + data = middleware(data, websocket) + if data is None: # ミドルウェアがNoneを返した場合は処理中断 + return + + # ルーティング実行 + if message_type in self.routes: + handler = self.routes[message_type] + response = handler(data, websocket, server) + + if response: + server.broadcast(json.dumps(response)) + else: + # 未定義メッセージタイプ + error_response = { + 'type': 'error', + 'message': f'未対応メッセージタイプ: {message_type}', + 'original_type': message_type + } + websocket.send(json.dumps(error_response)) + + except json.JSONDecodeError as e: + error_response = { + 'type': 'error', + 'message': f'JSON解析エラー: {e}' + } + websocket.send(json.dumps(error_response)) + except Exception as e: + error_response = { + 'type': 'error', + 'message': f'処理エラー: {e}' + } + websocket.send(json.dumps(error_response)) + + def subscription_middleware(self, data, websocket): + """購読管理ミドルウェア""" + message_type = data.get('type') + + if message_type == 'subscribe': + # 購読登録 + topics = data.get('topics', []) + client_id = id(websocket) + self.client_subscriptions[client_id] = topics + + response = { + 'type': 'subscription_confirmed', + 'topics': topics + } + websocket.send(json.dumps(response)) + return None # 処理終了 + + elif message_type == 'unsubscribe': + # 購読解除 + client_id = id(websocket) + if client_id in self.client_subscriptions: + del self.client_subscriptions[client_id] + + response = { + 'type': 'unsubscription_confirmed' + } + websocket.send(json.dumps(response)) + return None + + return data # そのまま次の処理に進む + + def authentication_middleware(self, data, websocket): + """認証ミドルウェア""" + # 簡易認証例 + api_key = data.get('api_key') + + if api_key != 'valid_api_key_123': + error_response = { + 'type': 'authentication_error', + 'message': '無効なAPIキー' + } + websocket.send(json.dumps(error_response)) + return None + + return data + + def logging_middleware(self, data, websocket): + """ログ記録ミドルウェア""" + client_ip = websocket.remote_address[0] if websocket.remote_address else 'unknown' + message_type = data.get('type', 'unknown') + + print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] {client_ip} -> {message_type}") + + return data + + def broadcast_to_subscribers(self, topic, message_data): + """購読者へのトピック配信""" + message_data['topic'] = topic + message_json = json.dumps(message_data) + + for client_id, topics in self.client_subscriptions.items(): + if topic in topics: + # 該当クライアントを検索 + for client in self.ws_server.clients: + if id(client) == client_id: + try: + client.send(message_json) + except Exception as e: + print(f"配信エラー: {e}") + break + + def start_router(self): + """ルーター開始""" + self.ws_server.start_server() + print(f"WebSocketルーター開始: ws://127.0.0.1:{self.ws_server.port}") + + def stop_router(self): + """ルーター停止""" + self.ws_server.stop_server() + +# 使用例 +def handle_chat_message(data, websocket, server): + """チャットメッセージハンドラー""" + username = data.get('username', 'Anonymous') + message = data.get('message', '') + + response = { + 'type': 'chat_broadcast', + 'username': username, + 'message': message, + 'timestamp': time.time() + } + + return response + +def handle_translation_request(data, websocket, server): + """翻訳要求ハンドラー""" + text = data.get('text', '') + # 翻訳処理(モック) + translated = f"[翻訳] {text}" + + response = { + 'type': 'translation_response', + 'original': text, + 'translated': translated + } + + return response + +# ルーター設定 +router = WebSocketRouter() + +# ミドルウェア登録 +router.add_middleware(router.logging_middleware) +router.add_middleware(router.subscription_middleware) +# router.add_middleware(router.authentication_middleware) # 認証が必要な場合 + +# ルート登録 +router.add_route('chat_message', handle_chat_message) +router.add_route('translation_request', handle_translation_request) + +router.start_router() + +# トピック配信テスト +time.sleep(2) +router.broadcast_to_subscribers('system_updates', { + 'type': 'system_notification', + 'message': 'システム更新完了', + 'severity': 'info' +}) + +time.sleep(10) +router.stop_router() +``` + +## 高度な機能・パターン + +### 接続プール管理 + +```python +class ConnectionPoolManager: + """WebSocket接続プール管理""" + + def __init__(self): + self.pools = {} # pool_name -> set of websockets + + def assign_to_pool(self, websocket, pool_name): + """クライアントをプールに割り当て""" + if pool_name not in self.pools: + self.pools[pool_name] = set() + + self.pools[pool_name].add(websocket) + print(f"クライアントを {pool_name} プールに追加") + + def remove_from_pools(self, websocket): + """すべてのプールからクライアントを削除""" + for pool_name, pool in self.pools.items(): + if websocket in pool: + pool.discard(websocket) + print(f"クライアントを {pool_name} プールから削除") + + def broadcast_to_pool(self, pool_name, message): + """特定プールに対してブロードキャスト""" + if pool_name in self.pools: + for websocket in self.pools[pool_name].copy(): + try: + websocket.send(message) + except Exception: + # 切断されたクライアントを削除 + self.pools[pool_name].discard(websocket) + + def get_pool_stats(self): + """プール統計情報""" + stats = {} + for pool_name, pool in self.pools.items(): + stats[pool_name] = len(pool) + return stats +``` + +### メッセージ永続化・再送機能 + +```python +class PersistentMessageSystem: + """メッセージ永続化・再送システム""" + + def __init__(self, max_history=1000): + self.message_history = [] + self.max_history = max_history + self.client_last_seen = {} # client_id -> last_message_id + + def store_message(self, message_data): + """メッセージを履歴に保存""" + message_id = len(self.message_history) + stored_message = { + 'id': message_id, + 'data': message_data, + 'timestamp': time.time() + } + + self.message_history.append(stored_message) + + # 履歴サイズ制限 + if len(self.message_history) > self.max_history: + self.message_history = self.message_history[-self.max_history:] + + return message_id + + def get_missed_messages(self, client_id, last_seen_id): + """クライアントが見逃したメッセージを取得""" + missed_messages = [] + + for msg in self.message_history: + if msg['id'] > last_seen_id: + missed_messages.append(msg) + + return missed_messages + + def client_reconnected(self, websocket, client_id): + """クライアント再接続時の処理""" + last_seen = self.client_last_seen.get(client_id, -1) + missed_messages = self.get_missed_messages(client_id, last_seen) + + # 見逃したメッセージを再送 + for msg in missed_messages: + try: + recovery_data = { + 'type': 'message_recovery', + 'original_message': msg['data'], + 'message_id': msg['id'], + 'original_timestamp': msg['timestamp'] + } + websocket.send(json.dumps(recovery_data)) + except Exception as e: + print(f"メッセージ再送エラー: {e}") + + print(f"クライアント {client_id} に {len(missed_messages)} 件のメッセージを再送") + + def update_client_position(self, client_id, message_id): + """クライアントの最新メッセージ位置更新""" + self.client_last_seen[client_id] = message_id +``` + +## パフォーマンス・スケーラビリティ + +### 負荷分散・最適化 + +```python +class OptimizedWebSocketServer(WebSocketServer): + """最適化されたWebSocketサーバー""" + + def __init__(self, host='127.0.0.1', port=8765): + super().__init__(host, port) + self.message_stats = { + 'total_messages': 0, + 'messages_per_second': 0, + 'last_reset_time': time.time() + } + self.compression_enabled = True + self.batch_size = 50 + self.batch_timeout = 0.1 + + def enable_message_batching(self, batch_size=50, timeout=0.1): + """メッセージバッチング有効化""" + self.batch_size = batch_size + self.batch_timeout = timeout + + async def optimized_broadcast(self, message_batch): + """最適化されたバッチブロードキャスト""" + if not self.clients: + return + + # 圧縮対応 + if self.compression_enabled and len(message_batch) > 1: + # 複数メッセージをまとめて送信 + combined_message = json.dumps({ + 'type': 'batch', + 'messages': message_batch, + 'count': len(message_batch) + }) + else: + combined_message = json.dumps(message_batch[0]) + + # 並列送信(エラー処理付き) + send_tasks = [] + for client in self.clients.copy(): + send_tasks.append(self.safe_send(client, combined_message)) + + results = await asyncio.gather(*send_tasks, return_exceptions=True) + + # 失敗したクライアントを削除 + for i, result in enumerate(results): + if isinstance(result, Exception): + failed_client = list(self.clients)[i] + self.clients.discard(failed_client) + print(f"クライアント削除(送信失敗): {result}") + + # 統計更新 + self.update_message_stats(len(message_batch)) + + async def safe_send(self, client, message): + """安全なメッセージ送信""" + try: + await client.send(message) + except Exception as e: + raise e # gather で捕捉される + + def update_message_stats(self, message_count): + """メッセージ統計更新""" + self.message_stats['total_messages'] += message_count + + current_time = time.time() + time_diff = current_time - self.message_stats['last_reset_time'] + + if time_diff >= 1.0: # 1秒ごとに速度計算 + self.message_stats['messages_per_second'] = message_count / time_diff + self.message_stats['last_reset_time'] = current_time + + def get_performance_stats(self): + """パフォーマンス統計取得""" + return { + 'connected_clients': len(self.clients), + 'total_messages': self.message_stats['total_messages'], + 'messages_per_second': self.message_stats['messages_per_second'], + 'compression_enabled': self.compression_enabled, + 'batch_size': self.batch_size + } +``` + +## 依存関係・システム要件 + +### 必須依存関係 +- `asyncio`: 非同期処理フレームワーク +- `websockets`: WebSocketライブラリ +- `threading`: マルチスレッド制御 +- `json`: JSON形式データ処理 + +### システム要件 +```python +system_requirements = { + "python_version": "3.7以上", + "asyncio_support": "非同期処理対応", + "network_stack": "TCP/WebSocket対応", + "memory": "同時接続数に応じた十分なメモリ" +} + +performance_characteristics = { + "concurrent_connections": "数百~数千接続対応", + "message_throughput": "秒間数千メッセージ処理可能", + "latency": "低レイテンシー(ミリ秒オーダー)", + "memory_per_connection": "約1-5MB(接続当たり)" +} +``` + +### オプション依存関係 +- `ujson`: 高速JSON処理(パフォーマンス向上) +- `compression`: メッセージ圧縮(帯域節約) + +## 注意事項・制限 + +### ネットワーク制限 +- ファイアウォール設定の要確認 +- プロキシ環境での制限可能性 +- ブラウザーのWebSocket接続制限 + +### スケーラビリティ制限 +- 単一プロセスでの同時接続数制限 +- メモリ使用量の線形増加 +- CPU集約的な処理での性能劣化 + +### セキュリティ考慮事項 +```python +security_considerations = { + "authentication": "認証機構の実装推奨", + "authorization": "適切な認可制御", + "rate_limiting": "レート制限の実装", + "input_validation": "入力データの検証必須", + "cors_policy": "CORS設定の適切な構成" +} +``` + +## 関連モジュール + +- `config.py`: WebSocket設定管理 +- `controller.py`: WebSocket制御インターフェース +- `utils.py`: エラーログ・ユーティリティ +- `model.py`: WebSocket機能統合 + +## 将来の改善点 + +- Redis等を用いたメッセージブローカー連携 +- 負荷分散・クラスタリング対応 +- より高度な認証・認可システム +- WebRTC等のより高速な通信プロトコル対応 +- GraphQL over WebSocketサポート +- リアルタイム監視・分析機能の強化 \ No newline at end of file