Skip to content

feat(voice): local speech stack by default — whisper.cpp STT + piper TTS - #10

Merged
darioalessandro merged 1 commit into
mainfrom
add-whisper
Jul 10, 2026
Merged

feat(voice): local speech stack by default — whisper.cpp STT + piper TTS#10
darioalessandro merged 1 commit into
mainfrom
add-whisper

Conversation

@darioalessandro

Copy link
Copy Markdown
Member

What

Speech runs local and keyless by default. videoeditor-voice now has two backends per direction:

default (local, no key) opt-in
STT whisper.cpp (whisper-cli, ggml-base.en) ElevenLabs Scribe via VIDEOEDITOR_STT=elevenlabs
TTS piper voice (en_US-lessac-medium) via sherpa-onnx ElevenLabs via tts: elevenlabs frontmatter / VIDEOEDITOR_TTS

Both STT backends return the same transcript shape ({text, words:[{type,text,start,end}]}), so analyze and the recorder coach consume either without change. voice_id frontmatter is now only required on the ElevenLabs path.

Nix closure

whisper-cpp 1.8.7, sherpa-onnx 1.13.3, and both models (~215 MB) are pinned; the wrapper bakes WHISPER_MODEL / PIPER_VOICE defaults and the dev shell exports the same pins. examples/hello-bench renders keyless end to end.

Why sherpa-onnx and not nixpkgs piper-tts: that package fails to evaluate on aarch64-darwin (broken pysilero-vad dep); sherpa-onnx runs genuine piper voices, is cached for all four flake platforms, and synthesizes at ~0.07 RTF on Apple Silicon.

Notes

  • Recorder e2e unsets WHISPER_MODEL alongside ELEVENLABS_API_KEY: whisper hallucinates words on the fake mic's tone, which would make coaching assertions flaky.
  • Pinned whisper model is English-only (base.en); WHISPER_MODEL swaps in a multilingual ggml. Whisper emits no audio_event entries, so background-noise coaching remains Scribe-only.
  • New demo: cargo run -p videoeditor-voice --example transcribe -- <audio>.

Verified

  • just check, cargo test --workspace, 9/9 recorder e2e (nix dev shell)
  • nix build .#videoeditor, then the store binary with env stripped: piper voiced all 5 hello-bench clips; analyze transcribed a video of that narration back word-for-word with a correct timing map — zero configuration, zero keys.

🤖 Generated with Claude Code

Speech no longer needs an API key. videoeditor-voice grows a second
backend for each direction, local and default:

- STT: whisper.cpp (`whisper-cli` over pinned ggml-base.en weights),
  word-level timestamps normalized to the same transcript shape Scribe
  returns, so analyze and the recorder coach work unchanged.
  `VIDEOEDITOR_STT=elevenlabs` opts back into Scribe.
- TTS: a piper voice (en_US-lessac-medium) rendered by
  sherpa-onnx-offline-tts, encoded to the pipeline's mp3 via ffmpeg.
  `tts: elevenlabs` frontmatter (or `VIDEOEDITOR_TTS`) opts back into
  ElevenLabs; voice_id is now only required on that path. nixpkgs'
  piper-tts is broken on aarch64-darwin, hence sherpa-onnx as the engine.

The nix closure pins the whole stack: whisper-cpp, sherpa-onnx, and both
models, with WHISPER_MODEL / PIPER_VOICE baked into the wrapper and
exported by the dev shell. hello-bench now renders keyless end to end.

The recorder e2e also unsets WHISPER_MODEL: whisper hallucinates words on
the fake mic's tone, which would make coaching assertions flaky.

New: `cargo run -p videoeditor-voice --example transcribe -- <audio>`
prints the normalized transcript for any file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@darioalessandro
darioalessandro merged commit fd448fb into main Jul 10, 2026
5 checks passed
@darioalessandro
darioalessandro deleted the add-whisper branch July 10, 2026 05:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant