diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 765da827..6a954046 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,16 +14,19 @@ Thank you for your interest in contributing to Voicebox! This document provides ### Prerequisites - **[Bun](https://bun.sh)** - Fast JavaScript runtime and package manager + ```bash curl -fsSL https://bun.sh/install | bash ``` - **[Python 3.11+](https://python.org)** - For backend development + ```bash python --version # Should be 3.11 or higher ``` - **[Rust](https://rustup.rs)** - For Tauri desktop app (installed automatically by Tauri CLI) + ```bash rustc --version # Check if installed ``` @@ -37,41 +40,46 @@ Thank you for your interest in contributing to Voicebox! This document provides **Manual setup (required for Windows):** 1. **Fork and clone the repository** + ```bash git clone https://github.com/YOUR_USERNAME/voicebox.git cd voicebox ``` 2. **Install JavaScript dependencies** + ```bash bun install ``` + This installs dependencies for: + - `app/` - Shared React frontend - `tauri/` - Tauri desktop wrapper - `web/` - Web deployment wrapper 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 ``` @@ -81,19 +89,24 @@ Thank you for your interest in contributing to Voicebox! This document provides Development requires two terminals: one for the Python backend, one for the Tauri app. **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` **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 @@ -104,26 +117,132 @@ Thank you for your interest in contributing to Voicebox! This document provides > 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` ### Model Downloads Models are automatically downloaded from HuggingFace Hub on first use: + - **Whisper** (transcription): Auto-downloads on first transcription - **Qwen3-TTS** (voice cloning): Auto-downloads on first generation (~2-4GB) First-time usage will be slower due to model downloads, but subsequent runs will use cached models. +### TTS Provider Development + +Voicebox uses a modular provider system to support different inference backends. Understanding this architecture is important when working on TTS features. + +#### Provider Types + +**Bundled Providers** — Included with the app binary: + +- `apple-mlx` — Bundled with macOS Apple Silicon builds (`.dmg` for aarch64) + - Uses MLX for native Metal acceleration + - Configured in `.github/workflows/release.yml` with `backend: "mlx"` + +**Hybrid Provider:** + +- `pytorch-cpu` — Can be bundled OR downloaded depending on platform + - **Bundled** with macOS Intel builds (`.dmg` for x64) + - Configured in `.github/workflows/release.yml` with `backend: "pytorch"` + - **Downloaded** on first use for Windows/Linux builds (~300MB) + - Falls back to bundled version if external binary not found + +**External-Only Providers:** + +- `pytorch-cuda` — NVIDIA GPU-accelerated provider (~2.4GB) + - Windows/Linux only (no NVIDIA GPUs on macOS) + - Downloaded on demand, not bundled + - Optional for users with CUDA-capable GPUs + +#### Provider Architecture + +``` +backend/providers/ +├── __init__.py # ProviderManager - lifecycle management +├── base.py # TTSProvider protocol +├── bundled.py # BundledProvider - wraps built-in backends +├── local.py # LocalProvider - wraps external subprocess +├── installer.py # Download and install external providers +└── types.py # Provider type definitions + +providers/ +├── pytorch-cpu/ # External PyTorch CPU provider +│ ├── main.py # FastAPI server +│ ├── build.py # PyInstaller build script +│ └── build_and_install.py # Build and install locally +└── pytorch-cuda/ # External PyTorch CUDA provider + ├── main.py + ├── build.py + └── build_and_install.py +``` + +**How it works:** + +1. **Bundled providers** run in-process within the main backend +2. **External providers** run as separate subprocess servers +3. **LocalProvider** communicates with external providers via HTTP +4. **ProviderManager** handles starting/stopping and health checks + +#### Building Providers Locally + +When developing provider features, you'll need to build and test external providers: + +**Build a single provider:** + +```bash +cd providers/pytorch-cpu +python build_and_install.py +``` + +**Build all providers:** + +```bash +bun run build:providers +``` + +This script: + +- Builds the provider binary with PyInstaller +- Detects your platform (Windows/macOS/Linux) +- Copies to the correct location: + - macOS: `~/Library/Application Support/voicebox/providers/` + - Windows: `%APPDATA%\voicebox\providers\` + - Linux: `~/.local/share/voicebox/providers/` +- Sets executable permissions on Unix + +**Testing provider changes:** + +1. Make changes to `providers/pytorch-cpu/main.py` +2. Run `bun run build:providers` +3. Restart the Voicebox app +4. Select the provider in Settings → TTS Provider + +#### Provider Binary Distribution + +For production releases, provider binaries are: + +1. Built by GitHub Actions for all platforms +2. Uploaded to Cloudflare R2 at `downloads.voicebox.sh/providers/v{VERSION}/` +3. Downloaded on-demand by users based on their platform and GPU + +See `.github/workflows/release.yml` for the build matrix. + ### Building **Build everything (recommended):** + ```bash bun run build ``` + This automatically: + 1. Builds the Python server binary (`./scripts/build-server.sh`) 2. Builds the Tauri desktop app (`cd tauri && bun run tauri build`) @@ -132,13 +251,23 @@ Creates platform-specific installers (`.dmg`, `.msi`, `.AppImage`) in `tauri/src **Note:** The build process detects your platform and includes the appropriate backend (MLX for Apple Silicon, PyTorch for others). **Build server binary only:** + ```bash bun run build:server # or ./scripts/build-server.sh ``` + Creates platform-specific binary in `tauri/src-tauri/binaries/` +**Build provider binaries (for development):** + +```bash +bun run build:providers +``` + +Builds all external provider binaries and installs them to the system provider directory. See [TTS Provider Development](#tts-provider-development) for details. + **Building with local Qwen3-TTS development version:** If you're actively developing or modifying the Qwen3-TTS library, set the `QWEN_TTS_PATH` environment variable to point to your local clone: @@ -151,34 +280,41 @@ bun run 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/` ### Generate OpenAPI Client After starting the backend server: + ```bash ./scripts/generate-api.sh ``` + This downloads the OpenAPI schema and generates the TypeScript client in `app/src/lib/api/` ### Convert Assets to Web Formats To optimize images and videos for the web, run: + ```bash bun run convert:assets ``` This script: + - Converts PNG → WebP (better compression, same quality) - Converts MOV → WebM (VP9 codec, smaller file size) - Processes files in `landing/public/` and `docs/public/` - **Deletes original files** after successful conversion **Requirements:** Install `webp` and `ffmpeg`: + ```bash brew install webp ffmpeg ``` @@ -225,6 +361,7 @@ git push origin feature/your-feature-name ``` Then create a pull request on GitHub with: + - Clear description of changes - Screenshots (for UI changes) - Reference to related issues @@ -370,21 +507,23 @@ Currently, testing is primarily manual. When adding tests: Releases are managed by maintainers: 1. **Bump version using bumpversion:** + ```bash # Install bumpversion (if not already installed) pip install bumpversion - + # Bump patch version (0.1.0 -> 0.1.1) bumpversion patch - + # Or bump minor version (0.1.0 -> 0.2.0) bumpversion minor - + # Or bump major version (0.1.0 -> 1.0.0) bumpversion major ``` - + This automatically: + - Updates version numbers in all files (`tauri.conf.json`, `Cargo.toml`, all `package.json` files, `backend/main.py`) - Creates a git commit with the version bump - Creates a git tag (e.g., `v0.1.1`, `v0.2.0`) @@ -392,6 +531,7 @@ Releases are managed by maintainers: 2. **Update CHANGELOG.md** with release notes 3. **Push commits and tags:** + ```bash git push git push --tags diff --git a/README.md b/README.md index 0b67840b..6b4cb0a0 100644 --- a/README.md +++ b/README.md @@ -78,12 +78,12 @@ Download a voice model, clone any voice from a few seconds of audio, and compose 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) | +| 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) | > **Linux builds coming soon** — Currently blocked by GitHub runner disk space limitations. @@ -176,17 +176,17 @@ Full API documentation available at `http://localhost:8000/docs` when running. ## Tech Stack -| Layer | Technology | -|-------|------------| -| Desktop App | Tauri (Rust) | -| Frontend | React, TypeScript, Tailwind CSS | -| State | Zustand, React Query | -| Backend | FastAPI (Python) | -| Voice Model | Qwen3-TTS (PyTorch or MLX) | -| Transcription | Whisper (PyTorch or MLX) | +| Layer | Technology | +| ---------------- | --------------------------------------------------- | +| Desktop App | Tauri (Rust) | +| 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) | -| Database | SQLite | -| Audio | WaveSurfer.js, librosa | +| Database | SQLite | +| Audio | WaveSurfer.js, librosa | **Why this stack?** @@ -194,6 +194,26 @@ Full API documentation available at `http://localhost:8000/docs` when running. - **FastAPI** — Async Python with automatic OpenAPI schema generation - **Type-safe end-to-end** — Generated TypeScript client from OpenAPI spec +### TTS Provider Architecture + +Voicebox uses a modular provider system to support different inference backends: + +- **`apple-mlx`** — Bundled with macOS Apple Silicon builds + + - Uses MLX with native Metal acceleration (4-5x faster) + - Works out of the box, no download required + +- **`pytorch-cpu`** — Universal CPU provider (bundled or downloaded) + + - Bundled with macOS Intel builds + - Downloaded on first use for Windows/Linux (~300MB) + +- **`pytorch-cuda`** — Optional NVIDIA GPU-accelerated provider + - Windows/Linux only (~2.4GB) + - 4-5x faster inference on CUDA-capable GPUs + +macOS builds work out of the box with bundled providers. Windows and Linux users download a provider on first launch. The app automatically detects your hardware and recommends the best option. All downloadable providers are distributed via Cloudflare R2 for fast, global delivery. + --- ## Roadmap @@ -202,13 +222,13 @@ 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 | -| **More Models** | XTTS, Bark, and other open-source voice models | +| 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 | +| **More Models** | XTTS, Bark, and other open-source voice models | ### Future Vision @@ -260,9 +280,10 @@ cd backend && pip install -r requirements.txt && cd .. bun run dev ``` -**Prerequisites:** [Bun](https://bun.sh), [Rust](https://rustup.rs), [Python 3.11+](https://python.org). +**Prerequisites:** [Bun](https://bun.sh), [Rust](https://rustup.rs), [Python 3.11+](https://python.org). + +**Performance:** -**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) diff --git a/app/src/components/ServerSettings/ProviderSettings.tsx b/app/src/components/ServerSettings/ProviderSettings.tsx index 16af4a34..b0bc5c50 100644 --- a/app/src/components/ServerSettings/ProviderSettings.tsx +++ b/app/src/components/ServerSettings/ProviderSettings.tsx @@ -25,7 +25,7 @@ const isMacOS = () => navigator.platform.toLowerCase().includes('mac'); type ProviderType = | 'auto' - | 'bundled-mlx' + | 'apple-mlx' | 'bundled-pytorch' | 'pytorch-cpu' | 'pytorch-cuda' @@ -286,14 +286,14 @@ export function ProviderSettings() { {/* MLX bundled (macOS Apple Silicon only) */} {isMacOS() && (
- + - {currentProvider === 'bundled-mlx' && ( + {currentProvider === 'apple-mlx' && ( Active diff --git a/backend/main.py b/backend/main.py index f6d00a1f..31089227 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1514,7 +1514,7 @@ async def list_providers(): # Get info for all known provider types all_providers = [ - "bundled-mlx", + "apple-mlx", "bundled-pytorch", "pytorch-cpu", "pytorch-cuda", diff --git a/backend/providers/__init__.py b/backend/providers/__init__.py index a901059a..f35ded88 100644 --- a/backend/providers/__init__.py +++ b/backend/providers/__init__.py @@ -3,6 +3,7 @@ Provider management system for TTS providers. """ from typing import Optional +import asyncio import platform from pathlib import Path @@ -50,7 +51,7 @@ class ProviderManager: Args: provider_type: Type of provider to start """ - if provider_type == "bundled-mlx": + if provider_type == "apple-mlx": # Use bundled MLX provider self.active_provider = self._get_default_provider() elif provider_type in ["pytorch-cpu", "pytorch-cuda"]: @@ -61,8 +62,15 @@ class ProviderManager: # Find a free port port = self._get_free_port() - # Start provider subprocess + # Start provider subprocess with stdout/stderr capture from ..config import get_data_dir + import logging + logger = logging.getLogger(__name__) + + logger.info(f"Starting provider {provider_type} on port {port}") + logger.info(f"Provider binary: {provider_path}") + logger.info(f"Data directory: {get_data_dir()}") + process = subprocess.Popen( [ str(provider_path), @@ -71,16 +79,49 @@ class ProviderManager: ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, + text=True, + bufsize=1, ) # Wait for provider to be ready base_url = f"http://127.0.0.1:{port}" - await self._wait_for_provider_health(base_url, timeout=30) + try: + await self._wait_for_provider_health(base_url, timeout=30) + except TimeoutError as e: + # Capture subprocess output for debugging + stdout_lines = [] + stderr_lines = [] + + # Try to read available output + import select + try: + if process.stdout and select.select([process.stdout], [], [], 0)[0]: + stdout_lines = process.stdout.readlines() + if process.stderr and select.select([process.stderr], [], [], 0)[0]: + stderr_lines = process.stderr.readlines() + except Exception: + # select might not work on all platforms + pass + + logger.error(f"Provider failed to start. Stdout: {stdout_lines}") + logger.error(f"Provider failed to start. Stderr: {stderr_lines}") + + # Terminate the process + process.terminate() + try: + process.wait(timeout=5) + except subprocess.TimeoutExpired: + process.kill() + + raise # Create LocalProvider instance self.active_provider = LocalProvider(base_url) self._provider_process = process self._provider_port = port + + # Start background task to log subprocess output + asyncio.create_task(self._log_subprocess_output(process)) else: # No external binary, use bundled provider (if available) if provider_type == "pytorch-cpu": @@ -131,7 +172,7 @@ class ProviderManager: if system == "Darwin" and machine == "arm64": # Apple Silicon gets MLX - installed.append("bundled-mlx") + installed.append("apple-mlx") # PyTorch CPU is available on all platforms (check if bundled or downloaded) # For now, assume it's bundled on macOS Intel, Windows, Linux @@ -163,7 +204,7 @@ class ProviderManager: Returns: Provider information dictionary """ - if provider_type in ["bundled-mlx", "bundled-pytorch"]: + if provider_type in ["apple-mlx", "bundled-pytorch"]: return { "type": provider_type, "name": "Bundled Provider", @@ -203,7 +244,7 @@ class ProviderManager: """Wait for provider to become healthy.""" import httpx import asyncio - + start_time = asyncio.get_event_loop().time() while True: try: @@ -213,12 +254,32 @@ class ProviderManager: return except Exception: pass - + if asyncio.get_event_loop().time() - start_time > timeout: raise TimeoutError(f"Provider did not become healthy within {timeout} seconds") - + await asyncio.sleep(0.5) + async def _log_subprocess_output(self, process: subprocess.Popen) -> None: + """Log subprocess stdout and stderr.""" + import logging + logger = logging.getLogger(__name__) + + async def read_stream(stream, prefix): + if stream: + loop = asyncio.get_event_loop() + while True: + line = await loop.run_in_executor(None, stream.readline) + if not line: + break + logger.info(f"{prefix}: {line.rstrip()}") + + await asyncio.gather( + read_stream(process.stdout, "Provider stdout"), + read_stream(process.stderr, "Provider stderr"), + return_exceptions=True, + ) + # Global provider manager instance _provider_manager: Optional[ProviderManager] = None diff --git a/backend/providers/types.py b/backend/providers/types.py index 8229cb74..b6b0aff1 100644 --- a/backend/providers/types.py +++ b/backend/providers/types.py @@ -8,7 +8,7 @@ from enum import Enum class ProviderType(str, Enum): """Available provider types.""" - BUNDLED_MLX = "bundled-mlx" + BUNDLED_MLX = "apple-mlx" BUNDLED_PYTORCH = "bundled-pytorch" PYTORCH_CPU = "pytorch-cpu" PYTORCH_CUDA = "pytorch-cuda" diff --git a/package.json b/package.json index f6af4cbd..fd5b78ed 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "build:web": "cd web && bun run build", "build:landing": "cd landing && bun run build", "build:release": "./scripts/prepare-release.sh", + "build:providers": "python scripts/build-providers-local.py", "generate:api": "./scripts/generate-api.sh", "generate:keys": "cd tauri && bun tauri signer generate -w ~/.tauri/voicebox.key", "build:server": "./scripts/build-server.sh", diff --git a/providers/pytorch-cpu/build.py b/providers/pytorch-cpu/build.py index 6596c266..b70100c5 100644 --- a/providers/pytorch-cpu/build.py +++ b/providers/pytorch-cpu/build.py @@ -61,7 +61,6 @@ def build_provider(): '--exclude-module', 'torch.utils.tensorboard', '--exclude-module', 'tensorboard', '--exclude-module', 'triton', - '--exclude-module', 'torch.distributed', '--exclude-module', 'torch._dynamo', '--exclude-module', 'torch._inductor', '--exclude-module', 'torch.testing', diff --git a/providers/pytorch-cpu/build_and_install.py b/providers/pytorch-cpu/build_and_install.py new file mode 100644 index 00000000..7a63e091 --- /dev/null +++ b/providers/pytorch-cpu/build_and_install.py @@ -0,0 +1,57 @@ +""" +Build PyTorch CPU provider and install to local provider directory. +""" + +import platform +import shutil +from pathlib import Path + +from build import build_provider + + +def get_providers_dir() -> Path: + """Get the directory where providers are stored.""" + system = platform.system() + + if system == "Windows": + appdata = Path.home() / "AppData" / "Roaming" + elif system == "Darwin": + appdata = Path.home() / "Library" / "Application Support" + else: # Linux + appdata = Path.home() / ".local" / "share" + + providers_dir = appdata / "voicebox" / "providers" + providers_dir.mkdir(parents=True, exist_ok=True) + return providers_dir + + +def main(): + """Build and install provider.""" + provider_dir = Path(__file__).parent + + # Build the provider + print("Building PyTorch CPU provider...") + build_provider() + + # Determine binary name + binary_name = "tts-provider-pytorch-cpu" + if platform.system() == "Windows": + binary_name += ".exe" + + # Source and destination paths + source = provider_dir / "dist" / binary_name + destination = get_providers_dir() / binary_name + + # Copy to provider directory + print(f"Installing to {destination}...") + shutil.copy2(source, destination) + + # Make executable on Unix systems + if platform.system() != "Windows": + destination.chmod(0o755) + + print(f"✓ Provider installed successfully to {destination}") + + +if __name__ == "__main__": + main() diff --git a/providers/pytorch-cpu/tts-provider-pytorch-cpu.spec b/providers/pytorch-cpu/tts-provider-pytorch-cpu.spec new file mode 100644 index 00000000..e4208043 --- /dev/null +++ b/providers/pytorch-cpu/tts-provider-pytorch-cpu.spec @@ -0,0 +1,48 @@ +# -*- mode: python ; coding: utf-8 -*- +from PyInstaller.utils.hooks import collect_data_files +from PyInstaller.utils.hooks import collect_submodules +from PyInstaller.utils.hooks import copy_metadata + +datas = [] +hiddenimports = ['backend', 'backend.backends', 'backend.backends.pytorch_backend', 'backend.config', 'backend.utils.audio', 'backend.utils.cache', 'backend.utils.progress', 'backend.utils.hf_progress', 'backend.utils.tasks', 'torch', 'transformers', 'qwen_tts', 'qwen_tts.inference', 'qwen_tts.inference.qwen3_tts_model', 'qwen_tts.inference.qwen3_tts_tokenizer', 'qwen_tts.core', 'qwen_tts.cli', 'pkg_resources.extern', 'fastapi', 'uvicorn', 'soundfile', 'numpy', 'librosa'] +datas += collect_data_files('qwen_tts') +datas += copy_metadata('qwen-tts') +hiddenimports += collect_submodules('qwen_tts') +hiddenimports += collect_submodules('jaraco') + + +a = Analysis( + ['main.py'], + pathex=['/Users/jamespine/Projects/voicebox'], + binaries=[], + datas=datas, + hiddenimports=hiddenimports, + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=['torch.utils.tensorboard', 'tensorboard', 'triton', 'torch._dynamo', 'torch._inductor', 'torch.testing', 'torch.utils.benchmark', 'IPython', 'matplotlib', 'PIL', 'cv2', 'torchvision', 'torchaudio'], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name='tts-provider-pytorch-cpu', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) diff --git a/providers/pytorch-cuda/build.py b/providers/pytorch-cuda/build.py index 565885b2..28a6fc8e 100644 --- a/providers/pytorch-cuda/build.py +++ b/providers/pytorch-cuda/build.py @@ -63,7 +63,6 @@ def build_provider(): '--exclude-module', 'torch.utils.tensorboard', '--exclude-module', 'tensorboard', '--exclude-module', 'triton', - '--exclude-module', 'torch.distributed', '--exclude-module', 'torch._dynamo', '--exclude-module', 'torch._inductor', '--exclude-module', 'torch.testing', diff --git a/providers/pytorch-cuda/build_and_install.py b/providers/pytorch-cuda/build_and_install.py new file mode 100644 index 00000000..227dca0e --- /dev/null +++ b/providers/pytorch-cuda/build_and_install.py @@ -0,0 +1,57 @@ +""" +Build PyTorch CUDA provider and install to local provider directory. +""" + +import platform +import shutil +from pathlib import Path + +from build import build_provider + + +def get_providers_dir() -> Path: + """Get the directory where providers are stored.""" + system = platform.system() + + if system == "Windows": + appdata = Path.home() / "AppData" / "Roaming" + elif system == "Darwin": + appdata = Path.home() / "Library" / "Application Support" + else: # Linux + appdata = Path.home() / ".local" / "share" + + providers_dir = appdata / "voicebox" / "providers" + providers_dir.mkdir(parents=True, exist_ok=True) + return providers_dir + + +def main(): + """Build and install provider.""" + provider_dir = Path(__file__).parent + + # Build the provider + print("Building PyTorch CUDA provider...") + build_provider() + + # Determine binary name + binary_name = "tts-provider-pytorch-cuda" + if platform.system() == "Windows": + binary_name += ".exe" + + # Source and destination paths + source = provider_dir / "dist" / binary_name + destination = get_providers_dir() / binary_name + + # Copy to provider directory + print(f"Installing to {destination}...") + shutil.copy2(source, destination) + + # Make executable on Unix systems + if platform.system() != "Windows": + destination.chmod(0o755) + + print(f"✓ Provider installed successfully to {destination}") + + +if __name__ == "__main__": + main() diff --git a/providers/pytorch-cuda/tts-provider-pytorch-cuda.spec b/providers/pytorch-cuda/tts-provider-pytorch-cuda.spec new file mode 100644 index 00000000..f8179e96 --- /dev/null +++ b/providers/pytorch-cuda/tts-provider-pytorch-cuda.spec @@ -0,0 +1,48 @@ +# -*- mode: python ; coding: utf-8 -*- +from PyInstaller.utils.hooks import collect_data_files +from PyInstaller.utils.hooks import collect_submodules +from PyInstaller.utils.hooks import copy_metadata + +datas = [] +hiddenimports = ['backend', 'backend.backends', 'backend.backends.pytorch_backend', 'backend.config', 'backend.utils.audio', 'backend.utils.cache', 'backend.utils.progress', 'backend.utils.hf_progress', 'backend.utils.tasks', 'torch', 'torch.cuda', 'torch.backends.cudnn', 'transformers', 'qwen_tts', 'qwen_tts.inference', 'qwen_tts.inference.qwen3_tts_model', 'qwen_tts.inference.qwen3_tts_tokenizer', 'qwen_tts.core', 'qwen_tts.cli', 'pkg_resources.extern', 'fastapi', 'uvicorn', 'soundfile', 'numpy', 'librosa'] +datas += collect_data_files('qwen_tts') +datas += copy_metadata('qwen-tts') +hiddenimports += collect_submodules('qwen_tts') +hiddenimports += collect_submodules('jaraco') + + +a = Analysis( + ['main.py'], + pathex=['/Users/jamespine/Projects/voicebox'], + binaries=[], + datas=datas, + hiddenimports=hiddenimports, + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=['torch.utils.tensorboard', 'tensorboard', 'triton', 'torch._dynamo', 'torch._inductor', 'torch.testing', 'torch.utils.benchmark', 'IPython', 'matplotlib', 'PIL', 'cv2', 'torchvision', 'torchaudio'], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name='tts-provider-pytorch-cuda', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) diff --git a/scripts/build-providers-local.py b/scripts/build-providers-local.py new file mode 100755 index 00000000..05edde2c --- /dev/null +++ b/scripts/build-providers-local.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +""" +Build and install all TTS providers locally for development. +""" + +import subprocess +import sys +from pathlib import Path + + +def main(): + """Build and install all providers.""" + project_root = Path(__file__).parent.parent + providers_dir = project_root / "providers" + + providers = ["pytorch-cpu", "pytorch-cuda"] + + for provider in providers: + provider_path = providers_dir / provider + script_path = provider_path / "build_and_install.py" + + if not script_path.exists(): + print(f"⚠ Skipping {provider}: build_and_install.py not found") + continue + + print(f"\n{'=' * 60}") + print(f"Building and installing {provider}...") + print(f"{'=' * 60}\n") + + try: + subprocess.run( + [sys.executable, str(script_path)], + cwd=provider_path, + check=True, + ) + except subprocess.CalledProcessError as e: + print(f"✗ Failed to build {provider}: {e}") + sys.exit(1) + + print(f"\n{'=' * 60}") + print("✓ All providers built and installed successfully!") + print(f"{'=' * 60}\n") + + +if __name__ == "__main__": + main() diff --git a/tauri/src-tauri/gen/Assets.car b/tauri/src-tauri/gen/Assets.car index 088d1610..f87bc4fb 100644 Binary files a/tauri/src-tauri/gen/Assets.car and b/tauri/src-tauri/gen/Assets.car differ