diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 37ef7924..bebc2594 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.1.12 +current_version = 0.2.3 commit = True tag = True tag_name = v{new_version} diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..e0c4637b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,46 @@ +# Version control +.git +.github +.gitignore + +# Desktop-only (not needed in web container) +tauri/ +landing/ +docs/ +mlx-test/ +scripts/ + +# Dependencies & build artifacts (rebuilt in Docker) +node_modules/ +__pycache__/ +*.pyc +*.pyo +*.egg-info/ +dist/ +build/ +*.spec + +# Data (will be bind-mounted) +data/ +backend/data/ + +# IDE & OS +.vscode/ +.idea/ +*.swp +*.swo +.DS_Store +Thumbs.db + +# Config files not needed in container +biome.json +.biomeignore +.bumpversion.cfg +.npmrc +Makefile +CHANGELOG.md +CONTRIBUTING.md +SECURITY.md +LICENSE +README.md +backend/README.md diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml new file mode 100644 index 00000000..520482e8 --- /dev/null +++ b/.github/workflows/build-windows.yml @@ -0,0 +1,63 @@ +name: Build Windows + +on: + workflow_dispatch: + +jobs: + build-windows: + permissions: + contents: write + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: "pip" + + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install pyinstaller + pip install -r backend/requirements.txt + + - name: Build Python server + shell: bash + run: | + cd backend + python build_binary.py + + PLATFORM=$(rustc --print host-tuple) + mkdir -p ../tauri/src-tauri/binaries + cp dist/voicebox-server.exe ../tauri/src-tauri/binaries/voicebox-server-${PLATFORM}.exe + echo "Built voicebox-server-${PLATFORM}.exe" + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + + - name: Rust cache + uses: swatinem/rust-cache@v2 + with: + workspaces: "./tauri/src-tauri -> target" + + - name: Install dependencies + run: bun install + + - uses: tauri-apps/tauri-action@v0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + projectPath: tauri + tagName: v__VERSION__ + releaseName: "voicebox v__VERSION__ (test build)" + releaseBody: "Test build for audio export fix" + releaseDraft: true + prerelease: true + args: "" + includeUpdaterJson: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9e65f520..b24862d8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,10 +22,6 @@ jobs: args: "--target x86_64-apple-darwin" python-version: "3.12" backend: "pytorch" - # - platform: 'ubuntu-22.04' - # args: '' - # python-version: '3.12' - # backend: 'pytorch' - platform: "windows-latest" args: "" python-version: "3.12" @@ -37,10 +33,10 @@ jobs: - uses: actions/checkout@v4 - name: Install dependencies (ubuntu only) - if: matrix.platform == 'ubuntu-22.04' + if: contains(matrix.platform, 'ubuntu') || contains(matrix.platform, 'namespace') run: | sudo apt-get update - sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf llvm-dev + sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf llvm-dev libasound2-dev - name: Install LLVM (macOS) if: matrix.platform == 'macos-latest' || matrix.platform == 'macos-15-intel' @@ -55,23 +51,23 @@ jobs: python-version: ${{ matrix.python-version }} cache: "pip" + - name: Install CPU-only PyTorch (Linux) + if: contains(matrix.platform, 'ubuntu') || contains(matrix.platform, 'namespace') + run: | + pip install torch torchaudio --index-url https://download.pytorch.org/whl/cpu + - name: Install Python dependencies run: | python -m pip install --upgrade pip pip install pyinstaller pip install -r backend/requirements.txt + pip install --no-deps chatterbox-tts - name: Install MLX dependencies (Apple Silicon only) if: matrix.backend == 'mlx' run: | pip install -r backend/requirements-mlx.txt - # - name: Install PyTorch with CUDA (Windows only) - # if: matrix.platform == 'windows-latest' - # run: | - # pip install torch --index-url https://download.pytorch.org/whl/cu121 --force-reinstall --no-deps - # pip install torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121 - - name: Build Python server (Linux/macOS) if: matrix.platform != 'windows-latest' run: | @@ -127,7 +123,7 @@ jobs: p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }} p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} - - uses: tauri-apps/tauri-action@v0 + - uses: tauri-apps/tauri-action@v0.6 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} @@ -151,10 +147,71 @@ jobs: - **macOS (Apple Silicon)**: Download the `aarch64.dmg` file - uses MLX for fast native inference - **macOS (Intel)**: Download the `x64.dmg` file - uses PyTorch - **Windows**: Download the `.msi` installer - - **Linux**: Download the `.AppImage` or `.deb` package + - **Linux**: Compile from source (see README) The app includes automatic updates - future updates will be installed automatically. releaseDraft: true prerelease: false args: ${{ matrix.args }} includeUpdaterJson: true + + build-cuda-windows: + runs-on: windows-latest + permissions: + contents: write + + steps: + - uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: "pip" + + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install pyinstaller + pip install -r backend/requirements.txt + pip install --no-deps chatterbox-tts + + - name: Install PyTorch with CUDA 12.1 + run: | + pip install torch --index-url https://download.pytorch.org/whl/cu121 --force-reinstall --no-deps + pip install torchaudio --index-url https://download.pytorch.org/whl/cu121 + + - name: Verify CUDA support in torch + run: | + python -c "import torch; print(f'CUDA available in build: {torch.cuda.is_available()}'); print(f'CUDA version: {torch.version.cuda}')" + + - name: Build CUDA server binary + shell: bash + working-directory: backend + run: python build_binary.py --cuda + + - name: Split binary for GitHub Releases + shell: bash + run: | + python scripts/split_binary.py \ + backend/dist/voicebox-server-cuda.exe \ + --output release-assets/ + + - name: Upload split parts to GitHub Release + if: startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v1 + with: + files: | + release-assets/voicebox-server-cuda.part*.exe + release-assets/voicebox-server-cuda.sha256 + release-assets/voicebox-server-cuda.manifest + draft: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload binary as workflow artifact + uses: actions/upload-artifact@v4 + with: + name: voicebox-server-cuda-windows + path: backend/dist/voicebox-server-cuda.exe + retention-days: 7 diff --git a/.gitignore b/.gitignore index 05f7ef0d..5f87d802 100644 --- a/.gitignore +++ b/.gitignore @@ -35,10 +35,7 @@ target/ Thumbs.db # Data (user-generated) -data/profiles/* -data/generations/* -data/projects/* -data/voicebox.db +data/ !data/.gitkeep # Logs @@ -52,6 +49,7 @@ logs/ # Generated files app/openapi.json tauri/src-tauri/binaries/* +tauri/src-tauri/gen/Assets.car # Temporary tmp/ diff --git a/CHANGELOG.md b/CHANGELOG.md index b7116d39..f3cab820 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to Voicebox will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Fixed +- **Profile Name Validation** - Added proper validation to prevent duplicate profile names ([#134](https://github.com/jamiepine/voicebox/issues/134)) + - Users now receive clear error messages when attempting to create or update profiles with duplicate names + - Improved error handling in create and update profile API endpoints + - Added comprehensive test suite for duplicate name validation + ## [0.1.0] - 2026-01-25 ### Added @@ -55,16 +63,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Audio export failing when Tauri save dialog returns object instead of string path +- OpenAPI client generator script now documents the local backend port and avoids an unused loop variable warning ### Added -- **Makefile** - Comprehensive development workflow automation with commands for setup, development, building, testing, and code quality checks - - Includes Python version detection and compatibility warnings - - Self-documenting help system with `make help` - - Colored output for better readability - - Supports parallel development server execution +- **justfile** - Comprehensive development workflow automation with commands for setup, development, building, testing, and code quality checks + - Cross-platform support (macOS, Linux, Windows) + - Python version detection and compatibility warnings + - Self-documenting help system with `just --list` ### Changed -- **README** - Added Makefile reference and updated Quick Start with Makefile-based setup instructions alongside manual setup +- **README** - Updated Quick Start with justfile-based setup instructions + +### Removed +- **Makefile** - Replaced by justfile (cross-platform, simpler syntax) --- diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 765da827..e9b7bf7d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,87 +27,47 @@ Thank you for your interest in contributing to Voicebox! This document provides ```bash rustc --version # Check if installed ``` +- **[Tauri Prerequisites](https://v2.tauri.app/start/prerequisites)** - Tauri-specific system dependencies (varies by OS). - **Git** - Version control ### Development Setup -**Using the Makefile (recommended for macOS/Linux):** Run `make setup` to install all dependencies, then `make dev` to start development servers. See `make help` for all available commands. +Install [just](https://github.com/casey/just) (`brew install just`, `cargo install just`, or `winget install Casey.Just`), then: -**Manual setup (required for Windows):** +```bash +git clone https://github.com/YOUR_USERNAME/voicebox.git +cd voicebox -1. **Fork and clone the repository** - ```bash - git clone https://github.com/YOUR_USERNAME/voicebox.git - cd voicebox - ``` +just setup # creates venv, installs Python + JS deps +just dev # starts backend + desktop app +``` -2. **Install JavaScript dependencies** - ```bash - bun install - ``` - This installs dependencies for: - - `app/` - Shared React frontend - - `tauri/` - Tauri desktop wrapper - - `web/` - Web deployment wrapper +`just setup` handles everything automatically, including: +- Creating a Python virtual environment +- Installing Python dependencies (with CUDA PyTorch on Windows if an NVIDIA GPU is detected) +- Installing MLX dependencies on Apple Silicon +- Installing JavaScript dependencies -3. **Set up Python backend** - ```bash - cd backend - - # Create virtual environment - python -m venv venv - - # Activate virtual environment - source venv/bin/activate # On macOS/Linux - # or - venv\Scripts\activate # On Windows - - # Install Python dependencies - pip install -r requirements.txt - - # Install MLX dependencies (Apple Silicon only - for faster inference) - # On Apple Silicon, this enables native Metal acceleration - if [[ $(uname -m) == "arm64" ]]; then - pip install -r requirements-mlx.txt - fi - - # Install Qwen3-TTS (required for voice synthesis) - pip install git+https://github.com/QwenLM/Qwen3-TTS.git - ``` +`just dev` starts the backend and desktop app together. If a backend is already running (e.g. from `just dev-backend` in another terminal), it detects it and only starts the frontend. -4. **Start development servers** +Other useful commands: - Development requires two terminals: one for the Python backend, one for the Tauri app. +```bash +just dev-web # backend + web app (no Tauri/Rust build) +just dev-backend # backend only +just dev-frontend # Tauri app only (backend must be running) +just kill # stop all dev processes +just clean-all # nuke everything and start fresh +just --list # see all available commands +``` - **Terminal 1: Backend server** (start this first) - ```bash - cd backend - source venv/bin/activate # Activate venv if not already active - bun run dev:server - # Or manually: uvicorn main:app --reload --port 17493 - ``` - Backend will be available at `http://localhost:17493` +> **Note:** In dev mode, the app connects to a manually-started Python server. +> The bundled server binary is only used in production builds. - **Terminal 2: Desktop app** - ```bash - bun run dev - ``` - This will: - - Create a placeholder sidecar binary (for Tauri compilation) - - Start Vite dev server on port 5173 - - Launch Tauri window pointing to localhost:5173 - - Connect to the Python server you started in Terminal 1 - - Enable hot reload +#### Windows Notes - > **Note:** In dev mode, the app connects to your manually-started Python server. - > The bundled server binary is only used in production builds. - - **Optional: Web app** - ```bash - bun run dev:web - ``` - Web app will be available at `http://localhost:5174` +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. ### Model Downloads @@ -119,25 +79,30 @@ First-time usage will be slower due to model downloads, but subsequent runs will ### Building -**Build everything (recommended):** +**Build production app:** + ```bash -bun run build +just build # Build CPU server binary + Tauri installer ``` -This automatically: -1. Builds the Python server binary (`./scripts/build-server.sh`) -2. Builds the Tauri desktop app (`cd tauri && bun run tauri build`) + +On Windows, to build with CUDA support for local testing: + +```bash +just build-local # Build CPU + CUDA server binaries + Tauri installer +``` + +This builds the CPU sidecar (bundled with the app), the CUDA binary (placed in `%APPDATA%/com.voicebox.app/backends/` for runtime GPU switching), and the installable Tauri app. Creates platform-specific installers (`.dmg`, `.msi`, `.AppImage`) in `tauri/src-tauri/target/release/bundle/`. -**Note:** The build process detects your platform and includes the appropriate backend (MLX for Apple Silicon, PyTorch for others). +**Individual build targets:** -**Build server binary only:** ```bash -bun run build:server -# or -./scripts/build-server.sh +just build-server # CPU server binary only +just build-server-cuda # CUDA server binary only (Windows) +just build-tauri # Tauri desktop app only +just build-web # Web app only ``` -Creates platform-specific binary in `tauri/src-tauri/binaries/` **Building with local Qwen3-TTS development version:** @@ -145,17 +110,10 @@ If you're actively developing or modifying the Qwen3-TTS library, set the `QWEN_ ```bash export QWEN_TTS_PATH=~/path/to/your/Qwen3-TTS -bun run build:server +just build-server ``` -This makes PyInstaller use your local qwen-tts version instead of the pip-installed package. Useful when testing changes to the TTS library before they're published to PyPI or when using an editable install (`pip install -e`). - -**Build web app:** -```bash -cd web -bun run build -``` -Output in `web/dist/` +This makes PyInstaller use your local qwen-tts version instead of the pip-installed package. ### Generate OpenAPI Client @@ -407,7 +365,7 @@ See [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) for common issues and sol - **Backend won't start:** Check Python version (3.11+), 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:8000/openapi.json` +- **OpenAPI client generation fails:** Ensure backend is running, check `curl http://localhost:17493/openapi.json` ## Questions? diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..4705f98c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,79 @@ +# ============================================================ +# Voicebox — Local TTS Server with Web UI (CPU) +# 3-stage build: Frontend → Python deps → Runtime +# ============================================================ + +# === Stage 1: Build frontend === +FROM oven/bun:1 AS frontend + +WORKDIR /build + +# Copy workspace config and frontend source +COPY package.json bun.lock ./ +COPY app/ ./app/ +COPY web/ ./web/ + +# Strip workspaces not needed for web build, and fix trailing comma +RUN sed -i '/"tauri"/d; /"landing"/d' package.json && \ + sed -i -z 's/,\n ]/\n ]/' package.json +RUN bun install --no-save +# Build frontend (skip tsc — upstream has pre-existing type errors) +RUN cd web && bunx --bun vite build + + +# === Stage 2: Build Python dependencies === +FROM python:3.11-slim AS backend-builder + +WORKDIR /build + +RUN apt-get update && apt-get install -y --no-install-recommends \ + git \ + build-essential \ + && rm -rf /var/lib/apt/lists/* + +COPY backend/requirements.txt . +RUN pip install --no-cache-dir --prefix=/install -r requirements.txt +RUN pip install --no-cache-dir --prefix=/install \ + git+https://github.com/QwenLM/Qwen3-TTS.git + + +# === Stage 3: Runtime === +FROM python:3.11-slim + +# Create non-root user for security +RUN groupadd -r voicebox && \ + useradd -r -g voicebox -m -s /bin/bash voicebox + +WORKDIR /app + +# Install only runtime system dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + ffmpeg \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# Copy installed Python packages from builder stage +COPY --from=backend-builder /install /usr/local + +# Copy backend application code +COPY --chown=voicebox:voicebox backend/ /app/backend/ + +# Copy built frontend from frontend stage +COPY --from=frontend --chown=voicebox:voicebox /build/web/dist /app/frontend/ + +# Create data directories owned by non-root user +RUN mkdir -p /app/data/generations /app/data/profiles /app/data/cache \ + && chown -R voicebox:voicebox /app/data + +# Switch to non-root user +USER voicebox + +# Expose the API port +EXPOSE 17493 + +# Health check — auto-restart if the server hangs +HEALTHCHECK --interval=30s --timeout=10s --retries=3 --start-period=60s \ + CMD curl -f http://localhost:17493/health || exit 1 + +# Start the FastAPI server +CMD ["uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "17493"] diff --git a/Makefile b/Makefile deleted file mode 100644 index 620f6c8c..00000000 --- a/Makefile +++ /dev/null @@ -1,245 +0,0 @@ -# Voicebox Makefile -# Unix-only (macOS/Linux). Windows users should use WSL. - -SHELL := /bin/bash -.DEFAULT_GOAL := help - -# Directories -BACKEND_DIR := backend -TAURI_DIR := tauri -WEB_DIR := web -APP_DIR := app - -# Python (prefer 3.12, fallback to 3.13, then python3) -PYTHON := $(shell command -v python3.12 2>/dev/null || command -v python3.13 2>/dev/null || echo python3) -VENV := $(CURDIR)/$(BACKEND_DIR)/venv -VENV_BIN := $(VENV)/bin -PIP := $(VENV_BIN)/pip -PYTHON_VENV := $(VENV_BIN)/python - -# Colors for output -BLUE := \033[0;34m -GREEN := \033[0;32m -YELLOW := \033[0;33m -NC := \033[0m # No Color - -.PHONY: help -help: ## Show this help message - @echo -e "$(BLUE)Voicebox$(NC) - Development Commands" - @echo "" - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | \ - awk 'BEGIN {FS = ":.*?## "}; {printf " $(GREEN)%-20s$(NC) %s\n", $$1, $$2}' - -# ============================================================================= -# SETUP -# ============================================================================= - -.PHONY: setup setup-js setup-python setup-rust - -setup: setup-js setup-python ## Full project setup (all dependencies) - @echo -e "$(GREEN)✓ Setup complete!$(NC)" - @echo -e " Run $(YELLOW)make dev$(NC) to start development servers" - -setup-js: ## Install JavaScript dependencies (bun) - @echo -e "$(BLUE)Installing JavaScript dependencies...$(NC)" - bun install - -setup-python: $(VENV)/bin/activate ## Set up Python virtual environment and dependencies - @echo -e "$(BLUE)Installing Python dependencies...$(NC)" - $(PIP) install --upgrade pip - $(PIP) install -r $(BACKEND_DIR)/requirements.txt - @if [ "$$(uname -m)" = "arm64" ] && [ "$$(uname)" = "Darwin" ]; then \ - echo -e "$(BLUE)Detected Apple Silicon - installing MLX dependencies...$(NC)"; \ - $(PIP) install -r $(BACKEND_DIR)/requirements-mlx.txt; \ - echo -e "$(GREEN)✓ MLX backend enabled (native Metal acceleration)$(NC)"; \ - fi - $(PIP) install git+https://github.com/QwenLM/Qwen3-TTS.git - @echo -e "$(GREEN)✓ Python environment ready$(NC)" - -$(VENV)/bin/activate: - @echo -e "$(BLUE)Creating Python virtual environment...$(NC)" - @PY_MINOR=$$($(PYTHON) -c "import sys; print(sys.version_info[1])"); \ - if [ "$$PY_MINOR" -gt 13 ]; then \ - echo -e "$(YELLOW)Warning: Python 3.$$PY_MINOR detected. ML packages may not be compatible.$(NC)"; \ - echo -e "$(YELLOW)Recommended: Use Python 3.12 or 3.13 (brew install python@3.12)$(NC)"; \ - fi - $(PYTHON) -m venv $(VENV) - -setup-rust: ## Install Rust toolchain (if not present) - @command -v rustc >/dev/null 2>&1 || curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - -# ============================================================================= -# DEVELOPMENT -# ============================================================================= - -.PHONY: dev dev-backend dev-frontend dev-web kill-dev - -dev: ## Start backend + desktop app (parallel) - @echo -e "$(BLUE)Starting development servers...$(NC)" - @echo -e "$(YELLOW)Note: If Tauri fails, run 'make build-server' first or use separate terminals$(NC)" - @trap 'kill 0' EXIT; \ - $(MAKE) dev-backend & \ - sleep 2 && $(MAKE) dev-frontend & \ - wait - -dev-backend: ## Start FastAPI backend server - @echo -e "$(BLUE)Starting backend server on http://localhost:17493$(NC)" - $(VENV_BIN)/uvicorn backend.main:app --reload --port 17493 - -dev-frontend: ## Start Tauri desktop app - @echo -e "$(BLUE)Starting Tauri desktop app...$(NC)" - bun run dev - -dev-web: ## Start backend + web app (parallel) - @echo -e "$(BLUE)Starting web development servers...$(NC)" - @trap 'kill 0' EXIT; \ - $(MAKE) dev-backend & \ - sleep 2 && cd $(WEB_DIR) && bun run dev & \ - wait - -kill-dev: ## Kill all development processes - @echo -e "$(YELLOW)Killing development processes...$(NC)" - -pkill -f "uvicorn main:app" 2>/dev/null || true - -pkill -f "vite" 2>/dev/null || true - @echo -e "$(GREEN)✓ Processes killed$(NC)" - -# ============================================================================= -# BUILD -# ============================================================================= - -.PHONY: build build-server build-tauri build-web - -build: build-server build-tauri ## Build everything (server binary + desktop app) - @echo -e "$(GREEN)✓ Build complete!$(NC)" - -build-server: ## Build Python server binary - @echo -e "$(BLUE)Building server binary...$(NC)" - PATH="$(VENV_BIN):$$PATH" ./scripts/build-server.sh - -build-tauri: ## Build Tauri desktop app - @echo -e "$(BLUE)Building Tauri desktop app...$(NC)" - cd $(TAURI_DIR) && bun run tauri build - -build-web: ## Build web app - @echo -e "$(BLUE)Building web app...$(NC)" - cd $(WEB_DIR) && bun run build - @echo -e "$(GREEN)✓ Web build output in $(WEB_DIR)/dist/$(NC)" - -# ============================================================================= -# DATABASE & API -# ============================================================================= - -.PHONY: db-init db-reset generate-api - -db-init: $(VENV)/bin/activate ## Initialize SQLite database - @echo -e "$(BLUE)Initializing database...$(NC)" - cd $(BACKEND_DIR) && $(PYTHON_VENV) -c "from database import init_db; init_db()" - @echo -e "$(GREEN)✓ Database created at $(BACKEND_DIR)/data/voicebox.db$(NC)" - -db-reset: ## Reset database (delete and reinitialize) - @echo -e "$(YELLOW)Resetting database...$(NC)" - rm -f $(BACKEND_DIR)/data/voicebox.db - $(MAKE) db-init - -generate-api: ## Generate TypeScript API client from OpenAPI schema - @echo -e "$(BLUE)Generating API client...$(NC)" - @echo -e "$(YELLOW)Note: Backend must be running (make dev-backend)$(NC)" - ./scripts/generate-api.sh - @echo -e "$(GREEN)✓ API client generated in $(APP_DIR)/src/lib/api/$(NC)" - -# ============================================================================= -# CODE QUALITY -# ============================================================================= - -.PHONY: lint format typecheck check - -lint: ## Run linter (Biome) - @echo -e "$(BLUE)Linting...$(NC)" - bun run lint - -format: ## Format code (Biome) - @echo -e "$(BLUE)Formatting...$(NC)" - bun run format - -typecheck: ## Run TypeScript type checking - @echo -e "$(BLUE)Type checking...$(NC)" - bun run tsc --noEmit - -check: ## Run all checks (Biome lint + format + type check) - @echo -e "$(BLUE)Running all checks...$(NC)" - bun run check - @echo -e "$(GREEN)✓ All checks passed$(NC)" - -# ============================================================================= -# TESTING -# ============================================================================= - -.PHONY: test test-backend test-frontend - -test: test-backend test-frontend ## Run all tests - @echo -e "$(GREEN)✓ All tests passed$(NC)" - -test-backend: ## Run Python backend tests (requires pytest) - @echo -e "$(BLUE)Running backend tests...$(NC)" - @if [ -f "$(VENV_BIN)/pytest" ]; then \ - cd $(BACKEND_DIR) && $(VENV_BIN)/pytest -v; \ - else \ - echo -e "$(YELLOW)pytest not installed. Run: $(PIP) install pytest$(NC)"; \ - exit 1; \ - fi - -test-frontend: ## Run frontend tests (requires test script in package.json) - @echo -e "$(BLUE)Running frontend tests...$(NC)" - @if bun run test --help >/dev/null 2>&1; then \ - bun run test; \ - else \ - echo -e "$(YELLOW)No test script configured$(NC)"; \ - exit 1; \ - fi - -# ============================================================================= -# LOGS & DEBUGGING -# ============================================================================= - -.PHONY: logs docs - -logs: ## Tail backend logs - @echo -e "$(BLUE)Tailing logs (Ctrl+C to stop)...$(NC)" - tail -f $(BACKEND_DIR)/logs/*.log 2>/dev/null || echo "No log files found" - -docs: ## Open API documentation (backend must be running) - @echo -e "$(BLUE)Opening API docs...$(NC)" - open http://localhost:17493/docs 2>/dev/null || xdg-open http://localhost:17493/docs - -# ============================================================================= -# CLEAN -# ============================================================================= - -.PHONY: clean clean-python clean-build clean-all - -clean: ## Clean build artifacts - @echo -e "$(BLUE)Cleaning build artifacts...$(NC)" - rm -rf $(TAURI_DIR)/src-tauri/target/release - rm -rf $(WEB_DIR)/dist - rm -rf $(APP_DIR)/dist - @echo -e "$(GREEN)✓ Build artifacts cleaned$(NC)" - -clean-python: ## Clean Python cache and virtual environment - @echo -e "$(BLUE)Cleaning Python files...$(NC)" - rm -rf $(VENV) - find $(BACKEND_DIR) -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true - find $(BACKEND_DIR) -type f -name "*.pyc" -delete 2>/dev/null || true - @echo -e "$(GREEN)✓ Python environment cleaned$(NC)" - -clean-build: ## Clean Rust/Tauri build cache - @echo -e "$(BLUE)Cleaning Rust build cache...$(NC)" - cd $(TAURI_DIR)/src-tauri && cargo clean - @echo -e "$(GREEN)✓ Rust cache cleaned$(NC)" - -clean-all: clean clean-python clean-build ## Nuclear clean (everything) - @echo -e "$(BLUE)Cleaning node_modules...$(NC)" - rm -rf node_modules - rm -rf $(APP_DIR)/node_modules - rm -rf $(TAURI_DIR)/node_modules - rm -rf $(WEB_DIR)/node_modules - @echo -e "$(GREEN)✓ Full clean complete$(NC)" diff --git a/PATCH_NOTES.md b/PATCH_NOTES.md new file mode 100644 index 00000000..2e0c983e --- /dev/null +++ b/PATCH_NOTES.md @@ -0,0 +1,58 @@ +# Voicebox Offline Mode Fix + +## Problem +Voicebox crashes when generating speech if HuggingFace is unreachable, even when models are fully cached locally. + +**Root Cause:** +- Voicebox downloads `mlx-community/Qwen3-TTS-12Hz-1.7B-Base-bf16` (MLX optimized version) +- But `mlx_audio.tts.load()` tries to fetch `config.json` from original repo `Qwen/Qwen3-TTS-12Hz-1.7B-Base` +- This network request fails → server crashes with `RemoteDisconnected` + +**Related Issues:** +- Issue #150: "Internet connection required, even though models are downloaded?" +- Issue #151: "API Stability Issues: Model Loading Hangs and Server Crashes" + +## Solution +Two-part fix: + +### 1. Monkey-patch huggingface_hub (`backend/utils/hf_offline_patch.py`) +- Intercepts cache lookup functions +- Forces offline mode early (before mlx_audio imports) +- Adds debug logging for cache hits/misses + +### 2. Symlink original repo to MLX version (`ensure_original_qwen_config_cached()`) +- When original `Qwen/Qwen3-TTS-12Hz-1.7B-Base` cache doesn't exist +- But MLX `mlx-community/Qwen3-TTS-12Hz-1.7B-Base-bf16` does exist +- Creates a symlink so cache lookups succeed + +## Files Changed +- `backend/backends/mlx_backend.py` - Added patch imports at top +- `backend/utils/hf_offline_patch.py` - New patch module + +## Testing +To test this fix: +1. Build Voicebox from source: `just build` +2. Disconnect from internet +3. Try generating speech +4. Should work without network requests + +## Build Instructions + +```bash +# Install dependencies +just setup + +# Build the app +just build + +# Or build just the server +just build-server +``` + +## Notes +- The patch is applied automatically when `mlx_backend.py` is imported +- Set `VOICEBOX_OFFLINE_PATCH=0` to disable the patch +- The symlink approach works because the config.json is compatible between versions + +--- +*Patch contributed by community* diff --git a/README.md b/README.md index 575918cf..d7943e86 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@

The open-source voice synthesis studio.
- Clone voices. Generate speech. Build voice-powered apps.
+ Clone voices. Generate speech. Apply effects. Build voice-powered apps.
All running locally on your machine.

@@ -59,118 +59,166 @@ ## What is Voicebox? -Voicebox is a **local-first voice cloning studio** with DAW-like features for professional voice synthesis. Think of it as a **local, free and open-source alternative to ElevenLabs** — download models, clone voices, and generate speech entirely on your machine. - -Unlike cloud services that lock your voice data behind subscriptions, Voicebox gives you: +Voicebox is a **local-first voice cloning studio** — a free and open-source alternative to ElevenLabs. Clone voices from a few seconds of audio, generate speech in 23 languages across 4 TTS engines, apply post-processing effects, and compose multi-voice projects with a timeline editor. - **Complete privacy** — models and voice data stay on your machine -- **Professional tools** — multi-track timeline editor, audio trimming, conversation mixing -- **Model flexibility** — currently powered by Qwen3-TTS, with support for XTTS, Bark, and other models coming soon -- **API-first** — use the desktop app or integrate voice synthesis into your own projects +- **4 TTS engines** — Qwen3-TTS, LuxTTS, Chatterbox Multilingual, and Chatterbox Turbo +- **23 languages** — from English to Arabic, Japanese, Hindi, Swahili, and more +- **Post-processing effects** — pitch shift, reverb, delay, chorus, compression, and filters +- **Expressive speech** — paralinguistic tags like `[laugh]`, `[sigh]`, `[gasp]` via Chatterbox Turbo +- **Unlimited length** — auto-chunking with crossfade for scripts, articles, and chapters +- **Stories editor** — multi-track timeline for conversations, podcasts, and narratives +- **API-first** — REST API for integrating voice synthesis into your own projects - **Native performance** — built with Tauri (Rust), not Electron -- **Super fast on Mac** — MLX backend with native Metal acceleration for 4-5x faster inference on Apple Silicon - -Download a voice model, clone any voice from a few seconds of audio, and compose multi-voice projects with studio-grade editing tools. No Python install required, no cloud dependency, no limits. +- **Runs everywhere** — macOS (MLX/Metal), Windows (CUDA), Linux, AMD ROCm, Intel Arc, Docker --- ## Download -Voicebox is available now for macOS and Windows. - | Platform | Download | |----------|----------| -| macOS (Apple Silicon) | [voicebox_aarch64.app.tar.gz](https://github.com/jamiepine/voicebox/releases/download/v0.1.0/voicebox_aarch64.app.tar.gz) | -| macOS (Intel) | [voicebox_x64.app.tar.gz](https://github.com/jamiepine/voicebox/releases/download/v0.1.0/voicebox_x64.app.tar.gz) | -| Windows (MSI) | [voicebox_0.1.0_x64_en-US.msi](https://github.com/jamiepine/voicebox/releases/download/v0.1.0/voicebox_0.1.0_x64_en-US.msi) | -| Windows (Setup) | [voicebox_0.1.0_x64-setup.exe](https://github.com/jamiepine/voicebox/releases/download/v0.1.0/voicebox_0.1.0_x64-setup.exe) | +| macOS (Apple Silicon) | [Download DMG](https://voicebox.sh/download/mac-arm) | +| macOS (Intel) | [Download DMG](https://voicebox.sh/download/mac-intel) | +| Windows | [Download MSI](https://voicebox.sh/download/windows) | +| Docker | `docker compose up` | -> **Linux builds coming soon** — Currently blocked by GitHub runner disk space limitations. +> **[View all binaries →](https://github.com/jamiepine/voicebox/releases/latest)** + +> **Linux** — Pre-built binaries are not yet available. See [voicebox.sh/linux-install](https://voicebox.sh/linux-install) for build-from-source instructions. --- ## Features -### Voice Cloning with Qwen3-TTS +### Multi-Engine Voice Cloning -Powered by Alibaba's **Qwen3-TTS** — a breakthrough model that achieves near-perfect voice cloning from just a few seconds of audio. +Four TTS engines with different strengths, switchable per-generation: -- **Instant cloning** — Upload a sample, get a voice profile -- **High fidelity** — Natural prosody, emotion, and cadence -- **Multi-language** — English, Chinese, and more coming -- **Lightning fast on Mac** — MLX backend leverages Apple Silicon's Neural Engine for super fast generation +| Engine | Languages | Strengths | +|--------|-----------|-----------| +| **Qwen3-TTS** (0.6B / 1.7B) | 10 | High-quality multilingual cloning, delivery instructions ("speak slowly", "whisper") | +| **LuxTTS** | English | Lightweight (~1GB VRAM), 48kHz output, 150x realtime on CPU | +| **Chatterbox Multilingual** | 23 | Broadest language coverage — Arabic, Danish, Finnish, Greek, Hebrew, Hindi, Malay, Norwegian, Polish, Swahili, Swedish, Turkish and more | +| **Chatterbox Turbo** | English | Fast 350M model with paralinguistic emotion/sound tags | + +### Emotions & Paralinguistic Tags + +Type `/` in the text input to insert expressive tags that the model synthesizes inline with speech (Chatterbox Turbo): + +`[laugh]` `[chuckle]` `[gasp]` `[cough]` `[sigh]` `[groan]` `[sniff]` `[shush]` `[clear throat]` + +### Post-Processing Effects + +8 audio effects powered by Spotify's `pedalboard` library. Apply after generation, preview in real time, build reusable presets. + +| Effect | Description | +|--------|-------------| +| Pitch Shift | Up or down by up to 12 semitones | +| Reverb | Configurable room size, damping, wet/dry mix | +| Delay | Echo with adjustable time, feedback, and mix | +| Chorus / Flanger | Modulated delay for metallic or lush textures | +| Compressor | Dynamic range compression | +| Gain | Volume adjustment (-40 to +40 dB) | +| High-Pass Filter | Remove low frequencies | +| Low-Pass Filter | Remove high frequencies | + +Ships with 4 built-in presets (Robotic, Radio, Echo Chamber, Deep Voice) and supports custom presets. Effects can be assigned per-profile as defaults. + +### Unlimited Generation Length + +Text is automatically split at sentence boundaries and each chunk is generated independently, then crossfaded together. Works with all engines. + +- Configurable auto-chunking limit (100–5,000 chars) +- Crossfade slider (0–200ms) for smooth transitions +- Max text length: 50,000 characters +- Smart splitting respects abbreviations, CJK punctuation, and `[tags]` + +### Generation Versions + +Every generation supports multiple versions with provenance tracking: + +- **Original** — clean TTS output, always preserved +- **Effects versions** — apply different effects chains from any source version +- **Takes** — regenerate with a new seed for variation +- **Source tracking** — each version records its lineage +- **Favorites** — star generations for quick access + +### Async Generation Queue + +Generation is non-blocking. Submit and immediately start typing the next one. + +- Serial execution queue prevents GPU contention +- Real-time SSE status streaming +- Failed generations can be retried +- Stale generations from crashes auto-recover on startup ### Voice Profile Management -- **Create profiles** from audio files or record directly in-app -- **Import/Export** profiles to share or backup -- **Multi-sample support** — combine multiple samples for higher quality cloning -- **Organize** with descriptions and language tags - -### Speech Generation - -- **Text-to-speech** with any cloned voice -- **Batch generation** for long-form content -- **Smart caching** — regenerate instantly with voice prompt caching +- Create profiles from audio files or record directly in-app +- Import/export profiles to share or back up +- Multi-sample support for higher quality cloning +- Per-profile default effects chains +- Organize with descriptions and language tags ### Stories Editor -Create multi-voice narratives, podcasts, and conversations with a timeline-based editor. +Multi-voice timeline editor for conversations, podcasts, and narratives. -- **Multi-track composition** — arrange multiple voice tracks in a single project -- **Inline audio editing** — trim and split clips directly in the timeline -- **Auto-playback** — preview stories with synchronized playhead -- **Voice mixing** — build conversations with multiple participants +- Multi-track composition with drag-and-drop +- Inline audio trimming and splitting +- Auto-playback with synchronized playhead +- Version pinning per track clip ### Recording & Transcription -- **In-app recording** with waveform visualization -- **System audio capture** — record desktop audio on macOS and Windows -- **Automatic transcription** powered by Whisper -- **Export recordings** in multiple formats +- In-app recording with waveform visualization +- System audio capture (macOS and Windows) +- Automatic transcription powered by Whisper (including Whisper Turbo) +- Export recordings in multiple formats -### Generation History +### Model Management -- **Full history** of all generated audio -- **Search & filter** by voice, text, or date -- **Re-generate** any past generation with one click +- Per-model unload to free GPU memory without deleting downloads +- Custom models directory via `VOICEBOX_MODELS_DIR` +- Model folder migration with progress tracking +- Download cancel/clear UI -### Flexible Deployment +### GPU Support -- **Local mode** — Everything runs on your machine -- **Remote mode** — Connect to a GPU server on your network -- **One-click server** — Turn any machine into a Voicebox server +| Platform | Backend | Notes | +|----------|---------|-------| +| macOS (Apple Silicon) | MLX (Metal) | 4-5x faster via Neural Engine | +| Windows / Linux (NVIDIA) | PyTorch (CUDA) | Auto-downloads CUDA binary from within the app | +| Linux (AMD) | PyTorch (ROCm) | Auto-configures HSA_OVERRIDE_GFX_VERSION | +| Windows (any GPU) | DirectML | Universal Windows GPU support | +| Intel Arc | IPEX/XPU | Intel discrete GPU acceleration | +| Any | CPU | Works everywhere, just slower | --- ## API -Voicebox exposes a full REST API, so you can integrate voice synthesis into your own apps. +Voicebox exposes a full REST API for integrating voice synthesis into your own apps. ```bash # Generate speech -curl -X POST http://localhost:8000/generate \ +curl -X POST http://localhost:17493/generate \ -H "Content-Type: application/json" \ -d '{"text": "Hello world", "profile_id": "abc123", "language": "en"}' # List voice profiles -curl http://localhost:8000/profiles +curl http://localhost:17493/profiles # Create a profile -curl -X POST http://localhost:8000/profiles \ +curl -X POST http://localhost:17493/profiles \ -H "Content-Type: application/json" \ -d '{"name": "My Voice", "language": "en"}' ``` -**Use cases:** +**Use cases:** game dialogue, podcast production, accessibility tools, voice assistants, content automation. -- Game dialogue systems -- Podcast/video production pipelines -- Accessibility tools -- Voice assistants -- Content creation automation - -Full API documentation available at `http://localhost:8000/docs` when running. +Full API documentation available at `http://localhost:17493/docs`. --- @@ -182,42 +230,24 @@ Full API documentation available at `http://localhost:8000/docs` when running. | Frontend | React, TypeScript, Tailwind CSS | | State | Zustand, React Query | | Backend | FastAPI (Python) | -| Voice Model | Qwen3-TTS (PyTorch or MLX) | -| Transcription | Whisper (PyTorch or MLX) | -| Inference Engine | MLX (Apple Silicon) / PyTorch (Windows/Linux/Intel) | +| TTS Engines | Qwen3-TTS, LuxTTS, Chatterbox, Chatterbox Turbo | +| Effects | Pedalboard (Spotify) | +| Transcription | Whisper / Whisper Turbo (PyTorch or MLX) | +| Inference | MLX (Apple Silicon) / PyTorch (CUDA/ROCm/XPU/CPU) | | Database | SQLite | | Audio | WaveSurfer.js, librosa | -**Why this stack?** - -- **Tauri over Electron** — 10x smaller bundle, native performance, lower memory -- **FastAPI** — Async Python with automatic OpenAPI schema generation -- **Type-safe end-to-end** — Generated TypeScript client from OpenAPI spec - --- ## Roadmap -Voicebox is the beginning of something bigger. Here's what's coming: - -### Coming Soon - | Feature | Description | |---------|-------------| -| **Real-time Synthesis** | Stream audio as it generates, word by word | -| **Conversation Mode** | Multi-speaker dialogues with automatic turn-taking | -| **Voice Effects** | Pitch shift, reverb, M3GAN-style effects | -| **Timeline Editor** | Audio studio with word-level precision editing | +| **Real-time Streaming** | Stream audio as it generates, word by word | +| **Voice Design** | Create new voices from text descriptions | | **More Models** | XTTS, Bark, and other open-source voice models | - -### Future Vision - -- **Voice Design** — Create new voices from text descriptions -- **Project System** — Save and load complex multi-voice sessions -- **Plugin Architecture** — Extend with custom models and effects -- **Mobile Companion** — Control Voicebox from your phone - -Voicebox aims to be the **one-stop shop for everything voice** — cloning, synthesis, editing, effects, and beyond. +| **Plugin Architecture** | Extend with custom models and effects | +| **Mobile Companion** | Control Voicebox from your phone | --- @@ -225,47 +255,27 @@ Voicebox aims to be the **one-stop shop for everything voice** — cloning, synt See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed setup and contribution guidelines. -**Using the Makefile (recommended):** Run `make help` to see all available commands for setup, development, building, and testing. - ### Quick Start -**With Makefile (Unix/macOS/Linux):** - ```bash -# Clone the repo -git clone https://github.com/voicebox-sh/voicebox.git +git clone https://github.com/jamiepine/voicebox.git cd voicebox -# Setup everything -make setup - -# Start development -make dev +just setup # creates Python venv, installs all deps +just dev # starts backend + desktop app ``` -**Manual setup (all platforms):** +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. + +### Building Locally ```bash -# Clone the repo -git clone https://github.com/voicebox-sh/voicebox.git -cd voicebox - -# Install dependencies -bun install - -# Install Python dependencies -cd backend && pip install -r requirements.txt && cd .. - -# Start development -bun run dev +just build # Build CPU server binary + Tauri app +just build-local # (Windows) Build CPU + CUDA server binaries + Tauri app ``` -**Prerequisites:** [Bun](https://bun.sh), [Rust](https://rustup.rs), [Python 3.11+](https://python.org). - -**Performance:** -- **Apple Silicon (M1/M2/M3)**: Uses MLX backend with native Metal acceleration for 4-5x faster inference -- **Windows/Linux/Intel Mac**: Uses PyTorch backend (CUDA GPU recommended, CPU supported but slower) - ### Project Structure ``` diff --git a/app/package.json b/app/package.json index 905dea23..55409654 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "@voicebox/app", - "version": "0.1.12", + "version": "0.2.3", "private": true, "type": "module", "scripts": { diff --git a/app/src/App.tsx b/app/src/App.tsx index fbe29118..458686c8 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -93,10 +93,12 @@ function App() { } serverStartingRef.current = true; - console.log('Production mode: Starting bundled server...'); + const isRemote = useServerStore.getState().mode === 'remote'; + const customModelsDir = useServerStore.getState().customModelsDir; + console.log(`Production mode: Starting bundled server... (remote: ${isRemote})`); platform.lifecycle - .startServer(false) + .startServer(isRemote, customModelsDir) .then((serverUrl) => { console.log('Server is ready at:', serverUrl); // Update the server URL in the store with the dynamically assigned port diff --git a/app/src/components/AudioPlayer/AudioPlayer.tsx b/app/src/components/AudioPlayer/AudioPlayer.tsx index 48dd9e78..667404f3 100644 --- a/app/src/components/AudioPlayer/AudioPlayer.tsx +++ b/app/src/components/AudioPlayer/AudioPlayer.tsx @@ -1,17 +1,18 @@ import { useQuery } from '@tanstack/react-query'; import { Pause, Play, Repeat, Volume2, VolumeX, X } from 'lucide-react'; -import { useEffect, useMemo, useRef, useState } from 'react'; +import { useEffect, useId, useMemo, useRef, useState } from 'react'; import WaveSurfer from 'wavesurfer.js'; import { Button } from '@/components/ui/button'; import { Slider } from '@/components/ui/slider'; import { apiClient } from '@/lib/api/client'; import { formatAudioDuration } from '@/lib/utils/audio'; import { debug } from '@/lib/utils/debug'; -import { usePlayerStore } from '@/stores/playerStore'; import { usePlatform } from '@/platform/PlatformContext'; +import { usePlayerStore } from '@/stores/playerStore'; export function AudioPlayer() { const platform = usePlatform(); + const volumeLabelId = useId(); const { audioUrl, audioId, @@ -138,7 +139,11 @@ export function AudioPlayer() { barRadius: 2, height: 80, normalize: true, - backend: 'WebAudio', + // Use MediaElement backend (default). Unlike the WebAudio backend, + // MediaElement uses a standard