Thanks for your interest in improving VoiceStudio! This guide covers everything you need to get started.
| 💬 Chat | Discord |
| 🐛 Bugs | GitHub Issues |
| 🏷️ Good First Issues | Filtered list |
| 📋 Roadmap | README → Roadmap |
New engines are hired for a named job, not added to a list — the bar, the current job map, and the out-of-tree path are in docs/engine-acceptance.md. Read it before opening a proposal; the licence check in particular ends most of them.
- Git
curl(used by the Bun / uv / rustup install one-liners on macOS and Linux)- Bun (frontend package manager)
- uv (Python environment manager)
- ffmpeg (audio/video processing)
- Rust / Cargo (desktop shell only)
- Python 3.10+ (managed automatically by
uv)
Linux desktop development also needs WebKitGTK/GTK development libraries. On Debian or Ubuntu, install the same packages used by CI:
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev libgtk-3-dev libpango1.0-dev libcairo2-dev \
libsoup-3.0-dev libgdk-pixbuf-2.0-dev \
libayatana-appindicator3-dev librsvg2-dev libssl-dev libxdo-dev \
gstreamer1.0-plugins-good \
libasound2-dev build-essential curl wget fileSee the Linux source-build guide for Fedora and Arch packages.
git clone https://github.com/debpalash/VoiceStudio.git
cd VoiceStudio
bun install
bun run devThis starts both services:
| Service | URL | What it does |
|---|---|---|
| Backend | localhost:3900 |
FastAPI server — TTS, ASR, diarization, dubbing pipeline |
| Frontend | localhost:3901 |
React + Vite UI |
The backend runs through scripts/dev-backend.mjs (the dev:api script): the
uvicorn command is unchanged, but if the backend dies (OOM kill, hard
crash), the wrapper prints a boxed exit banner with the exit code/signal and
the last 20 lines of omnivoice.log before the dev stack shuts down — so the
cause doesn't scroll away with the terminal. The same death is also reported
as a crash notice in the UI the next time the backend starts (see
docs/install/troubleshooting.md §14c).
bun run desktop # dev: hot-reload Tauri shell + backend
bun run desktop-prod # production: builds, bundles the backend, then launchesBoth run uv sync first (so the Python backend env is set up) and start the
backend automatically — you do not start it separately. Use the exact script
names: there is no desktop=prod (note the hyphen in desktop-prod).
desktop-prod is Windows-aware (auto-detects bash/git; see scripts/desktop-prod.mjs).
Requires Rust and platform-specific Tauri dependencies — see the Tauri prerequisites.
After installing Rust with rustup on macOS/Linux, either open a new terminal or load Cargo into the current one before starting the desktop app:
source "$HOME/.cargo/env"
bun desktopOn Linux, errors such as Package gdk-3.0 was not found, pango.pc missing,
or javascriptcoregtk-4.1 missing mean the native packages above were not
installed; changing PKG_CONFIG_PATH does not fix libraries that are absent.
If the app opens but stays on the setup splash with no buttons, the Python
backend didn't finish starting — the splash surfaces the stall reason, a log
panel, and a Retry button (and Settings → Logs → Backend has the full trace).
The most common from-source cause is uv or Python not being on your PATH.
VoiceStudio/
├── backend/ # Python FastAPI server
│ ├── api/ # Route handlers
│ ├── core/ # Config, prefs, constants
│ └── services/ # TTS engines, ASR, dubbing, audio DSP
│ └── tts_backend.py # ← Multi-engine TTS registry
├── frontend/ # React + Vite
│ ├── src/
│ │ ├── components/ # UI components
│ │ ├── hooks/ # Custom React hooks
│ │ ├── stores/ # Zustand state slices
│ │ └── utils/ # Shared utilities
│ └── src-tauri/ # Rust/Tauri desktop shell
├── deploy/ # Docker, CI configs
├── docs/ # Screenshots, MCP config
└── scripts/ # Build & release scripts
Open an issue with:
- What happened vs what you expected
- Steps to reproduce
- OS, GPU, and Python version (find in Settings → Logs)
- Error logs (Settings → Logs → copy relevant lines)
- Fork the repo and create a branch from
main - Keep PRs focused — one feature or fix per PR
- Run tests before pushing:
# Backend tests uv run pytest backend/ -x -q # Frontend build check cd frontend && npx vite build --mode development
- Write a clear PR title — it becomes the squash-merge commit message
- Don't include local machine stats, file paths, or private system info in PR descriptions
VoiceStudio's TTS backend is a plugin registry. Adding a new engine takes ~50 lines:
- Open
backend/services/tts_backend.py - Create a class extending
TTSBackend:
class MyEngineBackend(TTSBackend):
id = "my-engine"
display_name = "My Engine (description)"
@classmethod
def is_available(cls) -> tuple[bool, str]:
try:
import my_engine # noqa: F401
return True, "ready"
except ImportError:
return False, "my_engine not installed. pip install my-engine"
@property
def sample_rate(self) -> int:
return 24000
@property
def supported_languages(self) -> list[str]:
return ["en", "zh"]
def generate(self, text: str, **kw) -> torch.Tensor:
# ... call your engine, return [1, num_samples] tensor- Register it in
_REGISTRYat the bottom of the file - That's it — it auto-appears in Settings → TTS Engine
- Formatter: We don't enforce one globally — match the style of the file you're editing
- Logging: Use
logger.warning()/logger.error(), never bareprint() - Exceptions: Avoid bare
except: pass— catch specific exceptions - Type hints: Use them for public API functions and class methods
- Components: Functional components with hooks
- State: Zustand stores in
src/stores/, organized by slice - Brand assets: Reuse the canonical mark, palette, naming, and compatibility rules in
docs/branding.md; do not redraw or rename runtime identifiers ad hoc - CSS: Utilities-first + shadcn/ui, one stylesheet. UI is built on the shadcn/ui primitives in
src/components/ui/(wrapped by thesrc/ui/barrel, themed to the VoiceStudio palette), composed with Tailwind v4 utility classes. All styling now lives in a single file —src/index.css: the@theme/[data-theme]token foundation plus the irreducible set utilities can't express (@keyframes, glassmorphism/backdrop-filter, pseudo-elements,:has(), unlayered cascade overrides, and styling hooks on library-generated DOM like virtualized rows / WaveSurfer). The per-component.cssfiles were eliminated in the CSS→Tailwind/shadcn migration — do not create new ones. Reach for shadcn primitives + utilities; if a rule is genuinely irreducible, add it tosrc/index.csswith a provenance comment. (The only other.cssis the test-only visual harness. Seedocs/shadcn-migration.md.) - Naming:
PascalCasefor components,camelCasefor hooks and utils
- Format:
cargo fmtbefore committing - Modules: One concern per file (
bootstrap.rs,tools.rs,config.rs,commands.rs)
Frontend code stays modular so an edit loads one small file, not a 1900-line one. The rules:
- Size caps: soft 300 lines, hard 500 lines per
.jsxfile. Anything over 500 lines must be split. (The cap does not apply tosrc/index.css— it is the single, intentional styling foundation and the only app stylesheet; see the CSS rule above.) - Pages are thin orchestrators. A file in
frontend/src/pages/is just layout + routing + state wiring that composes feature components — no inline sub-component over ~50 lines. - One component per file. Co-locate
Foo.jsx+Foo.test.jsxtogether in a per-page feature folder underfrontend/src/components/(e.g.components/settings/,components/dub/). Styling is not co-located — it's utilities + shadcn, with any irreducible rules insrc/index.css. - Shared bits go in a
primitives/folder inside the feature folder (components/settings/primitives/is the existing example). - Enforced by ESLint
max-lines(max: 500) — warn-only for now so it never breaks CI, with the goal of upgrading toerroronce the backlog of oversized files clears.
Write clear, concise messages. The PR title becomes the squash-merge commit.
good: fix: prevent CUDA OOM during concurrent transcription + TTS
good: feat: add CosyVoice 3 TTS backend adapter
good: docs: add platform compatibility matrix to README
bad: fixed stuff
bad: update
bad: WIP
# Run all backend tests
uv run pytest backend/ -x -q
# Run a specific test file
uv run pytest backend/tests/test_api.py -x -q
# Frontend build validation (no test suite yet)
cd frontend && npx vite build --mode development
# Tauri shell check (requires Rust)
cd frontend/src-tauri && cargo checkEvery PR is reviewed by two AI reviewers before a human looks at it:
- CodeRabbit posts a walkthrough (with a sequence diagram, and an ASCII before/after sketch for UI changes), inline findings, and warning-mode pre-merge checks against the project's hard rules.
- Greptile reviews with the same project rubrics and learns from 👍/👎 reactions on its comments — react to train it.
Both are advisory, not gating: CI and the maintainer's approval decide. Don't be surprised by detailed bot comments minutes after you open a PR — address what's right, push back (in a reply) on what's wrong.
Commit & PR conventions: conventional-commit style with a scope
(fix(dub): …, feat(setup): …) and link the issue (Closes #N / Refs #N)
in the title or body.
Plenty of contributions here are built with Claude Code, Cursor, and similar agents — welcome, with the same quality bar as hand-written PRs (real bug, correct fix, regression test; see the quality gates below).
One practical tip: this codebase is large, and re-explaining it to your agent
every session burns context and tokens fast. A persistent memory layer fixes
that — the agent recalls the architecture, conventions, and your past findings
instead of re-reading the tree each time. memxt
(100% local, MCP-based, built by this project's maintainer) exists for exactly
this; any MCP memory server works. Pair it with the repo's agent skill —
npx skills add debpalash/omnivoice-studio — so your agent knows the project's
hard rules from the first prompt.
- Cross-platform parity (hard rule): anything that ships in default mode must behave identically on macOS, Windows, and Linux. Platform-specific implementation is fine; platform-divergent default behavior is a P0. Platform-only features go behind an explicit opt-in (Settings toggle, env var, or CLI flag).
- i18n — all 21 locales (hard rule): every user-facing string goes through
t('...')and the key must exist in all 21 files underfrontend/src/i18n/locales/. Translate; don't copy English into non-English locales. CI fails on hardcoded CJK outside the allowlist intests/test_no_hardcoded_cjk.py(extend_ALLOWED_FILESwith a justification for legitimate functional CJK). - DB schema changes go through an alembic migration with a tested upgrade
path — existing
omnivoice_data/must keep working with no manual steps. - Engine back-compat: already-installed engines (model weights on disk) must not require reinstall or re-download.
- Local-first: no new outbound calls except GitHub Issues (opt-in reporting) and HuggingFace model downloads. Never log or persist secrets or absolute home paths.
- Security posture: the backend serves loopback HTTP — treat every query/path/form parameter as hostile. User-chosen filesystem destinations are authorized in the Tauri process (save dialog), never via HTTP params.
VoiceStudio is AGPL-3.0-only, and the maintainer also offers a commercial license (see LICENSE). By submitting a contribution you agree that:
- you have the right to submit it (your own work, or compatibly licensed);
- it is licensed to the project under AGPL-3.0; and
- you grant the project maintainer a perpetual, worldwide, non-exclusive right to also distribute your contribution under the project's commercial license terms.
This inbound grant is what keeps the dual-license model viable. If you can't
agree to (3) for a particular contribution, say so in the PR and we'll discuss
before merging. Adding a Signed-off-by: line (DCO) to your commits is
appreciated but not required.
- Stuck on setup? Ask in Discord #help
- Not sure where to start? Check good first issues
- Want to discuss a big change? Open a discussion or Discord thread before coding
Thank you for contributing! 🎙️