import hashlib import os import subprocess SERVER_DIR = os.path.dirname(os.path.abspath(__file__)) DEFAULT_CACHE_DIR = os.path.join(SERVER_DIR, "data", "tts_cache") PIPER_BIN = os.environ.get("PIPER_BIN", "/opt/piper/venv/bin/piper") PIPER_MODELS_DIR = os.environ.get("PIPER_MODELS_DIR", "/opt/piper/voices") TTS_CACHE_DIR = os.environ.get("TTS_CACHE_DIR", DEFAULT_CACHE_DIR) VOICES = { "es": "es_ES-davefx-medium.onnx", "en": "en_US-lessac-medium.onnx", "zh": "zh_CN-huayan-medium.onnx", } def piper_available(): return bool(PIPER_BIN) and os.path.isfile(PIPER_BIN) def generate_speech(text, lang="es"): voice = VOICES.get(lang, VOICES["en"]) digest = hashlib.sha256((lang + "\0" + text).encode("utf-8")).hexdigest()[:24] cache_path = os.path.join(TTS_CACHE_DIR, f"{lang}_{digest}.wav") if os.path.isfile(cache_path): return cache_path model_path = os.path.join(PIPER_MODELS_DIR, voice) if not os.path.isfile(model_path): raise RuntimeError( f"Piper 模型不存在: {model_path},请先下载语音模型并配置 PIPER_MODELS_DIR" ) os.makedirs(TTS_CACHE_DIR, exist_ok=True) tmp_path = cache_path + ".tmp" try: proc = subprocess.run( [ PIPER_BIN, "--model", model_path, "--output_file", tmp_path, ], input=text.encode("utf-8"), capture_output=True, timeout=90, ) except subprocess.TimeoutExpired as exc: raise RuntimeError("Piper 生成超时") from exc if proc.returncode != 0 or not os.path.isfile(tmp_path): stderr = proc.stderr.decode("utf-8", errors="replace") raise RuntimeError(stderr or "Piper 生成失败") os.replace(tmp_path, cache_path) return cache_path