feat(tts): multi-voice switcher for EN & RU, Irina default voice, punctuation pause normalization and speed calibration
This commit is contained in:
1 parent
7893b353aa
commit
31e105b2c2
11 files changed
+486
-61
No files matched your search
+9
-11
@@ -66,6 +66,7 @@ from app.tts import (
|
||||
prewarm_card_audio,
|
||||
AUDIO_CACHE_DIR,
|
||||
is_piper_available,
|
||||
get_available_voices,
|
||||
)
|
||||
from app.listening import (
|
||||
get_listening_exercise,
|
||||
@@ -523,16 +524,17 @@ async def api_tts(
|
||||
text: str = Query(..., min_length=1, max_length=1000),
|
||||
lang: str = Query("en"),
|
||||
speed: float = Query(1.0, ge=0.5, le=2.0),
|
||||
voice: Optional[str] = Query(None),
|
||||
user: dict = Depends(require_user),
|
||||
):
|
||||
"""
|
||||
Synthesizes speech on-demand using local Piper TTS (en_US-lessac / ru_RU-dmitri).
|
||||
Instantly returns cached audio if previously synthesized.
|
||||
Synthesizes speech on-demand using local Piper TTS.
|
||||
Instantly returns cached audio if previously synthesized for this voice, speed, and text.
|
||||
"""
|
||||
clean_text = text.strip()
|
||||
if not clean_text:
|
||||
raise HTTPException(status_code=400, detail="Text cannot be empty")
|
||||
audio_path = await synthesize_speech(clean_text, lang=lang, speed=speed)
|
||||
audio_path = await synthesize_speech(clean_text, lang=lang, speed=speed, voice=voice)
|
||||
if not audio_path or not audio_path.is_file():
|
||||
raise HTTPException(status_code=500, detail="TTS synthesis failed or unavailable")
|
||||
return FileResponse(
|
||||
@@ -544,13 +546,8 @@ async def api_tts(
|
||||
|
||||
@app.get("/api/tts/status")
|
||||
async def api_tts_status(user: dict = Depends(require_user)):
|
||||
"""Returns status of local Piper TTS and available voice models."""
|
||||
return {
|
||||
"available": is_piper_available(),
|
||||
"engine": "piper-vits",
|
||||
"voices": ["en_US-lessac", "ru_RU-dmitri"],
|
||||
"cache_dir": str(AUDIO_CACHE_DIR),
|
||||
}
|
||||
"""Returns status of local Piper TTS and all available voice models."""
|
||||
return get_available_voices()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -561,6 +558,7 @@ async def api_tts_status(user: dict = Depends(require_user)):
|
||||
async def api_listening_exercise(
|
||||
topic: Optional[str] = Query("all"),
|
||||
level: Optional[str] = Query("B1"),
|
||||
voice: Optional[str] = Query(None),
|
||||
user: dict = Depends(require_user),
|
||||
):
|
||||
"""
|
||||
@@ -581,7 +579,7 @@ async def api_listening_exercise(
|
||||
).fetchall()
|
||||
user_cards = [dict(r) for r in rows]
|
||||
|
||||
ex = await get_listening_exercise(topic=topic, level=level, user_cards=user_cards)
|
||||
ex = await get_listening_exercise(topic=topic, level=level, user_cards=user_cards, voice=voice)
|
||||
# Strip keywords and ideal_answer from client response to prevent inspection
|
||||
safe_ex = {k: v for k, v in ex.items() if k not in ("keywords", "ideal_answer")}
|
||||
return safe_ex
|
||||
|
||||
Reference in new issue
Block a user