Files
VoiceStudio/docs/competitive-analysis.md
T
Palash Debnath 5cab8e0149 feat: rename the product to VoiceStudio (previously OmniVoice-Studio)
Renames what users see. The app, the installers, the window title, the
docs and all 21 locales now say VoiceStudio, with "(previously
OmniVoice-Studio)" noted near the title of each doc surface so people
recognise it.

Deliberately NOT renamed, because renaming any of them silently breaks
an existing install — there is no legacy-path fallback anywhere in this
codebase:

  - bundle identifier com.debpalash.omnivoice-studio (MSI UpgradeCode,
    macOS TCC grants, managed venv, WebView localStorage, the
    single-instance lock)
  - data directories OmniVoice / .omnivoice and omnivoice.db
  - the ~150 OMNIVOICE_* environment variables
  - the X-OmniVoice-* HTTP headers (a wire protocol)
  - the published Docker image paths
  - the OmniVoice ENGINE, which is a model name and not this product

tests/test_identity_paths_survive_the_rename.py pins every one of those
so a future well-meaning sweep cannot orphan a user's library.

Linux .deb users install a new package name and should apt remove
omnivoice-studio; that note is in the changelog.
2026-08-07 01:30:58 +05:30

1435 lines
108 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Competitive Analysis — voicebox · pyvideotrans · Patter (+ second-tier landscape)
*Compiled 2026-06-11 from four parallel research passes (one per competitor repo + a
full feature-surface inventory of this codebase). Star counts and versions are
as-of-date snapshots. VoiceStudio grades (A–D) come from the self-inventory: code
signals, test coverage, TODO density, and open-issue mentions — not marketing.*
*Expanded 2026-06-11 (second pass, six additional research agents): second-tier
landscape survey, source-level deep dives into the flagged components of all three
competitors (clean-room functional specs for the GPL one), implementation specs
mapping every ranked action onto this codebase, and user-sentiment / market-positioning
research. Three grades in the matrix were corrected where the original self-inventory
was stale — see the matrix legend.*
*Expanded again 2026-06-12 (third pass): the
[Roadmap directions](#roadmap-directions-community-discussion-346) section grounds
the feature roadmap announced in discussion #346 — agentic voice workflows, remote
GPU backends, audiobook creator, persona gallery, model/env + GPU-compat management —
in landscape, license, and regulatory research, with scope ladders and eight new
consolidated actions (15–22).*
## TL;DR
| | [voicebox](https://github.com/jamiepine/voicebox) | [pyvideotrans](https://github.com/jianchang512/pyvideotrans) | [Patter](https://github.com/PatterAI/Patter) |
|---|---|---|---|
| What | Local-first voice studio (ElevenLabs + WisprFlow alt) — **our most direct competitor** | Desktop video translate/dub pipeline (GUI + CLI) | Telephony voice-agent SDK (self-hosted Vapi/Retell alt) — adjacent, not competing |
| Stack | Tauri v2 + React + FastAPI (same as us) | PySide6 (Qt) + FFmpeg, 100 % Python | Python + TS dual SDK |
| Maturity | v0.5.0, ~29.7k★, fast cadence, beta-grade hardware backlog | V4.01, ~17.9k★, 2.5 yrs mature, monthly releases | v0.6.x, ~511★, 2 months old, exceptionally well-engineered |
| License | **MIT** | **GPL-3.0** | **MIT** |
| Code reuse for us | ✅ **Port directly** (keep MIT attribution header) | ⚠️ **Reimplement ideas only — never copy** | ✅ **Port directly** (keep MIT attribution header) |
**Second-pass promotion:** [KrillinAI / KlicStudio](https://github.com/krillinai/KlicStudio)
(Go, ~10.3k★, GPL-3.0, v2.0.3 released 2026-06-09, only 28 open issues) is now rated
**as direct a competitor as voicebox for the dubbing user** — desktop+web+CLI form
factors and a deliberate "for AI agents" strategy that competes with our MCP angle.
GPL-3.0 → study-only. Full profile in the [second-tier landscape](#second-tier-landscape)
below; it has not been folded into the per-capability matrix because we have not done
a capability-by-capability source pass on it yet.
**License ground rule.** VoiceStudio is **AGPL-3.0-only with a commercial
dual-license offering**. MIT code can be incorporated (attribution preserved) and
stays compatible with selling commercial exceptions. GPL-3.0 code is technically
combinable with AGPL-3.0 (GPLv3 §13), **but** copied GPL files stay GPL-3.0 forever
under the original author's copyright — which would break the commercial-license
model (we can only sell exceptions for code we own). So pyvideotrans is a
*design-document*, not a code source: study `_rate.py`, write our own.
The same logic applies to **third-party AGPL code** (e.g. alltalk_tts): even though
we are AGPL ourselves, we cannot sublicense someone else's AGPL code under our
commercial exception — study-only, same as GPL.
Fun fact discovered en route: **pyvideotrans already integrates VoiceStudio as
a first-class TTS/clone backend** (`videotrans/tts/_omnivoice.py`, via our Gradio
API). We are upstream for 17.9k-star project users. **Second-pass update: the
integration is verified broken** — it speaks Gradio to an endpoint we never exposed.
Details in the [pyvideotrans deep dive](#7-fact-check-the-pyvideotrans--omnivoice-integration);
the action item is now "ship a compat shim or upstream a REST integration", not
"verify".
---
## Big feature matrix
Legend — **Us**: A–D maturity grade from the self-inventory. **Them**: ✅ stable ·
🟡 beta/partial · ❌ absent. *(bold = they beat us; this is the gap list)*
*Second-pass corrections (marked △): three "Us" cells in the original were stale —
the self-inventory missed `scripts/validate-install-docs.py`, the probe-judge +
`omnivoice/eval/` stack, and the real FastMCP server at `backend/mcp_server.py`.*
| Capability | Us | voicebox | pyvideotrans | Patter | Notes |
|---|---|---|---|---|---|
| **Generation & cloning** |
| Zero-shot voice cloning | B | ✅ | ✅ (via clone-TTS engines) | ❌ | Parity; their multi-sample profiles are slightly ahead |
| Preset voice library (no reference audio) | B+ (20+ archetypes) | ✅ **50+ presets** (Kokoro/Qwen) | ❌ | ❌ | They win on count, we win on curation + degenerate-check |
| Voice design from text description | B | 🟡 (personality descriptors) | ❌ | ❌ | We're ahead (#317 shipped a deterministic mapper) |
| **Unlimited-length generation (chunk + crossfade)** | ❌ (no auto-chunking) | ✅ `chunked_tts.py` | ✅ (per-subtitle by design) | ❌ | **Gap.** Their crossfade chunker removes the length ceiling |
| Paralinguistic tags (`[laugh]`, `[sigh]`) | B (13 native reaction tags via ⊕ Insert; no `[breath]` yet) | ✅ (Chatterbox Turbo) | ❌ | ❌ | Near-parity — see docs/expressive-speech.md; CosyVoice 3 adds `[breath]`/`[laughter]` |
| Delivery instructions ("whisper", "slowly") | B (instruct field) | ✅ (Qwen NL control) | ❌ | ❌ | Parity-ish |
| Generation queue w/ cancel + SSE | B+ (job store, SSE replay) | ✅ | ✅ (9-queue pipeline) | n/a | Parity; our SSE reconnect-replay is ahead of voicebox |
| Post-processing FX chain (reverb/pitch/comp) | B (effect chain exists) | ✅ **Pedalboard, per-profile presets** | ❌ | ❌ | Theirs is richer + has preset UX |
| Multi-track timeline editor (stories/podcasts) | ❌ | ✅ **Stories editor (v0.5.0)** | ❌ | ❌ | **Gap** — also the #280-item-3 timeline ask |
| Audio watermarking (AudioSeal) | B | ❌ | ❌ | ❌ | **We're unique here** |
| **Dubbing pipeline** |
| Full video dub (ASR→translate→TTS→mux) | A– | ❌ | ✅ (1200-line battle-tested pipeline) | ❌ | Two-horse race; we're competitive |
| Incremental re-dub (change 1 line, regen 1 segment) | A– (#281 fixed) | ❌ | ❌ | ❌ | **We're unique here** |
| **Dub-length fitting (audio speedup + video slowdown)** | A– (Smart Fit complete: planner + generate path + fit fingerprints + two-tier video-retime export with drift absorption and fitted subtitles) | ❌ | ✅ **`_rate.py` — the crown jewel** | ❌ | **Gap closed** — Action 1 reimplemented clean-room (`services/fit_planner.py`, `services/video_retime.py`, [Spec 1](#spec-1--dub-length-fitting-v2)) |
| Vocal/BGM separation + re-mix | A– (Demucs 4-stem) | ❌ | ✅ (UVR/Spleeter ONNX) | ❌ | Parity; their ONNX models are lighter than Demucs |
| **Clone refs cut from separated vocals per segment** | 🟡 (speaker_clone refs 5–15 s/speaker) | ❌ | ✅ per-subtitle-line refs | ❌ | Their per-line granularity beats our per-speaker. Action 4 |
| Speaker diarization → multi-voice dub | B+ (pyannote) | ❌ | ✅ (4 backends incl. CAM++) | ❌ | Parity; their backend choice is wider |
| **Second-pass ASR on dubbed audio** (regenerate exact subtitle timings) | ❌ | ❌ | ✅ | ❌ | **Gap** — clever QC step. Action 5 |
| Subtitle styling / burn-in / dual-language | A– (#309 fixed) | ❌ | ✅ | ❌ | Parity |
| Batch processing (N videos) | B (50-job queue) | ❌ | ✅ (wave control, multi-GPU scaling) | ❌ | Their `batch_nums` waves + per-GPU thread scaling is ahead |
| Translation channel breadth | B (LLM 3-step chain + glossary) | ❌ | ✅ **~25 channels** | ❌ | Breadth vs depth: our reflect/adapt chain is deeper, their coverage wider |
| Translation caching + line-count validation | 🟡 (fingerprints #281) | ❌ | ✅ MD5 cache + timeline re-match | ❌ | Worth studying |
| **Dictation** |
| Global-hotkey dictation pill | B+ (#323 fixed) | ✅ (v0.5.0, auto-paste **macOS-only**) | ❌ | ❌ | We're ahead on cross-platform (their gap violates our parity rule) |
| **LLM transcript refinement (filler-word removal)** | ❌ | ✅ local Qwen3 0.6B–4B | ❌ | ❌ | **Gap.** Action 3 |
| **Captures library (replay / re-transcribe / refine)** | 🟡 (transcription history page) | ✅ richer (v0.5.0) | ❌ | ❌ | Partial gap — we store, they iterate |
| Dictation while audio plays (echo cancel) | ✅ opt-in (Settings → Capture): server-side NLMS AEC on `/ws/transcribe?aec=1` + AudioWorklet PCM mic + player far-end tap — Action 8 | ❌ | ❌ | ✅ NLMS AEC | Ported Patter's NLMS canceller. Action 8 |
| **Engines & platform** |
| TTS engine count | B (6) | ✅ 7 | ✅ **33 channels** (22 ASR, 25 translate) | ✅ 7 (cloud) | pyvideotrans = breadth king (incl. cloud); we + voicebox are local-only by design |
| Engine plugin protocol | B+ (ABC + registry) | ✅ Protocol + ModelConfig registry, **agent skill for adding engines** | ✅ lazy dataclass plugins | ✅ provider SDK | Everyone converged on the same pattern; their `requires_cuda`-gap lesson is free for us |
| **MLX runtime on Apple Silicon** | 🟡 (MLX-Audio engine only) | ✅ **MLX for TTS+STT, 4–5× claimed** | ❌ | ❌ | **Gap** — dual-runtime per engine. Action 6 |
| **CUDA binary auto-download (small installer)** | ❌ (venv on first run ships everything) | ✅ in-app CUDA swap incl. sm_120 | ❌ | ❌ | Different bootstrap philosophy; their #1 bug source too. Study only — failure-mode autopsy in the deep dive |
| Crash-isolated engine subprocesses | 🟡 (Demucs/ffmpeg subprocesses) | ❌ | ✅ (whisper.cpp etc. in child procs) | ❌ | Their JSON-log polling pattern is a cheap stability win. Action 7 |
| ROCm support | A– (with edge cases) | 🟡 (large breakage backlog) | 🟡 | n/a | We're ahead |
| **Integration surface** |
| OpenAI-compatible API | B+ | ✅ REST | ❌ | n/a | Parity |
| **MCP server (agent speaks in your voice)** | △ B– (FastMCP `backend/mcp_server.py`: 4 tools, stdio + SSE; **not mounted on the main app, no per-agent voice binding**) | ✅ **FastMCP at `/mcp` + stdio shim, per-agent voice binding** | ❌ | ✅ (client + server) | Gap is narrower than originally graded; what's missing is exactly the half voicebox shipped. Action 2 |
| Web/Docker deployment | B– (headless image exists) | ✅ (`docker compose up`) | ❌ (desktop only) | ✅ | Parity-ish; our :latest/:stable retag (PR #338) helps |
| CLI / headless batch | 🟡 (API only) | ❌ | ✅ `cli.py` (stt/tts/sts/vtv) | ✅ | Partial gap for power users |
| Streaming TTS (websocket, low TTFA) | C+ (`/ws/tts` experimental) | ❌ | ❌ | ✅ **sentence-chunked streaming, first-flush** | Patter's chunker + first-flush are portable. Action 8 |
| **Ops & quality discipline** |
| Eval harness for output quality | △ 🟡 (probe judges `tests/probe/judges/` + `omnivoice/eval/` WER/MOS/speaker-sim — deterministic tier exists, **no semantic/LLM-judge tier**) | ❌ | ❌ | ✅ LLM-judge evals + CLI | Patter's harness adds the missing *semantic* tier. Action 9 |
| **Docs-drift CI** | △ 🟡 (`scripts/validate-install-docs.py` gates `docs/install/*.md` in ci.yml — **no inventory-wide drift job**) | ❌ | ❌ | ✅ daily inventory-vs-docs diff job | Patter's rolling-issue automation is the missing half. Action 9 |
| Model-evaluation decision log | 🟡 (ROADMAP phases) | ✅ `PROJECT_STATUS.md` accepted/abandoned log | ❌ | ❌ | Cheap practice to adopt |
| Telemetry design (consent-bounded) | n/a (opt-in GH Issues only) | ❌ | ❌ | ✅ consent module, bucketed values | Reference design for the bug reporter — allowlist pattern in the deep dive |
### Where we are unique (defend these)
- **Incremental re-dub** with fingerprint tracking — nobody else has it.
- **646-language claim** via VoiceStudio model — voicebox tops out at 23, pyvideotrans is engine-dependent.
- **AudioSeal watermarking + detection** — unique among all three.
- **Cross-platform dictation as a default** (their auto-paste is macOS-only).
- **3-step LLM translation chain (translate → reflect → adapt) + glossary** — deeper than anyone's single-pass.
*Second-pass reality check: market evidence per item — which of these users actually
ask for — is in [User sentiment & positioning](#honest-verdicts-on-our-unique-five).
Short version: (a) and (d) are strong levers, (b) is a reach lever with a quality-risk
tail, (c) and (e) are real but nobody searches for them by name.*
---
## Second-tier landscape
*Surveyed 2026-06-11; stars / last-push / open-issue counts verified via the GitHub
API. These either compete for the same user or carry portable ideas, but none (except
KrillinAI) warrants a per-capability matrix column yet.*
| Project | What | Stack | Maturity (2026-06-11) | License | Overlap w/ us | Verdict |
|---|---|---|---|---|---|---|
| [KrillinAI/KlicStudio](https://github.com/krillinai/KlicStudio) | LLM video translate+dub for humans *and AI agents* | **Go** + web/desktop/CLI | 10.3k★, v2.0.3 **2026-06-09**, 28 issues | GPL-3.0 | Dubbing, cloning, API server | **Direct competitor (promoted)**; reimplement only |
| [VideoLingo](https://github.com/Huanshere/VideoLingo) | One-click "Netflix-grade" subtitle + dubbing pipeline | Python/Streamlit, cloud LLM | 17.4k★, v3.0.1 2026-02, 208 issues | Apache-2.0 | Dubbing, cloning (GPT-SoVITS), subtitles | Direct competitor (web, cloud-LLM-dependent); **port OK** |
| [voice-pro](https://github.com/abus-aikorea/voice-pro) | All-in-one local cloning/TTS/Whisper/Demucs/translate WebUI | Python/Gradio (Windows-leaning) | 10.9k★, push 2025-12, 47 issues | GPL-3.0 | Cloning, TTS, STT, separation, translation | Direct competitor (web form factor, 6 months quiet); **reimplement only** |
| [ebook2audiobook](https://github.com/DrewThomasson/ebook2audiobook) | Ebook → audiobook w/ cloning, 1,158+ languages | Python/Gradio + CLI/Docker | 19.2k★, push **2026-06-11**, **10 issues** | Apache-2.0 | Cloning, multi-engine TTS | Adjacent vertical; **port OK** |
| [GPT-SoVITS](https://github.com/RVC-Boss/GPT-SoVITS) | Few-shot cloning + TTS WebUI w/ training tools | Python/Gradio + API | 58.6k★, push 2026-04, 872 issues | MIT | Cloning, TTS (we already wrap it as `GPTSoVITSBackend`) | Component + partial competitor; **port OK** |
| [SoniTranslate](https://github.com/r3gm/SoniTranslate) | Gradio video-dubbing pipeline w/ diarization + cloning | Python/Gradio | 1.4k★, push 2026-04, **no release since 2024-05**, 122 issues | Apache-2.0 | Dubbing, cloning, multi-TTS (we sidecar it) | Component + adjacent; **port-with-attribution OK** |
| [Speaches](https://github.com/speaches-ai/speaches) | OpenAI-compatible local STT+TTS+Realtime server | Python/Docker | 3.4k★, push 2026-06-10, 136 issues | MIT | API-server surface only | Component-not-competitor; **port OK** |
### Notable ideas / gaps per project
**KrillinAI / KlicStudio** — the promotion case: released two days before this survey,
28 open issues at 10.3k★ (most operationally disciplined direct competitor), and a
`skills/` Agent-Skills framework exposing stable CLI contracts + JSON artifact
manifests so AI agents drive each pipeline stage independently — it validates and
extends our MCP-server direction. Also: portrait/landscape re-rendering + cover
generation for TikTok/Shorts, a social-publishing tail we ignore. GPL-3.0: ideas only.
**VideoLingo** — its "Translate-Reflect-Adaptation" chain is the same 3-step idea as
our `translator.py` chain (convergent evolution; theirs predates the comparison —
worth a diff of prompt strategies, Apache-2.0 so even portable). Netflix-standard
subtitle segmentation with word-level alignment overlaps our `subtitle_segmenter.py`.
Per-step pause/resume/stop on long pipelines is a UX gap we have. Counter-positioning:
it *requires* a cloud LLM key — our local-first translation is the differentiator to
message against it.
**voice-pro** — closest single-app feature overlap in the tier (cloning + TTS +
Whisper + Demucs + YouTube ingest + translation). Proves 10k+★ demand for exactly our
bundle in a clunkier (Gradio, Windows-leaning, GPL) package; quiet since 2025-12 —
its users are capturable if it stalls. The recurring expectation across this whole
tier (voice-pro, VideoLingo, KrillinAI, SoniTranslate): built-in
YouTube-download → process loop. Our URL-ingest dub path covers part of this.
**ebook2audiobook** — the long-form vertical we don't serve: chapterized m4b output,
inline SML tags (`break`/`pause`/voice-switch mid-text), OCR'd PDFs, per-file voice
mapping in batch. A "narrate a whole book" mode is a credible extension (Apache-2.0,
portable). Separately: 10 open issues at 19.2k★ is the best issue-hygiene benchmark
in the landscape — study their template/triage setup for our bug-reporting milestone.
**GPT-SoVITS** — dual role: an engine we wrap *and* a competitor for the DIY cloning
user. The big capability we lack vs. that crowd is its in-app fine-tuning chain
(dataset slicing → ASR → labeling → train); MIT, so the training-tool code is
portable. Version churn (v2/v2Pro/v3/v4) is a live threat to our `GPTSoVITSBackend`
engine-compat constraint — pin and contract-test the API surface.
**SoniTranslate** — we already depend on it: `backend/services/sonitranslate.py` runs
it as an isolated Gradio sidecar (port 7860) via `gradio_client`. Apache-2.0 means we
may also vendor its code in-tree with attribution if sidecar reliability ever becomes
a problem (process-level integration carries no derivative-work questions at all, so
the current setup is the safest). **Risk: its release cadence stalled (v0.5.0,
May 2024)** while we depend on it at runtime — consider pinning a fork.
**Speaches** — "Ollama for audio": dynamic model auto-load/unload per request is
directly applicable to our multi-engine VRAM juggling (MIT, portable). Its OpenAI
Realtime-API emulation is the compatibility surface third-party clients will
eventually ask our API server for. From the same bucket:
[Kokoro-FastAPI](https://github.com/remsky/Kokoro-FastAPI) (Apache-2.0, 5.0k★) ships
**weighted voice mixing** (blend voices by ratio) — a cheap, differentiating voice-design
feature. New Action 12.
### Watch items (not competitors)
- **[TTS-WebUI](https://github.com/rsxdalv/TTS-WebUI)** (MIT, 3.2k★, active) — a
40+-model local audio hub whose repo description **already advertises a VoiceStudio
extension**. Verify what that extension wraps and that AGPL terms are respected.
Its per-extension `uv` venv isolation parallels our sidecar approach.
- **[F5-TTS](https://github.com/SWivid/F5-TTS)** (14.7k★) — engine candidate, not an
app. Trap: code is MIT but **pre-trained weights are CC-BY-NC** — an F5-TTS engine
would need prominent non-commercial-weights labeling like our existing
engine-license gates.
- **[alltalk_tts](https://github.com/erew123/alltalk_tts)** (AGPL-3.0, 2.4k★, solo
maintainer) — adjacent TTS server. Third-party AGPL = study-only for us (see
license ground rule). Its narrator/character voice-switching markup and low-VRAM
modes are reimplementable ideas.
- **[Linly-Dubbing](https://github.com/Kedreamix/Linly-Dubbing)** (Apache-2.0, 3.2k★,
~15 months stale) — idea quarry only; its lip-sync integration (re-syncing mouth
movements to the dubbed track) is the one feature nobody in the landscape, us
included, ships.
- **[resemble-ai/chatterbox](https://github.com/resemble-ai/chatterbox)** (~25.0k★,
MIT) — engine candidate; full evaluation in the appendix.
---
## Ranked actions
Effort: S < 1 day · M = 1–3 days · L = 1–2 weeks. "Port" = copy + adapt MIT/Apache code
with attribution header. "Reimplement" = clean-room from the functional spec in this
doc — **do not open pyvideotrans source files while writing ours** (the specs below
were written by a dedicated spec pass precisely so implementers never have to).
Each action now links to an implementation spec mapped onto our codebase.
| # | Action | From | Mode | Effort | Why now |
|---|---|---|---|---|---|
| 1 | **Dub-length fitting v2**: absorb inter-segment silence slack → if speedup ≤ 1.2× stretch audio only (pitch-preserving) → else split burden ~50/50 with per-segment video slowdown → regenerate subtitle timeline from actual dub durations → freeze last frame for drift | pyvideotrans `_rate.py` design | **Reimplement** ([spec](#spec-1--dub-length-fitting-v2)) | L | Our #280 onset-snapping is a band-aid; this is the algorithm that makes dubs *fit*. Highest user-visible quality win available |
| 2 | **MCP server v1**: mount the existing FastMCP server on the main FastAPI app + stdio shim + per-agent voice binding | voicebox `backend/mcp_server/`, `mcp_shim/` | **Port** ([spec](#spec-2--mcp-server-v1)) | M | We already have the FastMCP half (`backend/mcp_server.py`); voicebox shipped exactly the missing half. Agents-speak-in-your-voice is organic marketing |
| 3 | **Dictation refinement**: deterministic repetition-collapse pre-pass + optional local-LLM filler-word removal before paste | voicebox `services/refinement.py` | **Port** (adapt to our `llm_backend.py`) ([spec](#spec-3--dictation-refinement)) | M | Biggest dictation quality jump per line of code; WisprFlow's whole pitch. The deterministic pre-pass alone (fixes Whisper hallucination loops) is worth shipping first |
| 4 | **Per-segment clone refs**: cut the voice-clone reference for each dub segment from the separated vocal track at that segment's timestamps, instead of one 5–15 s ref per speaker | pyvideotrans per-line ref idea | **Reimplement** ([spec](#spec-4--per-segment-clone-refs)) | S–M | Prosody of each line matches its source line; cheap because Demucs stems + segment times already exist |
| 5 | **Second-pass ASR QC**: after dub generation, re-run ASR on the synthetic audio to regenerate exactly-timed subtitles (and flag segments whose recognized text drifts from the target text) | pyvideotrans pipeline stage | **Reimplement** ([spec](#spec-5--second-pass-asr-qc)) | M | Turns subtitle timing from "trusted math" into "measured truth"; doubles as an automatic dub-quality check |
| 6 | **MLX runtime pass**: route Whisper + at least one TTS engine through MLX on Apple Silicon via backend-aware model-repo mapping | voicebox `mlx_backend.py` pattern | **Port** pattern ([spec](#spec-6--mlx-runtime-pass)) | L | M-series Macs are a huge slice of local-AI users; 4–5× claimed speedup |
| 7 | **Crash-isolated ASR subprocess**: run native-crashy engines (whisper.cpp class) in a single-use child process, so a segfault never kills the backend | pyvideotrans subprocess pattern | **Reimplement** ([spec](#spec-7--crash-isolated-asr)) | M | Directly serves "first-run that actually works"; engine crashes become per-job failures. The sentiment pass confirmed whisper GPU-teardown crashes are endemic to the category |
| 8 | **Streaming polish kit**: sentence-aware chunker (abbreviation + multilingual punctuation, aggressive first-flush for TTFA) for `/ws/tts` + NLMS AEC so dictation works during playback | Patter `sentence_chunker.py`, `aec.py` | **Port** ([spec](#spec-8--streaming-polish-kit)) | M | Moves `/ws/tts` from C+ experiment toward production; AEC unlocks dictate-over-playback |
| 9 | **Quality rails**: (a) extend our existing install-docs gate into a daily inventory-vs-docs drift job with rolling-issue automation; (b) LLM-judge eval harness as a *non-gating* semantic tier above our deterministic probe judges | Patter `docs-feature-drift.yml`, `evals/` | **Port** ([spec](#spec-9--quality-rails)) | S + M | (a) we already have `validate-install-docs.py` — this is the second half; (b) gives dub translation quality what the probe judges give DSP correctness |
| 10 | **Practice adoptions** (no code): `PROJECT_STATUS.md`-style engine decision log; platform-gating flag audit in our engine registry (pre-empt voicebox's top bug class); add Chatterbox Turbo to the engine roster for paralinguistic tags | voicebox | n/a | S each | Cheap, compounding |
| 11 | **Fix the pyvideotrans bridge**: their `_omnivoice.py` integration is **verified broken** (speaks Gradio `/_clone_fn` to our FastAPI :3900 — hard-stops on connect). Either ship a small Gradio-compatible shim or upstream a REST/OpenAI-style integration PR to pyvideotrans | pyvideotrans | **Build/PR** ([spec](#spec-11--pyvideotrans-bridge)) | S–M | 17.9k★ project routing users to us — the bridge is down right now |
| 12 | **Weighted voice mixing**: blend preset/cloned voices by ratio as a voice-design primitive | Kokoro-FastAPI (Apache-2.0) | **Port** | S–M | Cheap, differentiating, and feeds the voice-design surface where we're already ahead |
| 13 | **Dynamic engine load/unload**: per-request model lifecycle (load on demand, unload on idle/VRAM pressure) — 🟡 **idle-unload shipped for both engine classes**: in-process default model via `model_manager.idle_worker`; subprocess-engine sidecars via `subprocess_backend.reap_idle_sidecars` (lock-guarded, `OMNIVOICE_SIDECAR_IDLE_TIMEOUT_S`, default 300 s). Cross-engine VRAM-pressure preemption (unload B to make room for A) still TODO | Speaches (MIT) | **Port** pattern | M | Multi-engine VRAM juggling is our chronic background pain; "Ollama for audio" solved it |
| 14 | **Positioning kit** (no code): ElevenLabs-dubbing-cost comparison page + incremental-re-dub demo GIF; Show HN; name-collision disambiguation; "WisprFlow alternative" docs entry point | sentiment research | n/a | S each | Grounded in what users actually search; details in [positioning moves](#positioning-moves) |
### Explicitly not recommended
- **Copying any pyvideotrans / KrillinAI / voice-pro code** — GPL-3.0 files would stay
GPL under their author's copyright inside our AGPL tree and break the commercial
dual-license. Ideas are fair game; code is not. Same for third-party **AGPL** code
(alltalk_tts).
- **Cloud TTS/ASR/translate channel breadth** (pyvideotrans's 33/22/25) — violates
the local-first constraint. Our breadth play is local engines only.
- **voicebox's CUDA-binary-swap bootstrap** — their own top bug category; our uv-venv
bootstrap is healthier. The deep dive below documents six concrete failure modes to
avoid; the lessons transfer to our sidecar pattern, the mechanism does not.
- **Patter's telephony stack** — different product. Only the audio/streaming/ops
pieces above are relevant.
- **Patter's opt-out telemetry default** — its *allowlist/bucketing design* informs
our opt-in bug reporter (see deep dive), but the consent default and the
third-party endpoint both violate our local-first constraint.
- **F5-TTS as an unlabeled engine** — MIT code, but CC-BY-NC weights; only with the
same prominent license gate we use for OpenRAIL engines.
---
## Deep dives
*Source-level briefs from the second research pass. voicebox and Patter are MIT —
briefs may quote and porting means copying with attribution. The pyvideotrans
section is a **clean-room functional spec**: written by a dedicated spec pass that
read the GPL source so implementers never have to; it contains no code, only
behavior and constants. File paths are repo-relative, with approximate line ranges,
for verification only.*
### voicebox (MIT — portable)
#### 1. Chunked long-form TTS (`backend/utils/chunked_tts.py`, 299 LOC)
Engine-agnostic wrapper around any `generate()`. Text ≤ `max_chunk_chars` (default
**800**, per-request overridable) takes a zero-overhead single-shot path. Longer text
splits greedily left-to-right with descending boundary priority: sentence end
(`[.!?]` + CJK equivalents, skipping a 25-entry abbreviation set and decimals) →
clause boundary (`;:,—`) → whitespace → safe hard cut. Paralinguistic tags
(`[laugh]`) are atomic — a regex guards every split candidate. Per-chunk seed is
`seed + i` (decorrelates RNG artifacts, keeps (text, seed) reproducible). Optional
per-chunk `trim_fn` for engines that hallucinate trailing noise. Joining is a linear
crossfade, default **50 ms** (0 = hard cut), overlap clamped to the shorter side.
Known latent bug: sample rate is taken from the *first* chunk; later mismatches are
silently ignored. No per-chunk failure recovery — any chunk exception fails the whole
generation. Deps: numpy only.
**Porting:** new `backend/utils/` module invoked from our `tts_backend.py` synthesis
orchestration — it only needs an awaitable `generate()` returning `(ndarray, sr)`,
which our `TTSBackend` ABC already provides. Make the abbreviation set and
sentence-end regexes language-aware for our 646-language scope; wire
`max_chunk_chars`/`crossfade_ms` into the request models. Not a fit for streaming
`/ws/tts` as-is (batch concat, not incremental emit), but `split_text_into_chunks`
alone is reusable there. **Effort: S.**
#### 2. Dictation LLM refinement (`backend/services/refinement.py`, 295 LOC + capture plumbing)
Local Qwen3 **0.6B/1.7B/4B** (mlx-community 4-bit on Apple Silicon, upstream
elsewhere), default 0.6B, `temperature=0.2`, thinking off. Prompts are assembled from
three boolean toggles (`smart_cleanup`, `self_correction`, `preserve_technical`, all
default on) — no raw prompt editor. The base instruction frames the model as "a text
filter, not an assistant" with explicit anti-instruction-following rules. Seven
few-shot examples are passed as **structured chat turns**, not inline prompt text —
small models pattern-match and echo inline examples; ordering is recency-weighted
with hardest rules last.
The underrated half is a **deterministic pre-pass**, `collapse_repetitive_artifacts()`:
strips Whisper hallucination loops *before* the LLM — word-level (token repeated ≥6×)
and character-level (any 2–60-char unit repeating ≥6×, catches CJK loops with no
spaces). Rhetorical repeats below 6 survive. This alone fixes the classic Whisper
"thanks for watching" loop and works with no LLM configured.
Flow: record → full-audio STT (raw transcript persisted) → if auto-refine, one LLM
round-trip → refined-or-raw text is what auto-pastes. Refinement is re-runnable with
different flags. A readiness endpoint checks *on-disk* model presence so the global
hotkey never hangs on an un-downloaded model; refine errors leave the raw transcript
standing.
**Porting:** maps almost 1:1 onto our dictation stack. Pre-pass + prompt builder →
new `backend/services/refinement.py`; the LLM call goes through our existing
`llm_backend.py` OpenAI-compat adapter (Ollama/LM Studio) instead of an in-process
Qwen — simpler: the few-shot examples become standard `messages` pairs. Hook after
the **final** transcript in `capture_ws.py`, never on partials. Readiness becomes
"is an LLM endpoint configured/reachable". Needs Settings toggles + i18n keys.
**Effort: M** (service S; UX, persistence, ws-protocol addition make it M).
#### 3. MCP server + stdio shim (`backend/mcp_server/` ~650 LOC, shim ~200 LOC)
FastMCP **mounted on the main FastAPI app** at `/mcp` (Streamable HTTP); FastMCP's
session manager must run inside the ASGI lifespan — they stack their existing
startup/shutdown with FastMCP's via `AsyncExitStack`. Tools (dotted names):
`voicebox.speak(text, profile, engine, personality, language)` → async
`{generation_id, poll_url}`; `voicebox.transcribe(audio_base64 | audio_path)` —
`audio_path` **restricted to loopback callers** (so a 0.0.0.0-bound server isn't an
arbitrary-file-read primitive), 200 MB cap; `voicebox.list_captures`,
`voicebox.list_profiles`.
**Per-agent voice binding** — the headline: every MCP client sends an
`X-Voicebox-Client-Id` header (from its MCP config, or forwarded by the shim from an
env var). Middleware copies it into a `ContextVar` so tool handlers read it without
plumbing, and stamps `last_seen_at`. A `mcp_client_bindings` table holds per-client
`{label, profile_id, default_engine, default_personality}` — "Claude Code speaks in
Morgan, Cursor in Scarlett." Resolution precedence: explicit tool arg → per-client
binding → global default → helpful error. Bindings managed from Settings over plain
REST. The stdio shim is a ~200-line stdio↔HTTP proxy (waits for `/health`, relays
JSON-RPC, captures/replays the MCP session id, maps HTTP errors to JSON-RPC errors);
only dep is httpx.
**Porting:** we already have the FastMCP half (`backend/mcp_server.py`, stdio+SSE,
4 tools) — this is precisely the missing half. Order: (1) mount + lifespan
composition on our main app (wrap our startup hooks, don't replace); (2) client-id
middleware + bindings table (alembic migration per the backward-compat constraint) +
resolve chain over our voice profiles; (3) shim nearly verbatim. The loopback gate on
file-path tools is a security pattern worth copying anywhere. **Effort: M.**
#### 4. MLX runtime (`backend/backends/mlx_backend.py`, 367 LOC + factory routing)
Not per-engine plugins — a **fork inside the lazy backend factory**: only Qwen TTS,
Whisper STT, and the Qwen3 LLM branch on `get_backend_type()`; everything else is
torch-only everywhere. Detection: Darwin+arm64, then `import mlx.core` inside
try/except catching `ImportError, OSError, RuntimeError` — in a PyInstaller bundle
the native dylib/metallib can fail to load even when the package imports. The fork
extends to **model selection**: the same `model_size` key resolves to
`mlx-community/...` weights on MLX (~3× smaller downloads) vs upstream weights on
torch. Backends are duck-typed Protocols; inference runs via `asyncio.to_thread`;
clone failure degrades to generation without the voice prompt. Claimed 4–5× speedup
is README-grade (no benchmark file in the repo).
**Porting:** we already have `MLXAudioBackend` in the registry, so the structure
exists. Worth stealing: (a) backend-aware ModelConfig repo mapping — one engine key,
platform-resolved artifact; (b) the robust `import mlx.core` probe (we will hit the
same PyInstaller failure in bundled builds); (c) MLX Whisper for the dictation path —
we already have `MLXWhisperBackend` in `asr_backend.py`, the gap is routing dictation
through it. Cross-platform rule satisfied: MLX is implementation-level, default
behavior identical. **Effort: S–M.**
#### 5. Stories multi-track editor (shape only; `backend/services/stories.py`, 966 LOC)
Two tables: `Story {id, name, description, timestamps}` and `StoryItem {id, story_id,
generation_id FK, version_id FK nullable, start_time_ms, track, trim_start_ms,
trim_end_ms, volume, created_at}`. The key design move: **a clip references a
Generation, never copies audio** — trims are non-destructive offsets, and
`version_id` pins a clip to a specific regeneration take while sibling clips can use
different takes. Split = two items sharing one `generation_id` with complementary
trims (row-locked against double-click races). Export sums clips into a float32
buffer at sample offsets (overlaps mix additively), then peak-normalizes if needed.
**Porting:** the 9-column schema is the valuable part — a full multi-track NLE over
our existing generation history (additive alembic migration, backward-compatible).
The frontend timeline is the actual cost. **Effort: L full feature, S for data model
+ export mixer alone.**
#### 6. CUDA binary swap bootstrap — study-only failure autopsy (`backend/services/cuda.py`, 422 LOC + Rust launch logic)
Mechanism: CPU PyInstaller sidecar ships in the installer; a CUDA variant downloads
as two independently versioned tarballs (server core, versioned with the app; CUDA
libs ~4 GB, keyed on a toolkit string). At every launch the Rust shell runs the
downloaded binary with `--version` and compares; mismatch or error → silent CPU
fallback. Six failure modes documented from their tracker, **all to avoid**:
1. Staleness detection requires *spawning the possibly-broken binary* — corrupt
onedir → `--version` fails → silently CPU, never repaired this launch. (They also
paid a 30 s torch import on every version check until adding a fast path.)
2. One-launch GPU lag after every app update ("update disabled my GPU" reports).
3. Extract-over-old-dir without wiping — orphaned files from previous layouts shadow
the new binary while `--version` still passes.
4. Auto-update vs manual download raced on the same temp file (fixed late with a
lock that now silently *skips* user-initiated downloads).
5. Manifest-vs-disk drift: libs staleness reads a JSON manifest that survives failed
or hand-deleted extractions; toolkit bumps force full 4 GB re-downloads.
6. Every failure path degrades to CPU with stdout-only logging — users discover it
as "the app got slow", not as an error.
**Lessons for our sidecars** (`subprocess_backend.py` + engine bootstraps): write the
version manifest atomically *after* successful extract — never interrogate a binary
to learn its version; extract to temp dir + atomic rename; make GPU/CPU fallback
loudly visible in the UI; the split-archive idea (app-versioned core vs
toolkit-versioned libs) genuinely fixed their 4 GB re-download complaint and is the
one piece worth keeping. **Effort: n/a (declined) — this list is the deliverable.**
### pyvideotrans (GPL-3.0 — clean-room functional specs, no code)
#### 1. Dub-length fitting (`videotrans/task/_rate.py`, ~lines 288–877)
The algorithm that makes dubs fit their slots. All constants verified against source:
| Parameter | Value | Meaning |
|---|---|---|
| Audio-only threshold (both-mode) | **1.2** | Required speedup ≤ 1.2× → audio absorbs everything, video untouched |
| Burden split beyond 1.2× | **50/50** | Joint target = slot + (dub − slot)/2, applied to both audio stretch and video slowdown; caps deliberately ignored in this branch |
| Max audio speed (audio-only mode) | 100 (default setting) | Effectively unlimited; past it, slot is overrun instead |
| Max video slowdown (video-only mode) | 10× | Past it, video target clamps to slot × 10, dub truncated later |
| Video retime bias | +0.005 | Slowdown factor padded to compensate frame-rounding undershoot |
| Retime no-op epsilon | 0.001 | Near-1.0 factors skip the filter |
| Min valid video clip | 1024 bytes | Smaller output = failed; retried without retiming, then dropped |
| Audio stretch clamp | 0.2–50.0 (rubberband); ≤2.0-step chained tempo filter as fallback | Pitch-preserving when rubberband present |
| Working audio format | 48 kHz / 2ch / 16-bit PCM | All silence + dub segments normalized |
| Dub-clip silence trim | threshold = clip dBFS − 20; min silence 100 ms; keep 80 ms head / 200 ms tail | Applied to every synthesized clip, default on |
| First-segment snap | < 100 ms start → cut from 0 | Avoids sub-frame clips when video slowdown is on |
| Video encode for cuts | x264 CRF 20, veryfast, **GOP=1**, yuv420p | Every frame an I-frame → clean concat boundaries |
| End-of-video freeze | last-frame clone pad = audio − video duration | Applied at final mux |
**Decision tree.** *Pre-pass (slack absorption):* every segment's slot end is
rewritten to the next segment's start — the silent gap after each line is donated to
that line; the last line's end becomes total media duration. Failed/missing dubs get
a silent placeholder exactly one slot long so the pipeline never stalls. *Per-line:*
dub ≤ slot → untouched. Otherwise in the combined mode: ratio ≤ 1.2 → audio-only
compress to exactly the slot; ratio > 1.2 → both sides meet at slot + overflow/2.
*Execution:* audio stretches run on a CPU process pool; a stretch is skipped if the
target exceeds current length (silence padding covers that). Each line becomes an
independent cut of the original video (seek, window, retime, hard duration limit);
a pre-roll clip covers 0 → first line; failed clips are re-cut without retiming, then
dropped; clips concat-copied in order. *Timeline regeneration:* the **measured**
duration of each generated clip (not the requested target) becomes the line's final
slot; a running cursor rewrites every subtitle to start at the cursor and end at
cursor + slot — subtitles track actual dub placement exactly; overruns are
hard-truncated when video slowdown is active, appended whole otherwise; short dubs
get tail silence. Overlapping segments never reach this stage (an ASR post-fix clamps
each line's end to the next line's start upstream). A TTS-only variant forces fit to
the slot and restores the *original* timeline on output.
#### 2. Per-subtitle-line clone references (`trans_create.py` ~882–1009)
Reference source priority: separated vocal stem → fresh mono 44.1 kHz extraction from
the original media. Cut timestamps are the line's **original** SRT start/end (before
gap-absorption rewriting). One 16 kHz/16-bit wav per line, indexed by line number;
the source-language subtitle text at the same index rides along as the ref
transcript. Thread pool of min(8, line count, CPU count); all cuts complete before
TTS starts. **Deliberately no min/max duration and no neighbor-borrowing** — a 400 ms
subtitle yields a 400 ms reference and quality degradation is accepted; engines that
can't handle it fail that line only (a dub job fails only when *zero* lines succeed).
Dub-file cache key: MD5 of text + role + rate + volume + pitch + channel id — lines
whose output already exists are skipped entirely.
#### 3. Second-pass ASR QC (`trans_create.py` ~419–482)
Runs after alignment, before final mux, opt-in, only when source ≠ target language
and the subtitle-embed mode doesn't require matched line counts. The assembled dub
track is downsampled to 16 kHz mono and re-recognized with **deliberately halved VAD
windows** (min speech halved with 500 ms floor; max speech halved; min silence halved,
clamped 50–1000 ms) so recognized lines come out short; short-line merging is
disabled. The resulting SRT **wholesale replaces** the target-language subtitle file —
recognized text wins unconditionally; the design accepts ASR drift in exchange for
frame-accurate display timing (dub audio is already rendered and untouched). Every
failure is a silent skip leaving the align-stage subtitles in place, so this stage
can never break a job. Engine falls back to faster-whisper large-v3-turbo when the
user's ASR channel can't do the target language.
*Our spec deviates here (see Spec 5): we want drift flagged, not silently accepted.*
#### 4. Translation cache + line-count validation (`videotrans/translator/_base.py` ~46–175)
Cache: one text file **per batch** (5 lines plain mode / 20 lines or whole-file SRT
mode), keyed by MD5 of channel id + API URL + srt-mode flag + model + source lang +
target lang + serialized batch content — any config or content change is the
invalidation mechanism (no TTL). Empty results are never cached; an all-empty batch
set raises. Line-count defense, plain mode: response split on newlines, extra lines
discarded, short responses padded with empty strings, then mapped 1:1 by index. SRT
mode: on count mismatch (LLM merged/split lines), recovery is **exact
timestamp-string matching** — translated cues map back by their time-range string;
unmatched source cues get empty text rather than shifting subsequent lines. Source
timeline is always authoritative. A final cosmetic pass strips the leading/trailing
ellipsis runs LLMs tend to add.
#### 5. Crash-isolated engine subprocesses (`videotrans/configure/base.py` ~170–276)
Every native-crashy stage (ASR, separation, diarization, VAD, retiming, stretch) runs
in a process pool with **spawn** start method and **one task per child** — every job
gets a fresh interpreter, memory fully returned, a segfault can't poison a warm
worker, and "restart" semantics are free (the pool replaces dead workers
automatically). CPU pool size: manual cap → min(cap, 8, CPUs); else
clamp(available-RAM-GB / 4, 2, 8). GPU pool defaults to 1 (strict serialization);
multi-GPU opt-in scales to min(GPU count, 8, CPUs). Progress transport: the child
overwrites a single-JSON log file; a parent daemon thread polls mtime every 1 s and
forwards to the UI bus; monitor gives up after ~1 h of no change. Child contract:
return (result, error); falsy result or non-empty error → typed task error. Native
death → the pool's broken-pool exception, decorated with model name + GPU index
before surfacing. GPU selection: first card with > 24 GB free VRAM, else most-free;
CUDA re-verified at submit time, unavailable → kwargs rewritten to CPU.
#### 6. Batch wave control (`videotrans/task/mult_video.py`, `job.py`)
Nine FIFO stage queues (prepare → recognize → diarize → translate → dub → align →
second-pass ASR → assemble → done), each with dedicated workers. GPU-heavy stages get
1 thread by default (2 with 2–3 GPUs in multi-GPU mode, 4 with ≥ 4); network-bound or
globally stateful stages (translate, dub, align, second-pass, done) are always 1.
With waves off, all videos enter the conveyor at once — video A can be translating
while B is in ASR. With `batch_nums` > 0, the file list is chunked; a dispatcher
busy-waits (1 s ticks) until every task in a chunk finishes or is stopped before
releasing the next — bounding peak temp-disk and VRAM at the cost of inter-wave
overlap. Inside the dub stage, lines fan out on a thread pool (`dubbing_thread`,
default 1); per-line errors are collected and a job fails only if zero lines succeed.
Stage workers convert any exception into a stage-prefixed UI error and mark the task
ended, so a wave can never hang on a failed member.
#### 7. Fact-check: the pyvideotrans ↔ VoiceStudio integration
`videotrans/tts/_omnivoice.py` (~12–77) speaks **Gradio, not REST**: it builds a
`gradio_client.Client` against a user-pasted URL and calls the named endpoint
**`/_clone_fn`** with text, a natural-language language name (~35 ISO codes mapped),
a per-line reference wav + transcript, and a knob set (steps/guidance/denoise/
duration/post-process flags) that matches a 12-input Gradio clone function from an
older or forked VoiceStudio build. It expects a filesystem path to a wav back.
**Verdict: broken against current VoiceStudio.** Our backend is FastAPI on
:3900 (`backend/main.py`) with REST routers; we expose no Gradio app and no
`/_clone_fn` (our only Gradio surface is the optional SoniTranslate *subprocess* on
:7860 — a different application). A gradio client pointed at :3900 fails fetching
the Gradio config, which lands in pyvideotrans's fatal "Could not fetch config"
branch — users get an immediate hard stop. Their tracker already has an open bug
against this integration. Remedies in Spec 11.
### Patter (MIT — portable)
#### 1. Sentence chunker (`libraries/python/getpatter/services/sentence_chunker.py`, 565 LOC)
Streaming sentence segmentation for low-TTFA TTS. Accumulates tokens; `push(token)`
returns zero or more complete sentences. Boundary detection is regex
marker-replacement: protect non-terminal periods (honorifics for EN/IT/ES/DE/FR/PT,
website TLDs, decimals, ellipses, initials, acronym chains, company suffixes), mark
real terminators, split. Terminator tables cover Latin + CJK + 8 non-Latin scripts
(Devanagari, Arabic, Armenian, Ethiopic, Khmer, Burmese, Tibetan). Three emission
paths: standard (≥ min length and > 1 sentence → emit all but the buffered tail);
**short flush** for instant single-sentence replies (guards: one terminator, ≥ 1 word,
no preceding digit, no ALL-CAPS acronym tail, no honorific tail); **aggressive
first-clause flush** (opt-in, first clause of each turn only) on soft punctuation
`, — –` at ≥ 40 chars — claimed 200–500 ms TTFA savings — with seven guards
(decimals, currency within 8 chars, unbalanced brackets/quotes, ellipsis, sub-token
ambiguity) and a hard disable for Italian (comma = decimal separator). Constants:
`min_sentence_len=20`, `aggressive_first_min_len=40` (comment: below ~40 chars hurts
prosody; ElevenLabs buffers ~120 internally). `flush()` emits the remainder;
`reset()` discards (barge-in). Deps: stdlib `re` only. A TS mirror + golden parity
scenarios ship alongside.
**Porting:** feeds `/ws/tts` (`tts_stream.py`) so text synthesizes sentence-by-
sentence ahead of the PCM stream; aggressive first-flush directly serves the
< 100 ms TTFA goal. Distinct from `subtitle_segmenter.py` (offline length-balancing)
but the terminator/honorific tables could become shared constants. Port the parity
scenarios as pytest fixtures. **Effort: S.**
#### 2. NLMS acoustic echo canceller (`libraries/python/getpatter/audio/aec.py`, 333 LOC)
Time-domain sample-by-sample NLMS adaptive filter with leakage + frame-wise Geigel
double-talk detector. Far-end (TTS) PCM feeds a ring buffer; near-end (mic) frames
get `e = near − w·x` as output, with the weight update frozen during double-talk
(`max|near| > 0.6 · max|far|`) and when the far reference is near-silent (≤ −60 dBFS
— raised from a smaller epsilon after a weight-blowup bug). Two-phase step schedule:
mu 0.5 for the first 0.5 s, then 0.1. Pass-through guards: far buffer not primed,
and a **250 ms staleness window** — no recent far-end push → pass through rather
than convolve against a frozen reference (was producing an audible buzz during
silence). Defaults: **512 taps** (= 32 ms @ 16 kHz; 2048 tested → 8–12 s convergence,
rejected), leakage 0.9999, far buffer 0.5 s. I/O: int16 mono PCM, **8 or 16 kHz
only**, not thread-safe (one instance per session). Self-declared limitations: no
frequency-domain partitioning, no residual-echo suppressor, no delay estimation —
docstring recommends libwebrtc AEC3 for production-grade. Deps: numpy.
**Porting:** sits ahead of `/ws/transcribe` in `capture_ws.py` — `push_far_end()`
fed from every chunk `/ws/tts` ships, `process_near_end()` on mic frames. The real
work: our TTS streams 24 kHz while the canceller accepts 8/16 kHz (resample the
far-end to the capture rate, or relax the check and scale taps); the staleness clock
must track *playout* time, not send time (desktop speaker latency ≠ carrier RTT);
rho likely needs tuning for loud desktop-speaker bleed. Pure numpy → identical
default behavior on all three platforms. **Effort: M** (the port is an hour;
sample-rate plumbing, delay alignment, and tuning are the work).
#### 3. LLM-judge eval harness (`libraries/python/getpatter/evals/`, 1,636 LOC)
YAML/JSON suites of `EvalCase {name, turns, expected_behavior, rubric, tags}`;
per-case error containment (a mid-case exception keeps the partial transcript and
still judges it; a judge failure records score 0 + reasoning instead of aborting the
suite). Judge: chat-completions with JSON response format, temperature 0, pass
threshold 0.7 — with two hardening details worth copying verbatim: tolerant JSON
parsing (strips code fences; invalid JSON → fail with reasoning), and **the verdict
is recomputed locally** (`passed = score >= threshold`) because trusting the model's
self-reported `passed` once let a hallucinated pass through at score 0.2. A
deterministic chainable assertions layer (`expect(...).tool_called(...).judge(...)`)
raises plain `AssertionError`s so pytest reports work. CLI exits non-zero unless all
cases pass — CI-gateable. The judge backend is injectable (any object with
`judge(prompt)`).
**Porting:** complements our deterministic stack — probe judges (`tests/probe/judges/`)
score DSP correctness, `omnivoice/eval/` scores WER/MOS/speaker-sim; Patter's harness
adds the missing *semantic* tier (dub translation naturalness, dictation-correction
quality). Keep our "no LLM on the verdict path" rule for CI gates: run LLM-judge
suites as a separate **non-gating** job. Swap the judge backend for our
`llm_backend.py` (local model, keeps local-first). Port `case.py` + `runner.py` +
`assertions.py` nearly verbatim; drop `session.py` (telephony) in favor of our probe
Actor. **Effort: M.**
#### 4. Docs-drift CI (`.github/workflows/docs-feature-drift.yml` 112 LOC + checker 211 LOC)
Daily cron (03:00 UTC) + manual dispatch; `contents: read, issues: write`. Three-way
cross-reference: canonical feature inventory (theirs is an xlsx in a private sibling
repo — their weakest design point, soft-failing when the token is missing) × docs
filename stems × regex-parsed public SDK exports. Three drift buckets; only
inventory↔docs mismatches gate (export drift is report-only). The best part is the
**issue automation**: on failure, look up the open `docs-drift`-labeled issue and
**update its body in place** (single rolling issue, no spam); create it if absent;
on success, comment "drift resolved" and auto-close. Idempotent and self-healing.
**Porting:** we already gate `docs/install/*.md` via `scripts/validate-install-docs.py`
in ci.yml — this is the second half. Replace the private-repo xlsx with an
**in-repo** canonical inventory (a checked-in `features.yaml`, or generated from the
engine registry + FastAPI route table); adopt the rolling-issue pattern verbatim.
Note this auto-filed issue is maintainer-facing CI, distinct from the user-facing
opt-in bug reporter. **Effort: S** (workflow is copy-adapt; defining the inventory is
the only design decision).
#### 5. Consent-bounded telemetry (reference design only; `getpatter/telemetry/`, 1,235 LOC)
**Not adopting telemetry** — Patter's default is opt-OUT with a third-party endpoint,
both of which violate our local-first constraint. What transfers to our opt-in
prefilled-URL GitHub bug reporter (CLAUDE.md Capability 2):
1. **Two-layer key+value allowlist** before an event is built: unknown keys dropped,
values checked against closed enums with off-list values coerced to `"other"` —
making a leaked custom name "structurally impossible to emit, even from a buggy
caller". Stronger than regex-scrubbing after the fact; directly implements our
planned token/key/home-path exclusions for the issue body.
2. **Model-name sanitization**: anything with separators, whitespace, or > 40 chars
(fine-tune IDs, self-hosted paths) collapses to `"{vendor}-other"`; date suffixes
stripped; final shape re-checked by regex. Apply the same to user voice-profile
and engine names in bug reports.
3. **Coarse buckets**: counts → `{0, 1, 2_3, 4_6, 7_12, 13_plus}`, versions →
major.minor only, OS → family only, arch → `x86_64/arm64/other` — explicitly
anti-fingerprinting.
4. **Precedence-ordered consent resolver**, inverted to opt-IN for us (default OFF;
Settings toggle → marker file; `DO_NOT_TRACK` honored as an absolute OFF), with
the invariant "checking consent never writes to the filesystem".
**Effort: S** for the allowlist/sanitizer port (~250 LOC of pure stdlib functions);
the reporter UI around it is separate work.
---
## Implementation specs
*Each ranked action mapped onto this codebase: integration points, shapes, and a
test plan. File references verified against main as of 2026-06-11. These are
work-item-grade specs, not designs — the implementer still owns the details.*
### Spec 1 — Dub-length fitting v2
**Goal:** replace "trust the math + onset snap" with the measured-fit algorithm from
the functional spec above (reimplemented; do not open pyvideotrans source).
- **Files:** new `backend/services/length_fit.py` (the decision tree); integrate in
the dub orchestration in `backend/services/dub_pipeline.py` after TTS, before mux;
`backend/services/speech_rate.py` keeps its role as the *pre-generation* estimator
(LLM trim/expand to fit the slot) — length-fit is the *post-generation* enforcer;
`backend/services/onset_align.py` stays (it solves start alignment, not duration);
video cuts/retime/concat via `backend/services/ffmpeg_utils.py` (respect the
ffmpeg semaphore and `register_proc` for abortability).
- **Design deltas vs pyvideotrans:** keep their 1.2× audio-only threshold and 50/50
burden split as defaults but expose both in dub settings; integrate with our
incremental re-dub — slack absorption must be computed over the *full* segment
list even when only stale segments regenerate (`backend/services/incremental.py`
fingerprints must include the two new knobs in `_GEN_INPUT_FIELDS`, since they
affect output); time-stretch with pitch preservation (rubberband when available,
chained ffmpeg tempo fallback) under the existing GPU/CPU job queue.
- **Subtitle regeneration:** rewrite segment times from measured clip durations with
a running cursor (as specced) before `dub_export.py` renders SRT/burn-in.
- **Tests:** probe spec `dub_export.probe.yaml` + `tests/probe/judges/dubbing.py`
already gate segment duration ratio [0.5–1.6×] — add a judge check that final
audio fits final video ±1 frame and that regenerated SRT cue times equal measured
placements; unit tests for the decision tree at the 1.2 boundary, zero-length
slots, last-segment, and overlap-clamped inputs.
- **Effort: L.** Land the audio-only path first (pure win, no video retime), video
slowdown second.
### Spec 2 — MCP server v1
**Goal:** ship the missing half of our MCP story: mounted endpoint + per-agent voice
binding + stdio shim (ported from voicebox, MIT attribution).
- **Files:** `backend/mcp_server.py` (existing FastMCP: keep tools, add
`transcribe`); mount on the main app in `backend/main.py` with lifespan
composition (wrap existing startup hooks via AsyncExitStack — do not replace);
new middleware + `ContextVar` for `X-VoiceStudio-Client-Id`; new alembic migration
for `mcp_client_bindings {client_id, label, profile_id, default_engine,
last_seen_at}` (additive — satisfies the backward-compat constraint); REST CRUD
router `backend/api/routers/mcp_bindings.py`; new `backend/mcp_shim/` (port
nearly verbatim — httpx-only stdio↔HTTP proxy); Settings UI section in
`frontend/src/pages/Settings.jsx`; update `docs/mcp.json` + `docs/` MCP doc.
- **Resolution chain:** explicit tool arg → client binding → global default profile
→ helpful error. Copy voicebox's loopback-only gate for any file-path-accepting
tool argument.
- **Tests:** pytest for the resolve chain + middleware; probe spec addition driving
`speak`/`transcribe` over the mounted endpoint; shim smoke test against a live
backend.
- **Effort: M** (S shim, M mount + bindings + Settings).
### Spec 3 — Dictation refinement
**Goal:** refined-by-default-quality dictation: deterministic artifact collapse for
everyone, LLM filler-word removal for users with a local LLM configured.
- **Phase 1 (S, no LLM):** port `collapse_repetitive_artifacts()` (word-level ≥6
repeats; char-level 2–60-char units ≥6, catches no-space scripts) into a new
`backend/services/refinement.py`; apply to final transcripts in
`backend/api/routers/capture_ws.py` after `_transcribe_buffer`, never to partials.
Port voicebox's test corpus pattern.
- **Phase 2 (M):** prompt builder with the three toggles (`smart_cleanup`,
`self_correction`, `preserve_technical`), few-shot examples as structured
`messages` pairs, executed through `backend/services/llm_backend.py`
(`get_active_llm_backend().chat(...)`, timeout-bounded). Readiness = active LLM
backend reachable; on any failure the raw transcript stands. Persist both raw and
refined in the `transcriptions` table (`backend/core/db.py`, additive column via
alembic) so history supports re-refine. WS protocol: `{type:"final", text,
refined_text?, ...}` — frontend pastes `refined_text ?? text`.
- **Settings:** auto-refine toggle (default ON only when an LLM backend is active —
cross-platform default behavior stays identical: no LLM → identical pass-through
everywhere) + the three flag toggles; i18n keys for all labels.
- **Tests:** unit tests for the collapse pass (incl. CJK-free fixtures using Latin
repetition patterns); contract test that a dead LLM endpoint yields the raw
transcript within timeout.
### Spec 4 — Per-segment clone refs
**Goal:** per-line prosody matching — cut each dub segment's clone reference from
the separated vocal track at that segment's own timestamps.
- **Files:** `backend/services/speaker_clone.py` — add
`extract_segment_refs(vocals_path, segments, out_dir)` alongside the existing
per-speaker `extract_speaker_clones()`; dub pipeline passes the per-segment ref to
the TTS call when the engine supports reference audio.
- **Design deltas vs pyvideotrans:** unlike their no-floor policy, keep a quality
floor — segment shorter than `MIN_REF_DURATION_S` falls back to the existing
per-speaker 5–15 s reference (we already have it; they don't). Use original
(pre-slack-absorption) segment times. Cut with the thread pool pattern already
used in the pipeline; ride the source-language text along as ref transcript.
- **Mode:** per-segment refs default ON with per-speaker fallback; expose a dub
setting to force per-speaker (long-form consistency sometimes beats per-line
prosody). Add the mode to `incremental.py` `_GEN_INPUT_FIELDS`.
- **Tests:** unit test slicing math + fallback threshold; probe `voice_clone` /
`dub_export` flows with a two-speaker fixture asserting each segment got a ref
file of its own span (or the fallback).
- **Effort: S–M.**
### Spec 5 — Second-pass ASR QC
**Goal:** measured subtitle truth + automatic dub-quality flagging.
- **Files:** new stage in `backend/services/dub_pipeline.py` after assembly, before
export; reuse `backend/services/asr_backend.py` (active backend; fall back to
WhisperX defaults when the active backend can't do the target language); job
events via `backend/core/job_store.py` `append_event`.
- **Design deltas vs pyvideotrans:** they let recognized text *replace* subtitles
unconditionally; we keep generated text authoritative for *content* and use the
second pass for *timing* + *QC*: re-recognize the dubbed track with halved VAD
windows, re-time cues from recognized boundaries, and compute per-segment drift
(normalized WER between recognized and target text — scorer exists in
`omnivoice/eval/wer/`). Segments above a drift threshold get flagged in the job
events and surfaced in `DubTab.jsx` as "verify this line" markers feeding the
incremental re-dub loop. Stage is opt-out, never fatal: any failure leaves
align-stage subtitles in place.
- **Tests:** pipeline test with an injected mispronounced segment asserting the flag
fires; probe judge asserting second-pass SRT stays well-formed and within the
existing dubbing duration-ratio gates.
- **Effort: M.**
### Spec 6 — MLX runtime pass
**Goal:** Apple Silicon speedup via dual-runtime routing, no behavior divergence.
- **Files:** `backend/services/tts_backend.py` — adopt voicebox's backend-aware
model mapping inside the registry: one engine key resolving to
`mlx-community/...` vs upstream weights by platform probe; harden the probe to
`import mlx.core` catching `ImportError, OSError, RuntimeError` (PyInstaller
bundles); `backend/services/asr_backend.py` — route dictation + dub ASR through
the existing `MLXWhisperBackend` when the probe passes (today it exists but isn't
the default path on Apple Silicon).
- **Constraint check:** implementation-level only — output behavior, defaults, and
UI identical on all platforms (explicitly allowed by the parity rule).
- **Tests:** probe `engines` spec on macOS runner asserting MLX route is selected
and produces passing DSP judges; regression: CUDA/CPU platforms unaffected
(registry resolution unit tests with mocked probes).
- **Effort: L** across engines; ship Whisper-first (M) since `MLXWhisperBackend`
already exists.
### Spec 7 — Crash-isolated ASR
**Goal:** a native ASR crash becomes a failed job, never a dead backend.
- **Files:** generalize `backend/services/subprocess_backend.py` (today TTS-oriented:
length-prefixed JSON, GPU slots, op allowlists) with an ASR sidecar subclass —
`sidecar_script()` wrapping the crashy engine; wire as an `ASRBackend`
implementation in `asr_backend.py`.
- **Design choice vs pyvideotrans:** they use one-task-per-child process pools
(fresh interpreter per job — max isolation, max model-reload cost). Our sidecar is
long-lived with a handshake + health check. Hybrid: keep the long-lived sidecar
for warm-model latency, add their **automatic respawn-on-death** semantics —
parent detects EOF/broken pipe, marks the in-flight job failed with a decorated
error (engine + device, like their broken-pool message), respawns lazily on next
request. Their single-JSON progress-file pattern is unnecessary here — we already
have a frame protocol with `progress` ops.
- **Tests:** kill the sidecar mid-transcription in a pytest (send SIGKILL) — assert
job fails with the decorated error, backend stays healthy, next request respawns;
smoke-test addition for the respawn path.
- **Effort: M.**
### Spec 8 — Streaming polish kit
**Goal:** production-grade `/ws/tts` TTFA + dictation-during-playback.
- **Chunker (S):** port Patter's `sentence_chunker.py` (MIT) to
`backend/services/sentence_chunker.py`; use in `backend/api/routers/tts_stream.py`
to synthesize sentence-by-sentence and flush the first clause aggressively
(≥ 40 chars on soft punctuation, their seven guards, Italian comma disable).
Port their golden parity scenarios as pytest fixtures. Extend terminator/honorific
tables toward our language list; share constants with `subtitle_segmenter.py`
where they overlap.
- **AEC (M):** port Patter's `aec.py` (MIT) to `backend/services/aec.py`; in
`capture_ws.py`, feed `push_far_end()` from the audio `/ws/tts` ships (resampled
24 kHz → capture rate) and run `process_near_end()` on mic frames before
transcription. Key adaptations (from the deep dive): staleness clock must track
playout time, not send time; Geigel rho needs desktop-speaker tuning; one AEC
instance per ws session (not thread-safe). Behind a Settings toggle initially
("dictate during playback"), defaulting ON only once probe-verified on all three
platforms — until then it's opt-in, per the platform-default rule.
- **Tests:** chunker parity fixtures; AEC unit tests (echo-only input converges to
near-silence; double-talk freezes adaptation); probe dictation spec variant with
synthetic far-end playback.
- **Effort: M total.**
### Spec 9 — Quality rails
**(a) Docs-drift CI (S).** New scheduled workflow alongside
`.github/workflows/ci.yml` (daily cron + dispatch, `issues: write`): a checked-in
canonical inventory (`docs/features.yaml` — engines, capabilities, platform flags;
seed it from the README feature grid + engine tables) diffed against docs stems and
the engine registry (`list_backends()` output). Reuse the self-test pattern from
`tests/scripts/test_validate_install_docs.py` for the new checker. Adopt Patter's
rolling-issue automation verbatim: update one `docs-drift`-labeled issue in place,
auto-close on green. Existing `scripts/validate-install-docs.py` stays as the
PR-gating half.
**(b) LLM-judge eval tier (M).** Port Patter's `case.py` + `runner.py` +
`assertions.py` into `tests/evals/` with the judge backend swapped to
`llm_backend.py` (local model — keeps local-first). Hard rule preserved: **LLM
judges never gate CI** — they run as a separate non-blocking scheduled job whose
report lands as an artifact; deterministic probe judges remain the only gates. First
suites: dub translation naturalness (segments from the probe dub fixture) and
dictation-refinement quality (Spec 3 outputs). Copy their two hardening details:
recompute pass locally from score; tolerant JSON parsing.
### Spec 11 — pyvideotrans bridge
**Goal:** restore the inbound bridge from a 17.9k★ upstream integrator.
- **Option A — upstream a REST integration (preferred, S–M):** PR to pyvideotrans
replacing the Gradio call in their `_omnivoice.py` with our REST API (clone-TTS
endpoint or the OpenAI-compatible surface in
`backend/api/routers/openai_compat.py`). They already ship OpenAI-style TTS
channels, so the precedent exists. Friendly-fork etiquette: file their open
integration bug first, reference it.
- **Option B — Gradio compat shim (fallback, M):** a tiny optional Gradio app in our
backend exposing a `/_clone_fn`-compatible signature that proxies to our REST
pipeline. Only if upstream declines — it adds a gradio runtime dep for one
integration and another surface to keep compatible.
- **Either way:** add a contract test pinning whatever surface they consume, so the
bridge can't silently break again (the engine-compat constraint extended to an
external consumer).
### Specs 12–13 — voice mixing · dynamic engine lifecycle (sized, not yet designed)
**12 (S–M):** weighted voice mixing from Kokoro-FastAPI (Apache-2.0) — blend
embeddings/style vectors by ratio where the engine exposes them; surface as a
"blend" control in `CloneDesignTab.jsx`. Engine-dependent: start with the preset
engines whose voice representations are vectors.
**13 (M):** Speaches-style (MIT) per-request model lifecycle — idle-unload timers
and VRAM-pressure eviction layered on the existing `gpu_queue` + `unload()`
contract in `tts_backend.py`/`asr_backend.py`. Design doc first: interaction with
the GPU slot accounting in `subprocess_backend.py` is the tricky part.
*Actions 10 and 14 are practice/positioning items — no code spec needed; 14's
content is in [positioning moves](#positioning-moves).*
---
## Roadmap directions (community discussion #346)
*Researched 2026-06-12 (third pass, four research agents + five verification
sub-agents). The maintainer's [discussion #346](https://github.com/debpalash/VoiceStudio/discussions/346)
announced a feature roadmap toward full ElevenLabs feature-parity. This section
grounds each direction in the landscape: what exists, what's license-clean, what
the honest constraints are, and a scope ladder per direction.*
**Mapping the announcement to this doc** — several items are already covered:
| Discussion item | Status |
|---|---|
| Unlimited-length generation | Covered — voicebox chunked TTS ([deep dive](#1-chunked-long-form-tts-backendutilschunked_ttspy-299-loc), port, S) |
| WisprFlow-like dictation for agentic/code editors | Covered — [Spec 3](#spec-3--dictation-refinement) + [positioning move 4](#positioning-moves) |
| Polished dubbing experience | Covered — [Specs 1](#spec-1--dub-length-fitting-v2), [4](#spec-4--per-segment-clone-refs), [5](#spec-5--second-pass-asr-qc) |
| Better MLX / Nvidia / AMD / CPU | [Spec 6](#spec-6--mlx-runtime-pass) + new compat-matrix research (§R4) |
| Polished OpenAPI specs with Scalar | **Mostly shipped** — Scalar mounted at `/docs` since #307; remaining work is spec hygiene (§R2) |
| Agentic voice workflow | New — §R1 |
| Remote GPU · Tailscale · remote API in UI | New — §R2 |
| Ebook/audiobook/stories creator · persona gallery | New — §R3 |
| Better model & env management | New — §R4 |
### R1 — Agentic voice workflow
**Runtime landscape (licenses verified against LICENSE files, 2026-06):**
| Runtime | License | Fit |
|---|---|---|
| [pipecat](https://github.com/pipecat-ai/pipecat) (12.8k★, v1.0) | **BSD-2** | **Best fit.** A Python library that runs *inside* our existing FastAPI process (`FastAPIWebsocketTransport`) — no extra server. Local VAD (Silero) + smart turn detection + barge-in. Its `OpenAITTSService` takes a `base_url` and defaults to 24 kHz — our `/v1/audio/speech` plugs in with configuration, not code |
| [LiveKit Agents](https://github.com/livekit/agents) (10.9k★) | Apache-2.0 | Good, heavier: needs a LiveKit media server alongside. The right choice only if self-hosted SIP at scale becomes the priority (their [SIP server](https://github.com/livekit/sip) is Apache-2.0). Their openai plugin TTS/STT classes accept `base_url` (verified in source) — OVS works as a provider today |
| [Patter](https://github.com/PatterAI/Patter) (MIT) | MIT | Parts donor (already deep-dived). If embedded, its **opt-out telemetry must be hard-disabled** to honor our local-first guarantee |
| [vocode-core](https://github.com/vocodedev/vocode-core) | MIT | **Avoid as runtime** — no commits since Nov 2024 |
| [TEN Framework](https://github.com/TEN-framework/ten-framework) | Apache-2.0 **+ conditions** | **Disqualified**: LICENSE bans hosting on "End User devices" (fatal for a desktop app) + an Agora non-compete |
**Telephony honesty.** There is **no fully-local path to the PSTN** — reaching a real
phone number requires a carrier (Telnyx ~$0.005–0.007/min, Twilio ~$0.014/min; even
self-hosted Asterisk/FreeSWITCH needs a SIP trunk as the gateway). So under our
constraints, outbound calling must be an **explicit opt-in integration where the user
supplies carrier credentials** — never a default. Two prerequisite spikes before
promising calls: (a) TTFA benchmark of our engines in a streaming pipeline against
the ~600 ms p95 voice-to-voice budget; (b) AudioSeal detection survival through the
8 kHz G.711 phone leg (untested anywhere — phone-band downsampling may strip the
watermark, and it certainly reduces cloned-voice fidelity).
**Persona/community bots.** Prior art exists but is assembled hobby-grade
(closest: [Discord-Local-LLM-VoiceChat-Bot](https://github.com/KickerMix/Discord-Local-LLM-VoiceChat-Bot)
— local Whisper + LM Studio + cloning). Text-persona bots are trivial on our stack
(LLM adapter + `/v1/audio/speech` voice replies). Live voice-channel bots are harder:
discord.py has never shipped voice *receive* (years-open RFC); the working options
are Pycord's recording sinks or [discord-ext-voice-recv](https://github.com/imayhaveborkedit/discord-ext-voice-recv)
(both MIT, both single-maintainer risk). The conversation loop (VAD/turn-taking)
should come from pipecat, not from this prior art.
**Safety/regulatory (binding, not optional).** FCC ruling
[FCC 24-17](https://www.fcc.gov/document/fcc-makes-ai-generated-voices-robocalls-illegal)
(Feb 2024): AI/cloned voices are "artificial" under the TCPA — consumer calls require
prior express consent ($500–1,500/call private right of action). Texas SB 140
requires AI disclosure within the first 30 seconds of a call. Tennessee's ELVIS Act
extends liability to **tool providers**. **EU AI Act Article 50 applies from
2026-08-02**: people must be told they're talking to an AI, and generative-audio
output must be marked machine-readably — **the open-source exemption does not cover
Article 50**, and our AudioSeal default maps directly onto the marking obligation
(a structural advantage no competitor ships). The "my own cloned voice, my own
errand" single-call case is a genuine legal gray zone — docs should say so rather
than imply it's safe.
**Guardrails to build in (concrete):** (1) non-removable disclosure preamble on every
outbound call — satisfies Texas + FCC direction + EU Art 50(1) in one stroke;
(2) **consent-locked voice profiles** — agentic features require a profile flagged
verified-own-voice (recorded consent phrase), exactly the lock voicebox was
criticized for lacking; (3) AudioSeal always-on for agentic output, no toggle;
(4) destination allowlist + daily call cap, and no bulk-dial API surface ever —
architecturally incapable of being robocall infrastructure; (5) local immutable call
log (with two-party-consent warning before audio recording); (6) honest jurisdiction
notice in docs.
**Scope ladder:** **v1 (S–M, mostly docs):** OVS as TTS/STT provider for
pipecat/LiveKit — both verified to point at `localhost:3900/v1` via `base_url`
today; ship a `docs/agentic-voice.md` recipe + a pipecat smoke test, fix whatever
param mismatches it exposes. Users wire their own agent; we stay a model server.
**v2 (M–L):** built-in Discord persona bot (opt-in by construction — user supplies
their own bot token; identical on all platforms): text replies via the LLM adapter +
voice replies via `/v1/audio/speech`, persona attached to a consent-locked profile;
live voice-channel mode as a stretch. Mount the MCP server in the same milestone
(Spec 2) so external agents can drive OVS voices. **v3 (L, only after guardrails
1–5 exist):** telephony via opt-in carrier credentials, pipecat embedded with
Telnyx/Twilio serializers — disclosure preamble, watermark, allowlist, and call log
land in the same PR, not a follow-up.
### R2 — Remote GPU, Tailscale, remote API, Scalar
**The pattern is settled** across Ollama / LM Studio / Open WebUI / Jellyfin: server
binds a port, client has a *base URL* setting, optional bearer key. Nobody
comparable ships custom tunneling — LM Studio's remote story (LM Link, June 2026)
took a **Tailscale partnership on tsnet** to do more, which is exactly the bar we
should not chase. The existing Tauri app *is* the thin client; it needs a Backend
URL setting + `/health` handshake, with the local backend supervisor disabled when
remote.
**Security is the non-negotiable half.** The cautionary tale:
[~175,000 publicly exposed no-auth Ollama servers](https://thehackernews.com/2026/01/researchers-find-175000-publicly.html)
found in early-2026 scans, with documented LLMjacking. Our voice-cloning endpoints
are *more* sensitive than chat. The consensus mechanism (LM Studio, vLLM, Speaches):
optional bearer key — `OMNIVOICE_API_KEY`; when set, all non-loopback HTTP+WS
requires `Authorization: Bearer`. Our existing `NetworkAccessMiddleware` PIN gate
has the right ASGI shape and needs a bearer variant. Loopback-only stays the desktop
default. Note: Tailscale Serve terminates on-node and forwards from `127.0.0.1` —
Serve traffic looks loopback to the PIN gate, so the token must still apply in
server mode. Docs say plainly: bearer-over-plain-HTTP is sniffable; use Tailscale
(WireGuard) or Serve (TLS) beyond a trusted LAN; never Funnel without the key.
**Tailscale depth:** ship rung (a) — documentation ("install Tailscale both ends,
paste the MagicDNS URL"), plus a Serve recipe — which is all Home Assistant, Open
WebUI, and Jellyfin actually ship. Embedding is not viable from Python: tsnet is
Go-only; libtailscale's Python binding and tailscale-rs are explicitly
experimental/unaudited and not on PyPI. Mention [headscale](https://github.com/juanfont/headscale)
for users wanting a fully open control plane. Tailscale's client core is BSD-3;
documenting it imposes nothing on us.
**Remote LLM endpoint UI:** vLLM's OpenAI-compat server is verified drop-in for our
`llm_backend.py` (today env-only via `TRANSLATE_BASE_URL`) — the work is Settings
fields for base URL + model + optional API key, which Ollama ignores and
vLLM/LM Studio require. Watch item: **vLLM-Omni** now serves TTS first-class with an
OpenAI-compatible `/v1/audio/speech` — including **CosyVoice3, an engine we wrap** —
so "OVS on the GPU box" will eventually compete with "vLLM-Omni on the GPU box"; a
future option is consuming a remote vLLM-Omni endpoint *as an engine*.
**Scalar:** already shipped (#307 — mounted at `/docs`, `scalar-fastapi` is MIT,
actively maintained). The remaining "polished spec" work is OpenAPI hygiene Scalar
renders but can't create: stable `operation_id`s, router tags + descriptions,
`response_model` + examples on every endpoint — `/v1` and core TTS routes first,
since those are what remote users hit. ~1 day for tags/IDs; the response-model long
tail is incremental.
**Ladder:** (1) Backend URL setting + health handshake — S; (2) bearer token incl.
WS paths + tests — S–M; (3) "Remote GPU over Tailscale" docs page — S;
(4) remote LLM endpoint UI — S–M; (5) OpenAPI hygiene — M incremental.
**Don't build:** custom tunneling/relay, tsnet embedding, Funnel as a promoted
path, mTLS/OAuth (overkill vs bearer + WireGuard), a second thin-client binary.
### R3 — Audiobook/stories creator + persona gallery
> **Status (2026-06-13):** backend shipped — `backend/services/audiobook.py`
> parses a chapter-delimited script (Markdown `# H1` chapters + inline
> `[voice:NAME]`; `[pause …]` delegated to the shared `parse_pause_markers`)
> into a chapter/span plan, renders each chapter through the active TTS engine
> (`synthesize_chapter` + `chunked_tts`), and muxes a chapterized **m4b**
> (FFMETADATA1 chapters). `POST /audiobook/plan` previews the plan;
> `POST /audiobook` runs the synth job streaming SSE progress (ffmpeg-gated).
> A dedicated **Audiobook** tab (script editor → plan preview → streamed synth
> → m4b player/download) ships in the frontend. Deferred: epub/pdf/docx ingest,
> ACX `loudnorm` mastering, and crash-resume.
**The production bar** (verified against [ebook2audiobook](https://github.com/DrewThomasson/ebook2audiobook),
audiblez, epub2tts, abogen, Pandrator): broad ingest (epub/mobi/pdf/docx + OCR for
image PDFs), chapter detection (TOC-driven for epub — even the 19.2k★ leader's
algorithm is thinly documented), **chapterized m4b** output (FFMETADATA1 chapters +
cover via `attached_pic` — ffmpeg writes both Nero and QuickTime chapter forms),
inline tags (`[pause:N]`, `[voice:...]` — ebook2audiobook's square-bracket dialect
is Apache-2.0 and portable), batch with per-file voice mapping, and **crash resume**
(their `--session` pattern). The mastering bar is the
[ACX technical spec](https://help.acx.com/s/article/what-are-the-acx-audio-submission-requirements):
RMS −23 to −18 dB, peaks < −3 dB, noise floor < −60 dB RMS, ≥192 kbps CBR MP3,
≤120 min/file, room tone 1–5 s both ends — implementable as two-pass ffmpeg
`loudnorm` + an `astats` verifier. **Framing caveat:** ACX itself prohibits AI
narration unless authorized — market this as "masters to ACX technical spec," never
"Audible-ready."
**License traps in the parser stack (verified — this is the §R3 landmine):**
`ebooklib` is **AGPL-3.0 with no commercial-license option** — it poisons our
commercial build; parse EPUB with `zipfile` + `lxml` instead (EPUB is zip+XHTML).
PyMuPDF is AGPL (Artifex sells exceptions) — use **pypdf (BSD-3)**. The PyPI `mobi`
package is GPL — shell out to Calibre's `ebook-convert` instead (process boundary is
safe). OCR via pytesseract/tesseract is Apache-2.0, clean.
**What we already have:** voicebox's Stories schema (deep-dived above) is the
assembly/timeline half. The missing halves are symmetric: ingest (parsing,
chapterization, long-form batch with per-chapter resume) and export (m4b + ACX
mastering). **Ladder:** A1 EPUB ingest + TOC chapters + resumable per-chapter TTS
queue (M) → A2 chapterized m4b export (S) → A3 ACX mastering pass (S–M) → A4
PDF/txt/docx (M) → A5 inline tags + per-chapter voices (M) → A6 OCR + Calibre
shell-out (M) → A7 book→Stories timeline round-trip (L) — the differentiator no
surveyed tool has.
> **Status (2026-06-13):** the browse-preview-install surface already exists —
> `VoiceGallery.jsx`'s Community zone over the `omnivoice-gallery` git manifest
> (`backend/api/routers/community.py`), plus `.omnivoice` bundle export/import
> (`marketplace.py`). The missing piece for the synthetic-only gate was data
> integrity: imported community presets and bundle round-trips silently dropped
> `kind`/`vd_states`, demoting designed personas to clones. Fixed — community
> "preset" imports as `kind='design'` (a "voice" as `clone`), and bundles now
> carry `kind`+`vd_states` (old bundles import as clone). This makes the
> "accept only designed/synthetic" gate enforceable. Still to do: the consent
> attestation + AudioSeal-on-preview gate and the curation workflow below.
**Persona gallery — the territory is genuinely unoccupied.** The field splits into
consent-heavy commercial (ElevenLabs Voice Library: live-read Voice Captcha
verification, human review, sharing limited to professional clones), a consent-free
gray market (voice-models.com, ~28k RVC models), and read-only single-project
registries ([piper-voices](https://huggingface.co/rhasspy/piper-voices)' single
`voices.json` is the proven local-first pattern). **No OSS, consent-aware,
browse-preview-install voice gallery exists.** The build recipe: piper-voices-style
JSON index in a public git repo (checksums + preview URLs, payloads on HF) +
Obsidian-style PR curation + our existing `VoiceGallery.jsx` as the browser (it
already has community hooks: `useCommunityX`, `communitySubmitUrl`).
**Consent gates (legal floor, not nice-to-have):** Tennessee ELVIS reaches tools
whose "primary purpose" is unauthorized voice likeness — a gallery distributing
named-person clones is much closer to that line than a TTS engine; Illinois HB 4875
reaches distribution *facilitators*; EU Art 50 marking applies from 2026-08-02.
Gates: accept only **designed/synthetic voices** (#317 mapper personas) and
**self-recorded voices with a recorded consent statement** (spoken attestation, not
a checkbox — the Consumer Reports critique); AudioSeal watermark mandatory on
preview audio (it's already a direct dependency and now fully MIT incl. weights,
with a 16-bit payload — enough to carry a persona ID); PR-based human curation;
takedown via issue template propagating on index refresh. Honest note: none of this
stops a determined fork — the gates protect *the project* and set norms; the index
is the one chokepoint we actually control.
**Portable persona format:** no standard exists (the one attempt, vox-format, has
zero adoption; the de-facto reality is five incompatible engine-native formats).
Recommend a minimal **`.ovsvoice`** zip: manifest (schema version, engine + design
params for deterministic-mapper reproducibility, tags), optional reference audio +
transcript, `consent.json` (creation method + attestation + timestamp), SPDX-style
license tag, watermarked preview. Demand signal: voicebox's
[#138](https://github.com/jamiepine/voicebox/issues/138) (export profiles for
Piper/Home Assistant) — design the format so a Piper-ONNX export target can be added
later. **Ladder:** G1 `.ovsvoice` export/import (S–M, standalone value) → G2
community index + Gallery "Community" tab (M) → G3 in-app submission via prefilled
GitHub PR/issue — mirrors our bug-reporter pattern, no accounts (M) → G4
similarity-search/ratings/ONNX export (L, later).
### R4 — Model & env management, GPU compat matrix
**Environment management — what the field converged on** (licenses verified):
ComfyUI's one-shared-env model is the cautionary tale (conflict UIs, downgrade
blacklists, and pip-state-repair files *as product features*; its 2026 fix is uv +
whole-env lockfiles, not isolation). Pinokio (MIT) and StabilityMatrix (AGPL —
patterns only, no code) both landed on **one venv per app** — exactly our sidecar
architecture — then clawed disk back at the filesystem layer. LM Studio/Ollama
sidestep Python entirely with decoupled, hot-swappable native runtime packs — the
strongest pattern, but ours only if we ever ship prebuilt engine binaries.
Code-portable references: Pinokio, Ollama, llama.cpp, lms CLI (all MIT),
huggingface_hub/hf-xet (Apache-2.0), uv (MIT/Apache). **Not portable:**
StabilityMatrix (AGPL), ComfyUI + Manager + comfy-cli (all GPL-3.0).
**The torch-duplication math (measured 2026-06):** uv's global cache dedupes via
link mode — default **`clone` (CoW) on macOS *and* Linux, `hardlink` on Windows**;
same wheel across N venvs ≈ one copy on disk, *iff* cache and venvs share a
filesystem. But dedup is per-identical-wheel: our IndexTTS2 sidecar (torch 2.6.x)
vs parent (torch 2.8.0) shares nothing — the Windows cu128 torch wheel alone is
**~3.2 GiB** (measured), Linux ~0.83 GiB + multi-GB `nvidia-*` deps. Partial
consolation on Linux: `nvidia-*` packages dedupe independently wherever pinned
versions coincide across torch versions. Levers: keep `UV_CACHE_DIR` + sidecar
venvs on one filesystem; consider pinning `UV_LINK_MODE=hardlink` on Linux (reflink
degrades on ext4); and treat "align the sidecar's torch pin with the parent
whenever the engine permits" as the single biggest disk decision. Watch item:
PyTorch **wheel variants** (shipped experimental in 2.8, NVIDIA+Astral
collaboration) will eventually make `uv install torch` auto-select the right CUDA
build; uv already ships `--torch-backend=auto`.
**The compat matrix is two-dimensional** — `(torch version, CUDA wheel variant) →
supported sm_XX set`, published in
[pytorch RELEASE.md](https://github.com/pytorch/pytorch/blob/main/RELEASE.md):
Blackwell sm_120 needs **2.7.0 + cu128 or later**; from 2.8 the cu128+ wheels
dropped Maxwell/Pascal (Turing sm_75 is the floor; Pascal users must pin cu126
variants). Driver minimums: CUDA 12.x wheels ≥ 525, 13.x ≥ 580. Both failure
directions ("GPU too new" sm_120-on-cu126 and "GPU too old" sm_61-on-cu128) throw
the same lazy `no kernel image` error *after* `cuda.is_available()` returns True —
which is why preflight must check capability, not availability. The documented
antipattern is Ollama's silent CPU fallback
([their own #14258](https://github.com/ollama/ollama/issues/14258)); voicebox's
silent-fallback bootstrap is our other autopsy. Build: detect (capability via
torch, driver via NVML) → gate engine installs with a specific message ("this
engine's cu128 build needs Turing+; you have Pascal — installing the cu126 build
instead") → **loud persistent CPU-fallback banner**, never silent. We already have
probes to build on (`engine_env.py` compute-capability check — today it only gates
`torch.compile`; `hardware_probe.py`).
**Model management — the HF cache is the blessed single source of truth.** The hub
cache layout is now a [language-agnostic spec](https://huggingface.co/docs/hub/local-cache)
adopted by llama.cpp among others; blobs are content-addressed (LFS SHA-256 =
filename, so integrity is re-checkable offline), and the v1.x CLI ships exactly the
manager primitives a UI needs: `hf cache ls --filter "accessed>30d"`, `hf cache rm`,
`hf cache prune`, **`hf cache verify`**. Gotchas verified: Windows without
Developer Mode falls back to copy-per-snapshot (degraded dedup); concurrent
downloads are lock-protected (`.locks/`) but **deletion is not** — a delete UI over
a shared cache must handle delete-vs-reader races (fine on Linux fd semantics,
breaks on Windows); env vars are read at import time, so a Settings-controlled
cache path needs a restart. Offline/restricted: `HF_HUB_OFFLINE`,
`HF_HUB_ETAG_TIMEOUT` (falls back to cache on timeout), `HF_ENDPOINT` for mirrors —
hf-mirror.com is community-run, not HF-official, and its compatibility with the new
Xet/CAS download path is untested (escape hatch: `HF_HUB_DISABLE_XET=1`). Also:
`hf_transfer` is now fully deprecated (Xet is the default transfer path) —
consistent with our existing stack guidance.
**Ladder:** (a) uv link-mode + shared-cache audit, document the dedupe behavior +
sidecar pin-alignment policy — S; (b) in-app preflight compat gate (capability +
driver → engine × wheel-variant table) with specific errors + loud CPU banner — M;
(c) model manager UI over `scan_cache_dir()` (per-model disk usage, evict,
re-verify, mirror setting) — M; (d) LM-Studio-style decoupled runtime packs — L,
**not recommended now**: our sidecar architecture already decouples engines; revisit
only if we ship prebuilt binaries.
### Consolidated new actions
| # | Action | Mode | Effort | First rung |
|---|---|---|---|---|
| 15 | Agentic v1: provider recipe + pipecat smoke test against `:3900/v1` | Docs + test | S–M | §R1 v1 |
| 16 | Remote backend: URL setting + bearer token + Tailscale docs page | Build | M total | §R2 rungs 1–3 |
| 17 | Audiobook v1: EPUB ingest → chapterized m4b → ACX mastering | Build (+ port Apache-2.0 pieces) | M+S+S–M | §R3 A1–A3 |
| 18 | `.ovsvoice` portable persona export/import | Build | S–M | §R3 G1 |
| 19 | Engine preflight compat gate + loud CPU-fallback banner | Build | M | §R4 (b) |
| 20 | Model manager UI over the HF cache primitives | Build | M | §R4 (c) |
| 21 | OpenAPI hygiene pass under the shipped Scalar UI | Build | M incremental | §R2 rung 5 |
| 22 | Consent-locked voice profiles (prerequisite for §R1 v2/v3 and §R3 G2+) | Build | M | §R1 guardrail 2 |
---
## User sentiment & market positioning
*Researched 2026-06-11. Issue volumes are hand-clustered from title analysis (both
trackers use almost no labels): the 250 most recent open voicebox issues, and all 32
open + 80 recent closed pyvideotrans issues. Reddit data is partly secondhand
(reddit.com blocks direct fetching); flagged where so.*
### voicebox: what its users hit (~377 open / ~156 closed — maintainer drowning)
| Complaint theme | Volume | Representative | For us |
|---|---|---|---|
| CUDA/GPU bring-up failures (sm_120 "no kernel image", Pascal unsupported, AMD/Intel ignored, 2.4 GB CUDA re-downloads) | ~45–50 of 250; 4 of their top-10 most-commented ever | [#417](https://github.com/jamiepine/voicebox/issues/417), [#594](https://github.com/jamiepine/voicebox/issues/594), [#728](https://github.com/jamiepine/voicebox/issues/728), [#676](https://github.com/jamiepine/voicebox/issues/676) | **Warning + opportunity.** Endemic local-AI tax we share — but their Windows installers shipped with CUDA silently broken for months. GPU auto-detect with explicit per-arch errors is a real differentiator *if it holds on the edges* |
| Model download / offline failures (cached models phoning home, infinite offline retry, no mirror option) | ~25 of 250 | [#557](https://github.com/jamiepine/voicebox/issues/557), [#434](https://github.com/jamiepine/voicebox/issues/434), [#546](https://github.com/jamiepine/voicebox/issues/546) | **Opportunity.** Maps exactly to our uv-mirror + HF-token capabilities. A "local-first" app that breaks offline is a betrayed promise users notice loudly |
| Startup crashes / white screens / **no Linux binary at all** | ~30 of 250 | [#513](https://github.com/jamiepine/voicebox/issues/513), [#617](https://github.com/jamiepine/voicebox/issues/617), [#606](https://github.com/jamiepine/voicebox/issues/606) (regression of an earlier fix), [#682](https://github.com/jamiepine/voicebox/issues/682) | **Opportunity.** Regressions recur because fixes ship fast without cross-platform gates — our parity rule + 3-platform smoke matrix is precisely this gap |
| Generation quality bugs (30 s transcription cutoff, reference audio leaking into output, refinement silently translating to English) | ~25 of 250 | [#604](https://github.com/jamiepine/voicebox/issues/604), [#609](https://github.com/jamiepine/voicebox/issues/609), [#603](https://github.com/jamiepine/voicebox/issues/603) | **Warning.** Engine-level artifacts we inherit too; the fixable subset (silent language handling, truncation) are pipeline bugs — testable |
| Dictation/capture friction (Windows keyboard hooks, double auto-paste) | ~10 of 250 | [#687](https://github.com/jamiepine/voicebox/issues/687), [#697](https://github.com/jamiepine/voicebox/issues/697) | **Warning.** We shipped the same class of fix (#287/#299). OS-hook dictation is a permanent treadmill on all three platforms |
| Docs rage — their single most-reacted issue ever is a failed first run blamed on missing docs | top-reacted (17 reactions) | [#108](https://github.com/jamiepine/voicebox/issues/108); [#185](https://github.com/jamiepine/voicebox/issues/185) (32 comments, top open: fine-tune instructions) | **Opportunity.** This is literally our core value. The community reply in #108 — "this is open-source software, not free support" — is the failure mode we exist to avoid |
**Praise** (consistent across coverage): cloning quality ("near-perfect" from 3–5 s;
one reviewer scored it above ElevenLabs Multilingual v2 on cloning accuracy);
privacy + zero cost as the hook of every viral post; the Stories timeline editor and
MCP agent-voice as "genuinely innovative"; out-of-box Metal acceleration; maintainer
responsiveness — even as the backlog grows. Notably its 29.7k★ came from
X/Threads/LinkedIn/Reddit virality; it [barely registered on HN](https://news.ycombinator.com/item?id=47831411)
(1 point).
**Abandonment:** mostly switch-*backs* to ElevenLabs on Windows — the
[substack reviewer's verdict](https://theaitoolkit2.substack.com/p/i-tested-voicebox-the-free-local):
"Windows users should wait weeks for GPU fixes. Low-volume creators should stick
with ElevenLabs' $5/month simplicity." Plus churn-risk from unstable main
([#648](https://github.com/jamiepine/voicebox/issues/648)) and a reputational drag:
[TechTimes covered](https://www.techtimes.com/articles/316850/20260519/voicebox-clones-any-voice-3-seconds-audio-runs-locally-free-has-no-consent-lock.htm)
voicebox having "no consent lock" amid voice-fraud concerns — an angle where our
AudioSeal default is the counter-story.
**Most-engaged requests:** fine-tune instructions (top open, 32 comments); export
voice profiles to ONNX for Piper/Home Assistant
([#138](https://github.com/jamiepine/voicebox/issues/138) — a self-hosting crowd
signal); SenseVoice/FunASR STT requested **five separate times in one week**; AMD
DirectML; Linux support.
### pyvideotrans: what its users hit (32 open / ~887 closed — aggressive solo triage)
| Complaint theme | Volume | Representative | For us |
|---|---|---|---|
| Pipeline hangs + faster-whisper GPU teardown crashes (long-standing per users) | recurring across versions | [#1129](https://github.com/jianchang512/pyvideotrans/issues/1129), [#1118](https://github.com/jianchang512/pyvideotrans/issues/1118) | **Warning.** Upstream faster-whisper lifecycle bugs — we run the same stack. User-found mitigations (pre-segment audio, CPU fallback for long audio) are worth implementing as automatic fallbacks; Spec 7 contains the blast radius |
| Subtitle/audio sync drift, silence-removal eating final words, merged batch translations | ~10 open + the most-reacted closed bugs | [#923](https://github.com/jianchang512/pyvideotrans/issues/923) (22 comments), [#1012](https://github.com/jianchang512/pyvideotrans/issues/1012) | **Warning (endemic to dubbing).** Speech-rate mismatch is the hardest unsolved problem in the category — anyone evaluating our dub pipeline judges us on exactly this. Specs 1 + 5 are the answer |
| External TTS engine integration breakage (GPT-SoVITS, F5-TTS, index-tts break at the API seam) | ~10 open | [#636](https://github.com/jianchang512/pyvideotrans/issues/636) (26 comments), [#954](https://github.com/jianchang512/pyvideotrans/issues/954) | **Opportunity.** They delegate TTS to a zoo of self-hosted side-servers; every seam is a support ticket. Our bundled-engine model removes this entire class — a concrete pitch. (Their broken `_omnivoice.py` is this same theme pointed at us — Spec 11) |
| Install failures, especially macOS (source-only; Windows gets a praised .exe) | ~6 recent cluster; all-time #2 most-commented issue is literally "Installation tutorial" ([#193](https://github.com/jianchang512/pyvideotrans/issues/193), 67 comments) | [#950](https://github.com/jianchang512/pyvideotrans/issues/950), [#952](https://github.com/jianchang512/pyvideotrans/issues/952) | **Opportunity.** macOS/Linux users are second-class there; a signed mac installer with a working first run is a direct wedge |
| CUDA errors (GPU fails, silently falls back to CPU) | steady trickle; [#287](https://github.com/jianchang512/pyvideotrans/issues/287) 22 comments | [#177](https://github.com/jianchang512/pyvideotrans/issues/177), [#980](https://github.com/jianchang512/pyvideotrans/issues/980) | **Warning** — though notably smaller than voicebox's, because they treat CPU as the default path and GPU as opt-in |
| LLM translation plumbing (thinking-tags leaking into subtitles, stripped punctuation, merged lines) | ~8 | [#921](https://github.com/jianchang512/pyvideotrans/issues/921), [#979](https://github.com/jianchang512/pyvideotrans/issues/979) | **Opportunity (partial).** LLM-output sanitization is cheap, testable hygiene that visibly differentiates output quality |
| **Their VoiceStudio integration is reported broken by users** | open | [#1124](https://github.com/jianchang512/pyvideotrans/issues/1124) | Confirms the Spec 11 finding from their side of the bridge |
**Praise:** the packaged Windows .exe needing zero Python setup; completely free
with no login/registration/gates; breadth of integrations; responsive maintainer.
The [Aug 2024 HN thread (182 points)](https://news.ycombinator.com/item?id=41234713)
praised the democratization angle — dubbing for material nobody would pay a human
to dub. **Abandonment:** toward subtitles-over-dubbing entirely, Yandex Browser /
YouTube auto-dub for casual use, paid dubbing (ElevenLabs, DeepDub) when emotional
fidelity matters, and VideoLingo within the OSS niche for subtitle quality.
**Most-engaged requests:** model-chasing (SenseVoice — same ask hit voicebox 5× the
same month; index-TTS v2; Fish Audio; Qwen3-TTS), automatic per-speaker role
assignment, emotion transfer into dubs, a manual subtitle-proofread checkpoint
before merge, per-line audio export.
### Comparative read
- **voicebox↔pyvideotrans comparisons are essentially absent** — they own different
frames (English-social "ElevenLabs alternative" vs Chinese-ecosystem "video
translation pipeline"). **We are unusual in straddling both**, which is a
positioning asset nobody currently contests.
- **vs ElevenLabs, what tips the decision:** toward local — cost at volume and
privacy, every time; back toward cloud — (1) first-run failure (especially
Windows GPU), (2) raw quality ceiling / emotional fidelity, (3) "just works"
simplicity for low-volume users. Notably, *quality is no longer the automatic
cloud win* (voicebox cloning reviews beat ElevenLabs Multilingual v2) — the
deciding factor has shifted to **reliability of install and GPU bring-up**, i.e.
exactly our stated core value.
- **The category wishlist** (duplicated across both trackers): SenseVoice/FunASR
ASR, index-TTS v2, Fish Audio, per-speaker dubbing, emotion control, fine-tuning
instructions.
### ElevenLabs pricing pressure (verified on [elevenlabs.io/pricing](https://elevenlabs.io/pricing), 2026-06-11)
Free $0 / 10k credits (no commercial license, no cloning) · Starter $6/mo / instant
cloning · Creator $22/mo / professional cloning · Pro $99 · Scale $299 (3 pro
clones) · Business $990 (10 pro clones). The complaints that push users local:
- **Dubbing multiplies cost per target language** — a 10-min video into 3 languages
bills as 30 minutes; Creator includes ~50 dubbing minutes with $0.60/min overage
([their help article](https://help.elevenlabs.io/hc/en-us/articles/23338815703697-How-much-does-Dubbing-cost)).
This is exactly the multi-language batch workload where local-and-free is most
compelling — and our 50-video batch users' workload.
- **Editing a dub costs credits** — regenerating a clip bills each time; the credit
rebate covers roughly one full re-dub
([dubbing studio docs](https://elevenlabs.io/docs/dubbing/studio)). They built
per-segment regeneration *and had to bolt a rebate scheme onto it* — direct
evidence the incremental-re-dub pain is real and monetized against.
- **Cloning is paywalled** at every tier boundary; commercial use paywalled on Free.
- **Credits-as-abstraction** generates its own churn-intent search ecosystem
(third-party "what do credits actually cost" explainers).
### Honest verdicts on our unique five
| Differentiator | Verdict | Evidence |
|---|---|---|
| Incremental re-dub | **Strong lever, unmarketed** | ElevenLabs monetizes against this exact pain (rebate scheme). Nobody *searches* the term — show it (demo GIF), don't name it |
| 646 languages | **Strong reach/press lever; niche for retention** | It's the hook in all existing coverage of us; Chatterbox's English-only limit draws recurring complaints. Caveat: most users need 1–3 languages, and no third party has verified long-tail quality — overselling invites "language #412 sounds terrible" backlash |
| AudioSeal watermarking | **Nobody-asked (users) / press + compliance asset** | Zero end-user search demand (hobbyists prefer unwatermarked); but reviewers spontaneously praise it, roundups list missing watermarking as an open-source *limitation*, 2026 disclosure regulation makes it a compliance story, and voicebox's "no consent lock" press is the counter-example. Frame as the commercial-use objection-killer; don't lead with it |
| Cross-platform dictation default | **Strong lever, especially Linux** | The WisprFlow-alternative market is crowded on macOS, thin on Windows, and served only by single-purpose tools on Linux (active 2026 development = live demand). Nobody offers dictation+cloning+dubbing in one cross-platform app. Caveat: dictation searchers want a small focused app — message it as "already on your machine", not as a lightweight utility it isn't |
| 3-step translation chain | **Niche; users value the outcome, never the mechanism** | Real evidence that LLM translation quality matters (pyvideotrans documents it beats Google/DeepL; context-aware modes sell subtitle tools). Nobody searches "3-step chain" — market as "translations that don't sound like Google Translate" with a before/after; bury the architecture in docs. Note VideoLingo ships the same idea — it's a parity feature inside the niche, a differentiator outside it |
### Our own footprint (2026-06-11)
- Repo: 6,808★ / 1,045 forks, created 2026-04-09 — strong two-month trajectory.
- Press: [MarkTechPost](https://www.marktechpost.com/2026/05/26/meet-omnivoice-studio-a-local-open-source-alternative-to-elevenlabs/)
(accurate, positive) and an uncritical blog endorsement; a viral X post framing
us as killing "$700/year in ElevenLabs and HeyGen subscriptions" — note it
positions *dubbing* as the hero feature. No independent quality review of the
646-language claim exists yet — **our most exposed flank**: expectations are
being set high with no third-party validation behind them.
- **Hacker News: effectively absent.** No submission with traction; every
comparable tool got its bump there. An unclaimed opportunity.
- **Name collision (the big misconception risk):** three entities share "VoiceStudio"
— (1) the k2-fsa VoiceStudio *model* (our default engine; their community-projects
page lists us, underselling us as "desktop application for voice generation");
(2) omnivoice.app, an unrelated commercial cloud product; (3) us. Most "VoiceStudio"
YouTube traffic and the pyvideotrans VoiceStudio docs page are about the *model* —
search demand is being split three ways and both competitor trackers contain
"support VoiceStudio" requests that mean the model, not us.
### Positioning moves
1. **"vs ElevenLabs dubbing cost" comparison page** anchored on the multiplier math
(10 min × 3 languages = 30 billed minutes; $0.60/min overage; pay-to-edit), with
one table: "20-min video → 3 languages → fix 5 lines → re-export" priced on
Creator vs $0 local. Natural home for the incremental-re-dub demo (30 s GIF:
edit one line → only that segment regenerates). Targets the highest-intent query
cluster ("elevenlabs pricing/dubbing cost") that third parties currently
monetize.
2. **Show HN, leading with the install story, not the model.** HN's documented
objections to local TTS are install friction and English-only — our installer +
GPU auto-detect + 646 languages answer both. Title shape: "Show HN: Local
ElevenLabs alternative — dub, clone, dictate on your own GPU, one installer."
3. **Claim the name before the collision hardens:** README/FAQ disambiguation
("VoiceStudio **Studio**, the desktop app built on the k2-fsa VoiceStudio engine —
not omnivoice.app"); ask k2-fsa to upgrade our one-line community listing to
mention dubbing/dictation; get the pyvideotrans VoiceStudio docs page pointing at
Studio as the GUI path (pairs with Spec 11 — arrive with the fixed integration).
4. **A discoverable dictation entry point:** a docs/landing section "open-source
WisprFlow alternative for Mac, Windows, and Linux (built into VoiceStudio
Studio)" + PRs to the alternative-list aggregators. Proven, high-conversion
query pool; no incumbent covers Linux well.
---
## Appendix: engine evaluation — ResembleAI Chatterbox (2026-06-11)
**Verdict: integrate later — not now.** Full facts verified against the HF cards,
GitHub pyproject, and PyPI (0.1.7, 2026-03-26).
- **License: clean.** MIT on code *and* all three weight variants (original 0.5B EN,
Multilingual 23-lang, Turbo 350M) — compatible with our AGPL + commercial
dual-license. The "Resemble uses special weight terms" worry did not materialize.
- **What it would add:** Turbo's inline paralinguistic tags (`[laugh]`, `[cough]`,
`[chuckle]`) and the single-knob `exaggeration` expressiveness control — genuinely
unique in our roster. Fast English cloning (cloning + speed is a gap; KittenTTS is
fast but can't clone). The 23-lang multilingual cloning is **not** differentiating
for us (VoiceStudio 646, VoxCPM2 30 @ 48 kHz).
- **Why not now:**
1. `chatterbox-tts` hard-pins `torch==2.6.0` + `transformers==5.2.0`; we constrain
`torch==2.8.0` and require `transformers>=5.3.0` — **unresolvable in the parent
venv**, forcing a dedicated-venv sidecar (IndexTTS2 pattern, ~800–1000 LOC) that
downloads a *second multi-GB torch*. The disk/download cost is the price, not
the code.
2. `resemble-perth` (its built-in PerTh watermarker) is a **git-URL dependency** —
unmirrorable on restricted networks, against our bootstrap story. Also untested
interaction: PerTh + our AudioSeal = double watermarking.
3. MPS is buggy upstream (float64 conversion crash on Turbo; placeholder-storage
errors); honest Apple-Silicon support means carrying community patches. Mac-ARM
users already get Chatterbox today via our MLX-Audio curated list
(`mlx-community/Chatterbox-TTS-4bit`).
- **Cheapest path / re-eval triggers:** ResembleAI publishes official
[chatterbox-turbo-ONNX](https://huggingface.co/ResembleAI/chatterbox-turbo-ONNX)
exports. If a 1-day spike proves it runs on plain `onnxruntime`, Turbo slots into
the lightweight supertonic3-style sidecar (~700 LOC, **no second torch**) and we
get the paralinguistic tags cheaply. Also re-evaluate if upstream relaxes the
torch/transformers pins or publishes `resemble-perth` to PyPI.