import subprocess from pathlib import Path from fastapi import FastAPI from pydantic import BaseModel, Field app = FastAPI(title="Emotion Museum TTS") BASE_DIR = Path(__file__).resolve().parent PIPER_BIN = BASE_DIR / ".venv" / "bin" / "piper" PIPER_MODEL = BASE_DIR / "models" / "zh_CN-huayan-medium.onnx" PIPER_CONFIG = BASE_DIR / "models" / "zh_CN-huayan-medium.onnx.json" class SynthesizeRequest(BaseModel): text: str = Field(min_length=1, max_length=5000) voice: str = "default_zh_female" outputPath: str @app.get("/health") def health(): return { "status": "ok", "engine": "piper", "modelReady": PIPER_MODEL.exists() and PIPER_CONFIG.exists(), } @app.post("/synthesize") def synthesize(request: SynthesizeRequest): output = Path(request.outputPath) output.parent.mkdir(parents=True, exist_ok=True) try: if not PIPER_BIN.exists(): raise RuntimeError(f"piper binary not found: {PIPER_BIN}") if not PIPER_MODEL.exists() or not PIPER_CONFIG.exists(): raise RuntimeError("piper Chinese voice model is not installed") subprocess.run( [ str(PIPER_BIN), "--model", str(PIPER_MODEL), "--config", str(PIPER_CONFIG), "--output_file", str(output), "--sentence-silence", "0.35", ], input=request.text, text=True, check=True, capture_output=True, timeout=180, ) except Exception as exc: return { "success": False, "audioPath": None, "durationMs": None, "engine": "piper", "errorMessage": str(exc), } return { "success": True, "audioPath": str(output), "durationMs": None, "engine": "piper", }