PR #15 corrected the engine's page, the research brief's law bullet and the warmer's header, but the retired ladder had six more homes: both quadlet files ("6.2-8.0 s… 0.5 ms", "~6-8 s each", "paying the 5.5 s cold decode"), `scripts/frank-tts-engine.env` (the whole law paragraph and the WARM_TEXT note), `scripts/frank-tts.env`, the [[Frank TTS]] troubleshooting row that told an operator to expect ~5.5 s, the research brief's consequences paragraph, and one test comment. All re-keyed to what was measured on the deployed container 2026-10-18: the first synthesis of a freshly registered voice costs 1,883 ms against 1,226-1,283 ms afterwards, so the warm pass buys about 0.6 s — and the reason it exists at all, which the pages never led with, is that the registry is memory-only: without the hook every voice name is a 400 until somebody synthesizes. The trial's dated worklog entry stays as written; the correction points at it. No behavior change: comments, unit lore and docs only, so no cfhash move and no image rebuild.
82 lines
4.6 KiB
Bash
82 lines
4.6 KiB
Bash
# frank-tts.env — tunables for **frank-tts**, the fleet's cloned-voice speech
|
|
# endpoint on Dankclaw, host port **7772** (the LLM port family: 7770
|
|
# frank-dragoon, 7771 frank-zealot, 7772 frank-tts). Read by the Quadlet unit
|
|
# (EnvironmentFile=), which is why the paths below are CONTAINER paths.
|
|
#
|
|
# WHAT THIS IS. One OpenAI-compatible /v1/audio/speech façade in front of
|
|
# whichever TTS engine wins the engine decision (dankclaw/voiceservice/app.py
|
|
# is a single stdlib-only file in a locally built image). Consumers — omp's
|
|
# TTS device, OpenWebUI, open-notebook — address a VOICE BY NAME
|
|
# (voice=narrator1, or the harness' voice_id=narrator1) and never see a path:
|
|
# a name is real exactly when /voices/<name>/reference.wav exists, and
|
|
# anything else is a 400 that lists the names that are.
|
|
#
|
|
# THE PACKS. /home/dank/voices on the host, mounted read-only at /voices. One
|
|
# directory per voice, and a directory is a PACK only when it carries BOTH
|
|
# required members:
|
|
# reference.wav 24 kHz mono PCM, 18-25 s of clean single-speaker audio,
|
|
# plus the 16 kHz variant some engines want;
|
|
# the engine artifact the pinned backend's own speaker file (embedding /
|
|
# speaker weights). REQUIRED, beside the WAV and never
|
|
# replacing it — a voice that cannot be reused by another
|
|
# runtime is not a saved voice.
|
|
# plus manifest.json for provenance: source file, offsets (always past the
|
|
# first 10 s of that file, the source-material law), engine, sha256. Packs are
|
|
# host state and stay out of this repo; they must be world-readable because the
|
|
# container runs as uid 10000. Who enforces what: the shim gates on
|
|
# reference.wav because that is the only engine-agnostic term, so the pack
|
|
# builder is what refuses to emit a pack without its engine artifact — until a
|
|
# winning engine pins the format and this gate can name the file.
|
|
#
|
|
# BACKEND SWITCH. TTS_BACKEND picks the shape and nothing else changes for the
|
|
# consumer:
|
|
# omni-http forward to an OpenAI-shaped upstream, carrying the voice along
|
|
# as base64 reference_audio. The engine does the inference; the
|
|
# shim resolves the voice and re-encodes to mp3 when asked. The
|
|
# shipped engine (frank-tts-engine) speaks a voice it has
|
|
# REGISTERED on itself, from the same /home/dank/voices packs,
|
|
# and warms them at boot — see [[Frank TTS Engine]] for what that
|
|
# boot warm is for (a memory-only registry: without it every name
|
|
# is a 400) and what it saves (~0.6 s).
|
|
# llama-tts-cli run LLAMA_TTS_BIN once per request with
|
|
# --tts-speaker-file <reference.wav>, nice 19 so a narration
|
|
# never starves the text endpoints on this box. Needs
|
|
# LLAMA_TTS_BIN + LLAMA_TTS_MODEL filled in; LLAMA_TTS_MMPROJ
|
|
# only if that binary takes a projector.
|
|
|
|
# THE UPSTREAM ADDRESS. The engine is a container on the private bridge this
|
|
# unit also joins (dankclaw/voiceservice/quadlet/frank-tts-engine.network), so
|
|
# this is its container DNS name. It used to read
|
|
# `http://host.containers.internal:8880`, which is a shape that cannot work: a
|
|
# bridge container reaches the host's network namespace, netavark DNATs PUBLISHED
|
|
# ports only, and an engine on the host's loopback is published to nobody
|
|
# ([[Container Network Namespaces]] — the trap that 502'd hermes). Inert under
|
|
# TTS_BACKEND=llama-tts-cli, which needs no upstream at all.
|
|
VOICES_DIR=/voices
|
|
TTS_BACKEND=omni-http
|
|
TTS_UPSTREAM=http://frank-tts-engine:8880
|
|
TTS_UPSTREAM_MODEL=voxcpm2
|
|
|
|
# CLI-backend knobs, inert while TTS_BACKEND=omni-http. Empty means "not
|
|
# configured": the endpoint answers a 500 that says so instead of running
|
|
# `nice env -m ...` with an empty binary name.
|
|
LLAMA_TTS_BIN=
|
|
LLAMA_TTS_MODEL=
|
|
LLAMA_TTS_MMPROJ=
|
|
LLAMA_TTS_LANG=english
|
|
LLAMA_TTS_THREADS=8
|
|
|
|
# The wire shape. 7772 is frank-tts's host port (the container's too), the next
|
|
# slot in the fleet's model-endpoint family; the TTS engine upstream of it
|
|
# keeps 8880, which is not a fleet endpoint.
|
|
SHIM_PORT=7772
|
|
# Longest single narration, in characters of `input`. Podcast-length text is
|
|
# the consumer's job to chunk; one request over this limit is refused with 414
|
|
# rather than silently truncated into a half-sentence of audio.
|
|
SHIM_MAX_CHARS=15000
|
|
# Seconds one request may take, covering both an upstream synthesis and a CLI
|
|
# run. Voice is slow compared to text — a long paragraph on a CPU engine is
|
|
# minutes, not milliseconds — and a client that gave up earlier would only
|
|
# retry into a second synthesis competing for the same CPU.
|
|
SHIM_TIMEOUT=600
|