diff --git a/src-python/models/websocket/__init__.py b/src-python/models/websocket/__init__.py new file mode 100644 index 00000000..a01d3ba6 --- /dev/null +++ b/src-python/models/websocket/__init__.py @@ -0,0 +1,4 @@ +# WebSocketサーバーモジュール +from .websocket_server import WebSocketServer + +__all__ = ["WebSocketServer"] diff --git a/src-python/models/websocket/model_part.py b/src-python/models/websocket/model_part.py new file mode 100644 index 00000000..47758bf0 --- /dev/null +++ b/src-python/models/websocket/model_part.py @@ -0,0 +1,103 @@ +import copy +import gc +from subprocess import Popen +from os import makedirs as os_makedirs +from os import path as os_path +from datetime import datetime +from time import sleep +from queue import Queue +from threading import Thread +from requests import get as requests_get +from typing import Callable +from packaging.version import parse + +from flashtext import KeywordProcessor +from pykakasi import kakasi + +from device_manager import device_manager +from config import config + +from models.translation.translation_translator import Translator +from models.osc.osc import OSCHandler +from models.transcription.transcription_recorder import SelectedMicEnergyAndAudioRecorder, SelectedSpeakerEnergyAndAudioRecorder +from models.transcription.transcription_recorder import SelectedMicEnergyRecorder, SelectedSpeakerEnergyRecorder +from models.transcription.transcription_transcriber import AudioTranscriber +from models.translation.translation_languages import translation_lang +from models.transcription.transcription_languages import transcription_lang +from models.translation.translation_utils import checkCTranslate2Weight, downloadCTranslate2Weight, downloadCTranslate2Tokenizer +from models.transcription.transcription_whisper import checkWhisperWeight, downloadWhisperWeight +from models.overlay.overlay import Overlay +from models.overlay.overlay_image import OverlayImage +from models.watchdog.watchdog import Watchdog +from models.websocket.websocket_server import WebSocketServer +from utils import errorLogging, setupLogger + +class threadFnc(Thread): + def __init__(self, fnc, end_fnc=None, daemon=True, *args, **kwargs): + super(threadFnc, self).__init__(daemon=daemon, target=fnc, *args, **kwargs) + self.fnc = fnc + self.end_fnc = end_fnc + self.loop = True + self._pause = False + + def stop(self): + self.loop = False + + def pause(self): + self._pause = True + + def resume(self): + self._pause = False + + def run(self): + while self.loop: + self.fnc(*self._args, **self._kwargs) + while self._pause: + sleep(0.1) + + if callable(self.end_fnc): + self.end_fnc() + return + +class Model: + _instance = None + + def __new__(cls): + if cls._instance is None: + cls._instance = super(Model, cls).__new__(cls) + cls._instance.init() + return cls._instance + + def init(self): + self.logger = None + self.th_check_device = None + self.mic_print_transcript = None + self.mic_audio_recorder = None + self.mic_transcriber = None + self.mic_energy_recorder = None + self.mic_energy_plot_progressbar = None + self.speaker_print_transcript = None + self.speaker_audio_recorder = None + self.speaker_transcriber = None + self.speaker_energy_recorder = None + self.speaker_energy_plot_progressbar = None + + self.previous_send_message = "" + self.previous_receive_message = "" + self.translator = Translator() + self.keyword_processor = KeywordProcessor() + overlay_small_log_settings = copy.deepcopy(config.OVERLAY_SMALL_LOG_SETTINGS) + overlay_large_log_settings = copy.deepcopy(config.OVERLAY_LARGE_LOG_SETTINGS) + overlay_large_log_settings["ui_scaling"] = overlay_large_log_settings["ui_scaling"] * 0.25 + overlay_settings = { + "small": overlay_small_log_settings, + "large": overlay_large_log_settings, + } + self.overlay = Overlay(overlay_settings) + self.overlay_image = OverlayImage() + self.mic_audio_queue = None + self.mic_mute_status = None + self.kks = kakasi() + self.watchdog = Watchdog(config.WATCHDOG_TIMEOUT, config.WATCHDOG_INTERVAL) + self.osc_handler = OSCHandler(config.OSC_IP_ADDRESS, config.OSC_PORT) + self.websocket_server = WebSocketServer(host="0.0.0.0", port=8765) diff --git a/src-python/models/websocket/websocket_server.py b/src-python/models/websocket/websocket_server.py new file mode 100644 index 00000000..73ee3787 --- /dev/null +++ b/src-python/models/websocket/websocket_server.py @@ -0,0 +1,102 @@ +import asyncio +import json +import logging +from typing import Dict, Set, Optional +import websockets +from websockets.server import WebSocketServerProtocol + +class WebSocketServer: + def __init__(self, host: str = "0.0.0.0", port: int = 8765): + self.host = host + self.port = port + self.clients: Set[WebSocketServerProtocol] = set() + self.server = None + self.is_running = False + self.logger = logging.getLogger('websocket_server') + + async def register(self, websocket: WebSocketServerProtocol): + """クライアント接続を登録する""" + self.clients.add(websocket) + self.logger.info(f"クライアント接続: {websocket.remote_address}, 現在の接続数: {len(self.clients)}") + + async def unregister(self, websocket: WebSocketServerProtocol): + """クライアント接続を解除する""" + self.clients.remove(websocket) + self.logger.info(f"クライアント切断: {websocket.remote_address}, 現在の接続数: {len(self.clients)}") + + async def handler(self, websocket: WebSocketServerProtocol, path: str): + """WebSocket接続ハンドラー""" + await self.register(websocket) + try: + async for message in websocket: + # クライアントからのメッセージを処理(必要に応じて) + # 現在はクライアントからのメッセージは特に処理していません + self.logger.debug(f"クライアントからのメッセージ: {message}") + except websockets.exceptions.ConnectionClosed: + pass + finally: + await self.unregister(websocket) + + async def broadcast(self, message: Dict): + """全クライアントにメッセージをブロードキャストする""" + if not self.clients: + return + + json_message = json.dumps(message) + tasks = [client.send(json_message) for client in self.clients] + await asyncio.gather(*tasks, return_exceptions=True) + + def send_transcription(self, transcription_data: Dict): + """文字起こし結果を送信する関数(通常のコードから呼び出し可能)""" + if not self.is_running or not self.clients: + return + + # asyncioのイベントループがあれば利用し、なければ新たに作成 + loop = asyncio.get_event_loop() if asyncio.get_event_loop().is_running() else asyncio.new_event_loop() + + # ブロードキャスト関数を実行 + asyncio.run_coroutine_threadsafe( + self.broadcast(transcription_data), + loop + ) + + async def start_server(self): + """WebSocketサーバーを起動する""" + if not self.is_running: + self.server = await websockets.serve(self.handler, self.host, self.port) + self.is_running = True + self.logger.info(f"WebSocketサーバーを起動しました - {self.host}:{self.port}") + return True + return False + + def start(self): + """非同期ループでWebSocketサーバーを起動する(通常のコードから呼び出し可能)""" + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop.run_until_complete(self.start_server()) + # バックグラウンドでループを実行 + self._task = asyncio.run_coroutine_threadsafe(self._run_forever(), loop) + + async def _run_forever(self): + """サーバーを永続的に実行する""" + while self.is_running: + await asyncio.sleep(0.1) + + async def stop_server(self): + """WebSocketサーバーを停止する""" + if self.is_running and self.server: + self.server.close() + await self.server.wait_closed() + self.is_running = False + self.clients.clear() + self.logger.info("WebSocketサーバーを停止しました") + return True + return False + + def stop(self): + """WebSocketサーバーを停止する(通常のコードから呼び出し可能)""" + if not self.is_running: + return + + loop = asyncio.get_event_loop() + asyncio.run_coroutine_threadsafe(self.stop_server(), loop)