atlas-iac/dockerfiles/hermes_jetson_tts_cues.py

120 lines
4.2 KiB
Python

"""Precompute the small, localized Hermes thinking-cue audio set."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Callable
CUE_TEXT = {
"en": {
"thinking": "I'm thinking.",
"let_me_think": "Let me think.",
"still_working": "Still working on that.",
"one_more_moment": "One more moment.",
},
"ru": {
"thinking": "Я думаю.",
"let_me_think": "Дайте подумать.",
"still_working": "Я всё ещё думаю над этим.",
"one_more_moment": "Ещё мгновение.",
},
"es": {
"thinking": "Estoy pensando.",
"let_me_think": "Déjame pensar.",
"still_working": "Sigo pensando en eso.",
"one_more_moment": "Un momento más.",
},
}
LANGUAGE_VOICE = {
"en": "en_US-amy-medium",
"ru": "ru_RU-irina-medium",
"es": "es_MX-claude-high",
}
@dataclass(frozen=True)
class CachedCue:
"""One immutable PCM response synthesized before the server becomes ready."""
cue_id: str
language: str
voice_name: str
sample_rate: int
pcm: bytes
def build_cue_cache(
voices: dict,
synthesis_config_factory: Callable[..., object],
normalize_pcm: Callable[[object, int, int], object],
pause_for_text: Callable[[str], int],
) -> dict[tuple[str, str], CachedCue]:
"""Synthesize every allow-listed cue once, failing startup on voice drift."""
cache: dict[tuple[str, str], CachedCue] = {}
for language, entries in CUE_TEXT.items():
voice_name = LANGUAGE_VOICE[language]
voice = voices[voice_name]
sample_rate = int(voice.config.sample_rate)
for cue_id, text in entries.items():
chunks: list[bytes] = []
for result in voice.synthesize(text, synthesis_config_factory(length_scale=1.0)):
if (
result.sample_rate != sample_rate
or result.sample_width != 2
or result.sample_channels != 1
):
raise RuntimeError("Piper returned an unexpected cue PCM format")
chunks.append(
bytes(
normalize_pcm(
result.audio_int16_bytes,
sample_rate,
pause_for_text(text),
)
)
)
if not chunks:
raise RuntimeError(f"Piper returned no audio for cue {language}/{cue_id}")
cache[(language, cue_id)] = CachedCue(
cue_id, language, voice_name, sample_rate, b"".join(chunks)
)
return cache
def resolve_cached_cue(payload: object, cache: dict) -> CachedCue | None:
"""Resolve only an exact language/cue ID pair; client text is never trusted."""
if not isinstance(payload, dict) or "cue_id" not in payload:
return None
language = payload.get("language")
cue_id = payload.get("cue_id")
if not isinstance(language, str) or not isinstance(cue_id, str):
raise ValueError("invalid thinking cue")
cue = cache.get((language, cue_id))
if cue is None:
raise ValueError("unknown thinking cue")
return cue
def write_cached_cue(handler, cue: CachedCue, turn_id: str | None) -> None:
"""Return immutable PCM without acquiring the live Piper model lock."""
handler.send_response(200)
handler.send_header("Content-Type", "audio/pcm")
handler.send_header("Content-Length", str(len(cue.pcm)))
handler.send_header("Cache-Control", "private, max-age=86400, immutable")
handler.send_header("X-Audio-Format", "pcm_s16le")
handler.send_header("X-Audio-Sample-Rate", str(cue.sample_rate))
handler.send_header("X-Audio-Channels", "1")
handler.send_header("X-Audio-Sample-Width", "2")
handler.send_header("X-TTS-Voice", cue.voice_name)
handler.send_header("X-TTS-Cue-ID", cue.cue_id)
handler.send_header("X-TTS-Cache", "HIT")
if turn_id is not None:
handler.send_header("X-TTS-Turn-ID", turn_id)
handler.end_headers()
try:
handler.wfile.write(cue.pcm)
handler.wfile.flush()
except (BrokenPipeError, ConnectionResetError, TimeoutError, OSError):
handler.close_connection = True