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:
Benjamin Admin
2026-04-26 23:17:39 +02:00
parent 3cdab5a967
commit 0f0bbc3dc0
3 changed files with 82 additions and 40 deletions

View File

@@ -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}")