diff --git a/app/main.py b/app/main.py index 13adce6..a32b839 100644 --- a/app/main.py +++ b/app/main.py @@ -219,6 +219,71 @@ async def get_chapter_audio(book_id: str, chapter_id: str): return FileResponse(chapter.audio_file, media_type="audio/mpeg", filename=f"{chapter_id}.mp3") +# ── 实时 TTS 接口(兼容百度/阿里云风格)────────────────────────────────── + +from fastapi.responses import Response + +@app.post("/api/tts") +async def realtime_tts(request: Request): + """ + 实时 TTS 生成接口 + 请求体 (JSON): + - text: 要合成的文本(必填) + - style: 风格(可选,如"开心"、"东北话") + - speed: 语速调整(可选,暂未使用,MiMo 通过 style 控制语速) + 返回: MP3 音频二进制流 (audio/mpeg) + """ + data = await request.json() + text = data.get("text", "").strip() + style = data.get("style", "").strip() + + if not text: + return Response( + content=json.dumps({"status": 40000001, "message": "text 不能为空"}, ensure_ascii=False), + media_type="application/json", + status_code=400, + ) + + try: + # MiMo TTS 生成 WAV + wav_bytes = await call_mimo_tts(text, style) + + # WAV → MP3(内存中完成) + tmp_dir = Path(config.AUDIO_DIR) / "_tmp" + tmp_dir.mkdir(parents=True, exist_ok=True) + tmp_id = uuid.uuid4().hex + wav_path = str(tmp_dir / f"{tmp_id}.wav") + mp3_path = str(tmp_dir / f"{tmp_id}.mp3") + + with open(wav_path, "wb") as f: + f.write(wav_bytes) + + loop = asyncio.get_event_loop() + await loop.run_in_executor(None, wav_to_mp3, wav_path, mp3_path) + + with open(mp3_path, "rb") as f: + mp3_bytes = f.read() + + # 清理临时文件 + os.remove(wav_path) + os.remove(mp3_path) + + return Response(content=mp3_bytes, media_type="audio/mpeg") + + except HTTPException: + return Response( + content=json.dumps({"status": 50000001, "message": "TTS 生成失败"}, ensure_ascii=False), + media_type="application/json", + status_code=502, + ) + except Exception as e: + return Response( + content=json.dumps({"status": 50000002, "message": str(e)[:300]}, ensure_ascii=False), + media_type="application/json", + status_code=500, + ) + + # ── 管理 API ────────────────────────────────────────────────────────────── # --- Books ---