from typing import Literal from fastapi import APIRouter, Depends, HTTPException from fastapi.responses import Response from pydantic import BaseModel, Field from services.auth import require_service_key from services.tts_service import synthesize, is_available, backend_status router = APIRouter(prefix="/tts", tags=["tts"]) class TtsRequest(BaseModel): text: str = Field(..., min_length=1, max_length=1200) voice: str | None = None # "ogg" = Ogg/Opus (~10x smaller than WAV; not playable in Safari, so the # client negotiates via canPlayType and asks for "wav" when needed). format: Literal["wav", "ogg"] = "wav" @router.get("/health") def tts_health(): """Reports whether Kokoro TTS is installed/usable and which backend runs it.""" return {"available": is_available(), "backend": backend_status()} @router.post("", dependencies=[Depends(require_service_key)]) def tts(req: TtsRequest): """ Synthesize interviewer speech with Kokoro. Returns audio (WAV or Ogg/Opus) on success, or 503 when TTS is unavailable so the caller falls back to browser speech. Responses are LRU-cached per (text, voice, format). """ try: audio, mime = synthesize(req.text, req.voice, req.format) return Response(content=audio, media_type=mime) except Exception as e: # noqa: BLE001 — TTS is optional; never hard-fail raise HTTPException(status_code=503, detail=f"TTS unavailable: {e}")