* fix(build): bundle kokoro source files for transformers runtime introspection transformers opens .py source files at runtime to check attention/MoE implementation via regex (e.g. _can_set_attn_implementation). PyInstaller's --hidden-import only bundles .pyc bytecode, so kokoro/modules.py was missing from the bundle causing a FileNotFoundError on Kokoro model load. Switch from individual --hidden-import entries to --collect-all kokoro in both build_binary.py and voicebox-server.spec. The kokoro package is 172K so no meaningful bundle size impact. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix(build): use SPECPATH for runtime hook instead of hardcoded absolute path The linter expanded runtime_hooks=[] to an absolute /Users/... path which would break CI and other dev machines. Use os.path.join(SPECPATH, ...) to mirror the relative approach in build_binary.py. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix(build): runtime hook to work around PyInstaller + Python 3.12 import breakages Four distinct bundling-specific crashes blocked Kokoro and Qwen CustomVoice from loading in the frozen binary: 1. torch._dynamo import triggered via class-body decorators (@torch._dynamo.allow_in_graph on PreTrainedModel, @torch.compiler.disable in flex_attention) pulls in torch._numpy._ufuncs which crashes on module load with NameError: name 'name' is not defined. 2. AlbertModel (Kokoro) triggers @auto_docstring -> modeling_auto -> GenerationMixin -> candidate_generator -> sklearn -> scipy, which hits the same class of bug in scipy.stats._distn_infrastructure (NameError: name 'obj' is not defined). 3. AutoModel (Qwen) pulls the same sklearn -> scipy chain directly. 4. librosa (required by most TTS engines) -> scipy.signal -> scipy.stats hits the _distn_infrastructure crash regardless of the transformers stubs above. The root cause of (1) and (4) is that PyInstaller's frozen importer runs module-level `for X in [<list-comp using dir()>]:` loops with an empty iterable, leaving the loop variable unbound. Trailing `del obj` / unrelated references then crash. Fix: a single runtime hook (pyi_rth_torch_compiler_disable.py) installs: - sys.modules stubs for torch._dynamo and torch._dynamo.config, plus a meta-path finder for torch._dynamo.* submodules — voicebox never uses torch.compile/dynamo for inference, so a permissive no-op stub (callable as decorator, falsey as predicate, context-manager-safe for TransformGetItemToIndex) is drop-in safe. - meta-path finder stubs for transformers.utils.auto_docstring and transformers.generation.candidate_generator — both import-chain short-circuits; docstrings and speculative decoding aren't used for TTS. - meta-path finder for scipy.stats._distn_infrastructure that reads the real .py source via the wrapped loader's get_source(), replaces the bundling-broken `del obj` with `globals().pop('obj', None)`, and compile+exec's the patched source. This keeps the real scipy module intact so librosa and everything downstream works normally. Supporting changes: - backend/pyi_hooks/hook-scipy.stats._distn_infrastructure.py sets module_collection_mode = "pyz+py" so the .py source is actually in the bundle for the runtime patcher to read. - build_binary.py and voicebox-server.spec register the runtime hook and the new hooks dir. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix(build): force transformers torch<2.6 mask path and bundle spacy_pkuseg - patch transformers.masking_utils to set _is_torch_greater_or_equal_than_2_6 = False, forcing sdpa_mask_older_torch and avoiding the vmap .item() crash that breaks Qwen CustomVoice generation (our torch._dynamo stub can't reproduce TransformGetItemToIndex's graph transform). - add PyInstaller hook to bundle transformers.masking_utils .py source so the runtime finder can source-patch it. - --collect-all spacy_pkuseg so Chatterbox Multilingual can load its Chinese segmenter (dicts/default.pkl + native .so extensions). - add per-finder install diagnostics + _HOOK_VERSION marker to make future bundle-only regressions easier to triage. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * fix(build): pass PyInstaller hook paths relative so .spec is portable Absolute paths ended up in the auto-regenerated voicebox-server.spec because build_binary.py prefixed every --runtime-hook and --additional-hooks-dir with str(backend_dir / ...). That broke builds on any machine whose checkout wasn't at /Users/jamie/... and anyone invoking pyinstaller voicebox-server.spec directly. os.chdir(backend_dir) already runs before PyInstaller (same reason server.py works as a bare filename), so the backend_dir prefix is unnecessary. Drop it so the generated spec references pyi_hooks/, pyi_rth_numpy_compat.py, pyi_rth_torch_compiler_disable.py as repo- relative paths. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
Voicebox Backend
FastAPI server powering voice cloning, speech generation, and audio processing. Runs locally as a Tauri sidecar or standalone via python -m backend.main.
Running
# Via justfile (recommended)
just dev:server
# Standalone
python -m backend.main --host 127.0.0.1 --port 17493
# With custom data directory
python -m backend.main --data-dir /path/to/data
The server auto-initializes the SQLite database on first startup. Models are downloaded from HuggingFace on first use.
Architecture
backend/
app.py # FastAPI app factory, CORS, lifecycle events
main.py # Entry point (imports app, runs uvicorn)
config.py # Data directory paths and configuration
models.py # Pydantic request/response schemas
server.py # Tauri sidecar launcher, parent-pid watchdog
routes/ # Thin HTTP handlers — validation, delegation, response formatting
services/ # Business logic, CRUD, orchestration
backends/ # TTS/STT engine implementations (MLX, PyTorch, etc.)
database/ # ORM models, session management, migrations, seed data
utils/ # Shared utilities (audio, effects, caching, progress tracking)
Request flow
HTTP request
-> routes/ (validate input, parse params)
-> services/ (business logic, database queries, orchestration)
-> backends/ (TTS/STT inference)
-> utils/ (audio processing, effects, caching)
Route handlers are intentionally thin. They validate input, delegate to a service function, and format the response. All business logic lives in services/.
Key modules
services/generation.py -- Single run_generation() function that handles all three generation modes (generate, retry, regenerate). Manages model loading, voice prompt creation, chunked inference, normalization, effects, and version persistence.
services/task_queue.py -- Serial generation queue. Ensures only one GPU inference runs at a time. Background tasks are tracked to prevent garbage collection.
backends/__init__.py -- Protocol definitions (TTSBackend, STTBackend), model config registry, and factory functions. Adding a new engine means implementing the protocol and registering a config entry.
backends/base.py -- Shared utilities used across all engine implementations: HuggingFace cache checks, device detection, voice prompt combination, progress tracking.
database/ -- SQLAlchemy ORM models with a re-exporting __init__.py for backward compatibility. Migrations run automatically on startup.
Backend selection
The server detects the best inference backend at startup:
| Platform | Backend | Acceleration |
|---|---|---|
| macOS (Apple Silicon) | MLX | Metal / Neural Engine |
| Windows / Linux (NVIDIA) | PyTorch | CUDA |
| Linux (AMD) | PyTorch | ROCm |
| Intel Arc | PyTorch | IPEX / XPU |
| Windows (any GPU) | PyTorch | DirectML |
| Any | PyTorch | CPU fallback |
Detection is handled by utils/platform_detect.py. Both backends implement the same TTSBackend protocol, so the API layer is engine-agnostic.
API
90 endpoints organized by domain. Full interactive documentation available at http://localhost:17493/docs when the server is running.
| Domain | Prefix | Description |
|---|---|---|
| Health | /, /health |
Server status, GPU info, filesystem checks |
| Profiles | /profiles |
Voice profile CRUD, samples, avatars, import/export |
| Channels | /channels |
Audio channel management and voice assignment |
| Generation | /generate |
TTS generation, retry, regenerate, status SSE |
| History | /history |
Generation history, search, favorites, export |
| Transcription | /transcribe |
Whisper-based audio-to-text |
| Stories | /stories |
Multi-track timeline editor, audio export |
| Effects | /effects |
Effect presets, preview, version management |
| Audio | /audio, /samples |
Audio file serving |
| Models | /models |
Load, unload, download, migrate, status |
| Tasks | /tasks, /cache |
Active task tracking, cache management |
| CUDA | /backend/cuda-* |
CUDA binary download and management |
Quick examples
# Generate speech
curl -X POST http://localhost:17493/generate \
-H "Content-Type: application/json" \
-d '{"text": "Hello world", "profile_id": "...", "language": "en"}'
# List profiles
curl http://localhost:17493/profiles
# Stream generation status (SSE)
curl http://localhost:17493/generate/{id}/status
Data directory
{data_dir}/
voicebox.db # SQLite database
profiles/{id}/ # Voice samples per profile
generations/ # Generated audio files
cache/ # Voice prompt cache (memory + disk)
backends/ # Downloaded CUDA binary (if applicable)
Default location is the OS-specific app data directory. Override with --data-dir or the VOICEBOX_DATA_DIR environment variable.
Code quality
Linting and formatting are enforced by ruff, configured in pyproject.toml. See STYLE_GUIDE.md for conventions.
just check-python # lint + format check
just fix-python # auto-fix lint issues + reformat
just test # run pytest
Dependencies
Runtime dependencies are in requirements.txt. macOS-only MLX dependencies are in requirements-mlx.txt. Dev tools (ruff, pytest) are installed automatically by just setup-python.