import asyncio import httpx from app.core.exceptions import GenerationError class TTSClient: def __init__(self, tts_space_url: str, timeout_seconds: float) -> None: self._tts_space_url = tts_space_url.rstrip("/") self._timeout_seconds = timeout_seconds @property def is_configured(self) -> bool: return bool(self._tts_space_url) async def synthesize(self, text: str, voice: str = "am_adam") -> bytes: if not self.is_configured: raise GenerationError("TTS client is not configured") async def _call() -> bytes: async with httpx.AsyncClient(timeout=self._timeout_seconds) as client: response = await client.post( f"{self._tts_space_url}/synthesize", json={"text": text, "voice": voice}, headers={"Content-Type": "application/json"}, ) response.raise_for_status() audio_bytes = response.content if not audio_bytes: raise GenerationError("TTS response was empty") return audio_bytes try: return await asyncio.wait_for(_call(), timeout=self._timeout_seconds) except TimeoutError as exc: raise GenerationError("TTS request timed out") from exc except httpx.HTTPStatusError as exc: raise GenerationError( "TTS upstream returned an error", context={"status_code": exc.response.status_code}, ) from exc except GenerationError: raise except Exception as exc: raise GenerationError("TTS synthesis failed", context={"error": str(exc)}) from exc