From 130e9d739f47c5a153932866a25d359714735c61 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 20 Oct 2026 18:11:40 -0700 Subject: [PATCH] chore(frank-tts): repoint narrator slots to the live voice packs Frank replaced the narrator1/2/3 packs with cloned character voices (wilwheaton, vaati, dr.kern, garrickhagon, jeffhays). The three narrator slots are now carried by real packs: narrator1=wilwheaton, narrator2=vaati, narrator3=dr.kern. - frank-tts.env DEFAULT_VOICE narrator1 -> wilwheaton (a voice-less request 400'd on the dead name; wilwheaton is narrator1's successor). - Docs: correct the /health census and the podcast-speaker references in Frank TTS.md and Open Notebook.md to the live packs. Dated 2026-10-18/19 measurement logs are left as history. Open Notebook's live dank-duo speaker profile was repointed over REST (Frank->wilwheaton, Comrade->vaati) - app-side, not in this commit. Bats fixtures keep narrator1 as a self-contained sample pack; untouched. --- scripts/frank-tts.env | 4 ++-- serverAppConfigs-docs/Dankclaw/Frank TTS.md | 17 +++++++++-------- serverAppConfigs-docs/Dankclaw/Open Notebook.md | 10 +++++----- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/scripts/frank-tts.env b/scripts/frank-tts.env index 008183b..c8021b8 100644 --- a/scripts/frank-tts.env +++ b/scripts/frank-tts.env @@ -7,7 +7,7 @@ # 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: +# (voice=wilwheaton, or the harness' voice_id=wilwheaton) 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. # @@ -65,7 +65,7 @@ TTS_UPSTREAM_MODEL=voxcpm2 # 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 +DEFAULT_VOICE=wilwheaton # CLI-backend knobs, inert while TTS_BACKEND=omni-http. Empty means "not # configured": the endpoint answers a 500 that says so instead of running diff --git a/serverAppConfigs-docs/Dankclaw/Frank TTS.md b/serverAppConfigs-docs/Dankclaw/Frank TTS.md index 6a844fb..be84125 100644 --- a/serverAppConfigs-docs/Dankclaw/Frank TTS.md +++ b/serverAppConfigs-docs/Dankclaw/Frank TTS.md @@ -21,7 +21,7 @@ never learn a path. **Deployed 2026-10-18** — `./scripts/deploy.sh dankclaw --service frank-tts` both units `active`, no drop-ins, and the homepage card's `wip` gate came off in the same change. Probes on the deployed endpoint: `/health` answers -`{"status":"ok","backend":"omni-http","voices":["narrator1","narrator2","narrator3"]}` +`{"status":"ok","backend":"omni-http","voices":["dr.kern","garrickhagon","jeffhays","vaati","wilwheaton"]}` and two different voices rendered the same sentence into **different** bytes (24 kHz mono PCM16, 4.24 s, healthy RMS) — so name resolution is real, not a pass-through of one default. The operator harness routes its `speech` role here; @@ -42,8 +42,8 @@ render needs the app installed, not this endpoint. ## Naming a voice -A voice **is** a directory name. `voice=narrator1` resolves to -`/voices/narrator1/reference.wav`, and that file — not a manifest, not a config +A voice **is** a directory name. `voice=wilwheaton` resolves to +`/voices/wilwheaton/reference.wav`, and that file — not a manifest, not a config entry — is what makes the name real. omp's TTS device may send `voice_id`; the shim accepts either. A call that sends **neither** — the harness's normal shape, because no harness setting carries a voice for this transport — speaks @@ -59,10 +59,11 @@ loudly instead of letting it half-work. Names are user-visible: an unknown voice comes back as `400 unknown voice 'x'; available voices: …`, and that list is the honest -census. The podcast speakers are `narrator1`, `narrator2`, `narrator3` — -[[Open Notebook]] is configured with exactly those three, so renaming them -breaks episode rendering, not just speech. A personal voice gets the person's -name, lowercase. +census. As of 2026-10-20 the live packs are `dr.kern`, `garrickhagon`, +`jeffhays`, `vaati`, `wilwheaton`. [[Open Notebook]]'s `dank-duo` profile speaks +`wilwheaton` (Frank) and `vaati` (Comrade), and `DEFAULT_VOICE` is `wilwheaton`. +Renaming a pack that a speaker profile names breaks episode rendering, not just +speech. A personal voice gets the person's name, lowercase. ## Voice packs @@ -221,5 +222,5 @@ harness bundle and measured the day of the flip: | Endpoint answers, synthesis fails | the engine: `podman healthcheck run frank-tts-engine`, then `systemctl --user status frank-tts-engine.service` | | First request of the day is ~0.6 s slower than the rest — or every name returns 400 | the warm pass did not run; it carries `-`, so it cannot fail the unit. `journalctl --user -u frank-tts-engine` for the warmer's lines, then `podman exec frank-tts-engine /app/warm-voices.sh` — the registry is memory-only (law 1 of [[Frank TTS Engine]]) | | The unit never appears after a deploy | the generator dropped it: `frank-tts-engine.network` is missing from `~/.config/containers/systemd/` | -| A consumer rejects `narrator1` by listing 29 names (`af_heart … bm_fable`) and no request ever reaches this endpoint | that consumer is synthesizing **in the browser** with Kokoro.js, whose voice pack is fixed — OpenWebUI 0.11.4 ships it as the `browser-kokoro` TTS engine; see [[OpenWebUI]] | +| A consumer rejects `wilwheaton` by listing 29 names (`af_heart … bm_fable`) and no request ever reaches this endpoint | that consumer is synthesizing **in the browser** with Kokoro.js, whose voice pack is fixed — OpenWebUI 0.11.4 ships it as the `browser-kokoro` TTS engine; see [[OpenWebUI]] | --- diff --git a/serverAppConfigs-docs/Dankclaw/Open Notebook.md b/serverAppConfigs-docs/Dankclaw/Open Notebook.md index 547fe98..707f63b 100644 --- a/serverAppConfigs-docs/Dankclaw/Open Notebook.md +++ b/serverAppConfigs-docs/Dankclaw/Open Notebook.md @@ -70,7 +70,7 @@ Credentials live in the database, encrypted with `OPEN_NOTEBOOK_ENCRYPTION_KEY`, | Model row `voxcpm2` | type `text_to_speech` on the frank-tts credential. The name is decorative: frank-tts ignores the request's `model` field and synthesizes whatever `voice` names ([[Frank TTS]]) | | Model row `frank-archon` | type `language`; `PUT /api/models/defaults` points chat, transformation and large-context at it | | Model row `qwen3-embedding-0.6b` | type `embedding` on the NPU-lane credential; `default_embedding_model` points at it, so every note save embeds through the XDNA2 lane ([[frank-dragoon]]) | -| Speaker profile `dank-duo` | two speakers, Frank → `voice_id: narrator1`, Comrade → `narrator2`, `voice_model` = the TTS row | +| Speaker profile `dank-duo` | two speakers, Frank → `voice_id: wilwheaton`, Comrade → `vaati`, `voice_model` = the TTS row | | Episode profile `dank-daily` | `num_segments: 4`, `speaker_config` = `dank-duo`, outline and transcript LLM = the archon row, briefing that ends "Never invent numbers." | Order that works over REST: credentials → model rows (`POST /api/models` with `credential: `) → `PUT /api/models/defaults` → speaker profile (`voice_model: `) → episode profile (`speaker_config` / `outline_llm` / `transcript_llm`). `POST /api/models/{id}/test` proves each row: the language row answered with a real completion from frank-archon; the TTS row reached frank-tts and came back `unknown voice 'alloy'; available voices: narrator1, narrator2, narrator3` — the app's own test path hard-codes an OpenAI voice, so that message *is* the proof the leg works. @@ -108,13 +108,13 @@ A **speaker profile** carries one to four speakers (`docs/3-USER-GUIDE/creating- | Speaker | Voice ID to type | |---------|------------------| -| Speaker 1 | `narrator1` | -| Speaker 2 | `narrator2` | -| Speaker 3 | `narrator3` | +| Speaker 1 | `wilwheaton` | +| Speaker 2 | `vaati` | +| Speaker 3 | `dr.kern` | Those are the pack names frank-tts resolves from `/home/dank/voices` (`scripts/frank-tts.env` header: a voice is real exactly when `/voices//reference.wav` exists). Two traps, both from upstream's shipped defaults: -- The three seeded speaker profiles carry **OpenAI voice names** (`nova`, `alloy`, …) and **no voice model at all** — they show a "Setup required" badge, and generation fails with a voice error until they are edited (`creating-podcasts.md:13`, `:33`; `local-tts.md:89`). Add a `narrator1`/`narrator2` pair (or a third/fourth) rather than editing the seeded ones in place. +- The three seeded speaker profiles carry **OpenAI voice names** (`nova`, `alloy`, …) and **no voice model at all** — they show a "Setup required" badge, and generation fails with a voice error until they are edited (`creating-podcasts.md:13`, `:33`; `local-tts.md:89`). Add a `wilwheaton`/`vaati` pair (or a third/fourth) rather than editing the seeded ones in place. - Two speakers with the same voice are hard to follow (`creating-podcasts.md:89`). Distinct packs per speaker is the point of the 1–4 shape. ## Two buttons that fail against frank-tts, and why that is cosmetic -- 2.54.0