File size: 1,457 Bytes
bf6e45c
 
97e667f
bf6e45c
 
 
97e667f
bf6e45c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97e667f
bf6e45c
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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}")