From fac33e06ba5562306fbd569dfcaec92b6c8baad9 Mon Sep 17 00:00:00 2001
From: misyaguziya <53165965+misyaguziya@users.noreply.github.com>
Date: Thu, 15 May 2025 11:46:13 +0900
Subject: [PATCH 01/30] [Add] websocket server
---
src-python/models/websocket/__init__.py | 4 +
src-python/models/websocket/model_part.py | 103 ++++++++++++++++++
.../models/websocket/websocket_server.py | 102 +++++++++++++++++
3 files changed, 209 insertions(+)
create mode 100644 src-python/models/websocket/__init__.py
create mode 100644 src-python/models/websocket/model_part.py
create mode 100644 src-python/models/websocket/websocket_server.py
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)
From d940097e44991a05822d271098de66f22cec4a98 Mon Sep 17 00:00:00 2001
From: misyaguziya <53165965+misyaguziya@users.noreply.github.com>
Date: Sat, 17 May 2025 09:30:17 +0900
Subject: [PATCH 02/30] =?UTF-8?q?=F0=9F=91=8D=EF=B8=8F[Update]=20Model=20:?=
=?UTF-8?q?=20WebSocket=20module?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
requirements.txt | 1 +
requirements_cuda.txt | 1 +
src-python/config.py | 34 ++++++
src-python/controller.py | 112 ++++++++++++------
src-python/mainloop.py | 9 ++
src-python/model.py | 45 +++++++
src-python/models/websocket/model_part.py | 103 ----------------
.../models/websocket/websocket_server.py | 31 +++--
8 files changed, 188 insertions(+), 148 deletions(-)
delete mode 100644 src-python/models/websocket/model_part.py
diff --git a/requirements.txt b/requirements.txt
index b384b8a0..d4115f63 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -15,6 +15,7 @@ pydub==0.25.1
psutil==5.9.8
pykakasi==2.3.0
pycaw==20240210
+websockets==15.0.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
tinyoscquery @ git+https://github.com/cyberkitsune/tinyoscquery@0.1.3
\ No newline at end of file
diff --git a/requirements_cuda.txt b/requirements_cuda.txt
index 8642db8b..0f259323 100644
--- a/requirements_cuda.txt
+++ b/requirements_cuda.txt
@@ -16,6 +16,7 @@ pydub==0.25.1
psutil==5.9.8
pykakasi==2.3.0
pycaw==20240210
+websockets==15.0.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
tinyoscquery @ git+https://github.com/cyberkitsune/tinyoscquery@0.1.3
\ No newline at end of file
diff --git a/src-python/config.py b/src-python/config.py
index 175ffa0f..7910400b 100644
--- a/src-python/config.py
+++ b/src-python/config.py
@@ -954,6 +954,37 @@ class Config:
self._NOTIFICATION_VRC_SFX = 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):
# Read Only
self._VERSION = "3.1.1"
@@ -1139,6 +1170,9 @@ class Config:
self._LOGGER_FEATURE = False
self._VRC_MIC_MUTE_SYNC = False
self._NOTIFICATION_VRC_SFX = True
+ self._WEBSOCKET_SERVER = True
+ self._WEBSOCKET_HOST = "0.0.0.0"
+ self._WEBSOCKET_PORT = 8765
def load_config(self):
if os_path.isfile(self.PATH_CONFIG) is not False:
diff --git a/src-python/controller.py b/src-python/controller.py
index cdc1b037..398e3e37 100644
--- a/src-python/controller.py
+++ b/src-python/controller.py
@@ -294,6 +294,17 @@ class Controller:
"translation":translation,
"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 len(translation) > 0:
translation = " (" + "/".join(translation) + ")"
@@ -377,6 +388,17 @@ class Controller:
"translation":translation,
"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 len(translation) > 0:
translation = " (" + "/".join(translation) + ")"
@@ -434,6 +456,16 @@ class Controller:
overlay_image = model.createOverlayImageLargeLog("send", message, translation[0] if len(translation) > 0 else "")
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)
if config.LOGGER_FEATURE is True:
if len(translation) > 0:
@@ -1778,6 +1810,46 @@ class Controller:
model.stopWatchdog()
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):
self.run(200, self.run_mapping["initialization_progress"], progress)
@@ -1825,41 +1897,7 @@ class Controller:
printLog("Init Translation Engine Status")
for engine in config.SELECTABLE_TRANSLATION_ENGINE_LIST:
- match engine:
- 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
+ config.SELECTABLE_TRANSCRIPTION_ENGINE_STATUS[engine] = False
self.initializationProgress(2)
# set Translation Engine
@@ -1911,6 +1949,10 @@ class Controller:
if (config.OVERLAY_SMALL_LOG is True or config.OVERLAY_LARGE_LOG is True):
model.startOverlay()
+ printLog("Init WebSocket Server")
+ if config.WEBSOCKET_SERVER is True:
+ model.startWebSocketServer()
+
printLog("Update settings")
self.updateConfigSettings()
diff --git a/src-python/mainloop.py b/src-python/mainloop.py
index 5c5fb744..d8ad52e7 100644
--- a/src-python/mainloop.py
+++ b/src-python/mainloop.py
@@ -291,6 +291,15 @@ mapping = {
"/set/enable/send_received_message_to_vrc": {"status": True, "variable":controller.setEnableSendReceivedMessageToVrc},
"/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
"/get/data/osc_ip_address": {"status": True, "variable":controller.getOscIpAddress},
"/set/data/osc_ip_address": {"status": True, "variable":controller.setOscIpAddress},
diff --git a/src-python/model.py b/src-python/model.py
index 8f62aa5e..ee8ee250 100644
--- a/src-python/model.py
+++ b/src-python/model.py
@@ -29,6 +29,7 @@ from models.transcription.transcription_whisper import checkWhisperWeight, downl
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):
@@ -100,6 +101,50 @@ class Model:
self.watchdog = Watchdog(config.WATCHDOG_TIMEOUT, config.WATCHDOG_INTERVAL)
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):
return checkCTranslate2Weight(config.PATH_LOCAL, weight_type)
diff --git a/src-python/models/websocket/model_part.py b/src-python/models/websocket/model_part.py
deleted file mode 100644
index 47758bf0..00000000
--- a/src-python/models/websocket/model_part.py
+++ /dev/null
@@ -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)
diff --git a/src-python/models/websocket/websocket_server.py b/src-python/models/websocket/websocket_server.py
index 73ee3787..330f6b79 100644
--- a/src-python/models/websocket/websocket_server.py
+++ b/src-python/models/websocket/websocket_server.py
@@ -1,30 +1,29 @@
import asyncio
import json
import logging
-from typing import Dict, Set, Optional
+from typing import Dict, Set
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.clients = set()
self.server = None
self.is_running = False
self.logger = logging.getLogger('websocket_server')
- async def register(self, websocket: WebSocketServerProtocol):
+ async def register(self, websocket):
"""クライアント接続を登録する"""
self.clients.add(websocket)
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.logger.info(f"クライアント切断: {websocket.remote_address}, 現在の接続数: {len(self.clients)}")
- async def handler(self, websocket: WebSocketServerProtocol, path: str):
+ async def handler(self, websocket):
"""WebSocket接続ハンドラー"""
await self.register(websocket)
try:
@@ -46,17 +45,17 @@ class WebSocketServer:
tasks = [client.send(json_message) for client in self.clients]
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:
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),
+ self.broadcast(message),
loop
)
@@ -100,3 +99,15 @@ class WebSocketServer:
loop = asyncio.get_event_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)
\ No newline at end of file
From ed5ebcee9090fcee070955479199f54c18e81b1e Mon Sep 17 00:00:00 2001
From: misyaguziya <53165965+misyaguziya@users.noreply.github.com>
Date: Sun, 18 May 2025 13:16:52 +0900
Subject: [PATCH 03/30] [Update] WebSocket server: Refactor threading and
message handling
---
src-python/model.py | 21 +-
.../models/websocket/websocket_server.py | 278 ++++++++++++------
2 files changed, 200 insertions(+), 99 deletions(-)
diff --git a/src-python/model.py b/src-python/model.py
index ee8ee250..d848fd94 100644
--- a/src-python/model.py
+++ b/src-python/model.py
@@ -106,18 +106,12 @@ class Model:
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()
+ try:
+ self.websocket_server.start()
+ except Exception:
+ errorLogging()
def stopWebSocketServer(self):
if self.websocket_server:
@@ -125,7 +119,6 @@ class Model:
self.websocket_server.stop()
except Exception:
errorLogging()
- self.th_websocket_server = None
def checkWebSocketServer(self):
if self.websocket_server:
@@ -135,13 +128,13 @@ class Model:
errorLogging()
return False
- def websocketSendMessage(self, message_dict):
+ def websocketSendMessage(self, message):
"""
WebSocketサーバーから全クライアントにメッセージを送信する
- :param message_dict: 送信する辞書型データ
+ :param message: 送信するメッセージ
"""
try:
- self.websocket_server.send_message(message_dict)
+ self.websocket_server.send(str(message))
except Exception:
errorLogging()
diff --git a/src-python/models/websocket/websocket_server.py b/src-python/models/websocket/websocket_server.py
index 330f6b79..f01122a9 100644
--- a/src-python/models/websocket/websocket_server.py
+++ b/src-python/models/websocket/websocket_server.py
@@ -1,113 +1,221 @@
import asyncio
-import json
-import logging
-from typing import Dict, Set
+import threading
import websockets
+from websockets.legacy.server import WebSocketServerProtocol
+from typing import Callable, Set, Optional
class WebSocketServer:
- def __init__(self, host: str = "0.0.0.0", port: int = 8765):
+ """
+ WebSocketサーバーを管理するクラス。
+ 主な機能:
+ - サーバーの起動・停止
+ - クライアント接続管理 (接続/切断の追跡)
+ - メッセージ受信のコールバック処理
+ - メッセージのブロードキャスト機能
+ - GUIスレッド等からメッセージ送信するためのキュー
+ """
+ def __init__(self, host: str='localhost', port: int=8765):
+ """
+ サーバーのホスト名とポートを指定して初期化します。
+ """
self.host = host
self.port = port
- self.clients = set()
- self.server = None
- self.is_running = False
- self.logger = logging.getLogger('websocket_server')
+ self.clients: Set[WebSocketServerProtocol] = set() # 接続クライアント集合
+ self._message_handler: Optional[Callable[['WebSocketServer', WebSocketServerProtocol, str], None]] = None
+ self._loop: Optional[asyncio.AbstractEventLoop] = None
+ self._server: Optional[websockets.serve] = None
+ self._thread: Optional[threading.Thread] = None
+ self._send_queue: Optional[asyncio.Queue] = None # 外部スレッド向け非同期キュー
+ self.is_running: bool = False # サーバーの起動状態を示すフラグ
- async def register(self, websocket):
- """クライアント接続を登録する"""
+ def set_message_handler(self, handler: Callable[['WebSocketServer', WebSocketServerProtocol, str], None]):
+ """
+ クライアントからメッセージ受信時に呼び出すコールバックを設定します。
+ コールバックのシグネチャ: (server, websocket, message) -> None
+ """
+ self._message_handler = handler
+
+ async def _handler(self, websocket):
+ """
+ 単一クライアントとのセッションを処理するハンドラです。
+ 新規接続時にクライアントを集合に追加し、メッセージを受信してコールバックを呼び出します。
+ 切断時には集合からクライアントを削除します。
+ """
+ # 接続クライアントを集合に追加
self.clients.add(websocket)
- self.logger.info(f"クライアント接続: {websocket.remote_address}, 現在の接続数: {len(self.clients)}")
-
- async def unregister(self, websocket):
- """クライアント接続を解除する"""
- self.clients.remove(websocket)
- self.logger.info(f"クライアント切断: {websocket.remote_address}, 現在の接続数: {len(self.clients)}")
-
- async def handler(self, websocket):
- """WebSocket接続ハンドラー"""
- await self.register(websocket)
try:
async for message in websocket:
- # クライアントからのメッセージを処理(必要に応じて)
- # 現在はクライアントからのメッセージは特に処理していません
- self.logger.debug(f"クライアントからのメッセージ: {message}")
+ # メッセージ受信時にコールバック呼び出し
+ if self._message_handler:
+ self._message_handler(self, websocket, message)
except websockets.exceptions.ConnectionClosed:
+ # クライアントが切断した場合
pass
finally:
- await self.unregister(websocket)
+ # 切断時に集合から削除
+ self.clients.remove(websocket)
- async def broadcast(self, message: Dict):
- """全クライアントにメッセージをブロードキャストする"""
+ async def _broadcast_async(self, message: str):
+ """
+ すべての接続クライアントにメッセージを送信する非同期メソッド。
+ """
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_message(self, message: 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(message),
- loop
+ # 全クライアントへ並列に送信
+ await asyncio.gather(
+ *[client.send(message) for client in self.clients],
+ return_exceptions=True
)
- 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
+ async def _send_loop(self):
+ """
+ 内部キューからメッセージを取り出し、すべてのクライアントに送信するループ処理。
+ GUIなど他スレッドから送信メッセージをキューに入れてもらい、このコルーチンで配信します。
+ """
+ assert self._send_queue is not None
+ while True:
+ message = await self._send_queue.get()
+ if message is None:
+ # Noneを受け取ったらシャットダウン指示とみなしてループを抜ける
+ break
+ await self._broadcast_async(message)
+
+ def send(self, message: str):
+ """
+ 外部スレッドからサーバーにメッセージを送信するためのメソッドです。
+ イベントループ上で安全にキューにメッセージを積み、_send_loop()経由でブロードキャストします。
+ """
+ if self._loop and self._send_queue:
+ # キューにput_nowaitするコールをイベントループにスケジュール
+ self._loop.call_soon_threadsafe(self._send_queue.put_nowait, message)
+
+ def broadcast(self, message: str):
+ """
+ 外部スレッドや他コルーチンから全クライアントにメッセージを送信するユーティリティ。
+ asyncio.run_coroutine_threadsafe を使ってループ上でブロードキャストを実行します。
+ """
+ if self._loop:
+ # コルーチン自体をrun_coroutine_threadsafeに渡す
+ asyncio.run_coroutine_threadsafe(
+ self._broadcast_async(message), self._loop
+ )
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)
+ """
+ サーバーを起動します。新しいスレッド上で asyncio イベントループを動かし、serve()を実行します。
+ """
+ if self._thread and self._thread.is_alive():
+ return # 既に起動中
+ # 新しいスレッドでイベントループを開始
+ self._thread = threading.Thread(target=self._run_loop, daemon=True)
+ self._thread.start()
- async def _run_forever(self):
- """サーバーを永続的に実行する"""
- while self.is_running:
- await asyncio.sleep(0.1)
+ def _run_loop(self):
+ """
+ 別スレッド上で実行されるイベントループ用のメソッド。
+ サーバーの起動と、送信用キューのタスク登録を行います。
+ """
+ # 新しいイベントループを作成してこのスレッドの現在のループとして設定
+ self._loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(self._loop)
- 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
+ async def setup_server():
+ # サーバーを起動し、listenを開始
+ self._server = await websockets.serve(self._handler, self.host, self.port)
+ # 送信キューを初期化
+ self._send_queue = asyncio.Queue()
+ # 送信ループタスクを開始
+ self._loop.create_task(self._send_loop())
+ # サーバーの起動を待機
+ # 設定関数を実行してサーバーを起動
+ self._loop.run_until_complete(setup_server())
+ self.is_running = True
+ # サーバーが起動したら、接続待機を開始
+ # print(f"WebSocket server started on ws://{self.host}:{self.port}")
+ try:
+ # サーバーが停止するまでループを継続
+ self._loop.run_forever()
+ finally:
+ # 停止指示が出たらすべての接続を閉じ、イベントループを終了
+ self._loop.run_until_complete(self._shutdown())
+ self._loop.close()
+
+ async def _shutdown(self):
+ """
+ サーバーとクライアントを安全にシャットダウンする非同期処理。
+ serveオブジェクトをcloseし、wait_closed()で完全に終了を待ちます。
+ さらに接続中の各WebSocketをcloseします。
+ """
+ # サーバーのListenを停止
+ if self._server:
+ self._server.close()
+ await self._server.wait_closed()
+ # 接続中クライアントを順次クローズ
+ for ws in list(self.clients):
+ try:
+ await ws.close()
+ except Exception:
+ pass
def stop(self):
- """WebSocketサーバーを停止する(通常のコードから呼び出し可能)"""
- if not self.is_running:
- return
+ """
+ サーバーを停止します。別スレッドで動作中のイベントループに停止を指示し、スレッドを終了させます。
+ """
+ self.is_running = False
+ if self._loop:
+ # サーバーのlistenを停止し、ループ停止をスケジュール
+ self._loop.call_soon_threadsafe(self._server.close)
+ # None をキューに入れて_send_loopを抜けさせる
+ self._loop.call_soon_threadsafe(self._send_queue.put_nowait, None)
+ # ループ停止
+ self._loop.call_soon_threadsafe(self._loop.stop)
+ # スレッドの終了を待つ
+ if self._thread:
+ self._thread.join()
- loop = asyncio.get_event_loop()
- asyncio.run_coroutine_threadsafe(self.stop_server(), loop)
+if __name__ == "__main__":
+ # テスト用の簡単なメッセージハンドラ
+ def message_handler(server: WebSocketServer, websocket: WebSocketServerProtocol, message: str):
+ print(f"Received message from {websocket.remote_address}: {message}")
+ server.send(f"Echo: {message}")
- def is_running(self) -> bool:
- """サーバーが実行中かどうかを確認する"""
- return self.is_running
+ def send_message(server: WebSocketServer, message: str):
+ server.send(message)
- def get_clients(self) -> Set:
- """現在のクライアント接続を取得する"""
- return self.clients
+ # メイン処理を非同期関数に変更
+ async def main():
+ # サーバーを起動してメッセージハンドラを設定
+ ws_server = WebSocketServer()
+ ws_server.set_message_handler(message_handler)
+ ws_server.start()
+ print("WebSocket server started.")
+ # 定期的にサーバーからメッセージを送信する例
+ import threading
+ import time
+ def periodic_send():
+ print("Starting periodic message sender...")
+ while ws_server.is_running:
+ time.sleep(5)
+ print("Sending periodic message...")
+ send_message(ws_server, "Periodic message")
+ print("Periodic message sender stopped.")
+ # 別スレッドで定期的にメッセージを送信
+ time.sleep(5)
+ send_thread = threading.Thread(target=periodic_send, daemon=True)
+ send_thread.start()
+ # メインスレッドでサーバーを動かし続ける
+ try:
+ while True:
+ # 非同期スリープで待機
+ await asyncio.sleep(1)
- def get_client_count(self) -> int:
- """現在のクライアント接続数を取得する"""
- return len(self.clients)
\ No newline at end of file
+ except KeyboardInterrupt:
+ # Ctrl+Cでサーバーを停止
+ print("Stopping WebSocket server...")
+ ws_server.stop()
+
+ # 非同期メイン関数を実行
+ try:
+ asyncio.run(main())
+ except KeyboardInterrupt:
+ print("Stopping WebSocket server...")
\ No newline at end of file
From e7304247c78f4f85fc4848d5393aea45670eda63 Mon Sep 17 00:00:00 2001
From: misyaguziya <53165965+misyaguziya@users.noreply.github.com>
Date: Sun, 18 May 2025 15:20:27 +0900
Subject: [PATCH 04/30] [Update] WebSocket configuration: Change host to
127.0.0.1 and port to 2231; refactor WebSocket server initialization and
message handling
---
src-python/config.py | 6 +-
src-python/controller.py | 8 +-
src-python/model.py | 116 ++++++++++++------
.../models/websocket/websocket_server.py | 2 +-
4 files changed, 88 insertions(+), 44 deletions(-)
diff --git a/src-python/config.py b/src-python/config.py
index 7910400b..16f3802f 100644
--- a/src-python/config.py
+++ b/src-python/config.py
@@ -965,6 +965,7 @@ class Config:
self.saveConfig(inspect.currentframe().f_code.co_name, value)
@property
+ @json_serializable('WEBSOCKET_HOST')
def WEBSOCKET_HOST(self):
return self._WEBSOCKET_HOST
@@ -975,6 +976,7 @@ class Config:
self.saveConfig(inspect.currentframe().f_code.co_name, value)
@property
+ @json_serializable('WEBSOCKET_PORT')
def WEBSOCKET_PORT(self):
return self._WEBSOCKET_PORT
@@ -1171,8 +1173,8 @@ class Config:
self._VRC_MIC_MUTE_SYNC = False
self._NOTIFICATION_VRC_SFX = True
self._WEBSOCKET_SERVER = True
- self._WEBSOCKET_HOST = "0.0.0.0"
- self._WEBSOCKET_PORT = 8765
+ self._WEBSOCKET_HOST = "127.0.0.1"
+ self._WEBSOCKET_PORT = 2231
def load_config(self):
if os_path.isfile(self.PATH_CONFIG) is not False:
diff --git a/src-python/controller.py b/src-python/controller.py
index 398e3e37..0c3c4dca 100644
--- a/src-python/controller.py
+++ b/src-python/controller.py
@@ -298,7 +298,7 @@ class Controller:
if config.WEBSOCKET_SERVER is True:
model.websocketSendMessage(
{
- "type":"SEND",
+ "type":"SENT",
"message":message,
"translation":translation,
"transliteration":transliteration
@@ -466,11 +466,11 @@ class Controller:
}
)
- # update textbox message log (Sent)
+ # update textbox message log (Chat)
if config.LOGGER_FEATURE is True:
if len(translation) > 0:
translation_text = " (" + "/".join(translation) + ")"
- model.logger.info(f"[SENT] {message}{translation_text}")
+ model.logger.info(f"[CHAT] {message}{translation_text}")
return {"status":200,
"result":{
@@ -1840,8 +1840,8 @@ class Controller:
@staticmethod
def setEnableWebSocketServer(*args, **kwargs) -> dict:
- config.WEBSOCKET_SERVER = True
model.startWebSocketServer()
+ config.WEBSOCKET_SERVER = True
return {"status":200, "result":config.WEBSOCKET_SERVER}
@staticmethod
diff --git a/src-python/model.py b/src-python/model.py
index d848fd94..7d39294e 100644
--- a/src-python/model.py
+++ b/src-python/model.py
@@ -1,5 +1,7 @@
import copy
import gc
+import asyncio
+import json
from subprocess import Popen
from os import makedirs as os_makedirs
from os import path as os_path
@@ -100,43 +102,10 @@ class Model:
self.kks = kakasi()
self.watchdog = Watchdog(config.WATCHDOG_TIMEOUT, config.WATCHDOG_INTERVAL)
self.osc_handler = OSCHandler(config.OSC_IP_ADDRESS, config.OSC_PORT)
-
- # WebSocketサーバーの初期化
- self.websocket_server = WebSocketServer(
- host=config.WEBSOCKET_HOST,
- port=config.WEBSOCKET_PORT
- )
-
- def startWebSocketServer(self):
- try:
- self.websocket_server.start()
- except Exception:
- errorLogging()
-
- def stopWebSocketServer(self):
- if self.websocket_server:
- try:
- self.websocket_server.stop()
- except Exception:
- errorLogging()
-
- def checkWebSocketServer(self):
- if self.websocket_server:
- try:
- return self.websocket_server.is_running
- except Exception:
- errorLogging()
- return False
-
- def websocketSendMessage(self, message):
- """
- WebSocketサーバーから全クライアントにメッセージを送信する
- :param message: 送信するメッセージ
- """
- try:
- self.websocket_server.send(str(message))
- except Exception:
- errorLogging()
+ self.websocket_server = None
+ self.websocket_server_loop = False
+ self.websocket_server_alive = False
+ self.th_websocket_server = None
def checkTranslatorCTranslate2ModelWeight(self, weight_type:str):
return checkCTranslate2Weight(config.PATH_LOCAL, weight_type)
@@ -865,4 +834,77 @@ class Model:
self.th_watchdog.join()
self.th_watchdog = None
+ def startWebSocketServer(self):
+ """WebSocketサーバーを起動し、別スレッドで実行する"""
+ self.websocket_server_loop = True
+ self.websocket_server_alive = False # 初期状態を明示
+
+ async def WebSocketServerMain():
+ try:
+ self.websocket_server = WebSocketServer(
+ host=config.WEBSOCKET_HOST,
+ port=config.WEBSOCKET_PORT,
+ )
+ self.websocket_server.start()
+ self.websocket_server_alive = True
+
+ # イベントループが終了するまで待機
+ while self.websocket_server_loop:
+ self.websocket_server.send("Server is running...")
+ await asyncio.sleep(0.5) # 応答性向上のため間隔短縮
+
+ except Exception:
+ errorLogging()
+ # 具体的なエラー内容をログに残す場合
+ # self.logger.error(f"WebSocket server error: {str(e)}")
+ finally:
+ # 確実にサーバーを停止
+ if hasattr(self, 'websocket_server') and self.websocket_server:
+ self.websocket_server.stop()
+ self.websocket_server_alive = False
+
+ self.th_websocket_server = Thread(target=lambda: asyncio.run(WebSocketServerMain()))
+ self.th_websocket_server.daemon = True
+ self.th_websocket_server.start()
+
+ def stopWebSocketServer(self):
+ """WebSocketサーバーを停止する"""
+ if not hasattr(self, 'th_websocket_server') or self.th_websocket_server is None:
+ return
+
+ self.websocket_server_loop = False
+
+ try:
+ # 一定時間待機してからタイムアウト
+ self.th_websocket_server.join(timeout=2.0)
+
+ if self.th_websocket_server.is_alive():
+ # タイムアウト後もスレッドが生きている場合の処理
+ self.logger.warning("WebSocket server thread did not terminate properly")
+ except Exception:
+ errorLogging()
+ finally:
+ self.th_websocket_server = None
+ self.websocket_server = None
+ self.websocket_server_alive = False
+
+ def checkWebSocketServer(self):
+ """WebSocketサーバーの稼働状態を確認する"""
+ return self.websocket_server_alive
+
+ def websocketSendMessage(self, message_dict:dict):
+ """
+ WebSocketサーバーから全クライアントにメッセージを送信する
+ :param message_dict: 送信するメッセージの辞書
+ :return: 送信成功したかどうか
+ """
+ if not self.websocket_server_alive or not self.websocket_server:
+ return False
+ try:
+ message_json = json.dumps(message_dict)
+ return self.websocket_server.send(message_json)
+ except Exception:
+ errorLogging()
+ return False
+
model = Model()
\ No newline at end of file
diff --git a/src-python/models/websocket/websocket_server.py b/src-python/models/websocket/websocket_server.py
index f01122a9..34762e55 100644
--- a/src-python/models/websocket/websocket_server.py
+++ b/src-python/models/websocket/websocket_server.py
@@ -14,7 +14,7 @@ class WebSocketServer:
- メッセージのブロードキャスト機能
- GUIスレッド等からメッセージ送信するためのキュー
"""
- def __init__(self, host: str='localhost', port: int=8765):
+ def __init__(self, host: str='127.0.0.1', port: int=8765):
"""
サーバーのホスト名とポートを指定して初期化します。
"""
From 1cc22d35c3e3816e961a1ea8927a0733a0bec529 Mon Sep 17 00:00:00 2001
From: misyaguziya <53165965+misyaguziya@users.noreply.github.com>
Date: Sun, 18 May 2025 15:23:58 +0900
Subject: [PATCH 05/30] [Update] WebSocket server: Add message handler for
processing incoming messages
---
src-python/model.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src-python/model.py b/src-python/model.py
index 7d39294e..2bea7143 100644
--- a/src-python/model.py
+++ b/src-python/model.py
@@ -834,6 +834,10 @@ class Model:
self.th_watchdog.join()
self.th_watchdog = None
+ def message_handler(websocket, message):
+ """WebSocketメッセージ受信時の処理"""
+ pass
+
def startWebSocketServer(self):
"""WebSocketサーバーを起動し、別スレッドで実行する"""
self.websocket_server_loop = True
@@ -845,6 +849,7 @@ class Model:
host=config.WEBSOCKET_HOST,
port=config.WEBSOCKET_PORT,
)
+ self.websocket_server.set_message_handler(self.message_handler)
self.websocket_server.start()
self.websocket_server_alive = True
From 989abbe2c2e6686296ad22c81671f90e9ffe5366 Mon Sep 17 00:00:00 2001
From: misyaguziya <53165965+misyaguziya@users.noreply.github.com>
Date: Sun, 18 May 2025 16:24:20 +0900
Subject: [PATCH 06/30] =?UTF-8?q?[Update]=20check=E7=94=A8=E3=81=AE?=
=?UTF-8?q?=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=82=92=E3=82=B3?=
=?UTF-8?q?=E3=83=A1=E3=83=B3=E3=83=88=E3=82=A2=E3=82=A6=E3=83=88?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src-python/model.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src-python/model.py b/src-python/model.py
index 2bea7143..cbbb9562 100644
--- a/src-python/model.py
+++ b/src-python/model.py
@@ -855,7 +855,7 @@ class Model:
# イベントループが終了するまで待機
while self.websocket_server_loop:
- self.websocket_server.send("Server is running...")
+ # self.websocket_server.send("Server is running...")
await asyncio.sleep(0.5) # 応答性向上のため間隔短縮
except Exception:
From 0b486313636e4cd22e6efd740c848408c7f47fcd Mon Sep 17 00:00:00 2001
From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com>
Date: Mon, 19 May 2025 03:52:43 +0900
Subject: [PATCH 07/30] [Update] AdvancedSettings: Websocket: Add settings ui.
---
locales/en.yml | 6 ++
locales/ja.yml | 7 +-
.../advanced_settings/AdvancedSettings.jsx | 92 +++++++++++++++++++
.../configs/advanced_settings/useWebsocket.js | 67 ++++++++++++++
src-ui/logics/configs/index.js | 1 +
src-ui/logics/useReceiveRoutes.js | 16 ++++
src-ui/store.js | 4 +
7 files changed, 192 insertions(+), 1 deletion(-)
create mode 100644 src-ui/logics/configs/advanced_settings/useWebsocket.js
diff --git a/locales/en.yml b/locales/en.yml
index 9c45be98..d7a2347a 100644
--- a/locales/en.yml
+++ b/locales/en.yml
@@ -257,6 +257,12 @@ config_page:
label: "Open Config File"
switch_compute_device:
label: "Switch VRCT To CPU/GPU Version"
+ enable_websocket:
+ label: "Enable WebSocket Server"
+ websocket_host:
+ label: "WebSocket Host"
+ websocket_port:
+ label: "WebSocket Port"
plugin_notifications:
downloading: Downloading the plugin.
diff --git a/locales/ja.yml b/locales/ja.yml
index 8241d324..7286cb54 100644
--- a/locales/ja.yml
+++ b/locales/ja.yml
@@ -257,7 +257,12 @@ config_page:
label: "設定ファイルを開く"
switch_compute_device:
label: "VRCT CPU/GPUバージョンの切り替え"
-
+ enable_websocket:
+ label: "WebSocketサーバーを有効にする"
+ websocket_host:
+ label: "WebSocket Host"
+ websocket_port:
+ label: "WebSocket Port"
plugin_notifications:
downloading: プラグインをダウンロード中。
diff --git a/src-ui/app/config_page/setting_section/setting_box/advanced_settings/AdvancedSettings.jsx b/src-ui/app/config_page/setting_section/setting_box/advanced_settings/AdvancedSettings.jsx
index 7d96cda5..2f4c15cd 100644
--- a/src-ui/app/config_page/setting_section/setting_box/advanced_settings/AdvancedSettings.jsx
+++ b/src-ui/app/config_page/setting_section/setting_box/advanced_settings/AdvancedSettings.jsx
@@ -6,13 +6,19 @@ import { useOpenFolder } from "@logics_common";
import {
useOscIpAddress,
useOscPort,
+ useWebsocket,
} from "@logics_configs";
import {
+ CheckboxContainer,
ActionButtonContainer,
EntryWithSaveButtonContainer,
} from "../_templates/Templates";
+import {
+ SectionLabelComponent,
+} from "../_components/";
+
import OpenFolderSvg from "@images/open_folder.svg?react";
import HelpSvg from "@images/help.svg?react";
@@ -25,6 +31,7 @@ export const AdvancedSettings = () => {
+
);
};
@@ -87,6 +94,7 @@ const OscPortContainer = () => {
/>
);
};
+
const OpenConfigFolderContainer = () => {
const { t } = useTranslation();
const { openFolder_ConfigFile } = useOpenFolder();
@@ -120,4 +128,88 @@ const OpenSwitchComputeDeviceModalContainer = () => {
/>
>
);
+};
+
+
+const WebsocketContainer = () => {
+ return (
+
+
+
+
+
+
+ );
+};
+
+const EnableWebsocketContainer = () => {
+ const { t } = useTranslation();
+ const { currentEnableWebsocket, toggleEnableWebsocket } = useWebsocket();
+
+ return (
+
+ );
+};
+
+const WebsocketHostContainer = () => {
+ const { t } = useTranslation();
+ const { currentWebsocketHost, setWebsocketHost } = useWebsocket();
+ const [input_value, setInputValue] = useState(currentWebsocketHost.data);
+
+ const onChangeFunction = (value) => {
+ setInputValue(value);
+ };
+
+ const saveFunction = () => {
+ setWebsocketHost(input_value);
+ };
+
+ useEffect(()=> {
+ setInputValue(currentWebsocketHost.data);
+ }, [currentWebsocketHost]);
+
+ return (
+
+ );
+};
+
+const WebsocketPortContainer = () => {
+ const { t } = useTranslation();
+ const { currentWebsocketPort, setWebsocketPort } = useWebsocket();
+ const [input_value, setInputValue] = useState(currentWebsocketPort.data);
+
+ const onChangeFunction = (value) => {
+ value = value.replace(/[^0-9]/g, "");
+ setInputValue(value);
+ };
+
+ const saveFunction = () => {
+ setWebsocketPort(input_value);
+ };
+
+ useEffect(()=> {
+ setInputValue(currentWebsocketPort.data);
+ }, [currentWebsocketPort]);
+
+ return (
+
+ );
};
\ No newline at end of file
diff --git a/src-ui/logics/configs/advanced_settings/useWebsocket.js b/src-ui/logics/configs/advanced_settings/useWebsocket.js
new file mode 100644
index 00000000..51361489
--- /dev/null
+++ b/src-ui/logics/configs/advanced_settings/useWebsocket.js
@@ -0,0 +1,67 @@
+import {
+ useStore_EnableWebsocket,
+ useStore_WebsocketHost,
+ useStore_WebsocketPort,
+} from "@store";
+import { useStdoutToPython } from "@logics/useStdoutToPython";
+
+export const useWebsocket = () => {
+ const { asyncStdoutToPython } = useStdoutToPython();
+ const { currentEnableWebsocket, updateEnableWebsocket, pendingEnableWebsocket } = useStore_EnableWebsocket();
+ const { currentWebsocketHost, updateWebsocketHost, pendingWebsocketHost } = useStore_WebsocketHost();
+ const { currentWebsocketPort, updateWebsocketPort, pendingWebsocketPort } = useStore_WebsocketPort();
+
+ const getEnableWebsocket = () => {
+ pendingEnableWebsocket();
+ asyncStdoutToPython("/get/data/websocket_server");
+ };
+
+ const toggleEnableWebsocket = () => {
+ pendingEnableWebsocket();
+ if (currentEnableWebsocket.data) {
+ asyncStdoutToPython("/set/disable/websocket_server");
+ } else {
+ asyncStdoutToPython("/set/enable/websocket_server");
+ }
+ };
+
+
+ const getWebsocketHost = () => {
+ pendingWebsocketHost();
+ asyncStdoutToPython("/get/data/websocket_host");
+ };
+
+ const setWebsocketHost = (websocket_host) => {
+ pendingWebsocketHost();
+ asyncStdoutToPython("/set/data/websocket_host", websocket_host);
+ };
+
+
+ const getWebsocketPort = () => {
+ pendingWebsocketPort();
+ asyncStdoutToPython("/get/data/websocket_port");
+ };
+
+ const setWebsocketPort = (websocket_port) => {
+ pendingWebsocketPort();
+ asyncStdoutToPython("/set/data/websocket_port", websocket_port);
+ };
+
+ return {
+ currentEnableWebsocket,
+ updateEnableWebsocket,
+ getEnableWebsocket,
+ toggleEnableWebsocket,
+
+ currentWebsocketHost,
+ updateWebsocketHost,
+ getWebsocketHost,
+ setWebsocketHost,
+
+ currentWebsocketPort,
+ updateWebsocketPort,
+ getWebsocketPort,
+ setWebsocketPort,
+
+ };
+};
\ No newline at end of file
diff --git a/src-ui/logics/configs/index.js b/src-ui/logics/configs/index.js
index 912e2a7d..03214771 100644
--- a/src-ui/logics/configs/index.js
+++ b/src-ui/logics/configs/index.js
@@ -56,6 +56,7 @@ export { useHotkeys } from "./hotkeys/useHotkeys";
export { useOscIpAddress } from "./advanced_settings/useOscIpAddress";
export { useOscPort } from "./advanced_settings/useOscPort";
+export { useWebsocket } from "./advanced_settings/useWebsocket";
export { useSupporters } from "./supporters/useSupporters";
diff --git a/src-ui/logics/useReceiveRoutes.js b/src-ui/logics/useReceiveRoutes.js
index 4a4f944c..1d89be33 100644
--- a/src-ui/logics/useReceiveRoutes.js
+++ b/src-ui/logics/useReceiveRoutes.js
@@ -75,6 +75,7 @@ import {
usePlugins,
useOscIpAddress,
useOscPort,
+ useWebsocket,
} from "@logics_configs";
export const useReceiveRoutes = () => {
@@ -180,6 +181,11 @@ export const useReceiveRoutes = () => {
const { updateOscIpAddress } = useOscIpAddress();
const { updateOscPort } = useOscPort();
+ const {
+ updateEnableWebsocket,
+ updateWebsocketHost,
+ updateWebsocketPort,
+ } = useWebsocket();
@@ -505,6 +511,16 @@ export const useReceiveRoutes = () => {
"/get/data/osc_port": updateOscPort,
"/set/data/osc_port": updateOscPort,
+ "/get/data/websocket_server": updateEnableWebsocket,
+ "/set/enable/websocket_server": updateEnableWebsocket,
+ "/set/disable/websocket_server": updateEnableWebsocket,
+
+ "/get/data/websocket_host": updateWebsocketHost,
+ "/set/data/websocket_host": updateWebsocketHost,
+
+ "/get/data/websocket_port": updateWebsocketPort,
+ "/set/data/websocket_port": updateWebsocketPort,
+
"/get/data/mic_avg_logprob": ()=>{}, // Not implemented on UI yet
"/get/data/mic_no_speech_prob": ()=>{}, // Not implemented on UI yet
"/get/data/speaker_avg_logprob": ()=>{}, // Not implemented on UI yet
diff --git a/src-ui/store.js b/src-ui/store.js
index 8f5317a3..f33ebbfc 100644
--- a/src-ui/store.js
+++ b/src-ui/store.js
@@ -290,6 +290,10 @@ export const { atomInstance: Atom_PluginsData, useHook: useStore_PluginsData } =
export const { atomInstance: Atom_OscIpAddress, useHook: useStore_OscIpAddress } = createAtomWithHook("127.0.0.1", "OscIpAddress");
export const { atomInstance: Atom_OscPort, useHook: useStore_OscPort } = createAtomWithHook("9000", "OscPort");
+export const { atomInstance: Atom_EnableWebsocket, useHook: useStore_EnableWebsocket } = createAtomWithHook(true, "EnableWebsocket");
+export const { atomInstance: Atom_WebsocketHost, useHook: useStore_WebsocketHost } = createAtomWithHook("127.0.0.1", "WebsocketHost");
+export const { atomInstance: Atom_WebsocketPort, useHook: useStore_WebsocketPort } = createAtomWithHook("2231", "WebsocketPort");
+
// Supporters
From 60fb58f6d5e62f46090b8d38c89601ebc3d4132c Mon Sep 17 00:00:00 2001
From: misyaguziya <53165965+misyaguziya@users.noreply.github.com>
Date: Tue, 20 May 2025 11:08:11 +0900
Subject: [PATCH 08/30] [Update] WebSocket: Disable server by default and add
port availability check
---
src-python/config.py | 2 +-
src-python/controller.py | 83 ++++++++++++++++++++++++++++++++--------
src-python/model.py | 30 +++++++++++++--
3 files changed, 94 insertions(+), 21 deletions(-)
diff --git a/src-python/config.py b/src-python/config.py
index f1b7de65..cfeeec51 100644
--- a/src-python/config.py
+++ b/src-python/config.py
@@ -1172,7 +1172,7 @@ class Config:
self._LOGGER_FEATURE = False
self._VRC_MIC_MUTE_SYNC = False
self._NOTIFICATION_VRC_SFX = True
- self._WEBSOCKET_SERVER = True
+ self._WEBSOCKET_SERVER = False
self._WEBSOCKET_HOST = "127.0.0.1"
self._WEBSOCKET_PORT = 2231
diff --git a/src-python/controller.py b/src-python/controller.py
index 4890617e..e21e66e0 100644
--- a/src-python/controller.py
+++ b/src-python/controller.py
@@ -295,10 +295,12 @@ class Controller:
"transliteration":transliteration
})
- if config.WEBSOCKET_SERVER is True:
+ if model.checkWebSocketServerAlive() is True:
model.websocketSendMessage(
{
"type":"SENT",
+ "src_languages":config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO],
+ "dst_languages":config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO],
"message":message,
"translation":translation,
"transliteration":transliteration
@@ -389,10 +391,12 @@ class Controller:
"transliteration":transliteration,
})
- if config.WEBSOCKET_SERVER is True:
+ if model.checkWebSocketServerAlive() is True:
model.websocketSendMessage(
{
"type":"RECEIVED",
+ "src_languages":config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO],
+ "dst_languages":config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO],
"message":message,
"translation":translation,
"transliteration":transliteration
@@ -456,10 +460,12 @@ class Controller:
overlay_image = model.createOverlayImageLargeLog("send", message, translation[0] if len(translation) > 0 else "")
model.updateOverlayLargeLog(overlay_image)
- if config.WEBSOCKET_SERVER is True:
+ if model.checkWebSocketServerAlive() is True:
model.websocketSendMessage(
{
"type":"CHAT",
+ "src_languages":config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO],
+ "dst_languages":config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO],
"message":message,
"translation":translation,
"transliteration":transliteration
@@ -1816,11 +1822,31 @@ class Controller:
@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}
+ if isValidIpAddress(data) is False:
+ response = {
+ "status":400,
+ "result":{
+ "message":"Invalid IP address",
+ "data": config.WEBSOCKET_HOST
+ }
+ }
+ else:
+ if model.checkWebSocketServerAlive() is True:
+ model.stopWebSocketServer()
+
+ if model.checkWebSocketServerPortAvailable() is True:
+ model.startWebSocketServer(data, config.WEBSOCKET_PORT)
+ config.WEBSOCKET_HOST = data
+ response = {"status":200, "result":config.WEBSOCKET_HOST}
+ else:
+ response = {
+ "status":400,
+ "result":{
+ "message":"WebSocket server port is not available",
+ "data": config.WEBSOCKET_HOST
+ }
+ }
+ return response
@staticmethod
def getWebSocketPort(*args, **kwargs) -> dict:
@@ -1828,11 +1854,21 @@ class Controller:
@staticmethod
def setWebSocketPort(data, *args, **kwargs) -> dict:
- config.WEBSOCKET_PORT = int(data)
- if model.checkWebSocketServer() is True:
+ if model.checkWebSocketServerAlive() is True:
model.stopWebSocketServer()
- model.startWebSocketServer()
- return {"status":200, "result":config.WEBSOCKET_PORT}
+ if model.checkWebSocketServerPortAvailable() is True:
+ model.startWebSocketServer(config.WEBSOCKET_HOST, int(data))
+ config.WEBSOCKET_PORT = int(data)
+ response = {"status":200, "result":config.WEBSOCKET_PORT}
+ else:
+ response = {
+ "status":400,
+ "result":{
+ "message":"WebSocket server port is not available",
+ "data": config.WEBSOCKET_PORT
+ }
+ }
+ return response
@staticmethod
def getWebSocketServer(*args, **kwargs) -> dict:
@@ -1840,9 +1876,19 @@ class Controller:
@staticmethod
def setEnableWebSocketServer(*args, **kwargs) -> dict:
- model.startWebSocketServer()
- config.WEBSOCKET_SERVER = True
- return {"status":200, "result":config.WEBSOCKET_SERVER}
+ if model.checkWebSocketServerPortAvailable() is True:
+ model.startWebSocketServer(config.WEBSOCKET_HOST, config.WEBSOCKET_PORT)
+ config.WEBSOCKET_SERVER = True
+ response = {"status":200, "result":config.WEBSOCKET_SERVER}
+ else:
+ response = {
+ "status":400,
+ "result":{
+ "message":"WebSocket server port is not available",
+ "data": config.WEBSOCKET_SERVER
+ }
+ }
+ return response
@staticmethod
def setDisableWebSocketServer(*args, **kwargs) -> dict:
@@ -1951,7 +1997,12 @@ class Controller:
printLog("Init WebSocket Server")
if config.WEBSOCKET_SERVER is True:
- model.startWebSocketServer()
+ if model.checkWebSocketServerPortAvailable() is True:
+ model.startWebSocketServer(config.WEBSOCKET_HOST, config.WEBSOCKET_PORT)
+ else:
+ config.WEBSOCKET_SERVER = False
+ model.stopWebSocketServer()
+ printLog("WebSocket server port is not available")
printLog("Update settings")
self.updateConfigSettings()
diff --git a/src-python/model.py b/src-python/model.py
index cbbb9562..bdf8d523 100644
--- a/src-python/model.py
+++ b/src-python/model.py
@@ -1,6 +1,8 @@
import copy
import gc
import asyncio
+import socket
+import errno
import json
from subprocess import Popen
from os import makedirs as os_makedirs
@@ -838,16 +840,36 @@ class Model:
"""WebSocketメッセージ受信時の処理"""
pass
- def startWebSocketServer(self):
+ def checkWebSocketServerPortAvailable(self):
+ """WebSocketサーバーのポートが使用中かどうかを確認する"""
+ response = True
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as chk:
+ try:
+ chk.bind((config.WEBSOCKET_HOST, config.WEBSOCKET_PORT))
+ chk.shutdown(socket.SHUT_RDWR)
+ chk.close()
+ except OSError as e:
+ if e.errno == errno.EADDRINUSE:
+ response = False
+ else:
+ errorLogging(e)
+ response = False
+ return response
+
+ def startWebSocketServer(self, host, port):
"""WebSocketサーバーを起動し、別スレッドで実行する"""
+ if self.websocket_server_alive is True:
+ # サーバーが既に起動している場合は何もしない
+ return
+
self.websocket_server_loop = True
self.websocket_server_alive = False # 初期状態を明示
async def WebSocketServerMain():
try:
self.websocket_server = WebSocketServer(
- host=config.WEBSOCKET_HOST,
- port=config.WEBSOCKET_PORT,
+ host=host,
+ port=port,
)
self.websocket_server.set_message_handler(self.message_handler)
self.websocket_server.start()
@@ -893,7 +915,7 @@ class Model:
self.websocket_server = None
self.websocket_server_alive = False
- def checkWebSocketServer(self):
+ def checkWebSocketServerAlive(self):
"""WebSocketサーバーの稼働状態を確認する"""
return self.websocket_server_alive
From cfc26c4767e6ab7aabb73215fb892bae6e6b728a Mon Sep 17 00:00:00 2001
From: misyaguziya <53165965+misyaguziya@users.noreply.github.com>
Date: Tue, 20 May 2025 11:33:40 +0900
Subject: [PATCH 09/30] [Update] Model: Modify error logging to remove
exception details on address in use
---
src-python/model.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src-python/model.py b/src-python/model.py
index bdf8d523..60800750 100644
--- a/src-python/model.py
+++ b/src-python/model.py
@@ -852,7 +852,7 @@ class Model:
if e.errno == errno.EADDRINUSE:
response = False
else:
- errorLogging(e)
+ errorLogging()
response = False
return response
From f310e6d6d5fa16b856eb921fb5e8e2331cca7ebe Mon Sep 17 00:00:00 2001
From: misyaguziya <53165965+misyaguziya@users.noreply.github.com>
Date: Tue, 20 May 2025 13:12:31 +0900
Subject: [PATCH 10/30] [Update] WebSocket: Refactor server availability checks
and improve error handling
---
src-python/controller.py | 20 +++++++++++++-------
src-python/model.py | 31 +++++++++++++++++++------------
2 files changed, 32 insertions(+), 19 deletions(-)
diff --git a/src-python/controller.py b/src-python/controller.py
index e21e66e0..c2db0f45 100644
--- a/src-python/controller.py
+++ b/src-python/controller.py
@@ -1831,10 +1831,12 @@ class Controller:
}
}
else:
- if model.checkWebSocketServerAlive() is True:
+ if model.checkWebSocketServerAlive() is False:
+ config.WEBSOCKET_HOST = data
+ response = {"status":200, "result":config.WEBSOCKET_HOST}
+ else:
model.stopWebSocketServer()
-
- if model.checkWebSocketServerPortAvailable() is True:
+ if model.checkWebSocketServerAvailable() is True:
model.startWebSocketServer(data, config.WEBSOCKET_PORT)
config.WEBSOCKET_HOST = data
response = {"status":200, "result":config.WEBSOCKET_HOST}
@@ -1846,6 +1848,7 @@ class Controller:
"data": config.WEBSOCKET_HOST
}
}
+
return response
@staticmethod
@@ -1854,9 +1857,12 @@ class Controller:
@staticmethod
def setWebSocketPort(data, *args, **kwargs) -> dict:
- if model.checkWebSocketServerAlive() is True:
+ if model.checkWebSocketServerAlive() is False:
+ config.WEBSOCKET_PORT = int(data)
+ response = {"status":200, "result":config.WEBSOCKET_PORT}
+ else:
model.stopWebSocketServer()
- if model.checkWebSocketServerPortAvailable() is True:
+ if model.checkWebSocketServerAvailable() is True:
model.startWebSocketServer(config.WEBSOCKET_HOST, int(data))
config.WEBSOCKET_PORT = int(data)
response = {"status":200, "result":config.WEBSOCKET_PORT}
@@ -1876,7 +1882,7 @@ class Controller:
@staticmethod
def setEnableWebSocketServer(*args, **kwargs) -> dict:
- if model.checkWebSocketServerPortAvailable() is True:
+ if model.checkWebSocketServerAvailable() is True:
model.startWebSocketServer(config.WEBSOCKET_HOST, config.WEBSOCKET_PORT)
config.WEBSOCKET_SERVER = True
response = {"status":200, "result":config.WEBSOCKET_SERVER}
@@ -1997,7 +2003,7 @@ class Controller:
printLog("Init WebSocket Server")
if config.WEBSOCKET_SERVER is True:
- if model.checkWebSocketServerPortAvailable() is True:
+ if model.checkWebSocketServerAvailable() is True:
model.startWebSocketServer(config.WEBSOCKET_HOST, config.WEBSOCKET_PORT)
else:
config.WEBSOCKET_SERVER = False
diff --git a/src-python/model.py b/src-python/model.py
index 60800750..d52d6341 100644
--- a/src-python/model.py
+++ b/src-python/model.py
@@ -840,20 +840,27 @@ class Model:
"""WebSocketメッセージ受信時の処理"""
pass
- def checkWebSocketServerPortAvailable(self):
+ def checkWebSocketServerAvailable(self):
"""WebSocketサーバーのポートが使用中かどうかを確認する"""
response = True
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as chk:
- try:
- chk.bind((config.WEBSOCKET_HOST, config.WEBSOCKET_PORT))
- chk.shutdown(socket.SHUT_RDWR)
- chk.close()
- except OSError as e:
- if e.errno == errno.EADDRINUSE:
- response = False
- else:
- errorLogging()
- response = False
+ try:
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as chk:
+ # SO_REUSEADDRを設定してソケットの再利用を許可
+ chk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ try:
+ chk.bind((config.WEBSOCKET_HOST, config.WEBSOCKET_PORT))
+ # シャットダウン前にリッスン状態にする必要はない
+ chk.close()
+ except OSError as e:
+ if e.errno == errno.EADDRINUSE:
+ response = False
+ else:
+ errorLogging()
+ response = False
+ except Exception:
+ errorLogging()
+ response = False
+
return response
def startWebSocketServer(self, host, port):
From de4f22f7048e46d2b4433ca02b9f399bd4ad8d40 Mon Sep 17 00:00:00 2001
From: misyaguziya <53165965+misyaguziya@users.noreply.github.com>
Date: Tue, 20 May 2025 14:28:55 +0900
Subject: [PATCH 11/30] [Update] Controller: Update WebSocket error messages to
clarify host and port availability
---
src-python/controller.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src-python/controller.py b/src-python/controller.py
index c2db0f45..ccf44227 100644
--- a/src-python/controller.py
+++ b/src-python/controller.py
@@ -1844,7 +1844,7 @@ class Controller:
response = {
"status":400,
"result":{
- "message":"WebSocket server port is not available",
+ "message":"WebSocket server host is not available",
"data": config.WEBSOCKET_HOST
}
}
@@ -1890,7 +1890,7 @@ class Controller:
response = {
"status":400,
"result":{
- "message":"WebSocket server port is not available",
+ "message":"WebSocket server host or port is not available",
"data": config.WEBSOCKET_SERVER
}
}
@@ -2008,7 +2008,7 @@ class Controller:
else:
config.WEBSOCKET_SERVER = False
model.stopWebSocketServer()
- printLog("WebSocket server port is not available")
+ printLog("WebSocket server host or port is not available")
printLog("Update settings")
self.updateConfigSettings()
From f6927b9ae7bcffb8171506d3a169f9dbb3cc0182 Mon Sep 17 00:00:00 2001
From: misyaguziya <53165965+misyaguziya@users.noreply.github.com>
Date: Thu, 22 May 2025 11:35:48 +0900
Subject: [PATCH 12/30] [Update] zip.py: Enhance zip file creation with
improved error handling and verbose output
---
zip.py | 87 ++++++++++++++++++++++++++++++++++++++++------------------
1 file changed, 60 insertions(+), 27 deletions(-)
diff --git a/zip.py b/zip.py
index 168c5c87..553e51b3 100644
--- a/zip.py
+++ b/zip.py
@@ -1,37 +1,70 @@
-import os
import zipfile
import argparse
+from pathlib import Path
+import time
+from tqdm import tqdm # tqdmをインポート
-def zip_files_and_directory(zip_name, file_paths, dir_paths):
+def zip_files_and_directory(zip_name, file_paths, dir_paths, verbose=False):
+ zip_file_path = Path(zip_name)
# ZIPファイルを作成
- with zipfile.ZipFile(zip_name, 'w', zipfile.ZIP_DEFLATED) as zipf:
- # ファイルを追加
- for file_path in file_paths:
- if os.path.isfile(file_path):
- zipf.write(file_path, os.path.basename(file_path))
- print(f"Add file: {file_path}")
+ try:
+ with zipfile.ZipFile(zip_file_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
+ # ファイルを追加
+ for file_path_str in tqdm(file_paths, desc="Adding files", unit="file"):
+ file_path = Path(file_path_str)
+ if file_path.is_file():
+ zipf.write(file_path, file_path.name)
+ if verbose:
+ print(f"Add file: {file_path}")
+ else:
+ print(f"Warning: File not found or is not a file: {file_path}")
- # ディレクトリを追加
- for dir_path in dir_paths:
- if os.path.isdir(dir_path):
- for foldername, subfolders, filenames in os.walk(dir_path):
- for filename in filenames:
- file_full_path = os.path.join(foldername, filename)
- # ディレクトリを保持しつつ、ルートに配置
- arcname = os.path.join(
- os.path.basename(dir_path),
- os.path.relpath(file_full_path, dir_path)
- )
- zipf.write(file_full_path, arcname)
- print(f"Add file: {file_full_path}")
+ # ディレクトリを追加
+ for dir_path_str in dir_paths:
+ dir_path = Path(dir_path_str)
+ if dir_path.is_dir():
+ all_files_in_dir = [item for item in dir_path.rglob("*") if item.is_file()]
+ for item in tqdm(all_files_in_dir, desc=f"Adding files from {dir_path.name}", unit="file"):
+ # ディレクトリ構造を保持しつつ、ルートに配置
+ arcname = Path(dir_path.name) / item.relative_to(dir_path)
+ zipf.write(item, arcname)
+ if verbose:
+ print(f"Add file: {item}")
+ else:
+ print(f"Warning: Directory not found or is not a directory: {dir_path}")
+ print(f"Successfully created zip file: {zip_file_path}")
+ except IOError as e:
+ print(f"Error: Could not create zip file {zip_file_path}. Reason: {e}")
+ except Exception as e:
+ print(f"An unexpected error occurred: {e}")
if __name__ == "__main__":
- parser = argparse.ArgumentParser()
- parser.add_argument("--zip_name", type=str, default="VRCT.zip")
- parser.add_argument("--file_paths", type=str, nargs="*", default=["src-tauri/target/release/VRCT.exe", "src-tauri/target/release/VRCT-sidecar.exe"])
- parser.add_argument("--dir_paths", type=str, nargs="*", default=["src-tauri/target/release/_internal"])
+ start_time = time.time()
+ parser = argparse.ArgumentParser(description="Create a zip file from specified files and directories.")
+ parser.add_argument("--zip_name", type=str, default="VRCT.zip", help="Name of the output zip file.")
+ parser.add_argument(
+ "--file_paths",
+ type=str,
+ nargs="*",
+ default=["src-tauri/target/release/VRCT.exe", "src-tauri/target/release/VRCT-sidecar.exe"],
+ help="List of file paths to include in the zip."
+ )
+ parser.add_argument(
+ "--dir_paths",
+ type=str,
+ nargs="*",
+ default=["src-tauri/target/release/_internal"],
+ help="List of directory paths to include in the zip."
+ )
+ parser.add_argument(
+ "-v", "--verbose",
+ action="store_true",
+ help="Increase output verbosity."
+ )
args = parser.parse_args()
- zip_files_and_directory(args.zip_name, args.file_paths, args.dir_paths)
- print("Complete!")
\ No newline at end of file
+ zip_files_and_directory(args.zip_name, args.file_paths, args.dir_paths, args.verbose)
+ end_time = time.time()
+ processing_time = end_time - start_time
+ print(f"Complete! Processing time: {processing_time:.2f} seconds")
\ No newline at end of file
From 279fc1f66d4007c0c0bf0f84e96c7937a86e96f2 Mon Sep 17 00:00:00 2001
From: misyaguziya <53165965+misyaguziya@users.noreply.github.com>
Date: Sat, 24 May 2025 21:51:11 +0900
Subject: [PATCH 13/30] [Update] OSCHandler: Enhance OSC query handling and
refactor mute status management
---
src-python/model.py | 24 +++++++++++++-----------
src-python/models/osc/osc.py | 33 ++++++++++++++++++++++++++++++---
2 files changed, 43 insertions(+), 14 deletions(-)
diff --git a/src-python/model.py b/src-python/model.py
index d52d6341..ce40c589 100644
--- a/src-python/model.py
+++ b/src-python/model.py
@@ -301,11 +301,8 @@ class Model:
def oscSendMessage(self, message:str):
self.osc_handler.sendMessage(message=message, notification=config.NOTIFICATION_VRC_SFX)
- def getMuteSelfStatus(self):
- return self.osc_handler.getOSCParameterMuteSelf()
-
def setMuteSelfStatus(self):
- self.mic_mute_status = self.getMuteSelfStatus()
+ self.mic_mute_status = self.osc_handler.getOSCParameterMuteSelf()
def startReceiveOSC(self):
def changeHandlerMute(address, osc_arguments):
@@ -320,7 +317,8 @@ class Model:
dict_filter_and_target = {
self.osc_handler.osc_parameter_muteself: changeHandlerMute,
}
- self.osc_handler.receiveOscParameters(dict_filter_and_target)
+ self.osc_handler.setDictFilterAndTarget(dict_filter_and_target)
+ self.osc_handler.receiveOscParameters()
def stopReceiveOSC(self):
self.osc_handler.oscServerStop()
@@ -512,12 +510,16 @@ class Model:
def changeMicTranscriptStatus(self):
if config.VRC_MIC_MUTE_SYNC is True:
- if self.mic_mute_status is True:
- self.pauseMicTranscript()
- elif self.mic_mute_status is False:
- self.resumeMicTranscript()
- else:
- pass
+ match self.mic_mute_status:
+ case True:
+ self.pauseMicTranscript()
+ case False:
+ self.resumeMicTranscript()
+ case None:
+ # mute selfの状態が不明な場合は一時停止しない
+ self.resumeMicTranscript()
+ case _:
+ pass
else:
self.resumeMicTranscript()
diff --git a/src-python/models/osc/osc.py b/src-python/models/osc/osc.py
index 758918af..84717b61 100644
--- a/src-python/models/osc/osc.py
+++ b/src-python/models/osc/osc.py
@@ -11,6 +11,12 @@ from utils import errorLogging
class OSCHandler:
def __init__(self, ip_address="127.0.0.1", port=9000) -> None:
+
+ if ip_address in ["127.0.0.1", "localhost"]:
+ self.osc_query_enabled = True
+ else:
+ self.osc_query_enabled = False
+
self.osc_ip_address = ip_address
self.osc_port = port
self.osc_parameter_muteself = "/avatar/parameters/MuteSelf"
@@ -24,15 +30,25 @@ class OSCHandler:
self.osc_server_ip_address = ip_address
self.http_port = None
self.osc_server_port = None
+ self.dict_filter_and_target = {}
self.browser = None
def setOscIpAddress(self, ip_address:str) -> None:
+ if ip_address in ["127.0.0.1", "localhost"]:
+ self.osc_query_enabled = True
+ else:
+ self.osc_query_enabled = False
+
+ self.oscServerStop()
self.osc_ip_address = ip_address
self.udp_client = udp_client.SimpleUDPClient(self.osc_ip_address, self.osc_port)
+ self.receiveOscParameters()
def setOscPort(self, port:int) -> None:
+ self.oscServerStop()
self.osc_port = port
self.udp_client = udp_client.SimpleUDPClient(self.osc_ip_address, self.osc_port)
+ self.receiveOscParameters()
# send OSC message typing
def sendTyping(self, flag:bool=False) -> None:
@@ -44,6 +60,10 @@ class OSCHandler:
self.udp_client.send_message(self.osc_parameter_chatbox_input, [f"{message}", True, notification])
def getOSCParameterValue(self, address:str) -> Any:
+ if not self.osc_query_enabled:
+ # OSCQueryが無効な場合はNoneを返す
+ return None
+
value = None
try:
# browserインスタンスを再利用し、毎回の生成と破棄を避ける
@@ -71,11 +91,18 @@ class OSCHandler:
def getOSCParameterMuteSelf(self) -> bool:
return self.getOSCParameterValue(self.osc_parameter_muteself)
- def receiveOscParameters(self, dict_filter_and_target:dict) -> None:
+ def setDictFilterAndTarget(self, dict_filter_and_target:dict) -> None:
+ self.dict_filter_and_target = dict_filter_and_target
+
+ def receiveOscParameters(self) -> None:
+ if self.osc_query_enabled is False:
+ # OSCQueryが無効な場合は何もしない
+ return
+
self.osc_server_port = get_open_udp_port()
self.http_port = get_open_tcp_port()
osc_dispatcher = dispatcher.Dispatcher()
- for filter, target in dict_filter_and_target.items():
+ for filter, target in self.dict_filter_and_target.items():
osc_dispatcher.map(filter, target)
self.osc_server = osc_server.ThreadingOSCUDPServer((self.osc_server_ip_address, self.osc_server_port), osc_dispatcher, asyncio.get_event_loop())
Thread(target=self.oscServerServe, daemon=True).start()
@@ -83,7 +110,7 @@ class OSCHandler:
while True:
try:
self.osc_query_service = OSCQueryService(self.osc_query_service_name, self.http_port, self.osc_server_port)
- for filter, target in dict_filter_and_target.items():
+ for filter, target in self.dict_filter_and_target.items():
self.osc_query_service.advertise_endpoint(filter, access=OSCAccess.READWRITE_VALUE)
break
except Exception:
From 23fc46bd0f5dc8761facae36b0fc742719ad368e Mon Sep 17 00:00:00 2001
From: misyaguziya <53165965+misyaguziya@users.noreply.github.com>
Date: Sun, 25 May 2025 15:42:33 +0900
Subject: [PATCH 14/30] [Update] OSC: Refactor OSC query handling and add
enable/disable methods
---
src-python/controller.py | 21 ++++++++++++++++++---
src-python/mainloop.py | 2 ++
src-python/model.py | 3 +++
src-python/models/osc/osc.py | 15 +++++++++------
4 files changed, 32 insertions(+), 9 deletions(-)
diff --git a/src-python/controller.py b/src-python/controller.py
index c2db0f45..e59a5b50 100644
--- a/src-python/controller.py
+++ b/src-python/controller.py
@@ -1426,9 +1426,12 @@ class Controller:
@staticmethod
def setEnableVrcMicMuteSync(*args, **kwargs) -> dict:
- config.VRC_MIC_MUTE_SYNC = True
- model.setMuteSelfStatus()
- model.changeMicTranscriptStatus()
+ if model.getIsOscQueryEnabled() is False:
+ config.VRC_MIC_MUTE_SYNC = True
+ model.setMuteSelfStatus()
+ model.changeMicTranscriptStatus()
+ else:
+ config.VRC_MIC_MUTE_SYNC = False
return {"status":200, "result":config.VRC_MIC_MUTE_SYNC}
@staticmethod
@@ -1905,6 +1908,12 @@ class Controller:
def initializationProgress(self, progress):
self.run(200, self.run_mapping["initialization_progress"], progress)
+ def enableOscQuery(self):
+ self.run(200, self.run_mapping["enable_osc_query"], True)
+
+ def disableOscQuery(self):
+ self.run(200, self.run_mapping["enable_osc_query"], False)
+
def init(self, *args, **kwargs) -> None:
removeLog()
printLog("Start Initialization")
@@ -1982,6 +1991,12 @@ class Controller:
# init OSC receive
printLog("Init OSC Receive")
model.startReceiveOSC()
+ osc_query_enabled = model.getIsOscQueryEnabled()
+ if osc_query_enabled is True:
+ self.enableOscQuery()
+ else:
+ self.disableOscQuery()
+
if config.VRC_MIC_MUTE_SYNC is True:
self.setEnableVrcMicMuteSync()
diff --git a/src-python/mainloop.py b/src-python/mainloop.py
index d8ad52e7..2d07fd6a 100644
--- a/src-python/mainloop.py
+++ b/src-python/mainloop.py
@@ -42,6 +42,8 @@ run_mapping = {
"initialization_progress":"/run/initialization_progress",
"initialization_complete":"/run/initialization_complete",
+
+ "enable_osc_query":"/run/enable_osc_query",
}
def run(status:int, endpoint:str, result:Any) -> None:
diff --git a/src-python/model.py b/src-python/model.py
index ce40c589..400c38d3 100644
--- a/src-python/model.py
+++ b/src-python/model.py
@@ -323,6 +323,9 @@ class Model:
def stopReceiveOSC(self):
self.osc_handler.oscServerStop()
+ def getIsOscQueryEnabled(self):
+ return self.osc_handler.getIsOscQueryEnabled()
+
@staticmethod
def checkSoftwareUpdated():
# check update
diff --git a/src-python/models/osc/osc.py b/src-python/models/osc/osc.py
index 84717b61..9e995b21 100644
--- a/src-python/models/osc/osc.py
+++ b/src-python/models/osc/osc.py
@@ -13,9 +13,9 @@ class OSCHandler:
def __init__(self, ip_address="127.0.0.1", port=9000) -> None:
if ip_address in ["127.0.0.1", "localhost"]:
- self.osc_query_enabled = True
+ self.is_osc_query_enabled = True
else:
- self.osc_query_enabled = False
+ self.is_osc_query_enabled = False
self.osc_ip_address = ip_address
self.osc_port = port
@@ -33,11 +33,14 @@ class OSCHandler:
self.dict_filter_and_target = {}
self.browser = None
+ def getIsOscQueryEnabled(self) -> bool:
+ return self.is_osc_query_enabled
+
def setOscIpAddress(self, ip_address:str) -> None:
if ip_address in ["127.0.0.1", "localhost"]:
- self.osc_query_enabled = True
+ self.is_osc_query_enabled = True
else:
- self.osc_query_enabled = False
+ self.is_osc_query_enabled = False
self.oscServerStop()
self.osc_ip_address = ip_address
@@ -60,7 +63,7 @@ class OSCHandler:
self.udp_client.send_message(self.osc_parameter_chatbox_input, [f"{message}", True, notification])
def getOSCParameterValue(self, address:str) -> Any:
- if not self.osc_query_enabled:
+ if not self.is_osc_query_enabled:
# OSCQueryが無効な場合はNoneを返す
return None
@@ -95,7 +98,7 @@ class OSCHandler:
self.dict_filter_and_target = dict_filter_and_target
def receiveOscParameters(self) -> None:
- if self.osc_query_enabled is False:
+ if self.is_osc_query_enabled is False:
# OSCQueryが無効な場合は何もしない
return
From bdaccdf421c24d7913138572cb54aa4218b5eaad Mon Sep 17 00:00:00 2001
From: misyaguziya <53165965+misyaguziya@users.noreply.github.com>
Date: Sun, 25 May 2025 22:15:53 +0900
Subject: [PATCH 15/30] [Update] OSC: Improve error handling and refactor OSC
server initialization
---
src-python/models/osc/osc.py | 32 ++++++++++++++++++++++++++------
1 file changed, 26 insertions(+), 6 deletions(-)
diff --git a/src-python/models/osc/osc.py b/src-python/models/osc/osc.py
index 9e995b21..ef4e1152 100644
--- a/src-python/models/osc/osc.py
+++ b/src-python/models/osc/osc.py
@@ -7,7 +7,13 @@ from tinyoscquery.queryservice import OSCQueryService
from tinyoscquery.query import OSCQueryBrowser, OSCQueryClient
from tinyoscquery.utility import get_open_udp_port, get_open_tcp_port
from tinyoscquery.shared.node import OSCAccess
-from utils import errorLogging
+
+try:
+ from utils import errorLogging
+except ImportError:
+ def errorLogging():
+ import traceback
+ print("Error occurred:", traceback.format_exc())
class OSCHandler:
def __init__(self, ip_address="127.0.0.1", port=9000) -> None:
@@ -107,7 +113,7 @@ class OSCHandler:
osc_dispatcher = dispatcher.Dispatcher()
for filter, target in self.dict_filter_and_target.items():
osc_dispatcher.map(filter, target)
- self.osc_server = osc_server.ThreadingOSCUDPServer((self.osc_server_ip_address, self.osc_server_port), osc_dispatcher, asyncio.get_event_loop())
+ self.osc_server = osc_server.ThreadingOSCUDPServer((self.osc_server_ip_address, self.osc_server_port), osc_dispatcher)
Thread(target=self.oscServerServe, daemon=True).start()
while True:
@@ -142,12 +148,26 @@ class OSCHandler:
if __name__ == "__main__":
handler = OSCHandler()
- handler.receiveOscParameters({
- "/avatar/parameters/MuteSelf": print,
+ handler.setDictFilterAndTarget({
+ "/avatar/parameters/MuteSelf": lambda address, *args: print(f"Received {address} with args {args}"),
+ "/chatbox/typing": lambda address, *args: print(f"Received {address} with args {args}"),
+ "/chatbox/input": lambda address, *args: print(f"Received {address} with args {args}"),
})
+ handler.receiveOscParameters()
sleep(5)
handler.sendTyping(True)
sleep(1)
- handler.sendMessage(message="Hello World", notification=True)
- sleep(60)
+ handler.sendMessage(message="Hello World 1", notification=True)
+ sleep(10)
+
+ print("IP address changed to 192.168.193.2")
+ handler.setOscIpAddress("192.168.193.2")
+ sleep(5)
+ handler.sendMessage(message="Hello World 2", notification=True)
+
+ print("IP address changed to 127.0.0.1")
+ handler.setOscIpAddress("127.0.0.1")
+ sleep(5)
+ handler.sendMessage(message="Hello World 3", notification=True)
+ sleep(10)
handler.oscServerStop()
\ No newline at end of file
From ed27a8c7bac598f7bbecdd2fbad3f0b72bdd0017 Mon Sep 17 00:00:00 2001
From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com>
Date: Mon, 26 May 2025 12:06:36 +0900
Subject: [PATCH 16/30] [Update/Refactor] UI: Add websocket error handlings.
Refactor. Change the error handling switch method from message to endpoint
primarily.
---
src-ui/logics/_useBackendErrorHandling.js | 207 +++++++++++-------
.../configs/translation/useDeepLAuthKey.js | 6 -
src-ui/logics/useReceiveRoutes.js | 41 +---
3 files changed, 129 insertions(+), 125 deletions(-)
diff --git a/src-ui/logics/_useBackendErrorHandling.js b/src-ui/logics/_useBackendErrorHandling.js
index c9d99e50..d5987a1b 100644
--- a/src-ui/logics/_useBackendErrorHandling.js
+++ b/src-ui/logics/_useBackendErrorHandling.js
@@ -16,6 +16,7 @@ import {
useDeepLAuthKey,
useOscIpAddress,
+ useWebsocket,
} from "@logics_configs";
import { ui_configs } from "../ui_configs";
@@ -31,106 +32,144 @@ export const _useBackendErrorHandling = () => {
const { updateSpeakerPhraseTimeout } = useSpeakerPhraseTimeout();
const { updateSpeakerMaxWords } = useSpeakerMaxWords();
- const { updateDeepLAuthKey, saveErrorDeepLAuthKey } = useDeepLAuthKey();
+ const { updateDeepLAuthKey } = useDeepLAuthKey();
const { updateOscIpAddress } = useOscIpAddress();
+ const { updateEnableWebsocket, updateWebsocketHost, updateWebsocketPort } = useWebsocket();
- const errorHandling_Backend = ({message, data, endpoint, _result}) => {
- switch (message) {
- case "No mic device detected":
- showNotification_Error(t("common_error.no_device_mic"));
- break;
- case "No speaker device detected":
- showNotification_Error(t("common_error.no_device_speaker"));
- break;
+ const errorHandling_Backend = ({message, data, endpoint, result}) => {
+ switch (endpoint) {
+ case "/run/error_device":
+ if (message === "No mic device detected") showNotification_Error(t("common_error.no_device_mic"));
+ if (message === "No speaker device detected") showNotification_Error(t("common_error.no_device_speaker"));
+ return;
- case "Mic energy threshold value is out of range":
- showNotification_Error(t("common_error.threshold_invalid_value",
- { min: ui_configs.mic_threshold_min, max: ui_configs.mic_threshold_max },
- ));
- break;
- case "Speaker energy threshold value is out of range":
- showNotification_Error(t("common_error.threshold_invalid_value",
- { min: ui_configs.speaker_threshold_min, max: ui_configs.speaker_threshold_max },
- ));
- break;
+ case "/set/data/mic_threshold":
+ if (message === "Mic energy threshold value is out of range") {
+ showNotification_Error(t("common_error.threshold_invalid_value",
+ { min: ui_configs.mic_threshold_min, max: ui_configs.mic_threshold_max },
+ ));
+ };
+ return;
+ case "/set/data/speaker_threshold":
+ if (message === "Speaker energy threshold value is out of range") {
+ showNotification_Error(t("common_error.threshold_invalid_value",
+ { min: ui_configs.speaker_threshold_min, max: ui_configs.speaker_threshold_max },
+ ));
+ }
+ return;
- case "CTranslate2 weight download error":
- showNotification_Error(t("common_error.failed_download_weight_ctranslate2"));
- break;
- case "Whisper weight download error":
- showNotification_Error(t("common_error.failed_download_weight_whisper"));
- break;
+ case "/run/error_ctranslate2_weight":
+ if (message === "CTranslate2 weight download error") showNotification_Error(t("common_error.failed_download_weight_ctranslate2"));
+ return;
+ case "/run/error_whisper_weight":
+ if (message === "Whisper weight download error") showNotification_Error(t("common_error.failed_download_weight_whisper"));
+ return;
- case "Translation engine limit error":
- showNotification_Error(t("common_error.translation_limit"));
- break;
+ case "/run/error_translation_engine":
+ if (message === "Translation engine limit error") showNotification_Error(t("common_error.translation_limit"));
+ return;
- case "DeepL auth key length is not correct":
- updateDeepLAuthKey(data);
- showNotification_Error(t("common_error.deepl_auth_key_invalid_length"));
- break;
- case "Authentication failure of deepL auth key":
- updateDeepLAuthKey(data);
- showNotification_Error(t("common_error.deepl_auth_key_failed_authentication"));
- break;
+ case "/set/data/deepl_auth_key":
+ if (message === "DeepL auth key length is not correct") {
+ updateDeepLAuthKey(data);
+ showNotification_Error(t("common_error.deepl_auth_key_invalid_length"));
+ } else if (message === "Authentication failure of deepL auth key") {
+ updateDeepLAuthKey(data);
+ showNotification_Error(t("common_error.deepl_auth_key_failed_authentication"));
+ } else { // Exception
+ updateDeepLAuthKey(data);
+ showNotification_Error(message);
+ }
+ return;
- case "Mic record timeout value is out of range":
- updateMicRecordTimeout(data);
- showNotification_Error(
- t("common_error.invalid_value_mic_record_timeout",
- { mic_phrase_timeout_label: t("config_page.transcription.mic_phrase_timeout.label") }
- ));
- break;
- case "Mic phrase timeout value is out of range":
- updateMicPhraseTimeout(data);
- showNotification_Error(
- t("common_error.invalid_value_mic_phrase_timeout",
- { mic_record_timeout_label: t("config_page.transcription.mic_record_timeout.label") }
- ));
- break;
- case "Mic max phrases value is out of range":
- updateMicMaxWords(data);
- showNotification_Error(t("common_error.invalid_value_mic_max_phrase"));
- break;
+ case "/set/data/mic_record_timeout":
+ if (message === "Mic record timeout value is out of range") {
+ updateMicRecordTimeout(data);
+ showNotification_Error(t("common_error.invalid_value_mic_record_timeout", {
+ mic_phrase_timeout_label: t("config_page.transcription.mic_phrase_timeout.label")
+ }));
+ }
+ return;
+ case "/set/data/mic_phrase_timeout":
+ if (message === "Mic phrase timeout value is out of range") {
+ updateMicPhraseTimeout(data);
+ showNotification_Error(t("common_error.invalid_value_mic_phrase_timeout", {
+ mic_record_timeout_label: t("config_page.transcription.mic_record_timeout.label")
+ }));
+ }
+ return;
+ case "/set/data/mic_max_phrases":
+ if (message === "Mic max phrases value is out of range") {
+ updateMicMaxWords(data);
+ showNotification_Error(t("common_error.invalid_value_mic_max_phrase"));
+ }
+ return;
- case "Speaker record timeout value is out of range":
- updateSpeakerRecordTimeout(data);
- showNotification_Error(
- t("common_error.invalid_value_speaker_record_timeout",
- { speaker_phrase_timeout_label: t("config_page.transcription.speaker_phrase_timeout.label") }
- ));
- break;
- case "Speaker phrase timeout value is out of range":
- updateSpeakerPhraseTimeout(data);
- showNotification_Error(
- t("common_error.invalid_value_speaker_phrase_timeout",
- { speaker_record_timeout_label: t("config_page.transcription.speaker_record_timeout.label") }
- ));
- break;
- case "Speaker max phrases value is out of range":
- updateSpeakerMaxWords(data);
- showNotification_Error(t("common_error.invalid_value_speaker_max_phrase"));
- break;
+ case "/set/data/speaker_record_timeout":
+ if (message === "Speaker record timeout value is out of range") {
+ updateSpeakerRecordTimeout(data);
+ showNotification_Error(t("common_error.invalid_value_speaker_record_timeout", {
+ speaker_phrase_timeout_label: t("config_page.transcription.speaker_phrase_timeout.label")
+ }));
+ }
+ return;
+ case "/set/data/speaker_phrase_timeout":
+ if (message === "Speaker phrase timeout value is out of range") {
+ updateSpeakerPhraseTimeout(data);
+ showNotification_Error(t("common_error.invalid_value_speaker_phrase_timeout", {
+ speaker_record_timeout_label: t("config_page.transcription.speaker_record_timeout.label")
+ }));
+ }
+ return;
+ case "/set/data/speaker_max_phrases":
+ if (message === "Speaker max phrases value is out of range") {
+ updateSpeakerMaxWords(data);
+ showNotification_Error(t("common_error.invalid_value_speaker_max_phrase"));
+ }
+ return;
// Advanced Settings, error messages are set by Backend (EN only)
- case "Invalid IP address":
- updateOscIpAddress(data);
- showNotification_Error(message);
- break;
+ case "/set/data/osc_ip_address":
+ if (message === "Invalid IP address") {
+ updateOscIpAddress(data);
+ showNotification_Error(message);
+ } else if (message === "Cannot set IP address") {
+ updateOscIpAddress(data);
+ showNotification_Error(message);
+ } // else? (Backend will send the message "Cannot set IP address" when throw Exception)
+ return;
- case "Cannot set IP address":
- updateOscIpAddress(data);
- showNotification_Error(message);
- break;
+
+ case "/set/enable/websocket_server":
+ if (message === "WebSocket server host or port is not available") {
+ updateEnableWebsocket(data);
+ showNotification_Error(message);
+ }
+ return;
+
+ case "/set/data/websocket_host":
+ if (message === "Invalid IP address") {
+ updateWebsocketHost(data);
+ showNotification_Error(message);
+ } else if (message === "WebSocket server host is not available") {
+ updateWebsocketHost(data);
+ showNotification_Error(message);
+ }
+ return;
+
+ case "/set/data/websocket_port":
+ if (message === "WebSocket server port is not available") {
+ updateWebsocketPort(data);
+ showNotification_Error(message);
+ }
+ return;
default:
- // determine by endpoint, not message.
- if (endpoint === "/set/data/deepl_auth_key") saveErrorDeepLAuthKey({message, data, endpoint, _result});
-
- break;
+ console.error(`Invalid endpoint or message: ${endpoint}\nmessage: ${message}\nresult: ${JSON.stringify(result)}`);
+ return;
}
}
diff --git a/src-ui/logics/configs/translation/useDeepLAuthKey.js b/src-ui/logics/configs/translation/useDeepLAuthKey.js
index f93fdd4f..a568dfe9 100644
--- a/src-ui/logics/configs/translation/useDeepLAuthKey.js
+++ b/src-ui/logics/configs/translation/useDeepLAuthKey.js
@@ -29,11 +29,6 @@ export const useDeepLAuthKey = () => {
showNotification_Success(t("config_page.translation.deepl_auth_key.auth_key_success"));
};
- const saveErrorDeepLAuthKey = ({data, message}) => {
- updateDeepLAuthKey(data);
- showNotification_Error(message);
- };
-
return {
currentDeepLAuthKey,
getDeepLAuthKey,
@@ -41,7 +36,6 @@ export const useDeepLAuthKey = () => {
setDeepLAuthKey,
deleteDeepLAuthKey,
- saveErrorDeepLAuthKey,
savedDeepLAuthKey,
};
};
\ No newline at end of file
diff --git a/src-ui/logics/useReceiveRoutes.js b/src-ui/logics/useReceiveRoutes.js
index 1d89be33..3cde999a 100644
--- a/src-ui/logics/useReceiveRoutes.js
+++ b/src-ui/logics/useReceiveRoutes.js
@@ -530,30 +530,6 @@ export const useReceiveRoutes = () => {
"/get/data/transcription_engines": ()=>{}, // Not implemented on UI yet. (if ai_models has not been detected, this will be blank array[]. if the ai_models are ok but just network has not connected, it'l be only ["Whisper"])
};
- const error_status_routes = {
- "/run/error_device": errorHandling_Backend,
-
- "/run/error_ctranslate2_weight": errorHandling_Backend,
- "/run/error_whisper_weight": errorHandling_Backend,
-
- "/set/data/deepl_auth_key": errorHandling_Backend,
-
- "/run/error_translation_engine": errorHandling_Backend,
-
- "/set/data/mic_threshold": errorHandling_Backend,
- "/set/data/mic_record_timeout": errorHandling_Backend,
- "/set/data/mic_phrase_timeout": errorHandling_Backend,
- "/set/data/mic_max_phrases": errorHandling_Backend,
-
- "/set/data/speaker_threshold": errorHandling_Backend,
- "/set/data/speaker_record_timeout": errorHandling_Backend,
- "/set/data/speaker_phrase_timeout": errorHandling_Backend,
- "/set/data/speaker_max_phrases": errorHandling_Backend,
-
- "/set/data/osc_ip_address": errorHandling_Backend,
- };
-
-
const receiveRoutes = (parsed_data) => {
const initDataSyncProcess = (payload) => {
for (const [endpoint, value] of Object.entries(payload)) {
@@ -583,17 +559,12 @@ export const useReceiveRoutes = () => {
break;
case 400:
- const error_route = error_status_routes[parsed_data.endpoint];
- if (error_route) {
- error_route({
- message: parsed_data.result.message,
- data: parsed_data.result.data,
- endpoint: parsed_data.endpoint,
- _result: parsed_data.result,
- });
- } else {
- handleInvalidEndpoint(parsed_data);
- }
+ errorHandling_Backend({
+ message: parsed_data.result.message,
+ data: parsed_data.result.data,
+ endpoint: parsed_data.endpoint,
+ result: parsed_data.result,
+ });
break;
case 500:
showNotification_Error(
From 88c27a9c700cada31f2fbaa521cd7cae1e97f807 Mon Sep 17 00:00:00 2001
From: misyaguziya <53165965+misyaguziya@users.noreply.github.com>
Date: Mon, 26 May 2025 16:15:04 +0900
Subject: [PATCH 17/30] [Update] WebSocket: Implement availability check for
WebSocket server and refactor related logic
---
src-python/controller.py | 18 +++++++++++-------
src-python/model.py | 25 -------------------------
src-python/utils.py | 20 ++++++++++++++++++++
3 files changed, 31 insertions(+), 32 deletions(-)
diff --git a/src-python/controller.py b/src-python/controller.py
index ccf44227..75c4439a 100644
--- a/src-python/controller.py
+++ b/src-python/controller.py
@@ -6,7 +6,7 @@ import re
from device_manager import device_manager
from config import config
from model import model
-from utils import removeLog, printLog, errorLogging, isConnectedNetwork, isValidIpAddress
+from utils import removeLog, printLog, errorLogging, isConnectedNetwork, isValidIpAddress, isAvailableWebSocketServer
class Controller:
def __init__(self) -> None:
@@ -1835,8 +1835,10 @@ class Controller:
config.WEBSOCKET_HOST = data
response = {"status":200, "result":config.WEBSOCKET_HOST}
else:
- model.stopWebSocketServer()
- if model.checkWebSocketServerAvailable() is True:
+ if data == config.WEBSOCKET_HOST:
+ response = {"status":200, "result":config.WEBSOCKET_HOST}
+ elif isAvailableWebSocketServer(data, config.WEBSOCKET_PORT):
+ model.stopWebSocketServer()
model.startWebSocketServer(data, config.WEBSOCKET_PORT)
config.WEBSOCKET_HOST = data
response = {"status":200, "result":config.WEBSOCKET_HOST}
@@ -1861,8 +1863,10 @@ class Controller:
config.WEBSOCKET_PORT = int(data)
response = {"status":200, "result":config.WEBSOCKET_PORT}
else:
- model.stopWebSocketServer()
- if model.checkWebSocketServerAvailable() is True:
+ if int(data) == config.WEBSOCKET_PORT:
+ return {"status":200, "result":config.WEBSOCKET_PORT}
+ elif isAvailableWebSocketServer(config.WEBSOCKET_HOST, int(data)) is True:
+ model.stopWebSocketServer()
model.startWebSocketServer(config.WEBSOCKET_HOST, int(data))
config.WEBSOCKET_PORT = int(data)
response = {"status":200, "result":config.WEBSOCKET_PORT}
@@ -1882,7 +1886,7 @@ class Controller:
@staticmethod
def setEnableWebSocketServer(*args, **kwargs) -> dict:
- if model.checkWebSocketServerAvailable() is True:
+ if isAvailableWebSocketServer(config.WEBSOCKET_HOST, config.WEBSOCKET_PORT) is True:
model.startWebSocketServer(config.WEBSOCKET_HOST, config.WEBSOCKET_PORT)
config.WEBSOCKET_SERVER = True
response = {"status":200, "result":config.WEBSOCKET_SERVER}
@@ -2003,7 +2007,7 @@ class Controller:
printLog("Init WebSocket Server")
if config.WEBSOCKET_SERVER is True:
- if model.checkWebSocketServerAvailable() is True:
+ if isAvailableWebSocketServer(config.WEBSOCKET_HOST, config.WEBSOCKET_PORT) is True:
model.startWebSocketServer(config.WEBSOCKET_HOST, config.WEBSOCKET_PORT)
else:
config.WEBSOCKET_SERVER = False
diff --git a/src-python/model.py b/src-python/model.py
index d52d6341..c4bd6466 100644
--- a/src-python/model.py
+++ b/src-python/model.py
@@ -1,8 +1,6 @@
import copy
import gc
import asyncio
-import socket
-import errno
import json
from subprocess import Popen
from os import makedirs as os_makedirs
@@ -840,29 +838,6 @@ class Model:
"""WebSocketメッセージ受信時の処理"""
pass
- def checkWebSocketServerAvailable(self):
- """WebSocketサーバーのポートが使用中かどうかを確認する"""
- response = True
- try:
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as chk:
- # SO_REUSEADDRを設定してソケットの再利用を許可
- chk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- try:
- chk.bind((config.WEBSOCKET_HOST, config.WEBSOCKET_PORT))
- # シャットダウン前にリッスン状態にする必要はない
- chk.close()
- except OSError as e:
- if e.errno == errno.EADDRINUSE:
- response = False
- else:
- errorLogging()
- response = False
- except Exception:
- errorLogging()
- response = False
-
- return response
-
def startWebSocketServer(self, host, port):
"""WebSocketサーバーを起動し、別スレッドで実行する"""
if self.websocket_server_alive is True:
diff --git a/src-python/utils.py b/src-python/utils.py
index de538dae..7bc0997b 100644
--- a/src-python/utils.py
+++ b/src-python/utils.py
@@ -8,6 +8,7 @@ from logging.handlers import RotatingFileHandler
from ctranslate2 import get_supported_compute_types
import requests
import ipaddress
+import socket
def isConnectedNetwork(url="http://www.google.com", timeout=3) -> bool:
try:
@@ -16,6 +17,25 @@ def isConnectedNetwork(url="http://www.google.com", timeout=3) -> bool:
except requests.RequestException:
return False
+def isAvailableWebSocketServer(host:str, port:int) -> bool:
+ """WebSocketサーバーのポートが使用中かどうかを確認する"""
+ response = True
+ try:
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as chk:
+ try:
+ # SO_REUSEADDRを設定してソケットの再利用を許可
+ chk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ chk.bind((host, port))
+ # シャットダウン前にリッスン状態にする必要はない
+ chk.close()
+ except Exception:
+ response = False
+ except Exception:
+ errorLogging()
+ response = False
+
+ return response
+
def isValidIpAddress(ip_address: str) -> bool:
try:
ipaddress.ip_address(ip_address)
From 8dac3da93357645fc9b086cd7090a9cc5bcf77bd Mon Sep 17 00:00:00 2001
From: misyaguziya <53165965+misyaguziya@users.noreply.github.com>
Date: Tue, 27 May 2025 14:12:20 +0900
Subject: [PATCH 18/30] [Update] Add hf_xet and huggingface-hub to
requirements; enhance mainloop.py for Hugging Face caching
---
backend.spec | 2 +-
backend_cuda.spec | 2 +-
install.bat | 11 +++++++++++
requirements.txt | 2 ++
requirements_cuda.txt | 2 ++
src-python/mainloop.py | 9 ++++++++-
6 files changed, 25 insertions(+), 3 deletions(-)
diff --git a/backend.spec b/backend.spec
index 33ec3cae..3933d942 100644
--- a/backend.spec
+++ b/backend.spec
@@ -5,7 +5,7 @@ a = Analysis(
['src-python\\mainloop.py'],
pathex=[],
binaries=[],
- datas=[('./fonts', 'fonts/'), ('.venv/Lib/site-packages/zeroconf', 'zeroconf/'), ('.venv/Lib/site-packages/openvr', 'openvr/'), ('.venv/Lib/site-packages/pykakasi', 'pykakasi/'), ('.venv/Lib/site-packages/faster_whisper', 'faster_whisper/')],
+ datas=[('./fonts', 'fonts/'), ('.venv/Lib/site-packages/zeroconf', 'zeroconf/'), ('.venv/Lib/site-packages/openvr', 'openvr/'), ('.venv/Lib/site-packages/pykakasi', 'pykakasi/'), ('.venv/Lib/site-packages/faster_whisper', 'faster_whisper/'), ('.venv/Lib/site-packages/hf_xet', 'hf_xet/')],
hiddenimports=[],
hookspath=[],
hooksconfig={},
diff --git a/backend_cuda.spec b/backend_cuda.spec
index 34ed248f..08ba5fd7 100644
--- a/backend_cuda.spec
+++ b/backend_cuda.spec
@@ -5,7 +5,7 @@ a = Analysis(
['src-python\\mainloop.py'],
pathex=[],
binaries=[],
- datas=[('./fonts', 'fonts/'), ('.venv_cuda/Lib/site-packages/zeroconf', 'zeroconf/'), ('.venv_cuda/Lib/site-packages/openvr', 'openvr/'), ('.venv_cuda/Lib/site-packages/pykakasi', 'pykakasi/'), ('.venv_cuda/Lib/site-packages/faster_whisper', 'faster_whisper/')],
+ datas=[('./fonts', 'fonts/'), ('.venv_cuda/Lib/site-packages/zeroconf', 'zeroconf/'), ('.venv_cuda/Lib/site-packages/openvr', 'openvr/'), ('.venv_cuda/Lib/site-packages/pykakasi', 'pykakasi/'), ('.venv_cuda/Lib/site-packages/faster_whisper', 'faster_whisper/'), ('.venv/Lib/site-packages/hf_xet', 'hf_xet/')],
hiddenimports=[],
hookspath=[],
hooksconfig={},
diff --git a/install.bat b/install.bat
index 5a57e817..69c5152b 100644
--- a/install.bat
+++ b/install.bat
@@ -1,6 +1,17 @@
+REM .venv .venv_cuda があれば削除
+if exist .venv (
+ rmdir /s /q .venv
+)
+
+if exist .venv_cuda (
+ rmdir /s /q .venv_cuda
+)
+
+REM .venv .venv_cuda を作成
python -m venv .venv
python -m venv .venv_cuda
+REM .venv .venv_cuda に必要なパッケージをインストール
call .venv/Scripts/activate
python.exe -m pip install --upgrade pip
pip install --no-cache-dir --force-reinstall -r requirements.txt
diff --git a/requirements.txt b/requirements.txt
index 95501ea2..1f5cc9ed 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -16,6 +16,8 @@ psutil==5.9.8
pykakasi==2.3.0
pycaw==20240210
websockets==15.0.1
+huggingface-hub==0.31.2
+hf-xet==1.1.2
translators @ git+https://github.com/misyaguziya/translators@5.9.2.1
SpeechRecognition @ git+https://github.com/misyaguziya/custom_speech_recognition@3.10.4.1
tinyoscquery @ git+https://github.com/cyberkitsune/tinyoscquery@0.1.3
\ No newline at end of file
diff --git a/requirements_cuda.txt b/requirements_cuda.txt
index 768f45ba..347031cd 100644
--- a/requirements_cuda.txt
+++ b/requirements_cuda.txt
@@ -17,6 +17,8 @@ psutil==5.9.8
pykakasi==2.3.0
pycaw==20240210
websockets==15.0.1
+huggingface-hub==0.31.2
+hf-xet==1.1.2
translators @ git+https://github.com/misyaguziya/translators@5.9.2.1
SpeechRecognition @ git+https://github.com/misyaguziya/custom_speech_recognition@3.10.4.1
tinyoscquery @ git+https://github.com/cyberkitsune/tinyoscquery@0.1.3
\ No newline at end of file
diff --git a/src-python/mainloop.py b/src-python/mainloop.py
index d8ad52e7..f5e2318b 100644
--- a/src-python/mainloop.py
+++ b/src-python/mainloop.py
@@ -1,9 +1,16 @@
+import os
import sys
import json
import time
from typing import Any
from threading import Thread
from queue import Queue
+import warnings
+warnings.filterwarnings('ignore')
+huggingface_cache_dir = os.path.join(os.path.dirname(__file__), ".cache")
+os.makedirs(huggingface_cache_dir, exist_ok=True)
+os.environ["HF_HOME"] = os.path.join(huggingface_cache_dir)
+
from controller import Controller
from utils import printLog, printResponse, errorLogging, encodeBase64
@@ -376,7 +383,7 @@ class Main:
if status == 423:
self.queue.put((endpoint, data))
else:
- printLog(endpoint, {"send_data":result})
+ printLog(endpoint, {"status": status, "send_data": result})
printResponse(status, endpoint, result)
time.sleep(0.1)
From 238bd41109b085d63b8fc32febc470169ebc1332 Mon Sep 17 00:00:00 2001
From: misyaguziya <53165965+misyaguziya@users.noreply.github.com>
Date: Wed, 28 May 2025 11:14:39 +0900
Subject: [PATCH 19/30] [Update] Refactor Hugging Face dependencies in
requirements files and enhance cache directory handling in mainloop.py
---
requirements.txt | 4 ++--
requirements_cuda.txt | 4 ++--
src-python/mainloop.py | 14 +++++++++++---
3 files changed, 15 insertions(+), 7 deletions(-)
diff --git a/requirements.txt b/requirements.txt
index 1f5cc9ed..c6b7ba57 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -16,8 +16,8 @@ psutil==5.9.8
pykakasi==2.3.0
pycaw==20240210
websockets==15.0.1
-huggingface-hub==0.31.2
-hf-xet==1.1.2
+huggingface_hub[hf-xet]
+setuptools==80.8.0
translators @ git+https://github.com/misyaguziya/translators@5.9.2.1
SpeechRecognition @ git+https://github.com/misyaguziya/custom_speech_recognition@3.10.4.1
tinyoscquery @ git+https://github.com/cyberkitsune/tinyoscquery@0.1.3
\ No newline at end of file
diff --git a/requirements_cuda.txt b/requirements_cuda.txt
index 347031cd..00293ea3 100644
--- a/requirements_cuda.txt
+++ b/requirements_cuda.txt
@@ -17,8 +17,8 @@ psutil==5.9.8
pykakasi==2.3.0
pycaw==20240210
websockets==15.0.1
-huggingface-hub==0.31.2
-hf-xet==1.1.2
+huggingface[hub]
+setuptools==80.8.0
translators @ git+https://github.com/misyaguziya/translators@5.9.2.1
SpeechRecognition @ git+https://github.com/misyaguziya/custom_speech_recognition@3.10.4.1
tinyoscquery @ git+https://github.com/cyberkitsune/tinyoscquery@0.1.3
\ No newline at end of file
diff --git a/src-python/mainloop.py b/src-python/mainloop.py
index f5e2318b..c2429a58 100644
--- a/src-python/mainloop.py
+++ b/src-python/mainloop.py
@@ -7,9 +7,17 @@ from threading import Thread
from queue import Queue
import warnings
warnings.filterwarnings('ignore')
-huggingface_cache_dir = os.path.join(os.path.dirname(__file__), ".cache")
-os.makedirs(huggingface_cache_dir, exist_ok=True)
-os.environ["HF_HOME"] = os.path.join(huggingface_cache_dir)
+
+if getattr(sys, 'frozen', False):
+ cache_dir = os.path.join(os.path.dirname(sys.executable), ".cache")
+ hub_dir = os.path.join(os.path.dirname(sys.executable), ".cache", "hub")
+else:
+ cache_dir = os.path.join(os.path.dirname(__file__), ".cache")
+ hub_dir = os.path.join(os.path.dirname(__file__), ".cache", "hub")
+
+os.makedirs(cache_dir, exist_ok=True)
+os.makedirs(hub_dir, exist_ok=True)
+os.environ["HF_HOME"] = os.path.join(cache_dir)
from controller import Controller
from utils import printLog, printResponse, errorLogging, encodeBase64
From 67d06ab1e1859e34b1fd4ea0939b4d91769801fd Mon Sep 17 00:00:00 2001
From: misyaguziya <53165965+misyaguziya@users.noreply.github.com>
Date: Thu, 29 May 2025 08:04:06 +0900
Subject: [PATCH 20/30] [Update] Enhance build scripts and requirements for
Hugging Face integration; improve logging and error handling
---
build.bat | 2 +-
build_cuda.bat | 2 +-
install.bat | 20 ++++++++++++--------
requirements.txt | 3 ++-
requirements_cuda.txt | 3 ++-
src-python/controller.py | 2 +-
src-python/mainloop.py | 20 ++++----------------
7 files changed, 23 insertions(+), 29 deletions(-)
diff --git a/build.bat b/build.bat
index 89ee8cbb..3c08629c 100644
--- a/build.bat
+++ b/build.bat
@@ -1,2 +1,2 @@
call .venv/Scripts/activate
-pyinstaller backend.spec --distpath src-tauri/bin --clean --noconfirm
\ No newline at end of file
+pyinstaller backend.spec --distpath src-tauri/bin --clean --noconfirm --log-level ERROR
\ No newline at end of file
diff --git a/build_cuda.bat b/build_cuda.bat
index 73c85676..308b9174 100644
--- a/build_cuda.bat
+++ b/build_cuda.bat
@@ -1,2 +1,2 @@
call .venv_cuda/Scripts/activate
-pyinstaller backend_cuda.spec --distpath src-tauri/bin --clean --noconfirm
\ No newline at end of file
+pyinstaller backend_cuda.spec --distpath src-tauri/bin --clean --noconfirm --log-level ERROR
\ No newline at end of file
diff --git a/install.bat b/install.bat
index 69c5152b..aeb3d9b5 100644
--- a/install.bat
+++ b/install.bat
@@ -1,21 +1,25 @@
-REM .venv .venv_cuda があれば削除
+REM .venv exists
if exist .venv (
rmdir /s /q .venv
)
-if exist .venv_cuda (
- rmdir /s /q .venv_cuda
-)
-
-REM .venv .venv_cuda を作成
+REM make .venv
python -m venv .venv
-python -m venv .venv_cuda
-REM .venv .venv_cuda に必要なパッケージをインストール
+REM install packages for .venv
call .venv/Scripts/activate
python.exe -m pip install --upgrade pip
pip install --no-cache-dir --force-reinstall -r requirements.txt
+REM if .venv_cuda exists
+if exist .venv_cuda (
+ rmdir /s /q .venv_cuda
+)
+
+REM make .venv_cuda
+python -m venv .venv_cuda
+
+REM install packages for .venv_cuda
call .venv_cuda/Scripts/activate
python.exe -m pip install --upgrade pip
pip install --no-cache-dir --force-reinstall -r requirements_cuda.txt
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index c6b7ba57..244a9238 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -16,7 +16,8 @@ psutil==5.9.8
pykakasi==2.3.0
pycaw==20240210
websockets==15.0.1
-huggingface_hub[hf-xet]
+huggingface_hub==0.32.2
+hf-xet==1.1.2
setuptools==80.8.0
translators @ git+https://github.com/misyaguziya/translators@5.9.2.1
SpeechRecognition @ git+https://github.com/misyaguziya/custom_speech_recognition@3.10.4.1
diff --git a/requirements_cuda.txt b/requirements_cuda.txt
index 00293ea3..4dadf192 100644
--- a/requirements_cuda.txt
+++ b/requirements_cuda.txt
@@ -17,7 +17,8 @@ psutil==5.9.8
pykakasi==2.3.0
pycaw==20240210
websockets==15.0.1
-huggingface[hub]
+huggingface_hub==0.32.2
+hf-xet==1.1.2
setuptools==80.8.0
translators @ git+https://github.com/misyaguziya/translators@5.9.2.1
SpeechRecognition @ git+https://github.com/misyaguziya/custom_speech_recognition@3.10.4.1
diff --git a/src-python/controller.py b/src-python/controller.py
index c2db0f45..f74b01ca 100644
--- a/src-python/controller.py
+++ b/src-python/controller.py
@@ -1,4 +1,4 @@
-from typing import Callable, Union, Any
+from typing import Callable, Any
from time import sleep
from subprocess import Popen
from threading import Thread
diff --git a/src-python/mainloop.py b/src-python/mainloop.py
index c2429a58..6b684b38 100644
--- a/src-python/mainloop.py
+++ b/src-python/mainloop.py
@@ -1,26 +1,14 @@
-import os
import sys
import json
import time
from typing import Any
from threading import Thread
from queue import Queue
-import warnings
-warnings.filterwarnings('ignore')
+import logging
+from controller import Controller # noqa: E402
+from utils import printLog, printResponse, errorLogging, encodeBase64 # noqa: E402
-if getattr(sys, 'frozen', False):
- cache_dir = os.path.join(os.path.dirname(sys.executable), ".cache")
- hub_dir = os.path.join(os.path.dirname(sys.executable), ".cache", "hub")
-else:
- cache_dir = os.path.join(os.path.dirname(__file__), ".cache")
- hub_dir = os.path.join(os.path.dirname(__file__), ".cache", "hub")
-
-os.makedirs(cache_dir, exist_ok=True)
-os.makedirs(hub_dir, exist_ok=True)
-os.environ["HF_HOME"] = os.path.join(cache_dir)
-
-from controller import Controller
-from utils import printLog, printResponse, errorLogging, encodeBase64
+logging.getLogger("huggingface_hub").setLevel(logging.ERROR)
run_mapping = {
"connected_network":"/run/connected_network",
From 6c5c9d2c9c1126773eabe4f0442eb92bf26cbff0 Mon Sep 17 00:00:00 2001
From: misyaguziya <53165965+misyaguziya@users.noreply.github.com>
Date: Thu, 29 May 2025 09:12:11 +0900
Subject: [PATCH 21/30] [Update] .gitignore: Add VRCT.zip and VRCT_cuda.zip to
ignore list
---
.gitignore | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/.gitignore b/.gitignore
index e628117a..6ff13fd6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,7 +12,8 @@ weights/
error.log
*.exe
*.ipynb
-
+VRCT.zip
+VRCT_cuda.zip
# Added by WebUI migration
# Logs
From ae38ed165df27ef5e43a09192532e494924edceb Mon Sep 17 00:00:00 2001
From: misyaguziya <53165965+misyaguziya@users.noreply.github.com>
Date: Fri, 30 May 2025 11:31:09 +0900
Subject: [PATCH 22/30] [Update] controller.py: Refactor OSC IP address
handling and improve VRC mic mute sync logic
---
src-python/controller.py | 39 ++++++++++++++++++++++++++++++++-------
1 file changed, 32 insertions(+), 7 deletions(-)
diff --git a/src-python/controller.py b/src-python/controller.py
index 821cd397..767fb21c 100644
--- a/src-python/controller.py
+++ b/src-python/controller.py
@@ -1137,8 +1137,7 @@ class Controller:
def getOscIpAddress(*args, **kwargs) -> dict:
return {"status":200, "result":config.OSC_IP_ADDRESS}
- @staticmethod
- def setOscIpAddress(data, *args, **kwargs) -> dict:
+ def setOscIpAddress(self, data, *args, **kwargs) -> dict:
if isValidIpAddress(data) is False:
response = {
"status":400,
@@ -1151,6 +1150,11 @@ class Controller:
try:
model.setOscIpAddress(data)
config.OSC_IP_ADDRESS = data
+ if model.getIsOscQueryEnabled() is True:
+ self.enableOscQuery()
+ else:
+ self.setDisableVrcMicMuteSync()
+ self.disableOscQuery()
response = {"status":200, "result":config.OSC_IP_ADDRESS}
except Exception:
model.setOscIpAddress(config.OSC_IP_ADDRESS)
@@ -1426,13 +1430,20 @@ class Controller:
@staticmethod
def setEnableVrcMicMuteSync(*args, **kwargs) -> dict:
- if model.getIsOscQueryEnabled() is False:
+ if model.getIsOscQueryEnabled() is True:
config.VRC_MIC_MUTE_SYNC = True
model.setMuteSelfStatus()
model.changeMicTranscriptStatus()
+ response = {"status":200, "result":config.VRC_MIC_MUTE_SYNC}
else:
- config.VRC_MIC_MUTE_SYNC = False
- return {"status":200, "result":config.VRC_MIC_MUTE_SYNC}
+ response = {
+ "status":400,
+ "result":{
+ "message":"Cannot enable VRC mic mute sync while OSC query is disabled",
+ "data": config.VRC_MIC_MUTE_SYNC
+ }
+ }
+ return response
@staticmethod
def setDisableVrcMicMuteSync(*args, **kwargs) -> dict:
@@ -1913,10 +1924,22 @@ class Controller:
self.run(200, self.run_mapping["initialization_progress"], progress)
def enableOscQuery(self):
- self.run(200, self.run_mapping["enable_osc_query"], True)
+ self.run(
+ 200,
+ self.run_mapping["enable_osc_query"],
+ {
+ "data": True,
+ "disabled_functions": []
+ }
+ )
def disableOscQuery(self):
- self.run(200, self.run_mapping["enable_osc_query"], False)
+ self.run(200, self.run_mapping["enable_osc_query"], {
+ "data": False,
+ "disabled_functions": [
+ "vrc_mic_mute_sync",
+ ]
+ })
def init(self, *args, **kwargs) -> None:
removeLog()
@@ -1999,6 +2022,8 @@ class Controller:
if osc_query_enabled is True:
self.enableOscQuery()
else:
+ # OSC Query is disabled, so disable VRC some features
+ config.VRC_MIC_MUTE_SYNC = False
self.disableOscQuery()
if config.VRC_MIC_MUTE_SYNC is True:
From 88cb4f72b53c71ca0c05d277532af00b7d2bec5f Mon Sep 17 00:00:00 2001
From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com>
Date: Fri, 30 May 2025 15:32:03 +0900
Subject: [PATCH 23/30] [Update/bugfix] UI: Add disable/enable UI function when
osc query is disabled. Fix the error that the mic mute sync couldn't
enable/disable.
---
locales/en.yml | 3 ++
locales/ja.yml | 3 ++
src-python/controller.py | 2 +-
src-ui/app/_index_css/variables.css | 1 +
.../setting_box/others/Others.jsx | 8 +++-
.../RightSideComponents.jsx | 2 +-
.../SnackbarController.jsx | 1 +
.../SnackbarController.module.scss | 3 ++
.../common_components/checkbox/Checkbox.jsx | 6 +--
.../checkbox/Checkbox.module.scss | 9 +++-
src-ui/logics/common/index.js | 1 +
src-ui/logics/common/useHandleOscQuery.js | 44 +++++++++++++++++++
src-ui/logics/common/useNotificationStatus.js | 11 +++++
.../configs/others/useEnableVrcMicMuteSync.js | 2 +-
src-ui/logics/useReceiveRoutes.js | 20 +++++++--
src-ui/store.js | 13 +++++-
16 files changed, 116 insertions(+), 13 deletions(-)
create mode 100644 src-ui/logics/common/useHandleOscQuery.js
diff --git a/locales/en.yml b/locales/en.yml
index d7a2347a..5138f697 100644
--- a/locales/en.yml
+++ b/locales/en.yml
@@ -24,6 +24,9 @@ common_error:
invalid_value_speaker_phrase_timeout: "It cannot be set lower than '{{speaker_record_timeout_label}}' with a value of 0 or more."
invalid_value_speaker_max_phrase: "You can set a number equal to or greater than 0."
+common_warning:
+ unable_to_use_osc_query: "The functions below have been automatically disabled because receiving OSC data is not possible due to OSC IP Address settings."
+
main_page:
translation: "Translation"
transcription_send: "Voice2Chatbox"
diff --git a/locales/ja.yml b/locales/ja.yml
index 7286cb54..b8d70695 100644
--- a/locales/ja.yml
+++ b/locales/ja.yml
@@ -24,6 +24,9 @@ common_error:
invalid_value_speaker_phrase_timeout: "0 以上で 「{{speaker_record_timeout_label}}」 より小さくすることはできません。"
invalid_value_speaker_max_phrase: "0 以上の数値を設定できます。"
+common_warning:
+ unable_to_use_osc_query: "OSC IP Address の設定によりOSCデータの受信ができないため、以下の機能が自動的に無効になっています。"
+
main_page:
translation: "翻訳"
transcription_send: "音声認識 マイク"
diff --git a/src-python/controller.py b/src-python/controller.py
index 767fb21c..080df101 100644
--- a/src-python/controller.py
+++ b/src-python/controller.py
@@ -1443,7 +1443,7 @@ class Controller:
"data": config.VRC_MIC_MUTE_SYNC
}
}
- return response
+ return response
@staticmethod
def setDisableVrcMicMuteSync(*args, **kwargs) -> dict:
diff --git a/src-ui/app/_index_css/variables.css b/src-ui/app/_index_css/variables.css
index 6a3c48e4..ca2beca1 100644
--- a/src-ui/app/_index_css/variables.css
+++ b/src-ui/app/_index_css/variables.css
@@ -25,6 +25,7 @@
--error_bc_active_color: #9c3938;
--success_bc_color: #368777;
--waring_color: #cb944f;
+ --waring_bc_color: #cf7b1b;
--dark_basic_text_color: #f2f2f2;
--dark_100_color: #f5f7fb;
diff --git a/src-ui/app/config_page/setting_section/setting_box/others/Others.jsx b/src-ui/app/config_page/setting_section/setting_box/others/Others.jsx
index d8d7b818..b6c9c9b6 100644
--- a/src-ui/app/config_page/setting_section/setting_box/others/Others.jsx
+++ b/src-ui/app/config_page/setting_section/setting_box/others/Others.jsx
@@ -101,11 +101,17 @@ export const VrcMicMuteSyncContainer = () => {
const { t } = useTranslation();
const { currentEnableVrcMicMuteSync, toggleEnableVrcMicMuteSync } = useEnableVrcMicMuteSync();
+ const variable = {
+ state: currentEnableVrcMicMuteSync.state,
+ data: currentEnableVrcMicMuteSync.data.is_enabled,
+ };
+
return (
);
diff --git a/src-ui/app/main_page/main_section/top_bar/right_side_components/RightSideComponents.jsx b/src-ui/app/main_page/main_section/top_bar/right_side_components/RightSideComponents.jsx
index 7bd3ae59..3f8be6e2 100644
--- a/src-ui/app/main_page/main_section/top_bar/right_side_components/RightSideComponents.jsx
+++ b/src-ui/app/main_page/main_section/top_bar/right_side_components/RightSideComponents.jsx
@@ -77,7 +77,7 @@ const OpenVrcMicMuteSyncQuickSetting = () => {
return (
);
diff --git a/src-ui/app/snackbar_controller/SnackbarController.jsx b/src-ui/app/snackbar_controller/SnackbarController.jsx
index 0b7c9609..e503e695 100644
--- a/src-ui/app/snackbar_controller/SnackbarController.jsx
+++ b/src-ui/app/snackbar_controller/SnackbarController.jsx
@@ -14,6 +14,7 @@ export const SnackbarController = () => {
const snackbar_classname = clsx(styles.snackbar_content, {
[styles.is_success]: currentNotificationStatus.data.status === "success",
+ [styles.is_warning]: currentNotificationStatus.data.status === "warning",
[styles.is_error]: currentNotificationStatus.data.status === "error",
});
diff --git a/src-ui/app/snackbar_controller/SnackbarController.module.scss b/src-ui/app/snackbar_controller/SnackbarController.module.scss
index b0b96ce9..075e4419 100644
--- a/src-ui/app/snackbar_controller/SnackbarController.module.scss
+++ b/src-ui/app/snackbar_controller/SnackbarController.module.scss
@@ -6,6 +6,9 @@
&.is_success {
background-color: var(--success_bc_color);
}
+ &.is_warning {
+ background-color: var(--waring_bc_color);
+ }
&.is_error {
background-color: var(--error_bc_color);
}
diff --git a/src-ui/common_components/checkbox/Checkbox.jsx b/src-ui/common_components/checkbox/Checkbox.jsx
index 50ee51a9..64904243 100644
--- a/src-ui/common_components/checkbox/Checkbox.jsx
+++ b/src-ui/common_components/checkbox/Checkbox.jsx
@@ -3,15 +3,16 @@ import styles from "./Checkbox.module.scss";
export const Checkbox = ({
checkboxId,
variable,
+ is_available = true,
toggleFunction,
size = "2.8rem",
- color = "var(--primary_600_color)",
borderWidth = "0.2rem",
padding = "2rem",
}) => {
const wrapper_class_names = clsx(styles.checkbox_wrapper, {
- [styles.is_disabled]: variable.state === "pending",
+ [styles.is_disabled]: !is_available,
+ [styles.is_pending]: variable.state === "pending",
});
return (
@@ -21,7 +22,6 @@ export const Checkbox = ({
htmlFor={checkboxId}
style={{
"--checkbox-size": size,
- "--checkbox-color": color,
"--checkbox-border-width": borderWidth,
"--checkbox-padding": padding,
}}
diff --git a/src-ui/common_components/checkbox/Checkbox.module.scss b/src-ui/common_components/checkbox/Checkbox.module.scss
index 70f47654..090fed2b 100644
--- a/src-ui/common_components/checkbox/Checkbox.module.scss
+++ b/src-ui/common_components/checkbox/Checkbox.module.scss
@@ -18,12 +18,19 @@
border: var(--checkbox-color, var(--primary_600_color)) solid var(--checkbox-border-width, 0.2rem);
}
}
- &.is_disabled {
+ &.is_pending {
pointer-events: none;
& .cbx {
border-color: var(--primary_800_color);
}
}
+ &.is_disabled {
+ pointer-events: none;
+ & .cbx {
+ filter: grayscale(100%);
+ border-color: var(--dark_800_color);
+ }
+ }
}
.checkbox_wrapper .cbx {
diff --git a/src-ui/logics/common/index.js b/src-ui/logics/common/index.js
index e95bae8f..c03d13eb 100644
--- a/src-ui/logics/common/index.js
+++ b/src-ui/logics/common/index.js
@@ -11,5 +11,6 @@ export { useMessage } from "./useMessage";
export { useUpdateSoftware } from "./useUpdateSoftware";
export { useVolume } from "./useVolume";
export { useHandleNetworkConnection } from "./useHandleNetworkConnection";
+export { useHandleOscQuery } from "./useHandleOscQuery";
export { useIsVrctAvailable } from "./useIsVrctAvailable";
export { useFetch } from "./useFetch";
\ No newline at end of file
diff --git a/src-ui/logics/common/useHandleOscQuery.js b/src-ui/logics/common/useHandleOscQuery.js
new file mode 100644
index 00000000..357df7fd
--- /dev/null
+++ b/src-ui/logics/common/useHandleOscQuery.js
@@ -0,0 +1,44 @@
+import { useTranslation } from "react-i18next";
+import { useNotificationStatus } from "@logics_common";
+import {
+ useEnableVrcMicMuteSync,
+} from "@logics_configs";
+
+export const useHandleOscQuery = () => {
+ const { t } = useTranslation();
+
+ const { showNotification_Warning } = useNotificationStatus();
+ const { updateEnableVrcMicMuteSync } = useEnableVrcMicMuteSync();
+
+ const handleOscQuery = ({is_osc_query_enabled, disabled_functions}) => {
+ if (!is_osc_query_enabled && disabled_functions.length > 0) {
+ const BASE_LABEL = t("common_warning.unable_to_use_osc_query");
+ let items_label = "";
+
+ for (const disabled_function of disabled_functions) {
+ if (disabled_function === "vrc_mic_mute_sync") {
+ updateEnableVrcMicMuteSync({
+ is_enabled: !is_osc_query_enabled,
+ is_available: false,
+ });
+ const item = `- ${t("config_page.others.vrc_mic_mute_sync.label")}`;
+ items_label = `${items_label}\n${item}`;
+ }
+ }
+ const label = `${BASE_LABEL}${items_label}`;
+ showNotification_Warning(
+ label,
+ { hide_duration: 10000, }
+ );
+ } else if (is_osc_query_enabled) {
+ updateEnableVrcMicMuteSync((old_value) => ({
+ ...old_value.data,
+ is_available: true,
+ }));
+ }
+ };
+
+ return {
+ handleOscQuery,
+ };
+};
\ No newline at end of file
diff --git a/src-ui/logics/common/useNotificationStatus.js b/src-ui/logics/common/useNotificationStatus.js
index a5b7bc3c..f4aab3ed 100644
--- a/src-ui/logics/common/useNotificationStatus.js
+++ b/src-ui/logics/common/useNotificationStatus.js
@@ -5,6 +5,16 @@ export const useNotificationStatus = () => {
const generateRandomKey = () => Math.random();
+ const showNotification_Warning = (message, options = {}) => {
+ updateNotificationStatus({
+ status: "warning",
+ is_open: true,
+ key: generateRandomKey(),
+ message: message,
+ options: options,
+ });
+ };
+
const showNotification_Error = (message, options = {}) => {
updateNotificationStatus({
status: "error",
@@ -37,6 +47,7 @@ export const useNotificationStatus = () => {
currentNotificationStatus,
updateNotificationStatus,
+ showNotification_Warning,
showNotification_Error,
showNotification_Success,
closeNotification,
diff --git a/src-ui/logics/configs/others/useEnableVrcMicMuteSync.js b/src-ui/logics/configs/others/useEnableVrcMicMuteSync.js
index 16a853ab..6515ffc2 100644
--- a/src-ui/logics/configs/others/useEnableVrcMicMuteSync.js
+++ b/src-ui/logics/configs/others/useEnableVrcMicMuteSync.js
@@ -12,7 +12,7 @@ export const useEnableVrcMicMuteSync = () => {
const toggleEnableVrcMicMuteSync = () => {
pendingEnableVrcMicMuteSync();
- if (currentEnableVrcMicMuteSync.data) {
+ if (currentEnableVrcMicMuteSync.data.is_enabled) {
asyncStdoutToPython("/set/disable/vrc_mic_mute_sync");
} else {
asyncStdoutToPython("/set/enable/vrc_mic_mute_sync");
diff --git a/src-ui/logics/useReceiveRoutes.js b/src-ui/logics/useReceiveRoutes.js
index 3cde999a..b1d3421b 100644
--- a/src-ui/logics/useReceiveRoutes.js
+++ b/src-ui/logics/useReceiveRoutes.js
@@ -7,6 +7,7 @@ import {
useIsVrctAvailable,
useNotificationStatus,
useHandleNetworkConnection,
+ useHandleOscQuery,
useSoftwareVersion,
useComputeMode,
@@ -83,6 +84,7 @@ export const useReceiveRoutes = () => {
const { updateComputeMode } = useComputeMode();
const { updateInitProgress } = useInitProgress();
const { updateIsBackendReady } = useIsBackendReady();
+ const { handleOscQuery } = useHandleOscQuery();
const { restoreWindowGeometry } = useWindow();
const { updateIsMainPageCompactMode } = useIsMainPageCompactMode();
const {
@@ -219,6 +221,12 @@ export const useReceiveRoutes = () => {
}));
},
"/run/connected_network": handleNetworkConnection,
+ "/run/enable_osc_query": ({data, disabled_functions}) => {
+ handleOscQuery({
+ is_osc_query_enabled: data,
+ disabled_functions: disabled_functions,
+ });
+ },
// Main Page
// Page Controls
@@ -480,9 +488,15 @@ export const useReceiveRoutes = () => {
"/set/enable/logger_feature": updateEnableAutoExportMessageLogs,
"/set/disable/logger_feature": updateEnableAutoExportMessageLogs,
- "/get/data/vrc_mic_mute_sync": updateEnableVrcMicMuteSync,
- "/set/enable/vrc_mic_mute_sync": updateEnableVrcMicMuteSync,
- "/set/disable/vrc_mic_mute_sync": updateEnableVrcMicMuteSync,
+ "/get/data/vrc_mic_mute_sync": (payload) => updateEnableVrcMicMuteSync((old_value) => {
+ return {...old_value.data, is_enabled: payload};
+ }),
+ "/set/enable/vrc_mic_mute_sync": (payload) => updateEnableVrcMicMuteSync((old_value) => {
+ return {...old_value.data, is_enabled: payload};
+ }),
+ "/set/disable/vrc_mic_mute_sync": (payload) => updateEnableVrcMicMuteSync((old_value) => {
+ return {...old_value.data, is_enabled: payload};
+ }),
"/get/data/send_message_to_vrc": updateEnableSendMessageToVrc,
"/set/enable/send_message_to_vrc": updateEnableSendMessageToVrc,
diff --git a/src-ui/store.js b/src-ui/store.js
index f33ebbfc..06fc859a 100644
--- a/src-ui/store.js
+++ b/src-ui/store.js
@@ -60,10 +60,19 @@ export const createAtomWithHook = (initialValue, base_name, options) => {
};
const updateAtom = (payload, options = {}) => {
- const { remain_state = false, set_state } = options;
+ const { remain_state = false, set_state, lock_state } = options;
setAtom((currentValue) => {
- const new_state = set_state ?? (remain_state ? currentValue.state : "ok");
+ let new_state;
+ if (lock_state) {
+ new_state = set_state;
+ } else {
+ if (currentValue.lock_state) {
+ new_state = currentValue.state;
+ } else {
+ new_state = set_state ?? (remain_state ? currentValue.state : "ok");
+ }
+ }
const updated_data = typeof payload === "function"
? payload(currentValue)
From 149c3b10933b51bd2fde2abe6596c556f7dcb552 Mon Sep 17 00:00:00 2001
From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com>
Date: Fri, 30 May 2025 16:36:29 +0900
Subject: [PATCH 24/30] =?UTF-8?q?[bugfix]=20UI:=20OSC=E3=82=AF=E3=82=A8?=
=?UTF-8?q?=E3=83=AA=E7=84=A1=E5=8A=B9=E6=99=82=E3=80=81=E3=83=9F=E3=83=A5?=
=?UTF-8?q?=E3=83=BC=E3=83=88=E5=90=8C=E6=9C=9F=E6=A9=9F=E8=83=BD=E3=81=8C?=
=?UTF-8?q?=E7=84=A1=E5=8A=B9=E3=81=AB=E3=81=AA=E3=82=89=E3=81=AA=E3=81=84?=
=?UTF-8?q?=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3=E3=80=82?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src-ui/logics/common/useHandleOscQuery.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src-ui/logics/common/useHandleOscQuery.js b/src-ui/logics/common/useHandleOscQuery.js
index 357df7fd..2e6b1681 100644
--- a/src-ui/logics/common/useHandleOscQuery.js
+++ b/src-ui/logics/common/useHandleOscQuery.js
@@ -18,7 +18,7 @@ export const useHandleOscQuery = () => {
for (const disabled_function of disabled_functions) {
if (disabled_function === "vrc_mic_mute_sync") {
updateEnableVrcMicMuteSync({
- is_enabled: !is_osc_query_enabled,
+ is_enabled: false,
is_available: false,
});
const item = `- ${t("config_page.others.vrc_mic_mute_sync.label")}`;
From 51c90b3b740807a1cc465fe9ddcba1b7d290dfc9 Mon Sep 17 00:00:00 2001
From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com>
Date: Fri, 30 May 2025 17:04:42 +0900
Subject: [PATCH 25/30] [Update] Config Page: Supporters. Adjust period box
designs.(Add second line.)
---
.../supporters_wrapper/SupportersWrapper.jsx | 48 ++++++++++---------
.../SupportersWrapper.module.scss | 23 ++++++---
2 files changed, 41 insertions(+), 30 deletions(-)
diff --git a/src-ui/app/config_page/setting_section/setting_box/supporters/supporters_container/supporters_wrapper/SupportersWrapper.jsx b/src-ui/app/config_page/setting_section/setting_box/supporters/supporters_container/supporters_wrapper/SupportersWrapper.jsx
index 73209c10..3cfde7f1 100644
--- a/src-ui/app/config_page/setting_section/setting_box/supporters/supporters_container/supporters_wrapper/SupportersWrapper.jsx
+++ b/src-ui/app/config_page/setting_section/setting_box/supporters/supporters_container/supporters_wrapper/SupportersWrapper.jsx
@@ -272,30 +272,32 @@ const SupporterPeriodContainer = ({ settings, calc_support_period }) => {
return (
- {Object.entries(period_data).map(([key, item], index) => {
- if (item === "") return null;
- const period_box_class_name = clsx(styles.period_box, {
- [styles.mogu_bar]: item === "mogu_2000",
- [styles.mochi_bar]: item === "mochi_1000",
- [styles.fuwa_bar]: item === "fuwa_500",
- [styles.basic_bar]: item === "basic_300",
- });
+
+ {Object.entries(period_data).map(([key, item], index) => {
+ if (item === "") return null;
+ const period_box_class_name = clsx(styles.period_box, {
+ [styles.mogu_bar]: item === "mogu_2000",
+ [styles.mochi_bar]: item === "mochi_1000",
+ [styles.fuwa_bar]: item === "fuwa_500",
+ [styles.basic_bar]: item === "basic_300",
+ });
- return (
-
{key}
- }
- placement="top"
- slotProps={offset}
- >
-
-
- );
- })}
+ return (
+
{key}
+ }
+ placement="top"
+ slotProps={offset}
+ >
+
+
+ );
+ })}
+
);
};
diff --git a/src-ui/app/config_page/setting_section/setting_box/supporters/supporters_container/supporters_wrapper/SupportersWrapper.module.scss b/src-ui/app/config_page/setting_section/setting_box/supporters/supporters_container/supporters_wrapper/SupportersWrapper.module.scss
index c0c35d76..3d392642 100644
--- a/src-ui/app/config_page/setting_section/setting_box/supporters/supporters_container/supporters_wrapper/SupportersWrapper.module.scss
+++ b/src-ui/app/config_page/setting_section/setting_box/supporters/supporters_container/supporters_wrapper/SupportersWrapper.module.scss
@@ -13,7 +13,7 @@
align-content: start;
flex-wrap: wrap;
column-gap: 1.8rem;
- row-gap: 0.4rem;
+ row-gap: 2rem;
}
.supporter_image_container {
@@ -209,13 +209,24 @@ $progress_ease: cubic-bezier(0, 1, 0.75, 1);
.supporter_period_container {
+ position: absolute;
+ top: 100%;
+ left: 0;
+}
+.supporter_period_wrapper {
display: flex;
- gap: 0.2rem;
- padding-left: 0.4rem;
+ gap: 0.4rem 0.2rem;
+ flex-shrink: 0;
+ flex-wrap: wrap;
+ padding: 0.3rem 0.4rem 0.4rem 0.4rem;
+}
+
+.period_box_wrapper {
+ flex-shrink: 0;
}
.period_box {
- width: 1.8rem;
+ width: 1.7rem;
height: 0.3rem;
border-radius: 0.3rem;
&.mogu_bar {
@@ -231,9 +242,7 @@ $progress_ease: cubic-bezier(0, 1, 0.75, 1);
background-color: var(--dark_800_color);
}
}
-.period_box_wrapper {
- padding: 0.3rem 0 0.4rem 0;
-}
+
.tooltip_period_label {
font-size: 1.4rem;
}
From 32e4ec0682f3fbc5d5ee7b580d3558cbe9ccbddb Mon Sep 17 00:00:00 2001
From: misyaguziya <53165965+misyaguziya@users.noreply.github.com>
Date: Sat, 31 May 2025 04:09:28 +0900
Subject: [PATCH 26/30] [Bugfix] Controller: Fixed degradation that occurred
with commit hash: d940097.
---
src-python/controller.py | 36 +++++++++++++++++++++++++++++++++++-
1 file changed, 35 insertions(+), 1 deletion(-)
diff --git a/src-python/controller.py b/src-python/controller.py
index 080df101..89a07d3a 100644
--- a/src-python/controller.py
+++ b/src-python/controller.py
@@ -1985,7 +1985,41 @@ class Controller:
printLog("Init Translation Engine Status")
for engine in config.SELECTABLE_TRANSLATION_ENGINE_LIST:
- config.SELECTABLE_TRANSCRIPTION_ENGINE_STATUS[engine] = False
+ match engine:
+ 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)
# set Translation Engine
From 09865d02beaa7c9ec1c9417c69d3484cb559b6eb Mon Sep 17 00:00:00 2001
From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com>
Date: Mon, 2 Jun 2025 12:17:00 +0900
Subject: [PATCH 27/30] [Update] Config Page: VR: Add adjust buttons to each
position and rotation sliders for become much easier handling on VR
controller.
---
.../setting_box/_components/slider/Slider.jsx | 115 +++++++---
.../setting_section/setting_box/vr/Vr.jsx | 205 ++++++++++++++++--
.../setting_box/vr/Vr.module.scss | 120 ++++++++--
src-ui/ui_configs.js | 6 +
4 files changed, 388 insertions(+), 58 deletions(-)
diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/slider/Slider.jsx b/src-ui/app/config_page/setting_section/setting_box/_components/slider/Slider.jsx
index 8f61d725..827c0253 100644
--- a/src-ui/app/config_page/setting_section/setting_box/_components/slider/Slider.jsx
+++ b/src-ui/app/config_page/setting_section/setting_box/_components/slider/Slider.jsx
@@ -4,44 +4,107 @@ import MUI_Slider from "@mui/material/Slider";
import clsx from "clsx";
export const Slider = (props) => {
+ const location = props.valueLabelDisplayLocation || "top";
+
+ const sliderSx = {
+ color: "var(--dark_700_color)",
+ "& .MuiSlider-thumb": {
+ backgroundColor: "var(--primary_600_color)",
+ "&:hover, &.Mui-focusVisible, &.Mui-active": {
+ boxShadow: `0 0 0 0.8rem var(--primary_600_color_44)`,
+ },
+ "& .MuiSlider-valueLabel": {
+ position: "absolute",
+ backgroundColor: "var(--dark_800_color)",
+ width: "fit-content",
+ minWidth: "4.8rem",
+ padding: "0.4rem 0.8rem",
+ lineHeight: "1.15",
+ "& .MuiSlider-valueLabelLabel": {
+ fontSize: "1.4rem",
+ },
+ ...(location === "top" && {
+ top: "-110%",
+ left: "50%",
+ transform: "translate(-50%, -50%) scale(0)",
+ transformOrigin: "bottom center",
+ "&.MuiSlider-valueLabelOpen": {
+ transform: "translate(-50%, -50%) scale(1)",
+ },
+ "&::before": {
+ bottom: "0%",
+ left: "50%",
+ },
+ }),
+ ...(location === "right" && {
+ top: "50%",
+ left: "150%",
+ transform: "translate(0, -50%) scale(0)",
+ transformOrigin: "left center",
+ "&.MuiSlider-valueLabelOpen": {
+ transform: "translate(0, -50%) scale(1)",
+ },
+ "&::before": {
+ bottom: "50%",
+ left: "0",
+ },
+ }),
+ ...(location === "left" && {
+ // top: "50%",
+ // right: "50%",
+ // transform: "translate(-50%, -50%) scale(0)",
+ // transformOrigin: "bottom center",
+ // "&.MuiSlider-valueLabelOpen": {
+ // transform: "translate(-50%, -50%) scale(1)",
+ // },
+ // "&::before": {
+ // bottom: "50%",
+ // left: "100%",
+ // },
+ }),
+ },
+ },
+ "& .MuiSlider-markLabel": {
+ fontSize: "1.4rem",
+ color: "var(--dark_550_color)",
+ whiteSpace: "nowrap",
+ },
+ "& .MuiSlider-markLabelActive": {
+ color: "var(--primary_300_color)",
+ },
+ };
+
return (
-
+
props.onchangeFunction(value)}
- onChangeCommitted={(_e, value) => props.onchangeCommittedFunction ? props.onchangeCommittedFunction(value) : null}
+ onChangeCommitted={(_e, value) =>
+ props.onchangeCommittedFunction ? props.onchangeCommittedFunction(value) : null
+ }
+ onMouseEnter={(event) =>
+ props.onMouseEnterFunction ? props.onMouseEnterFunction(event) : null
+ }
+ onMouseLeave={(event) =>
+ props.onMouseLeaveFunction ? props.onMouseLeaveFunction(event) : null
+ }
marks={props.marks}
track={props.track}
orientation={props.orientation}
valueLabelFormat={`${props.valueLabelFormat ? props.valueLabelFormat : props.variable}`}
- sx={{
- color: "var(--dark_700_color)",
- "& .MuiSlider-thumb": {
- backgroundColor: "var(--primary_600_color)",
- "&:hover, &.Mui-focusVisible, &.Mui-active": {
- boxShadow: `0 0 0 0.8rem var(--primary_600_color_44)`,
- },
- "& .MuiSlider-valueLabel": {
- fontSize: "1.4rem",
- backgroundColor: "var(--dark_800_color)",
- padding: "0.6rem 1rem",
- lineHeight: "1.15",
- },
- },
- "& .MuiSlider-markLabel": {
- fontSize: "1.4rem",
- color: "var(--dark_550_color)",
- whiteSpace: "nowrap",
- },
- "& .MuiSlider-markLabelActive": {
- color: "var(--primary_300_color)",
- },
- }}
+ sx={sliderSx}
/>
);
diff --git a/src-ui/app/config_page/setting_section/setting_box/vr/Vr.jsx b/src-ui/app/config_page/setting_section/setting_box/vr/Vr.jsx
index 06920712..4c70e61c 100644
--- a/src-ui/app/config_page/setting_section/setting_box/vr/Vr.jsx
+++ b/src-ui/app/config_page/setting_section/setting_box/vr/Vr.jsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect } from "react";
+import React, { useState, useEffect, useRef } from "react";
import { useTranslation } from "react-i18next";
import clsx from "clsx";
import styles from "./Vr.module.scss";
@@ -147,7 +147,7 @@ const OverlaySettingsContainer = ({
) : (
)}
-
+
{
);
};
-const PositionControls = ({settings, onchangeFunction, selectFunction, ui_configs, default_ui_configs}) => {
+
+/**
+ * PositionControls:
+ * - x_pos, y_pos, z_pos 用のスライダーとリセットボタン、上下ボタンを表示
+ * - useVariableControl を使って、is_max/is_min/variable_display/countUp/countDown を共通化
+ */
+const PositionControls = ({ settings, onchangeFunction, selectFunction, ui_configs, default_ui_configs }) => {
const { t } = useTranslation();
+ // x_pos 用のフック呼び出し
+ const {
+ variable_display: x_variable_display,
+ is_max: is_max_position_x,
+ is_min: is_min_position_x,
+ countUp: countUpPositionX,
+ countDown: countDownPositionX,
+ } = useVariableControl("x_pos", settings, onchangeFunction, ui_configs);
+
+ // y_pos 用のフック呼び出し
+ const {
+ variable_display: y_variable_display,
+ is_max: is_max_position_y,
+ is_min: is_min_position_y,
+ countUp: countUpPositionY,
+ countDown: countDownPositionY,
+ } = useVariableControl("y_pos", settings, onchangeFunction, ui_configs);
+
+ // z_pos 用のフック呼び出し
+ const {
+ variable_display: z_variable_display,
+ is_max: is_max_position_z,
+ is_min: is_min_position_z,
+ countUp: countUpPositionZ,
+ countDown: countDownPositionZ,
+ } = useVariableControl("z_pos", settings, onchangeFunction, ui_configs);
+
+ // 各ボタンの className を生成
+ const x_position_button_up_classname = clsx(styles.x_position_button_up, { [styles.is_disabled]: is_max_position_x });
+ const x_position_button_down_classname = clsx(styles.x_position_button_down, { [styles.is_disabled]: is_min_position_x });
+
+ const y_position_button_up_classname = clsx(styles.y_position_button_up, { [styles.is_disabled]: is_max_position_y });
+ const y_position_button_down_classname = clsx(styles.y_position_button_down, { [styles.is_disabled]: is_min_position_y });
+
+ const z_position_button_up_classname = clsx(styles.z_position_button_up, { [styles.is_disabled]: is_max_position_z });
+ const z_position_button_down_classname = clsx(styles.z_position_button_down, { [styles.is_disabled]: is_min_position_z });
+
return (
+ {/* X Position */}
{t("config_page.vr.x_position")}
@@ -205,8 +249,16 @@ const PositionControls = ({settings, onchangeFunction, selectFunction, ui_config
min={ui_configs.x_pos.min}
max={ui_configs.x_pos.max}
onchangeFunction={(value) => onchangeFunction("x_pos", value)}
+ valueLabelDisplay={x_variable_display}
+ valueLabelDisplayLocation="top"
/>
+
+
+ {/* Y Position */}
{t("config_page.vr.y_position")}
@@ -221,8 +273,16 @@ const PositionControls = ({settings, onchangeFunction, selectFunction, ui_config
max={ui_configs.y_pos.max}
onchangeFunction={(value) => onchangeFunction("y_pos", value)}
orientation="vertical"
+ valueLabelDisplay={y_variable_display}
+ valueLabelDisplayLocation="right"
/>
+
+
+ {/* Z Position */}
{t("config_page.vr.z_position")}
@@ -237,69 +297,127 @@ const PositionControls = ({settings, onchangeFunction, selectFunction, ui_config
max={ui_configs.z_pos.max}
onchangeFunction={(value) => onchangeFunction("z_pos", value)}
orientation="vertical"
+ valueLabelDisplay={z_variable_display}
+ valueLabelDisplayLocation="left"
/>
+
);
};
-const RotationControls = ({settings, onchangeFunction, selectFunction, default_ui_configs}) => {
+const RotationControls = ({settings, onchangeFunction, selectFunction, ui_configs, default_ui_configs}) => {
const { t } = useTranslation();
+ const {
+ variable_display: x_variable_display,
+ is_max: is_max_rotation_x,
+ is_min: is_min_rotation_x,
+ countUp: countUpRotationX,
+ countDown: countDownRotationX,
+ } = useVariableControl("x_rotation", settings, onchangeFunction, ui_configs);
+
+ const {
+ variable_display: y_variable_display,
+ is_max: is_max_rotation_y,
+ is_min: is_min_rotation_y,
+ countUp: countUpRotationY,
+ countDown: countDownRotationY,
+ } = useVariableControl("y_rotation", settings, onchangeFunction, ui_configs);
+
+ const {
+ variable_display: z_variable_display,
+ is_max: is_max_rotation_z,
+ is_min: is_min_rotation_z,
+ countUp: countUpRotationZ,
+ countDown: countDownRotationZ,
+ } = useVariableControl("z_rotation", settings, onchangeFunction, ui_configs);
+
+ const x_rotation_button_up_classname = clsx(styles.x_rotation_button_up, { [styles.is_disabled]: is_min_rotation_x });
+ const x_rotation_button_down_classname = clsx(styles.x_rotation_button_down, { [styles.is_disabled]: is_max_rotation_x });
+
+ const y_rotation_button_up_classname = clsx(styles.y_rotation_button_up, { [styles.is_disabled]: is_max_rotation_y });
+ const y_rotation_button_down_classname = clsx(styles.y_rotation_button_down, { [styles.is_disabled]: is_min_rotation_y });
+
+ const z_rotation_button_up_classname = clsx(styles.z_rotation_button_up, { [styles.is_disabled]: is_max_rotation_z });
+ const z_rotation_button_down_classname = clsx(styles.z_rotation_button_down, { [styles.is_disabled]: is_min_rotation_z });
+
return (
{t("config_page.vr.x_rotation")}
- selectFunction("x_rotation", default_ui_configs.y_pos)} />
+ selectFunction("x_rotation", default_ui_configs.x_rotation)} />
onchangeFunction("x_rotation", -value)}
orientation="vertical"
+ valueLabelDisplay={x_variable_display}
+ valueLabelDisplayLocation="right"
/>
+
{t("config_page.vr.y_rotation")}
- selectFunction("y_rotation", default_ui_configs.y_pos)} />
+ selectFunction("y_rotation", default_ui_configs.y_rotation)} />
onchangeFunction("y_rotation", value)}
+ valueLabelDisplay={y_variable_display}
+ valueLabelDisplayLocation="top"
/>
+
{t("config_page.vr.z_rotation")}
- selectFunction("z_rotation", default_ui_configs.y_pos)} />
+ selectFunction("z_rotation", default_ui_configs.z_rotation)} />
onchangeFunction("z_rotation", value)}
orientation="vertical"
+ valueLabelDisplay={z_variable_display}
+ valueLabelDisplayLocation="left"
/>
+
);
};
+
const OtherControls = ({settings, onchangeFunction, ui_configs}) => {
const { t } = useTranslation();
@@ -445,4 +563,59 @@ const SendSampleTextToggleButton = () => {
{label}
);
+};
+
+
+
+const useVariableControl = (key, settings, onchangeFunction, ui_configs) => {
+ const [variable_display, setVariableDisplay] = useState("auto");
+
+ const [is_max, setIsMax] = useState(settings[key] >= ui_configs[key].max);
+ const [is_min, setIsMin] = useState(settings[key] <= ui_configs[key].min);
+
+ const timerRef = useRef();
+
+ // アンマウント時にタイマーをクリアする
+ useEffect(() => {
+ return () => {
+ clearTimeout(timerRef.current);
+ };
+ }, []);
+
+ const triggerDisplay = () => {
+ setVariableDisplay("on");
+ clearTimeout(timerRef.current);
+ timerRef.current = setTimeout(() => {
+ setVariableDisplay("auto");
+ }, 2000);
+ };
+
+ useEffect(() => {
+ setIsMax(settings[key] >= ui_configs[key].max);
+ setIsMin(settings[key] <= ui_configs[key].min);
+ }, [settings[key]]);
+
+ const countUp = () => {
+ if (is_max) return;
+ const step = ui_configs[key].step;
+ const new_value = parseFloat((settings[key] + step).toFixed(2));
+ onchangeFunction(key, new_value);
+ triggerDisplay();
+ };
+
+ const countDown = () => {
+ if (is_min) return;
+ const step = ui_configs[key].step;
+ const new_value = parseFloat((settings[key] - step).toFixed(2));
+ onchangeFunction(key, new_value);
+ triggerDisplay();
+ };
+
+ return {
+ variable_display,
+ is_max,
+ is_min,
+ countUp,
+ countDown,
+ };
};
\ No newline at end of file
diff --git a/src-ui/app/config_page/setting_section/setting_box/vr/Vr.module.scss b/src-ui/app/config_page/setting_section/setting_box/vr/Vr.module.scss
index 35798530..eb37c7ea 100644
--- a/src-ui/app/config_page/setting_section/setting_box/vr/Vr.module.scss
+++ b/src-ui/app/config_page/setting_section/setting_box/vr/Vr.module.scss
@@ -16,7 +16,7 @@
justify-content: center;
width: 100%;
max-width: 56rem;
- gap: 2rem;
+ gap: 4rem;
}
.controller_type_switch {
@@ -58,12 +58,13 @@
.sample_text_button_wrapper {
position: absolute;
- bottom: 0;
- left: -74%;
+ bottom: -12%;
+ left: -80%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
+ // transform: translate(-50%, -50%);
}
.sample_text_button {
background-color: var(--dark_850_color);
@@ -121,20 +122,20 @@
}
.x_position_label {
position: absolute;
- bottom: -4.6rem;
+ bottom: -5rem;
right: -46%;
justify-content: end;
}
.y_position_label {
position: absolute;
- top: -44%;
- left: 10%;
- justify-content: start;
+ bottom: 110%;
+ right: 119%;
+ justify-content: end;
}
.z_position_label {
position: absolute;
- top: 30%;
- left: 80%;
+ top: 14%;
+ left: 110%;
}
.x_position_slider {
@@ -155,14 +156,72 @@
.z_position_slider {
position: absolute;
- bottom: 61%;
- left: 61%;
+ bottom: 80%;
+ left: 88%;
transform: translate(50%,50%) rotate(45deg);
width: 0%;
height: 100%;
}
+%variable-button {
+ width: 3.8rem;
+ border-radius: 0.4rem;
+ aspect-ratio: 1.2 / 1;
+ background-color: var(--dark_850_color);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-size: 1.6rem;
+ cursor: pointer;
+
+ &:hover {
+ background-color: var(--primary_500_color);
+ }
+ &:active {
+ background-color: var(--primary_600_color);
+ }
+ &.is_disabled {
+ pointer-events: none;
+ background-color: var(--dark_875_color);
+ color: var(--dark_600_color);
+ }
+}
+
+@mixin variable-button-wrapper($vertical-pos, $vertical-value, $horizontal-pos, $horizontal-value, $rotate: 0deg) {
+ position: absolute;
+ #{$vertical-pos}: $vertical-value;
+ #{$horizontal-pos}: $horizontal-value;
+ display: flex;
+ gap: 1.6rem;
+ flex-direction: column;
+ transform: translate(-50%) rotate($rotate);
+}
+
+.y_position_button_wrapper {
+ @include variable-button-wrapper(top, 30%, left, -26%);
+}
+.y_position_button_up,
+.y_position_button_down {
+ @extend %variable-button;
+}
+
+.x_position_button_wrapper {
+ @include variable-button-wrapper(bottom, -38%, left, 46%, 90deg);
+}
+.x_position_button_up,
+.x_position_button_down {
+ @extend %variable-button;
+}
+
+.z_position_button_wrapper {
+ @include variable-button-wrapper(bottom, 26%, right, -4%, 45deg);
+}
+.z_position_button_up,
+.z_position_button_down {
+ @extend %variable-button;
+}
+
// .rotation_controls {
@@ -175,19 +234,20 @@
.x_rotation_label {
position: absolute;
- top: -44%;
- left: 10%;
+ bottom: 110%;
+ right: 119%;
+ justify-content: end;
}
.y_rotation_label {
position: absolute;
- bottom: -4.6rem;
+ bottom: -5rem;
right: -46%;
justify-content: end;
}
.z_rotation_label {
position: absolute;
- top: -10%;
- right: -110%;
+ top: -20%;
+ right: -100%;
}
.x_rotation_slider {
@@ -216,6 +276,34 @@
}
+
+.x_rotation_button_wrapper {
+ @include variable-button-wrapper(top, 30%, left, -26%);
+}
+.x_rotation_button_up,
+.x_rotation_button_down {
+ @extend %variable-button;
+}
+
+.y_rotation_button_wrapper {
+ @include variable-button-wrapper(bottom, -38%, left, 46%, 90deg);
+}
+.y_rotation_button_up,
+.y_rotation_button_down {
+ @extend %variable-button;
+}
+
+.z_rotation_button_wrapper {
+ @include variable-button-wrapper(bottom, 50%, right, -60%, -45deg);
+}
+.z_rotation_button_up,
+.z_rotation_button_down {
+ @extend %variable-button;
+}
+
+
+
+
.slider_reset_button {
background-color: var(--dark_875_color);
padding: 0.6rem;
diff --git a/src-ui/ui_configs.js b/src-ui/ui_configs.js
index d6ef28eb..915d6a6b 100644
--- a/src-ui/ui_configs.js
+++ b/src-ui/ui_configs.js
@@ -7,12 +7,18 @@ export const ui_configs = {
x_pos: { step: 0.05, min: -0.5, max: 0.5 },
y_pos: { step: 0.05, min: -0.8, max: 0.8 },
z_pos: { step: 0.05, min: -0.5, max: 1.5 },
+ x_rotation: { min: -180, max: 180, step: 5 },
+ y_rotation: { min: -180, max: 180, step: 5 },
+ z_rotation: { min: -180, max: 180, step: 5 },
ui_scaling: { step: 10, min: 40, max: 200 },
},
overlay_large_log: {
x_pos: { step: 0.05, min: -0.5, max: 0.5 },
y_pos: { step: 0.05, min: -0.8, max: 0.8 },
z_pos: { step: 0.05, min: -0.5, max: 1.5 },
+ x_rotation: { min: -180, max: 180, step: 5 },
+ y_rotation: { min: -180, max: 180, step: 5 },
+ z_rotation: { min: -180, max: 180, step: 5 },
ui_scaling: { step: 10, min: 40, max: 200 },
},
From 2ea0a3b5291ee096a34ccd2ef2fa28c24963ae77 Mon Sep 17 00:00:00 2001
From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com>
Date: Mon, 2 Jun 2025 13:33:00 +0900
Subject: [PATCH 28/30] [Update/Refactor] Config Page: VR: Change triangle icon
from symbol to svg. Refactor the common component and function.
---
.../setting_section/setting_box/vr/Vr.jsx | 141 ++++++++++--------
.../setting_box/vr/Vr.module.scss | 51 ++++---
2 files changed, 105 insertions(+), 87 deletions(-)
diff --git a/src-ui/app/config_page/setting_section/setting_box/vr/Vr.jsx b/src-ui/app/config_page/setting_section/setting_box/vr/Vr.jsx
index 4c70e61c..b145bb44 100644
--- a/src-ui/app/config_page/setting_section/setting_box/vr/Vr.jsx
+++ b/src-ui/app/config_page/setting_section/setting_box/vr/Vr.jsx
@@ -25,6 +25,10 @@ import {
import RedoSvg from "@images/redo.svg?react";
+import SquareSvg from "@images/square.svg?react";
+import TriangleSvg from "@images/triangle.svg?react";
+import { randomIntMinMax } from "@utils";
+
export const Vr = () => {
const { t } = useTranslation();
const [is_opened_small_settings, setIsOpenedSmallSettings] = useState(true);
@@ -188,15 +192,9 @@ const PageSwitcherContainer = (props) => {
};
-/**
- * PositionControls:
- * - x_pos, y_pos, z_pos 用のスライダーとリセットボタン、上下ボタンを表示
- * - useVariableControl を使って、is_max/is_min/variable_display/countUp/countDown を共通化
- */
-const PositionControls = ({ settings, onchangeFunction, selectFunction, ui_configs, default_ui_configs }) => {
+export const PositionControls = ({ settings, onchangeFunction, selectFunction, ui_configs, default_ui_configs }) => {
const { t } = useTranslation();
- // x_pos 用のフック呼び出し
const {
variable_display: x_variable_display,
is_max: is_max_position_x,
@@ -205,7 +203,6 @@ const PositionControls = ({ settings, onchangeFunction, selectFunction, ui_confi
countDown: countDownPositionX,
} = useVariableControl("x_pos", settings, onchangeFunction, ui_configs);
- // y_pos 用のフック呼び出し
const {
variable_display: y_variable_display,
is_max: is_max_position_y,
@@ -214,7 +211,6 @@ const PositionControls = ({ settings, onchangeFunction, selectFunction, ui_confi
countDown: countDownPositionY,
} = useVariableControl("y_pos", settings, onchangeFunction, ui_configs);
- // z_pos 用のフック呼び出し
const {
variable_display: z_variable_display,
is_max: is_max_position_z,
@@ -223,19 +219,8 @@ const PositionControls = ({ settings, onchangeFunction, selectFunction, ui_confi
countDown: countDownPositionZ,
} = useVariableControl("z_pos", settings, onchangeFunction, ui_configs);
- // 各ボタンの className を生成
- const x_position_button_up_classname = clsx(styles.x_position_button_up, { [styles.is_disabled]: is_max_position_x });
- const x_position_button_down_classname = clsx(styles.x_position_button_down, { [styles.is_disabled]: is_min_position_x });
-
- const y_position_button_up_classname = clsx(styles.y_position_button_up, { [styles.is_disabled]: is_max_position_y });
- const y_position_button_down_classname = clsx(styles.y_position_button_down, { [styles.is_disabled]: is_min_position_y });
-
- const z_position_button_up_classname = clsx(styles.z_position_button_up, { [styles.is_disabled]: is_max_position_z });
- const z_position_button_down_classname = clsx(styles.z_position_button_down, { [styles.is_disabled]: is_min_position_z });
-
return (
- {/* X Position */}
{t("config_page.vr.x_position")}
@@ -252,13 +237,15 @@ const PositionControls = ({ settings, onchangeFunction, selectFunction, ui_confi
valueLabelDisplay={x_variable_display}
valueLabelDisplayLocation="top"
/>
-
+
- {/* Y Position */}
{t("config_page.vr.y_position")}
@@ -276,13 +263,15 @@ const PositionControls = ({ settings, onchangeFunction, selectFunction, ui_confi
valueLabelDisplay={y_variable_display}
valueLabelDisplayLocation="right"
/>
-
+
- {/* Z Position */}
{t("config_page.vr.z_position")}
@@ -300,16 +289,19 @@ const PositionControls = ({ settings, onchangeFunction, selectFunction, ui_confi
valueLabelDisplay={z_variable_display}
valueLabelDisplayLocation="left"
/>
-
+
);
};
-const RotationControls = ({settings, onchangeFunction, selectFunction, ui_configs, default_ui_configs}) => {
+export const RotationControls = ({ settings, onchangeFunction, selectFunction, ui_configs, default_ui_configs }) => {
const { t } = useTranslation();
const {
@@ -336,15 +328,6 @@ const RotationControls = ({settings, onchangeFunction, selectFunction, ui_config
countDown: countDownRotationZ,
} = useVariableControl("z_rotation", settings, onchangeFunction, ui_configs);
- const x_rotation_button_up_classname = clsx(styles.x_rotation_button_up, { [styles.is_disabled]: is_min_rotation_x });
- const x_rotation_button_down_classname = clsx(styles.x_rotation_button_down, { [styles.is_disabled]: is_max_rotation_x });
-
- const y_rotation_button_up_classname = clsx(styles.y_rotation_button_up, { [styles.is_disabled]: is_max_rotation_y });
- const y_rotation_button_down_classname = clsx(styles.y_rotation_button_down, { [styles.is_disabled]: is_min_rotation_y });
-
- const z_rotation_button_up_classname = clsx(styles.z_rotation_button_up, { [styles.is_disabled]: is_max_rotation_z });
- const z_rotation_button_down_classname = clsx(styles.z_rotation_button_down, { [styles.is_disabled]: is_min_rotation_z });
-
return (
@@ -365,11 +348,15 @@ const RotationControls = ({settings, onchangeFunction, selectFunction, ui_config
valueLabelDisplay={x_variable_display}
valueLabelDisplayLocation="right"
/>
-
+
+
{t("config_page.vr.y_rotation")}
@@ -386,11 +373,15 @@ const RotationControls = ({settings, onchangeFunction, selectFunction, ui_config
valueLabelDisplay={y_variable_display}
valueLabelDisplayLocation="top"
/>
-
+
+
{t("config_page.vr.z_rotation")}
@@ -408,10 +399,43 @@ const RotationControls = ({settings, onchangeFunction, selectFunction, ui_config
valueLabelDisplay={z_variable_display}
valueLabelDisplayLocation="left"
/>
-
+
+
+
+ );
+};
+
+const AdjustButtonContainer = ({ wrapper_class_name, is_max, is_min, countUp, countDown }) => {
+ return (
+
);
@@ -511,10 +535,6 @@ const ResetButton = ({onClickFunction}) => {
);
};
-import SquareSvg from "@images/square.svg?react";
-import TriangleSvg from "@images/triangle.svg?react";
-import { randomIntMinMax } from "@utils";
-
const SendSampleTextToggleButton = () => {
const { t } = useTranslation();
const { sendTextToOverlay } = useSendTextToOverlay();
@@ -575,7 +595,6 @@ const useVariableControl = (key, settings, onchangeFunction, ui_configs) => {
const timerRef = useRef();
- // アンマウント時にタイマーをクリアする
useEffect(() => {
return () => {
clearTimeout(timerRef.current);
diff --git a/src-ui/app/config_page/setting_section/setting_box/vr/Vr.module.scss b/src-ui/app/config_page/setting_section/setting_box/vr/Vr.module.scss
index eb37c7ea..9d0963e8 100644
--- a/src-ui/app/config_page/setting_section/setting_box/vr/Vr.module.scss
+++ b/src-ui/app/config_page/setting_section/setting_box/vr/Vr.module.scss
@@ -184,7 +184,9 @@
&.is_disabled {
pointer-events: none;
background-color: var(--dark_875_color);
- color: var(--dark_600_color);
+ & .adjust_button_triangle_svg {
+ color: var(--dark_800_color);
+ }
}
}
@@ -198,29 +200,39 @@
transform: translate(-50%) rotate($rotate);
}
+.button_wrapper {
+ @extend %variable-button;
+
+ &.up .adjust_button_triangle_svg {
+ transform: rotate(0deg);
+ }
+ &:not(.up) .adjust_button_triangle_svg {
+ transform: rotate(180deg);
+ }
+ &.is_disabled {
+ pointer-events: none;
+ color: var(--dark_875_color);
+ }
+}
+
+.adjust_button_triangle_svg {
+ width: 1.8rem;
+ color: var(--dark_400_color);
+}
+
+
+
.y_position_button_wrapper {
@include variable-button-wrapper(top, 30%, left, -26%);
}
-.y_position_button_up,
-.y_position_button_down {
- @extend %variable-button;
-}
.x_position_button_wrapper {
@include variable-button-wrapper(bottom, -38%, left, 46%, 90deg);
}
-.x_position_button_up,
-.x_position_button_down {
- @extend %variable-button;
-}
.z_position_button_wrapper {
@include variable-button-wrapper(bottom, 26%, right, -4%, 45deg);
}
-.z_position_button_up,
-.z_position_button_down {
- @extend %variable-button;
-}
@@ -280,27 +292,14 @@
.x_rotation_button_wrapper {
@include variable-button-wrapper(top, 30%, left, -26%);
}
-.x_rotation_button_up,
-.x_rotation_button_down {
- @extend %variable-button;
-}
.y_rotation_button_wrapper {
@include variable-button-wrapper(bottom, -38%, left, 46%, 90deg);
}
-.y_rotation_button_up,
-.y_rotation_button_down {
- @extend %variable-button;
-}
.z_rotation_button_wrapper {
@include variable-button-wrapper(bottom, 50%, right, -60%, -45deg);
}
-.z_rotation_button_up,
-.z_rotation_button_down {
- @extend %variable-button;
-}
-
From cda42a0901add7ffef20420be1701eb2fca6a641 Mon Sep 17 00:00:00 2001
From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com>
Date: Mon, 2 Jun 2025 14:52:33 +0900
Subject: [PATCH 29/30] =?UTF-8?q?[Update]=20Change=20labels=20Japanese:=20?=
=?UTF-8?q?Main=20Page:=20Main=20Functions.=20=E9=9F=B3=E5=A3=B0=E8=AA=8D?=
=?UTF-8?q?=E8=AD=98=E2=97=AF=E2=97=AF=20->=20=E3=83=9E=E3=82=A4=E3=82=AF?=
=?UTF-8?q?=E5=85=A5=E5=8A=9B,=20=E8=81=9E=E3=81=8D=E5=8F=96=E3=82=8A.=20a?=
=?UTF-8?q?nd=20change=20some=20labels.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
locales/ja.yml | 6 +++---
src-ui/app/modal_controller/update_modal/UpdateModal.jsx | 4 ++--
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/locales/ja.yml b/locales/ja.yml
index b8d70695..a9a6901b 100644
--- a/locales/ja.yml
+++ b/locales/ja.yml
@@ -29,8 +29,8 @@ common_warning:
main_page:
translation: "翻訳"
- transcription_send: "音声認識 マイク"
- transcription_receive: "音声認識 スピーカー"
+ transcription_send: "マイク入力"
+ transcription_receive: "聞き取り"
foreground: "最前面固定"
language_settings: "言語設定"
@@ -134,7 +134,7 @@ config_page:
ctranslate2_compute_device:
label: "AI翻訳 {{ctranslate2}} の処理デバイス"
deepl_auth_key:
- label: "DeepL API 認証キー"
+ label: "DeepL APIキーの登録"
desc: "使用の際は、メイン画面にある {{translator}} をDeepL_APIに変更してください。\n※対応していない言語もあります。"
open_auth_key_webpage: "DeepLアカウントページを開く"
save: "保存"
diff --git a/src-ui/app/modal_controller/update_modal/UpdateModal.jsx b/src-ui/app/modal_controller/update_modal/UpdateModal.jsx
index c35a240c..dd8f0f43 100644
--- a/src-ui/app/modal_controller/update_modal/UpdateModal.jsx
+++ b/src-ui/app/modal_controller/update_modal/UpdateModal.jsx
@@ -59,7 +59,7 @@ export const UpdateModal = () => {
-
+
{!is_cpu_version ? : null}
@@ -85,7 +85,7 @@ const VersionDescComponent = (props) => {
return (
-
{props.desc}
+
{`- ${props.desc}`}
);
};
From 2118ec9d0149b14b209b9ab1a10f0fa683595612 Mon Sep 17 00:00:00 2001
From: misyaguziya <53165965+misyaguziya@users.noreply.github.com>
Date: Tue, 3 Jun 2025 11:08:31 +0900
Subject: [PATCH 30/30] [Update] Version 3.1.2 -> 3.2.0
---
src-python/config.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src-python/config.py b/src-python/config.py
index cfeeec51..64c09440 100644
--- a/src-python/config.py
+++ b/src-python/config.py
@@ -989,7 +989,7 @@ class Config:
def init_config(self):
# Read Only
- self._VERSION = "3.1.2"
+ self._VERSION = "3.2.0"
if getattr(sys, 'frozen', False):
self._PATH_LOCAL = os_path.dirname(sys.executable)
else: