stage-whisper / tts_engine.py
Onur Kansoy
Update: Fixed AI Playwright generation logic with Gemma, resolved UI input box freezing, and added dynamic voice assignment for new characters.
39f0786 verified
Raw
History Blame Contribute Delete
1.83 kB
"""
tts_engine.py — ElevenLabs TTS engine.
Returns raw MP3 bytes for a spoken line given a voice_id.
"""
import os
import requests
class TTSEngine:
"""Wraps the ElevenLabs REST API for text-to-speech synthesis."""
_BASE_URL = "https://api.elevenlabs.io/v1/text-to-speech/{voice_id}"
_MODEL = "eleven_multilingual_v2"
def __init__(self, api_key: str):
self.api_key = api_key
if not self.api_key:
print("[TTSEngine] WARNING: api_key is empty — TTS will be skipped.")
def speak(self, text: str, voice_id: str) -> bytes:
"""
Synthesize `text` with `voice_id`.
Returns raw MP3 bytes on success.
Raises RuntimeError on API failure.
Falls through to raise so callers can catch and skip audio gracefully.
"""
if not self.api_key:
raise RuntimeError("ElevenLabs API key not configured.")
text = text.strip()
if not text:
raise ValueError("speak() called with empty text.")
url = self._BASE_URL.format(voice_id=voice_id)
headers = {
"xi-api-key": self.api_key,
"Content-Type": "application/json",
"Accept": "audio/mpeg",
}
payload = {
"text": text,
"model_id": self._MODEL,
"voice_settings": {
"stability": 0.35,
"similarity_boost": 0.75,
"style": 0.65,
"use_speaker_boost": True
},
}
response = requests.post(url, json=payload, headers=headers, timeout=30)
if response.status_code == 200:
return response.content
else:
raise RuntimeError(
f"ElevenLabs API error {response.status_code}: {response.text[:300]}"
)