Add audio generation config, refactor rep_announcer
- AudioConfig now includes rep_max_count and rep_audio_dir - app/audio/generate.py uses config instead of hardcoded constants - RepAnnouncer rewrote with pre-generated audio cache - Supports Windows winsound, macOS afplay, Linux paplay/aplay - Pin requirements back to mediapipe==0.10.21 with numpy<2
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
# app/audio/generate.py
|
||||
from __future__ import annotations
|
||||
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
from loguru import logger
|
||||
|
||||
|
||||
def generate_rep_audio_files(
|
||||
*,
|
||||
max_count: int,
|
||||
rate: int,
|
||||
output_dir: Path,
|
||||
overwrite: bool = False,
|
||||
) -> None:
|
||||
"""
|
||||
确保 0~max_count 的运动次数语音 wav 文件存在。
|
||||
|
||||
默认生成到:
|
||||
|
||||
app/audio/reps/0.wav
|
||||
app/audio/reps/1.wav
|
||||
...
|
||||
app/audio/reps/200.wav
|
||||
|
||||
服务启动时调用一次即可。
|
||||
"""
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
missing_counts = [
|
||||
count
|
||||
for count in range(0, max_count + 1)
|
||||
if overwrite or not _audio_path(output_dir, count).exists()
|
||||
]
|
||||
|
||||
if not missing_counts:
|
||||
logger.info("Rep audio files already prepared: {}", output_dir)
|
||||
return
|
||||
|
||||
system = platform.system().lower()
|
||||
|
||||
logger.info(
|
||||
"Preparing rep audio files, system={}, count={}, output_dir={}",
|
||||
system,
|
||||
len(missing_counts),
|
||||
output_dir,
|
||||
)
|
||||
|
||||
if system == "darwin":
|
||||
_generate_with_macos_say(
|
||||
counts=missing_counts,
|
||||
output_dir=output_dir,
|
||||
rate=rate,
|
||||
)
|
||||
else:
|
||||
_generate_with_pyttsx3(
|
||||
counts=missing_counts,
|
||||
output_dir=output_dir,
|
||||
rate=rate,
|
||||
)
|
||||
|
||||
logger.info("Rep audio files prepared: {}", output_dir)
|
||||
|
||||
|
||||
def _generate_with_macos_say(
|
||||
*,
|
||||
counts: list[int],
|
||||
output_dir: Path,
|
||||
rate: int,
|
||||
) -> None:
|
||||
"""macOS 使用 say 命令生成 wav。"""
|
||||
if platform.system().lower() != "darwin":
|
||||
raise RuntimeError("say command is only available on macOS")
|
||||
|
||||
if shutil.which("say") is None:
|
||||
raise RuntimeError("macOS say command not found")
|
||||
|
||||
for count in counts:
|
||||
audio_file = _audio_path(output_dir, count)
|
||||
|
||||
subprocess.run(
|
||||
[
|
||||
"say",
|
||||
"-r",
|
||||
str(rate),
|
||||
"--file-format=WAVE",
|
||||
"-o",
|
||||
str(audio_file),
|
||||
str(count),
|
||||
],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
check=True,
|
||||
)
|
||||
|
||||
|
||||
def _generate_with_pyttsx3(
|
||||
*,
|
||||
counts: list[int],
|
||||
output_dir: Path,
|
||||
rate: int,
|
||||
) -> None:
|
||||
"""Windows / Linux 使用 pyttsx3 生成 wav。"""
|
||||
try:
|
||||
import pyttsx3
|
||||
except Exception as exc:
|
||||
raise RuntimeError(f"pyttsx3 unavailable: {exc}") from exc
|
||||
|
||||
engine = pyttsx3.init()
|
||||
engine.setProperty("rate", rate)
|
||||
engine.setProperty("volume", 1.0)
|
||||
|
||||
for count in counts:
|
||||
audio_file = _audio_path(output_dir, count)
|
||||
engine.save_to_file(str(count), str(audio_file))
|
||||
|
||||
engine.runAndWait()
|
||||
|
||||
|
||||
def _audio_path(output_dir: Path, count: int) -> Path:
|
||||
return output_dir / f"{count}.wav"
|
||||
Reference in New Issue
Block a user