""" PyInstaller build script for creating standalone Python server binary. Usage: python build_binary.py # Build default (CPU) server binary python build_binary.py --cuda # Build CUDA-enabled server binary """ import PyInstaller.__main__ import argparse import os import platform from pathlib import Path def is_apple_silicon(): """Check if running on Apple Silicon.""" return platform.system() == "Darwin" and platform.machine() == "arm64" def build_server(cuda=False): """Build Python server as standalone binary. Args: cuda: If True, build with CUDA support and name the binary voicebox-server-cuda instead of voicebox-server. """ backend_dir = Path(__file__).parent binary_name = 'voicebox-server-cuda' if cuda else 'voicebox-server' # PyInstaller arguments args = [ 'server.py', # Use server.py as entry point instead of main.py '--onefile', '--name', binary_name, ] # Add local qwen_tts path if specified (for editable installs) qwen_tts_path = os.getenv('QWEN_TTS_PATH') if qwen_tts_path and Path(qwen_tts_path).exists(): args.extend(['--paths', str(qwen_tts_path)]) print(f"Using local qwen_tts source from: {qwen_tts_path}") # Add common hidden imports args.extend([ '--hidden-import', 'backend', '--hidden-import', 'backend.main', '--hidden-import', 'backend.config', '--hidden-import', 'backend.database', '--hidden-import', 'backend.models', '--hidden-import', 'backend.profiles', '--hidden-import', 'backend.history', '--hidden-import', 'backend.tts', '--hidden-import', 'backend.transcribe', '--hidden-import', 'backend.platform_detect', '--hidden-import', 'backend.backends', '--hidden-import', 'backend.backends.pytorch_backend', '--hidden-import', 'backend.utils.audio', '--hidden-import', 'backend.utils.cache', '--hidden-import', 'backend.utils.progress', '--hidden-import', 'backend.utils.hf_progress', '--hidden-import', 'backend.utils.validation', '--hidden-import', 'backend.cuda_download', '--hidden-import', 'torch', '--hidden-import', 'transformers', '--hidden-import', 'fastapi', '--hidden-import', 'uvicorn', '--hidden-import', 'sqlalchemy', '--hidden-import', 'librosa', '--hidden-import', 'soundfile', '--hidden-import', 'qwen_tts', '--hidden-import', 'qwen_tts.inference', '--hidden-import', 'qwen_tts.inference.qwen3_tts_model', '--hidden-import', 'qwen_tts.inference.qwen3_tts_tokenizer', '--hidden-import', 'qwen_tts.core', '--hidden-import', 'qwen_tts.cli', '--copy-metadata', 'qwen-tts', '--collect-submodules', 'qwen_tts', '--collect-data', 'qwen_tts', # Fix for pkg_resources and jaraco namespace packages '--hidden-import', 'pkg_resources.extern', '--collect-submodules', 'jaraco', ]) # Add CUDA-specific hidden imports if cuda: print("Building with CUDA support") args.extend([ '--hidden-import', 'torch.cuda', '--hidden-import', 'torch.backends.cudnn', ]) else: # Exclude NVIDIA CUDA packages from CPU-only builds to keep binary under 4GB. # On Linux, pip may pull CUDA-enabled PyTorch by default which includes ~3GB # of NVIDIA shared libraries that PyInstaller would bundle. nvidia_packages = [ 'nvidia', 'nvidia.cublas', 'nvidia.cuda_cupti', 'nvidia.cuda_nvrtc', 'nvidia.cuda_runtime', 'nvidia.cudnn', 'nvidia.cufft', 'nvidia.curand', 'nvidia.cusolver', 'nvidia.cusparse', 'nvidia.nccl', 'nvidia.nvjitlink', 'nvidia.nvtx', ] for pkg in nvidia_packages: args.extend(['--exclude-module', pkg]) # Add MLX-specific imports if building on Apple Silicon (never for CUDA builds) if is_apple_silicon() and not cuda: print("Building for Apple Silicon - including MLX dependencies") args.extend([ '--hidden-import', 'backend.backends.mlx_backend', '--hidden-import', 'mlx', '--hidden-import', 'mlx.core', '--hidden-import', 'mlx.nn', '--hidden-import', 'mlx_audio', '--hidden-import', 'mlx_audio.tts', '--hidden-import', 'mlx_audio.stt', '--collect-submodules', 'mlx', '--collect-submodules', 'mlx_audio', # Use --collect-all so PyInstaller bundles both data files AND # native shared libraries (.dylib, .metallib) for MLX. # Previously only --collect-data was used, which caused MLX to # raise OSError at runtime inside the bundled binary because # the Metal shader libraries were missing. '--collect-all', 'mlx', '--collect-all', 'mlx_audio', ]) elif not cuda: print("Building for non-Apple Silicon platform - PyTorch only") args.extend([ '--noconfirm', '--clean', ]) # Change to backend directory os.chdir(backend_dir) # Run PyInstaller PyInstaller.__main__.run(args) print(f"Binary built in {backend_dir / 'dist' / binary_name}") if __name__ == '__main__': parser = argparse.ArgumentParser(description="Build voicebox-server binary") parser.add_argument( '--cuda', action='store_true', help="Build CUDA-enabled binary (voicebox-server-cuda)", ) cli_args = parser.parse_args() build_server(cuda=cli_args.cuda)