feat: add real-time TTS endpoint POST /api/tts
This commit is contained in:
65
app/main.py
65
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")
|
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 ──────────────────────────────────────────────────────────────
|
# ── 管理 API ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
# --- Books ---
|
# --- Books ---
|
||||||
|
|||||||
Reference in New Issue
Block a user