Files
posefit-server/app/audio/rep_announcer.py
T

91 lines
3.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
import queue
import subprocess
import sys
import threading
from typing import Any
from loguru import logger
class RepAnnouncer:
"""运动次数语音播报器"""
def __init__(self, *, enabled: bool = True, rate: int = 185, volume: float = 1.0) -> None:
"""初始化TTS引擎(macOS用say,其他系统用pyttsx3"""
self.enabled = enabled
self.rate = rate
self.volume = volume
self._queue: queue.Queue[str | None] = queue.Queue()
self._thread: threading.Thread | None = None
self._engine: Any | None = None
self._use_macos_say = False
self._current_process: subprocess.Popen | None = None
if self.enabled:
self._start()
def announce_count(self, count: int) -> None:
"""将次数放入队列进行异步语音播报"""
if not self.enabled or count <= 0:
return
while True:
try:
self._queue.get_nowait()
except queue.Empty:
break
self._queue.put(str(count))
def close(self) -> None:
"""停止播报线程并释放资源"""
if not self.enabled:
return
self._queue.put(None)
if self._thread is not None:
self._thread.join(timeout=1.0)
if self._current_process is not None and self._current_process.poll() is None:
self._current_process.terminate()
def _start(self) -> None:
"""根据平台初始化TTS引擎并启动后台播报线程"""
if sys.platform == "darwin":
self._use_macos_say = True
logger.info("Rep announcer initialized with macOS say")
else:
try:
import pyttsx3
self._engine = pyttsx3.init()
self._engine.setProperty("rate", self.rate)
self._engine.setProperty("volume", self.volume)
logger.info("Rep announcer initialized with pyttsx3")
except Exception as exc:
self.enabled = False
logger.warning("Rep announcer disabled, pyttsx3 unavailable: {}", exc)
return
self._thread = threading.Thread(target=self._run, name="RepAnnouncer", daemon=True)
self._thread.start()
def _run(self) -> None:
"""后台线程:从队列读取文本并调用TTS播放"""
while True:
text = self._queue.get()
if text is None:
return
try:
if self._use_macos_say:
if self._current_process is not None and self._current_process.poll() is None:
self._current_process.terminate()
self._current_process = subprocess.Popen(
["say", "-r", str(self.rate), text],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
elif self._engine is not None:
self._engine.say(text)
self._engine.runAndWait()
except Exception as exc:
logger.warning("Failed to announce rep count {}: {}", text, exc)