Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,25 @@

## [Unreleased]

### Developer Experience

- **`just setup` now pins the Python venv to 3.12.** Setup preferred `python3.12` when
it found one but silently fell back to 3.13 or bare `python3`, so a machine on
3.13/3.14 built a venv that then failed to install dependencies capped below 3.13
(kokoro, misaki, numpy, numba) with `Could not find a version that satisfies the
requirement`. Setup now requires 3.12, and recreates a venv that is on the wrong
version or whose pip doesn't run, so a stale or half-built one heals on the next
`just setup` rather than needing a manual `rm -rf backend/venv`. Reporting 3.12
isn't enough for an interpreter to be chosen — it has to actually build a working
venv, so setup falls through to the next candidate when one can't (Debian packages
`ensurepip` separately as `python3.12-venv`; current Homebrew `python@3.12` bottles
carry a `pyexpat` that won't load on macOS 26). If none work it exits with the
install command for your platform instead of failing deep inside pip. Windows
resolves it through the `py -3.12` launcher, since Windows installers don't create
a `python3.12` command.
- **The 3.12 requirement is now machine-readable.** `backend/pyproject.toml`
declares `requires-python = ">=3.12,<3.13"` rather than an open-ended `>=3.12`,
so the constraint the docs describe is the one tooling resolves against.
### Linux

- **ROCm setup works on Linux AMD systems.** Docker ROCm builds now keep PyTorch
Expand Down
12 changes: 7 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ Thank you for your interest in contributing to Voicebox! This document provides
curl -fsSL https://bun.sh/install | bash
```

- **[Python 3.11+](https://python.org)** - For backend development
- **[Python 3.12](https://python.org)** - For backend development. Voicebox pins its
virtual environment to 3.12 to match CI, because some dependencies have no 3.13
release yet. On Debian/Ubuntu also install `python3.12-venv`.
```bash
python --version # Should be 3.11 or higher
python3.12 --version # Should be 3.12.x
```

- **[Rust](https://rustup.rs)** - For Tauri desktop app (installed automatically by Tauri CLI)
Expand All @@ -44,7 +46,7 @@ just dev # starts backend + desktop app
```

`just setup` handles everything automatically, including:
- Creating a Python virtual environment
- Creating a Python virtual environment pinned to 3.12 (recreating it if an existing venv is on the wrong version)
- Installing Python dependencies (with CUDA PyTorch on Windows if an NVIDIA GPU is detected)
- Installing MLX dependencies on Apple Silicon
- Installing JavaScript dependencies
Expand All @@ -67,7 +69,7 @@ just --list # see all available commands

#### Windows Notes

The justfile works natively on Windows via PowerShell. No WSL or Git Bash required. On Windows with an NVIDIA GPU, `just setup` automatically installs CUDA-enabled PyTorch for GPU acceleration.
The justfile works natively on Windows via PowerShell. No WSL or Git Bash required. On Windows with an NVIDIA GPU, `just setup` automatically installs CUDA-enabled PyTorch for GPU acceleration. Python 3.12 is located via the `py -3.12` launcher; install it with `winget install -e --id Python.Python.3.12` if it's missing.

### Model Downloads

Expand Down Expand Up @@ -363,7 +365,7 @@ See [docs/content/docs/overview/troubleshooting.mdx](docs/content/docs/overview/

**Quick fixes:**

- **Backend won't start:** Check Python version (3.11+), ensure venv is activated, install dependencies
- **Backend won't start:** Check Python version (must be 3.12), ensure venv is activated, install dependencies
- **Tauri build fails:** Ensure Rust is installed, clean build with `cd tauri/src-tauri && cargo clean`
- **OpenAPI client generation fails:** Ensure backend is running, check `curl http://localhost:17493/openapi.json`

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ just dev # starts backend + desktop app

Install [just](https://github.com/casey/just): `brew install just` or `cargo install just`. Run `just --list` to see all commands.

**Prerequisites:** [Bun](https://bun.sh), [Rust](https://rustup.rs), [Python 3.11+](https://python.org), [Tauri Prerequisites](https://v2.tauri.app/start/prerequisites/), and [Xcode](https://developer.apple.com/xcode/) on macOS.
**Prerequisites:** [Bun](https://bun.sh), [Rust](https://rustup.rs), [Python 3.12](https://python.org), [Tauri Prerequisites](https://v2.tauri.app/start/prerequisites/), and [Xcode](https://developer.apple.com/xcode/) on macOS.

The repo ships a pre-wired `.mcp.json` at the root — running Claude Code inside this checkout picks up the Voicebox MCP tools automatically once the dev app is running.

Expand Down
7 changes: 6 additions & 1 deletion backend/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
[project]
name = "voicebox-backend"
version = "0.2.3"
requires-python = ">=3.12"
# This is the one place the Python pin is explained; the justfile and docs point here
# rather than restating it. The upper bound is real, not defensive: requirements.txt
# currently pulls in kokoro and misaki (which declare requires-python <3.13) plus
# numpy<2 and numba<0.61 (no 3.13 wheels), so `pip install -r requirements.txt` fails
# outright on a later interpreter. Recheck those four before raising it.
requires-python = ">=3.12,<3.13"

# ---------------------------------------------------------------------------
# Ruff – linter + formatter
Expand Down
2 changes: 1 addition & 1 deletion docs/content/docs/developer/architecture.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ These two layers communicate via HTTP on `localhost:17493`, with the frontend ma

### Tech Stack

- **Framework**: FastAPI (Python 3.11+)
- **Framework**: FastAPI (Python 3.12)
- **TTS Engines**: Qwen3-TTS, Qwen CustomVoice, LuxTTS, Chatterbox, Chatterbox Turbo, TADA, Kokoro
- **Transcription**: Whisper (PyTorch or MLX-Whisper)
- **Inference Backends**: MLX (Apple Silicon), PyTorch (CUDA / ROCm / XPU / DirectML / CPU)
Expand Down
13 changes: 7 additions & 6 deletions docs/content/docs/developer/setup.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ Ensure you have these installed:
curl -fsSL https://bun.sh/install | bash
```
</Card>
<Card title="Python 3.11+" icon={<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>}>
<Card title="Python 3.12" icon={<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>}>
[Download Python](https://python.org)
```bash
python --version
python3.12 --version # Should be 3.12.x
```
Voicebox pins its venv to 3.12 to match CI, because some dependencies have no 3.13 release yet. On Debian/Ubuntu also install `python3.12-venv`.
Comment thread
dhananjaypai08 marked this conversation as resolved.
</Card>
<Card title="Rust" icon={<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m6 9 6 6 6-6"/></svg>}>
[Install Rust](https://rustup.rs)
Expand Down Expand Up @@ -65,7 +66,7 @@ Run `just --list` to see all available commands. Highlights:

| Command | Description |
|---------|-------------|
| `just setup` | Full setup (Python venv + JS deps + dev sidecar). Detects Apple Silicon for MLX and NVIDIA/Intel Arc on Windows for accelerated PyTorch. |
| `just setup` | Full setup (Python venv + JS deps + dev sidecar). Pins the venv to Python 3.12 (recreating a wrong-version one) and detects Apple Silicon for MLX and NVIDIA/Intel Arc on Windows for accelerated PyTorch. |
| `just setup-python` | Python venv + dependencies only |
| `just setup-js` | `bun install` only |

Expand Down Expand Up @@ -214,8 +215,8 @@ This installs dependencies for:
```bash
cd backend

# Create virtual environment
python -m venv venv
# Create virtual environment (use Python 3.12 — see Prerequisites)
python3.12 -m venv venv

# Activate virtual environment
source venv/bin/activate # macOS/Linux
Expand Down Expand Up @@ -277,7 +278,7 @@ bun run tauri dev

<AccordionGroup>
<Accordion title="Backend won't start">
- Check Python version (must be 3.11+)
- Check Python version (must be 3.12)
- Ensure virtual environment is activated: `source backend/venv/bin/activate`
- Verify all dependencies are installed: `pip install -r requirements.txt`
- Check if port 17493 is available
Expand Down
12 changes: 9 additions & 3 deletions docs/content/docs/overview/troubleshooting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -292,13 +292,19 @@ This is expected behavior. The first generation downloads the selected TTS engin

<AccordionGroup>
<Accordion title="Python Version">
Ensure Python 3.11 or higher:
Ensure Python 3.12:

```bash
python --version
python3.12 --version
```

If not, install Python 3.11+ and recreate the virtual environment.
Voicebox pins its venv to Python 3.12 because some dependencies have no 3.13
release yet. `just setup` recreates a venv that was built on the wrong version,
so re-running it is usually the fix.

If `just setup` can't find a usable 3.12, install one: `uv python install 3.12`
works anywhere, or use `brew install python@3.12` on macOS and `sudo apt install
python3.12 python3.12-venv` on Debian/Ubuntu.
</Accordion>

<Accordion title="Virtual Environment">
Expand Down
112 changes: 97 additions & 15 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ pip := if os() == "windows" { venv_bin / "pip.exe" } else { venv_bin / "pip" }
# Shell selection: use powershell on Windows, bash elsewhere
set windows-shell := ["powershell", "-NoProfile", "-Command"]

# Detect best python for venv creation (platform-aware)
system_python := if os() == "windows" { "python" } else { `command -v python3.12 2>/dev/null || command -v python3.13 2>/dev/null || echo python3` }
# Pin the venv to one version instead of following whatever `python3` happens to be;
# a newer interpreter fails `pip install` with "no matching distribution". See
# requires-python in backend/pyproject.toml for which dependencies set the ceiling.
# Keep in sync with that and with actions/setup-python in .github/workflows.
python_version := "3.12"

# ─── Setup ────────────────────────────────────────────────────────────

Expand All @@ -32,14 +35,66 @@ setup: setup-python setup-js
setup-python:
#!/usr/bin/env bash
set -euo pipefail
if [ ! -d "{{ venv }}" ]; then
echo "Creating Python virtual environment..."
PY_MINOR=$({{ system_python }} -c "import sys; print(sys.version_info[1])")
if [ "$PY_MINOR" -gt 13 ]; then
echo "Warning: Python 3.$PY_MINOR detected. ML packages may not be compatible."
echo "Recommended: brew install python@3.12"

is_pinned() {
"$1" -c 'import sys; sys.exit(sys.version_info[:2] != tuple(map(int, "{{ python_version }}".split("."))))' 2>/dev/null
}

# Testing for an executable file is not enough: console scripts hard-code the
# venv's absolute path in their shebang, so a venv that was built elsewhere and
# moved into place has a pip that exists, is executable, and still dies with
# "cannot execute: required file not found". Running it is the only real check.
pip_works() {
"{{ pip }}" --version >/dev/null 2>&1
}
Comment thread
dhananjaypai08 marked this conversation as resolved.

# Every interpreter on PATH that could be the pinned version, plus uv's, which
# keeps its Pythons off PATH. More than one can match, and the first is not
# necessarily the good one.
candidates() {
type -aP "python{{ python_version }}" python3 python 2>/dev/null || true
if command -v uv >/dev/null 2>&1; then
uv python find "{{ python_version }}" 2>/dev/null || true
fi
{{ system_python }} -m venv {{ venv }}
}

# Reuse the venv only if it is on the pinned version and has a working pip: a
# wrong-version venv resolves dependencies against the wrong Python, and a
# broken pip means an earlier `-m venv` was interrupted or the venv was moved.
if [ -d "{{ venv }}" ] && ! { is_pinned "{{ python }}" && pip_works; }; then
echo "Existing venv is not a working Python {{ python_version }} environment — recreating..."
rm -rf "{{ venv }}"
fi

if [ ! -d "{{ venv }}" ]; then
# Building the venv is itself the test, so there is no separate probe to keep
# in sync. An interpreter can report the right version and still fail inside
# `-m venv` — Debian packages ensurepip separately, and Homebrew has shipped a
# pyexpat that won't dlopen — so fall through to the next candidate rather
# than giving up on the first. Their output is held back until every candidate
# has failed, so a run that recovers doesn't look like a broken one.
log="$(mktemp)"
while IFS= read -r py; do
is_pinned "$py" || continue
echo "Creating Python virtual environment with $py ..."
"$py" -m venv "{{ venv }}" >>"$log" 2>&1 && pip_works && break
echo " ...it reports {{ python_version }} but cannot build a working venv; trying the next."
rm -rf "{{ venv }}"
done < <(candidates | sed 's|//*|/|g' | awk '!seen[$0]++')
pip_works || cat "$log"
rm -f "$log"
fi

if ! pip_works; then
echo ""
echo "ERROR: Voicebox needs a Python {{ python_version }} that can create virtual"
echo " environments (see requires-python in backend/pyproject.toml)."
echo " Any: uv python install {{ python_version }}"
echo " macOS: brew install python@{{ python_version }}"
echo " Debian: sudo apt install python{{ python_version }} python{{ python_version }}-venv"
echo ""
echo "Then re-run: just setup"
exit 1
fi
echo "Installing Python dependencies..."
{{ pip }} install --upgrade pip -q
Expand Down Expand Up @@ -85,13 +140,40 @@ setup-python:

[windows]
setup-python:
$target = "{{ python_version }}"; \
function Test-Py($exe) { \
if (-not $exe -or -not (Test-Path $exe)) { return $false }; \
$v = & $exe -c "import sys; print('%s.%s' % sys.version_info[:2])" 2>$null; \
return ($LASTEXITCODE -eq 0 -and $v -eq $target); \
}; \
function Test-Pip { \
if (-not (Test-Path "{{ pip }}")) { return $false }; \
& "{{ pip }}" --version *> $null; \
return ($LASTEXITCODE -eq 0); \
}; \
$py = $null; \
if (Get-Command py -ErrorAction SilentlyContinue) { \
$p = & py "-$target" -c "import sys; print(sys.executable)" 2>$null; \
if ($LASTEXITCODE -eq 0) { $py = $p }; \
}; \
if (-not $py) { foreach ($n in @("python$target", "python")) { \
$c = Get-Command $n -ErrorAction SilentlyContinue; \
if ($c -and (Test-Py $c.Source)) { $py = $c.Source; break }; \
} }; \
if (-not $py) { \
Write-Host "ERROR: Voicebox requires Python $target (see requires-python in backend/pyproject.toml)."; \
Write-Host "Install it from https://python.org or: winget install -e --id Python.Python.$target"; \
Write-Host "Then re-run: just setup"; \
exit 1; \
}; \
if ((Test-Path "{{ venv }}") -and (-not (Test-Py "{{ python }}") -or -not (Test-Pip))) { \
Write-Host "Existing venv is not a working Python $target env - recreating..."; \
Remove-Item -Recurse -Force "{{ venv }}"; \
}; \
if (-not (Test-Path "{{ venv }}")) { \
Write-Host "Creating Python virtual environment..."; \
$pyMinor = & {{ system_python }} -c "import sys; print(sys.version_info[1])"; \
if ([int]$pyMinor -gt 13) { \
Write-Host "Warning: Python 3.$pyMinor detected. ML packages may not be compatible."; \
}; \
& {{ system_python }} -m venv {{ venv }}; \
Write-Host "Creating Python virtual environment with $py ..."; \
& $py -m venv "{{ venv }}"; \
if ($LASTEXITCODE -ne 0 -or -not (Test-Pip)) { Write-Host "ERROR: could not create a working venv with $py"; exit 1 }; \
}
Write-Host "Installing Python dependencies..."
& "{{ python }}" -m pip install --upgrade pip -q
Expand Down