Switch AudioButton to Piper TTS (Thorsten/Lessac voices)
AudioButton now tries Piper TTS via /api/vocabulary/tts endpoint first, falls back to Browser Web Speech API if unavailable. Backend: New GET /api/vocabulary/tts?text=...&lang=de endpoint. audio_service.py: Fixed presigned URL flow for MinIO download. This gives the same high-quality voice as the Investor Agent in the pitch deck (Thorsten DE / Lessac EN, MIT license). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -74,16 +74,24 @@ async def synthesize_word(
|
||||
return None
|
||||
|
||||
data = resp.json()
|
||||
audio_url = data.get("audio_url") or data.get("presigned_url")
|
||||
bucket = data.get("bucket")
|
||||
object_key = data.get("object_key")
|
||||
|
||||
if audio_url:
|
||||
# Download the audio file
|
||||
audio_resp = await client.get(audio_url)
|
||||
if audio_resp.status_code == 200:
|
||||
with open(cached, "wb") as f:
|
||||
f.write(audio_resp.content)
|
||||
logger.info(f"TTS cached: '{text}' ({language}) → {cached}")
|
||||
return cached
|
||||
if bucket and object_key:
|
||||
# Get presigned URL to download the audio
|
||||
url_resp = await client.post(
|
||||
f"{TTS_SERVICE_URL}/presigned-url",
|
||||
json={"bucket": bucket, "object_key": object_key, "expires": 300},
|
||||
)
|
||||
if url_resp.status_code == 200:
|
||||
audio_url = url_resp.json().get("url")
|
||||
if audio_url:
|
||||
audio_resp = await client.get(audio_url)
|
||||
if audio_resp.status_code == 200:
|
||||
with open(cached, "wb") as f:
|
||||
f.write(audio_resp.content)
|
||||
logger.info(f"TTS cached: '{text}' ({language}) → {cached}")
|
||||
return cached
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"TTS service unavailable: {e}")
|
||||
|
||||
@@ -161,6 +161,22 @@ async def api_get_syllable_audio(word_id: str, lang: str = "en"):
|
||||
return FastAPIResponse(content=audio_bytes, media_type="audio/mpeg")
|
||||
|
||||
|
||||
@router.get("/tts")
|
||||
async def api_tts(text: str = Query("", min_length=1), lang: str = Query("de")):
|
||||
"""Text-to-Speech endpoint. Returns MP3 audio for any text.
|
||||
|
||||
Uses Piper TTS (Thorsten DE / Lessac EN). Cached by text+lang.
|
||||
"""
|
||||
from fastapi.responses import Response as FastAPIResponse
|
||||
from services.audio import get_or_generate_audio
|
||||
|
||||
audio_bytes = await get_or_generate_audio(text, language=lang)
|
||||
if not audio_bytes:
|
||||
raise HTTPException(status_code=503, detail="TTS Service nicht verfuegbar")
|
||||
|
||||
return FastAPIResponse(content=audio_bytes, media_type="audio/mpeg")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Learning Unit Creation from Word Selection
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user