Commit graph

198 commits

Author SHA1 Message Date
585571c609
speech.py --engines: default to F5 only; 'all' opts into every engine
Previous default ('--engines unset' == all engines enabled) made it too
easy for stray tts-1-qwen requests to silently load 10 GiB of weights
onto a GPU shared with an LLM. Flipped: the lean F5-only allowlist is
now the default, and operators explicitly opt into more.

  python speech.py                       # default: {tts-1-f5}
  python speech.py --engines f5,piper    # F5 + Piper
  python speech.py --engines all         # every importable engine (old default)
  python speech.py --engines qwen,f5     # back to Qwen + F5 for GPU servers

Implementation: _parse_engines_env() now returns set(DEFAULT_ENGINES)
when SPEECH_ENABLED_ENGINES is unset, and treats the literal 'all' as a
None sentinel (no allowlist applied). DEFAULT_ENGINES = frozenset({'tts-1-f5'}).

Workers still pick this up via SPEECH_ENABLED_ENGINES env var; __main__
only sets the var when --engines was passed, so default-path workers
re-parse the empty env -> DEFAULT_ENGINES path.
2026-06-10 12:39:28 -04:00
a0d9e9a966
add --engines CLI flag to allowlist TTS backends
Operators can now restrict speech.py to a subset of TTS engines via
--engines (or the SPEECH_ENABLED_ENGINES env var). Disabled engines:
  - hidden from /v1/voices
  - short-circuited at the TTS request handler with a clean
    BadRequestError ('Model X is not enabled on this server')
  - skipped at app.register_model() time so they don't appear in
    /v1/models
Default (--engines unset) preserves current behavior: every engine whose
Python deps are importable is enabled.

Motivation: running speech.py on a GPU shared with an LLM server (e.g.
llama-qwen on the same 4090) used to require commenting out
register_model lines AND auditing every caller to never hit
tts-1-qwen. Both forms of discipline broke in practice — speech.py was
seen squatting 10 GiB of VRAM for 3 days because the qwen branch loaded
on a stray request. Single allowlist closes that hole.

Usage:
  python speech.py --engines f5,piper        # lean: F5 + Piper only
  python speech.py --engines tts-1-f5        # equivalent (full IDs OK)
  python speech.py                           # default: all engines

Workers (uvicorn -W N) re-import this module, so the allowlist lives in
SPEECH_ENABLED_ENGINES env var (set by __main__ before uvicorn.run);
each worker re-parses at module load. Module-level set ENABLED_ENGINES
is populated by _parse_engines_env() — None = no restriction.

Validation routes through is_engine_available(model_id), which now ANDs
two gates: (a) operator allowlist, (b) Python-deps importable. Same
helper drives /v1/voices filtering, request-handler short-circuit, and
app.register_model() loop in __main__.
2026-06-10 12:37:58 -04:00
timehexon
d4ff4e8da4 speech.py: disable tts-1-qwen registration — free VRAM for llama-qwen LLM
qwen3-tts (1.7B params) on speech.py was holding 6-7 GB VRAM that
collided with llama-qwen.service (Qwen3.6-27B, 20 GB @ -c 65536) on
the same 4090. Result: speech.py OOMed mid-inference, returning HTTP
500 InternalServerError that browsers surfaced as NetworkError. Reverse
order made llama-qwen OOM during model load.

Drop tts-1-qwen registration so only f5-tts (336M params, ~1.3 GB
VRAM) lands on the GPU. Speech requests for tts-1-f5 work; tts-1-qwen
returns 500 (model still listed in /v1/models via the loop at line
758 — refine to a proper 4xx in a later pass).

To reverse: uncomment line 1497 + reduce llama-qwen context (e.g.
-c 32768 frees ~10 GB, enough for both engines).
2026-06-07 08:39:59 -04:00
33bd85f1cb
CLAUDE.md: document mispronunciation fix workflow
Records the pre_process_map.yaml respelling pattern we used for
Provenance so future blackops sessions don't have to rediscover
that preprocess() reads per-request and no restart is needed.
2026-06-05 19:34:24 -04:00
9f54ae9053
pre_process_map: drop unused Providence stub + stale A/B comment 2026-06-05 19:29:14 -04:00
09ae91a26a
pre_process_map: Provenance -> prahvanans (enable, per fox) 2026-06-05 19:28:37 -04:00
d6cd1614ca
pre_process_map: stage Provenance + Providence fixes, commented for A/B
Both respellings present but disabled so you can hear before/after
without redeploying. Uncomment one or both lines on the live config
(preprocess() re-reads per request).
2026-06-05 19:21:39 -04:00
c161a95eef
pre_process_map: target Provenance (not Providence)
Earlier patch targeted the wrong word; the actual mispronunciation
fox heard was 'Provenance'. Respell as 'Prov-uh-nuns' to hint
syllables + closing /nz/ sound.
2026-06-05 19:18:32 -04:00
5f2f4bfcc8
pre_process_map: Providence -> Prav-uh-dence (fix vowel)
Previous 'Prov-uh-dence' kept the wrong open vowel; native RI
pronunciation is 'prav-uh-dence' with short-a in the first syllable.
2026-06-05 18:40:58 -04:00
221756e501
pre_process_map: track config file in repo; fix Providence pronunciation
F5-TTS mispronounces "Providence" — add hyphenation regex
(Prov-uh-dence). Promote config/pre_process_map.yaml from
gitignored generated file to tracked repo file so the regex
map ships via git pull instead of needing per-host manual edits.
.default.yaml retained as seed for fresh installs.
2026-06-05 18:03:39 -04:00
timehexon
468cd1dfc0 Makefile: fold sox into apt-deps (now a required dep) 2026-06-05 12:33:10 -04:00
timehexon
5e41ab6779 Makefile: add apt-deps + apt-deps-sox targets for venv path 2026-06-05 12:04:24 -04:00
3b58f0c867
F5-TTS: add SSE streaming mode with per-sentence audio + timing
Add `sse: bool` to the request. For tts-1-f5, stream text/event-stream:
one 'sentence' event per sentence the moment it renders, carrying that
sentence's mp3 (base64) plus exact start_ms/end_ms, then a 'done' event
with total duration_ms. Clients begin playback after the first sentence
(no wait for the full clip) and drive a per-sentence highlight off the
timing. A single generation feeds both audio and timing — no double render.
2026-05-29 15:15:32 -04:00
b503c4d5d6
F5-TTS: add opt-in sentence timestamps mode
Add `timestamps: bool` to GenerateSpeechRequest. When set for tts-1-f5,
generate the whole clip up front and return JSON {audio (base64), format,
sample_rate, duration_ms, sentences:[{index,text,start_ms,end_ms}]} instead
of streaming audio. F5 already synthesizes one PCM chunk per sentence, so
each chunk's sample count is its exact duration — no forced aligner needed.
Lets clients highlight each sentence as it is spoken. Non-F5 models with
timestamps=true get a clear 400. Default off preserves streaming behavior.
2026-05-29 12:29:18 -04:00
timehexon
e511d4d105 whisper_refs: fill foxhop ref_text from actual recording 2026-05-25 06:23:18 -04:00
9f15d01569
Add voice 42: foxhop (self-recorded)
Slot 41 intentionally empty (Hitchhiker tribute). Voice is self-recorded by
speaker, AGPL-cleared, registered in voice_registry.json under new
`self-recorded` corpus. ref_text is a placeholder pending `make whisper-refs`
on a GPU host.

  cloned-voices/foxhop.wav            313K  mono 16kHz s16 PCM, ~10s, -3dBFS peak
  voice_registry.json                 +foxhop entry, +self-recorded corpus
  voice_to_speaker.default.yaml       +foxhop block in tts-1-qwen and tts-1-f5
  cloned-voices/voices_metadata.json  +foxhop entry
2026-05-25 06:21:31 -04:00
c5247cd993
whisper_refs: normalize transcripts (sentence-case, strip quote artifacts)
normalize_text() handles three Whisper quirks that produced messy F5
ref_texts:

  1. Lowercase output with no terminal period — capitalize first letter,
     append "." if missing.
  2. Hallucinated "' clusters Whisper inserts when it interprets a
     fragment as quoted dialogue (cora, ivan, atlas, hope had these).
     Strip everywhere; never legitimate English punctuation.
  3. Trailing apostrophe-then-period (.'.) from earlier rounds where a
     closing-quoted line got an extra "." appended — collapse to single
     terminal. Function is now idempotent.

Adds --from-cache flag: skip ASR, re-apply normalize from cached
whisper_refs.json. No GPU needed, useful after tuning the normalizer.
Lazy-imports torch so --from-cache works on any host.

Affects 9 of 40 voices: clara, grace, hazel, iris, felix, hugo
(lowercase fix); cora, ivan, atlas, hope (quote-cluster fix).
2026-05-24 16:02:18 -04:00
72a9553047
CLAUDE.md: replace stale 3090-ai+docker prod docs with 4090+bare-python reality
The prior docs claimed prod = 3090-ai running uncloseai-speech-server-1 via
docker compose. Reality (verified today): prod is bare python on the 4090
(ai.foxhop.net) using the f5-sidecar venv at /mnt/data/f5-sidecar/venv,
fronted by Caddy on 80/443. The container on 3090-ai exited 2 months ago and
docker compose v2 isn't installed there.

Cost of the drift today: one wrong-host deploy attempt that bounced off dead
infrastructure. This rewrites Production Deployment, Quick Production
Commands, Testing, When Things Break, and Common Mistakes to match reality.

Also drops hardcoded tmux window numbers (they shift) — every example now
reads <prod-window> with a reminder to rediscover via tmux-hosts.
2026-05-24 16:02:08 -04:00
4ac192585d F5-TTS: replace LibriSpeech ground-truth labels with real Whisper transcripts
Previous ref_texts were LibriSpeech dataset labels (ALL CAPS, no
punctuation). F5-TTS conditions on ref_text to align reference audio
prosody — commas, periods, casing matter. Now using whisper-large-v3
transcripts of the actual cloned-voices/*.wav files.

Generated via: make whisper-refs (on a GPU host).

Affects all 40 voices in both tts-1-f5 and tts-1-qwen engine blocks.
Sidecar cloned-voices/whisper_refs.json kept for traceability.
2026-05-24 14:47:25 -04:00
e9c978b051
endpoints: filter /v1/models and /v1/voices by engine availability flags 2026-05-24 11:58:35 -04:00
f25731ca08
Add make whisper-refs — re-transcribe cloned-voices/*.wav with whisper-large-v3
F5-TTS cloning quality depends on ref_text matching the prosody of ref_audio
(commas, periods, casing). Previous ref_texts were LibriSpeech ground-truth
labels: ALL CAPS, no punctuation — wrong signal for a flow-matching TTS
conditioned on text. Whisper hears what F5 will hear.

- scripts/whisper_refs.py — transcribe all wavs, rewrite
  voice_to_speaker.default.yaml + cloned-voices/voices_metadata.json
  in place. Also writes cloned-voices/whisper_refs.json sidecar.
- Makefile: whisper-refs target. Idempotent, rerun whenever
  cloned-voices/ changes.

Run on a GPU host (4090/3090). ~30s for 40 short clips on a 4090.
2026-05-24 11:56:15 -04:00
3a76ba6748
CLAUDE.md: refine our 'avoid the' style rule (match unsandbox shard) 2026-05-24 11:56:10 -04:00
831b937fd1
F5-TTS: per-chunk silence trim + fade (port from VoiceClone) + restore per-sentence streaming 2026-05-24 11:39:49 -04:00
timehexon
015bbdfad6 F5-TTS: Whisper-transcribed ref_texts with full punctuation for all 40 cloned voices 2026-05-24 11:34:12 -04:00
960fda4f5c
F5-TTS: sentence-case + period the LibriSpeech ref_texts for cleaner ref-to-gen transition 2026-05-24 11:26:34 -04:00
fc5af2b653
F5-TTS: drop per-sentence split — single infer() call avoids ref-to-gen artifact per chunk 2026-05-24 11:12:57 -04:00
482b17960c
F5-TTS engine consistency sweep (Makefile, startup, README, docs) 2026-05-24 08:24:38 -04:00
2df34f85cc
Add F5-TTS as tts-1-f5 engine (additive, alongside tts-1-qwen) 2026-05-23 13:24:25 -04:00
bfc5260dfc we update gitignore 2026-04-03 14:17:01 -04:00
074cbbd22f style: avoid "the", use "our" — writing style rule + sweep 2026-04-03 14:17:01 -04:00
timehexon
d4d6344326 Replace "AI" with "machine learning" in CLAUDE.md
Machine learning is what we grow. "AI" is forbidden in all
permacomputer discourse, marketing, & documentation.
2026-02-02 19:56:24 +00:00
7e77c3452d Add voices-qwen Makefile target to download cloned voice samples
Runs scripts/download_diverse_voices.py which pulls speaker samples
from LibriSpeech and assigns permanent human names via voice registry.
Works with venv or system python. Also adds Voices section to help.
2026-01-27 17:31:15 -05:00
fox
f58da54fa3 Expand to all 40 test-clean voices with idempotent registry 2026-01-27 15:30:42 -05:00
69d039d1a5 Add datasets and torchcodec to requirements.txt 2026-01-27 15:15:09 -05:00
3f3918fed2 Simplify CLAUDE.md: remove SSH/rsync references, local-only workflow 2026-01-27 13:59:52 -05:00
c3ac2def48 Add idempotent voice registry system for permanent speaker-to-name assignments
- voice_registry.json: append-only registry with 50 name pools per gender,
  locked speaker assignments, and multi-corpus support
- Rewrite download script to be registry-driven: loads registry, assigns
  names deterministically (sorted by speaker ID), never changes existing
  assignments
- Update docs/VOICES.md with registry system documentation
- Support --registry and --corpora CLI flags for multi-corpus downloads
2026-01-27 13:59:39 -05:00
fox
af5c81928d Regenerate voices with upstream-verified genders from SPEAKERS.TXT 2026-01-27 13:22:26 -05:00
35b083ed34 Fetch speaker genders from upstream LibriSpeech SPEAKERS.TXT 2026-01-27 13:17:52 -05:00
b77af6d603 Fix voice gender assignments - verified against LibriSpeech SPEAKERS.TXT 2026-01-27 13:13:07 -05:00
c56b8cb24e Simplify Makefile to run locally, remove all remote SSH/rsync
Remove REMOTE_HOST, REMOTE_USER, REMOTE_PATH, vars.sh loading.
All docker commands use sudo locally. Tests hit localhost:8000.
Delete old non-gendered voice WAV files (alloy, echo, fable, etc).
2026-01-27 12:51:08 -05:00
ebfa0319d1 Replace voices with 21 distinct gendered speakers from LibriSpeech
11 female (aria, clara, elena, grace, hazel, iris, luna, maya, ruby, sage, sofia)
10 male (atlas, caleb, felix, hugo, jasper, kai, leo, marcus, owen, theo)
Each voice is a unique LibriSpeech test-clean speaker for voice cloning diversity.
2026-01-27 12:41:37 -05:00
cd60e31aa2 Use gendered voice names: 11 female + 10 male = 21 distinct voices 2026-01-27 12:07:31 -05:00
4c5b42c062 Mount cloned-voices directory in container 2026-01-27 11:56:17 -05:00
648281cdb6 Update diverse voices script with correct voice names 2026-01-27 11:53:33 -05:00
aa8af56835 Fix remote paths in CLAUDE.md, add tmux-hosts discovery 2026-01-27 09:32:27 -05:00
16b281bab4 Add webm response format (opus in webm container)
Firefox MediaSource API supports audio/webm;codecs=opus but not
audio/ogg. Adding webm format lets Firefox clients use true
streaming playback via MediaSource instead of full buffering.
2026-01-27 09:28:38 -05:00
8f7f1318a1 Split on every sentence for streaming (no combining) 2026-01-26 19:52:41 -05:00
0a0d023517 Fix sentence splitter to split on every sentence boundary
Previous version accumulated sentences until 500 chars, defeating
the purpose of streaming. Now splits on every sentence, only
combining very short sentences (<50 chars) with the next.
2026-01-26 19:41:44 -05:00
802eaf2b29 Add sentence-by-sentence streaming for Qwen TTS
Split text into sentences and stream each as it's generated,
so first audio arrives much faster for long text.
2026-01-26 19:32:51 -05:00
a148088cb0 Fix Qwen TTS deadlock, reduce workers to 1 for GPU
- Fix subprocess deadlock in Qwen TTS by using threading for stdin write
  (prevents pipe buffer deadlock on large audio output)
- Set WORKERS=1 for GPU models to avoid VRAM duplication
  (4 workers × 3GB model = OOM, 1 worker works fine)
- Update CLAUDE.md: use git push/pull instead of rsync for deployment
2026-01-26 19:14:19 -05:00