👍️[Update] Model : WebSocket module

This commit is contained in:
misyaguziya
2025-05-17 09:30:17 +09:00
parent fac33e06ba
commit d940097e44
8 changed files with 188 additions and 148 deletions

View File

@@ -15,6 +15,7 @@ pydub==0.25.1
psutil==5.9.8 psutil==5.9.8
pykakasi==2.3.0 pykakasi==2.3.0
pycaw==20240210 pycaw==20240210
websockets==15.0.1
translators @ git+https://github.com/misyaguziya/translators@5.9.2.1 translators @ git+https://github.com/misyaguziya/translators@5.9.2.1
SpeechRecognition @ git+https://github.com/misyaguziya/custom_speech_recognition@3.10.4.1 SpeechRecognition @ git+https://github.com/misyaguziya/custom_speech_recognition@3.10.4.1
tinyoscquery @ git+https://github.com/cyberkitsune/tinyoscquery@0.1.3 tinyoscquery @ git+https://github.com/cyberkitsune/tinyoscquery@0.1.3

View File

@@ -16,6 +16,7 @@ pydub==0.25.1
psutil==5.9.8 psutil==5.9.8
pykakasi==2.3.0 pykakasi==2.3.0
pycaw==20240210 pycaw==20240210
websockets==15.0.1
translators @ git+https://github.com/misyaguziya/translators@5.9.2.1 translators @ git+https://github.com/misyaguziya/translators@5.9.2.1
SpeechRecognition @ git+https://github.com/misyaguziya/custom_speech_recognition@3.10.4.1 SpeechRecognition @ git+https://github.com/misyaguziya/custom_speech_recognition@3.10.4.1
tinyoscquery @ git+https://github.com/cyberkitsune/tinyoscquery@0.1.3 tinyoscquery @ git+https://github.com/cyberkitsune/tinyoscquery@0.1.3

View File

@@ -954,6 +954,37 @@ class Config:
self._NOTIFICATION_VRC_SFX = value self._NOTIFICATION_VRC_SFX = value
self.saveConfig(inspect.currentframe().f_code.co_name, value) self.saveConfig(inspect.currentframe().f_code.co_name, value)
@property
def WEBSOCKET_SERVER(self):
return self._WEBSOCKET_SERVER
@WEBSOCKET_SERVER.setter
def WEBSOCKET_SERVER(self, value):
if isinstance(value, bool):
self._WEBSOCKET_SERVER = value
self.saveConfig(inspect.currentframe().f_code.co_name, value)
@property
def WEBSOCKET_HOST(self):
return self._WEBSOCKET_HOST
@WEBSOCKET_HOST.setter
def WEBSOCKET_HOST(self, value):
if isinstance(value, str):
self._WEBSOCKET_HOST = value
self.saveConfig(inspect.currentframe().f_code.co_name, value)
@property
def WEBSOCKET_PORT(self):
return self._WEBSOCKET_PORT
@WEBSOCKET_PORT.setter
def WEBSOCKET_PORT(self, value):
if isinstance(value, int):
self._WEBSOCKET_PORT = value
self.saveConfig(inspect.currentframe().f_code.co_name, value)
def init_config(self): def init_config(self):
# Read Only # Read Only
self._VERSION = "3.1.1" self._VERSION = "3.1.1"
@@ -1139,6 +1170,9 @@ class Config:
self._LOGGER_FEATURE = False self._LOGGER_FEATURE = False
self._VRC_MIC_MUTE_SYNC = False self._VRC_MIC_MUTE_SYNC = False
self._NOTIFICATION_VRC_SFX = True self._NOTIFICATION_VRC_SFX = True
self._WEBSOCKET_SERVER = True
self._WEBSOCKET_HOST = "0.0.0.0"
self._WEBSOCKET_PORT = 8765
def load_config(self): def load_config(self):
if os_path.isfile(self.PATH_CONFIG) is not False: if os_path.isfile(self.PATH_CONFIG) is not False:

View File

@@ -294,6 +294,17 @@ class Controller:
"translation":translation, "translation":translation,
"transliteration":transliteration "transliteration":transliteration
}) })
if config.WEBSOCKET_SERVER is True:
model.websocketSendMessage(
{
"type":"SEND",
"message":message,
"translation":translation,
"transliteration":transliteration
}
)
if config.LOGGER_FEATURE is True: if config.LOGGER_FEATURE is True:
if len(translation) > 0: if len(translation) > 0:
translation = " (" + "/".join(translation) + ")" translation = " (" + "/".join(translation) + ")"
@@ -377,6 +388,17 @@ class Controller:
"translation":translation, "translation":translation,
"transliteration":transliteration, "transliteration":transliteration,
}) })
if config.WEBSOCKET_SERVER is True:
model.websocketSendMessage(
{
"type":"RECEIVED",
"message":message,
"translation":translation,
"transliteration":transliteration
}
)
if config.LOGGER_FEATURE is True: if config.LOGGER_FEATURE is True:
if len(translation) > 0: if len(translation) > 0:
translation = " (" + "/".join(translation) + ")" translation = " (" + "/".join(translation) + ")"
@@ -434,6 +456,16 @@ class Controller:
overlay_image = model.createOverlayImageLargeLog("send", message, translation[0] if len(translation) > 0 else "") overlay_image = model.createOverlayImageLargeLog("send", message, translation[0] if len(translation) > 0 else "")
model.updateOverlayLargeLog(overlay_image) model.updateOverlayLargeLog(overlay_image)
if config.WEBSOCKET_SERVER is True:
model.websocketSendMessage(
{
"type":"CHAT",
"message":message,
"translation":translation,
"transliteration":transliteration
}
)
# update textbox message log (Sent) # update textbox message log (Sent)
if config.LOGGER_FEATURE is True: if config.LOGGER_FEATURE is True:
if len(translation) > 0: if len(translation) > 0:
@@ -1778,6 +1810,46 @@ class Controller:
model.stopWatchdog() model.stopWatchdog()
return {"status":200, "result":True} return {"status":200, "result":True}
@staticmethod
def getWebSocketHost(*args, **kwargs) -> dict:
return {"status":200, "result":config.WEBSOCKET_HOST}
@staticmethod
def setWebSocketHost(data, *args, **kwargs) -> dict:
config.WEBSOCKET_HOST = data
if model.checkWebSocketServer() is True:
model.stopWebSocketServer()
model.startWebSocketServer()
return {"status":200, "result":config.WEBSOCKET_HOST}
@staticmethod
def getWebSocketPort(*args, **kwargs) -> dict:
return {"status":200, "result":config.WEBSOCKET_PORT}
@staticmethod
def setWebSocketPort(data, *args, **kwargs) -> dict:
config.WEBSOCKET_PORT = int(data)
if model.checkWebSocketServer() is True:
model.stopWebSocketServer()
model.startWebSocketServer()
return {"status":200, "result":config.WEBSOCKET_PORT}
@staticmethod
def getWebSocketServer(*args, **kwargs) -> dict:
return {"status":200, "result":config.WEBSOCKET_SERVER}
@staticmethod
def setEnableWebSocketServer(*args, **kwargs) -> dict:
config.WEBSOCKET_SERVER = True
model.startWebSocketServer()
return {"status":200, "result":config.WEBSOCKET_SERVER}
@staticmethod
def setDisableWebSocketServer(*args, **kwargs) -> dict:
config.WEBSOCKET_SERVER = False
model.stopWebSocketServer()
return {"status":200, "result":config.WEBSOCKET_SERVER}
def initializationProgress(self, progress): def initializationProgress(self, progress):
self.run(200, self.run_mapping["initialization_progress"], progress) self.run(200, self.run_mapping["initialization_progress"], progress)
@@ -1825,41 +1897,7 @@ class Controller:
printLog("Init Translation Engine Status") printLog("Init Translation Engine Status")
for engine in config.SELECTABLE_TRANSLATION_ENGINE_LIST: for engine in config.SELECTABLE_TRANSLATION_ENGINE_LIST:
match engine: config.SELECTABLE_TRANSCRIPTION_ENGINE_STATUS[engine] = False
case "CTranslate2":
if model.checkTranslatorCTranslate2ModelWeight(config.CTRANSLATE2_WEIGHT_TYPE) is True:
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True
else:
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False
case "DeepL_API":
printLog("Start check DeepL API Key")
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False
if config.AUTH_KEYS[engine] is not None:
if model.authenticationTranslatorDeepLAuthKey(auth_key=config.AUTH_KEYS[engine]) is True:
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True
else:
# error update Auth key
auth_keys = config.AUTH_KEYS
auth_keys[engine] = None
config.AUTH_KEYS = auth_keys
case _:
if connected_network is True:
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True
else:
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False
for engine in config.SELECTABLE_TRANSCRIPTION_ENGINE_LIST:
match engine:
case "Whisper":
if model.checkTranscriptionWhisperModelWeight(config.WHISPER_WEIGHT_TYPE) is True:
config.SELECTABLE_TRANSCRIPTION_ENGINE_STATUS[engine] = True
else:
config.SELECTABLE_TRANSCRIPTION_ENGINE_STATUS[engine] = False
case _:
if connected_network is True:
config.SELECTABLE_TRANSCRIPTION_ENGINE_STATUS[engine] = True
else:
config.SELECTABLE_TRANSCRIPTION_ENGINE_STATUS[engine] = False
self.initializationProgress(2) self.initializationProgress(2)
# set Translation Engine # set Translation Engine
@@ -1911,6 +1949,10 @@ class Controller:
if (config.OVERLAY_SMALL_LOG is True or config.OVERLAY_LARGE_LOG is True): if (config.OVERLAY_SMALL_LOG is True or config.OVERLAY_LARGE_LOG is True):
model.startOverlay() model.startOverlay()
printLog("Init WebSocket Server")
if config.WEBSOCKET_SERVER is True:
model.startWebSocketServer()
printLog("Update settings") printLog("Update settings")
self.updateConfigSettings() self.updateConfigSettings()

View File

@@ -291,6 +291,15 @@ mapping = {
"/set/enable/send_received_message_to_vrc": {"status": True, "variable":controller.setEnableSendReceivedMessageToVrc}, "/set/enable/send_received_message_to_vrc": {"status": True, "variable":controller.setEnableSendReceivedMessageToVrc},
"/set/disable/send_received_message_to_vrc": {"status": True, "variable":controller.setDisableSendReceivedMessageToVrc}, "/set/disable/send_received_message_to_vrc": {"status": True, "variable":controller.setDisableSendReceivedMessageToVrc},
# WebSocket Settings
"/get/data/websocket_host": {"status": True, "variable":controller.getWebSocketHost},
"/set/data/websocket_host": {"status": True, "variable":controller.setWebSocketHost},
"/get/data/websocket_port": {"status": True, "variable":controller.getWebSocketPort},
"/set/data/websocket_port": {"status": True, "variable":controller.setWebSocketPort},
"/get/data/websocket_server": {"status": True, "variable":controller.getWebSocketServer},
"/set/enable/websocket_server": {"status": True, "variable":controller.setEnableWebSocketServer},
"/set/disable/websocket_server": {"status": True, "variable":controller.setDisableWebSocketServer},
# Advanced Settings # Advanced Settings
"/get/data/osc_ip_address": {"status": True, "variable":controller.getOscIpAddress}, "/get/data/osc_ip_address": {"status": True, "variable":controller.getOscIpAddress},
"/set/data/osc_ip_address": {"status": True, "variable":controller.setOscIpAddress}, "/set/data/osc_ip_address": {"status": True, "variable":controller.setOscIpAddress},

View File

@@ -29,6 +29,7 @@ from models.transcription.transcription_whisper import checkWhisperWeight, downl
from models.overlay.overlay import Overlay from models.overlay.overlay import Overlay
from models.overlay.overlay_image import OverlayImage from models.overlay.overlay_image import OverlayImage
from models.watchdog.watchdog import Watchdog from models.watchdog.watchdog import Watchdog
from models.websocket.websocket_server import WebSocketServer
from utils import errorLogging, setupLogger from utils import errorLogging, setupLogger
class threadFnc(Thread): class threadFnc(Thread):
@@ -100,6 +101,50 @@ class Model:
self.watchdog = Watchdog(config.WATCHDOG_TIMEOUT, config.WATCHDOG_INTERVAL) self.watchdog = Watchdog(config.WATCHDOG_TIMEOUT, config.WATCHDOG_INTERVAL)
self.osc_handler = OSCHandler(config.OSC_IP_ADDRESS, config.OSC_PORT) self.osc_handler = OSCHandler(config.OSC_IP_ADDRESS, config.OSC_PORT)
# WebSocketサーバーの初期化
self.websocket_server = WebSocketServer(
host=config.WEBSOCKET_HOST,
port=config.WEBSOCKET_PORT
)
self.th_websocket_server = None
def startWebSocketServer(self):
if self.th_websocket_server is None:
from threading import Thread
def run_server():
try:
self.websocket_server.start()
except Exception:
errorLogging()
self.th_websocket_server = Thread(target=run_server, daemon=True)
self.th_websocket_server.start()
def stopWebSocketServer(self):
if self.websocket_server:
try:
self.websocket_server.stop()
except Exception:
errorLogging()
self.th_websocket_server = None
def checkWebSocketServer(self):
if self.websocket_server:
try:
return self.websocket_server.is_running
except Exception:
errorLogging()
return False
def websocketSendMessage(self, message_dict):
"""
WebSocketサーバーから全クライアントにメッセージを送信する
:param message_dict: 送信する辞書型データ
"""
try:
self.websocket_server.send_message(message_dict)
except Exception:
errorLogging()
def checkTranslatorCTranslate2ModelWeight(self, weight_type:str): def checkTranslatorCTranslate2ModelWeight(self, weight_type:str):
return checkCTranslate2Weight(config.PATH_LOCAL, weight_type) return checkCTranslate2Weight(config.PATH_LOCAL, weight_type)

View File

@@ -1,103 +0,0 @@
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)

View File

@@ -1,30 +1,29 @@
import asyncio import asyncio
import json import json
import logging import logging
from typing import Dict, Set, Optional from typing import Dict, Set
import websockets import websockets
from websockets.server import WebSocketServerProtocol
class WebSocketServer: class WebSocketServer:
def __init__(self, host: str = "0.0.0.0", port: int = 8765): def __init__(self, host: str = "0.0.0.0", port: int = 8765):
self.host = host self.host = host
self.port = port self.port = port
self.clients: Set[WebSocketServerProtocol] = set() self.clients = set()
self.server = None self.server = None
self.is_running = False self.is_running = False
self.logger = logging.getLogger('websocket_server') self.logger = logging.getLogger('websocket_server')
async def register(self, websocket: WebSocketServerProtocol): async def register(self, websocket):
"""クライアント接続を登録する""" """クライアント接続を登録する"""
self.clients.add(websocket) self.clients.add(websocket)
self.logger.info(f"クライアント接続: {websocket.remote_address}, 現在の接続数: {len(self.clients)}") self.logger.info(f"クライアント接続: {websocket.remote_address}, 現在の接続数: {len(self.clients)}")
async def unregister(self, websocket: WebSocketServerProtocol): async def unregister(self, websocket):
"""クライアント接続を解除する""" """クライアント接続を解除する"""
self.clients.remove(websocket) self.clients.remove(websocket)
self.logger.info(f"クライアント切断: {websocket.remote_address}, 現在の接続数: {len(self.clients)}") self.logger.info(f"クライアント切断: {websocket.remote_address}, 現在の接続数: {len(self.clients)}")
async def handler(self, websocket: WebSocketServerProtocol, path: str): async def handler(self, websocket):
"""WebSocket接続ハンドラー""" """WebSocket接続ハンドラー"""
await self.register(websocket) await self.register(websocket)
try: try:
@@ -46,17 +45,17 @@ class WebSocketServer:
tasks = [client.send(json_message) for client in self.clients] tasks = [client.send(json_message) for client in self.clients]
await asyncio.gather(*tasks, return_exceptions=True) await asyncio.gather(*tasks, return_exceptions=True)
def send_transcription(self, transcription_data: Dict): def send_message(self, message: Dict):
"""文字起こし結果を送信する関数(通常のコードから呼び出し可能)""" """メッセージを送信する関数(通常のコードから呼び出し可能)"""
if not self.is_running or not self.clients: if not self.is_running or not self.clients:
return return
# asyncioのイベントループがあれば利用し、なければ新たに作成 # asyncioのイベントループがあれば利用し、なければ新たに作成
loop = asyncio.get_event_loop() if asyncio.get_event_loop().is_running() else asyncio.new_event_loop() loop = asyncio.get_event_loop() if asyncio.get_event_loop().is_running() else asyncio.new_event_loop()
# ブロードキャスト関数を実行 # ブロードキャスト関数を実行
asyncio.run_coroutine_threadsafe( asyncio.run_coroutine_threadsafe(
self.broadcast(transcription_data), self.broadcast(message),
loop loop
) )
@@ -100,3 +99,15 @@ class WebSocketServer:
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
asyncio.run_coroutine_threadsafe(self.stop_server(), loop) asyncio.run_coroutine_threadsafe(self.stop_server(), loop)
def is_running(self) -> bool:
"""サーバーが実行中かどうかを確認する"""
return self.is_running
def get_clients(self) -> Set:
"""現在のクライアント接続を取得する"""
return self.clients
def get_client_count(self) -> int:
"""現在のクライアント接続数を取得する"""
return len(self.clients)