# 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 文件存在。 默认生成到: resources/audio/reps/0.aiff # macOS resources/audio/reps/0.wav # Windows / Linux ... resources/audio/reps/200.aiff 或 200.wav 服务启动时调用一次即可。 """ output_dir.mkdir(parents=True, exist_ok=True) system = platform.system().lower() suffix = ".aiff" if system == "darwin" else ".wav" missing_counts = [ count for count in range(0, max_count + 1) if overwrite or not _audio_path(output_dir, count, suffix=suffix).exists() ] if not missing_counts: logger.info("Rep audio files already prepared: {}", output_dir) return 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, suffix=".aiff") try: subprocess.run( [ "say", "-r", str(rate), "--file-format=AIFF", "-o", str(audio_file), str(count), ], stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, text=True, check=True, ) except subprocess.CalledProcessError as exc: message = exc.stderr.strip() or f"exit status {exc.returncode}" raise RuntimeError(f"Failed to generate {audio_file}: {message}") from exc 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, suffix=".wav") engine.save_to_file(str(count), str(audio_file)) engine.runAndWait() def _audio_path(output_dir: Path, count: int, *, suffix: str) -> Path: return output_dir / f"{count}{suffix}"