# 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//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 , 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 # THE DEFAULT VOICE — what a request that names NO voice speaks. The omp harness # has no setting that feeds a voice to its speech device (`voice_id` is a # per-call argument; `speech.voice` belongs to the TUI's Kokoro speak-aloud and # never reaches an HTTP request), so a harness voiceover arrives voice-less and # would otherwise be a 400 for every caller that does not name one. An UNKNOWN # name still 400s with the list that exists — this covers the absent case only — # and the journal marks such a line `(default)` so an unrequested voice never # reads as a requested one. Any pack directory in /home/dank/voices is legal. DEFAULT_VOICE=narrator1 # 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