mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-15 04:40:40 -07:00
fix take-label race in regeneration, add accessible focus to select
- Use DB COUNT query instead of list length for take-N label to avoid TOCTOU race between list_versions and create_version - Add focus:bg-muted to SelectTrigger for keyboard focus visibility
This commit is contained in:
+266
-139
@@ -8,11 +8,14 @@ Usage:
|
||||
|
||||
import PyInstaller.__main__
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def is_apple_silicon():
|
||||
"""Check if running on Apple Silicon."""
|
||||
@@ -28,155 +31,246 @@ def build_server(cuda=False):
|
||||
"""
|
||||
backend_dir = Path(__file__).parent
|
||||
|
||||
binary_name = 'voicebox-server-cuda' if cuda else 'voicebox-server'
|
||||
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,
|
||||
"server.py", # Use server.py as entry point instead of main.py
|
||||
"--onefile",
|
||||
"--name",
|
||||
binary_name,
|
||||
]
|
||||
|
||||
# Hide console window on Windows only. On macOS/Linux the sidecar needs
|
||||
# stdout/stderr for Tauri to capture logs.
|
||||
if platform.system() == "Windows":
|
||||
args.append('--noconsole')
|
||||
args.append("--noconsole")
|
||||
|
||||
# Add local qwen_tts path if specified (for editable installs)
|
||||
qwen_tts_path = os.getenv('QWEN_TTS_PATH')
|
||||
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)])
|
||||
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.services.profiles',
|
||||
'--hidden-import', 'backend.services.history',
|
||||
'--hidden-import', 'backend.services.tts',
|
||||
'--hidden-import', 'backend.services.transcribe',
|
||||
'--hidden-import', 'backend.utils.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.services.cuda',
|
||||
'--hidden-import', 'backend.services.effects',
|
||||
'--hidden-import', 'backend.utils.effects',
|
||||
'--hidden-import', 'backend.services.versions',
|
||||
'--hidden-import', 'pedalboard',
|
||||
'--hidden-import', 'chatterbox',
|
||||
'--hidden-import', 'chatterbox.tts_turbo',
|
||||
'--hidden-import', 'chatterbox.mtl_tts',
|
||||
'--hidden-import', 'backend.backends.chatterbox_backend',
|
||||
'--hidden-import', 'backend.backends.chatterbox_turbo_backend',
|
||||
'--hidden-import', 'backend.backends.luxtts_backend',
|
||||
'--hidden-import', 'zipvoice',
|
||||
'--hidden-import', 'zipvoice.luxvoice',
|
||||
'--collect-all', 'zipvoice',
|
||||
'--collect-all', 'linacodec',
|
||||
'--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',
|
||||
'--copy-metadata', 'requests',
|
||||
'--copy-metadata', 'transformers',
|
||||
'--copy-metadata', 'huggingface-hub',
|
||||
'--copy-metadata', 'tokenizers',
|
||||
'--copy-metadata', 'safetensors',
|
||||
'--copy-metadata', 'tqdm',
|
||||
'--hidden-import', 'requests',
|
||||
'--collect-submodules', 'qwen_tts',
|
||||
'--collect-data', 'qwen_tts',
|
||||
# Fix for pkg_resources and jaraco namespace packages
|
||||
'--hidden-import', 'pkg_resources.extern',
|
||||
'--collect-submodules', 'jaraco',
|
||||
# inflect uses typeguard @typechecked which calls inspect.getsource()
|
||||
# at import time — needs .py source files, not just .pyc bytecode
|
||||
'--collect-all', 'inflect',
|
||||
# perth ships pretrained watermark model files (hparams.yaml, .pth.tar)
|
||||
# in perth/perth_net/pretrained/ — needed by chatterbox at runtime
|
||||
'--collect-all', 'perth',
|
||||
# piper_phonemize ships espeak-ng-data/ (phoneme tables, language dicts)
|
||||
# needed by LuxTTS for text-to-phoneme conversion
|
||||
'--collect-all', 'piper_phonemize',
|
||||
])
|
||||
args.extend(
|
||||
[
|
||||
"--hidden-import",
|
||||
"backend",
|
||||
"--hidden-import",
|
||||
"backend.main",
|
||||
"--hidden-import",
|
||||
"backend.config",
|
||||
"--hidden-import",
|
||||
"backend.database",
|
||||
"--hidden-import",
|
||||
"backend.models",
|
||||
"--hidden-import",
|
||||
"backend.services.profiles",
|
||||
"--hidden-import",
|
||||
"backend.services.history",
|
||||
"--hidden-import",
|
||||
"backend.services.tts",
|
||||
"--hidden-import",
|
||||
"backend.services.transcribe",
|
||||
"--hidden-import",
|
||||
"backend.utils.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.services.cuda",
|
||||
"--hidden-import",
|
||||
"backend.services.effects",
|
||||
"--hidden-import",
|
||||
"backend.utils.effects",
|
||||
"--hidden-import",
|
||||
"backend.services.versions",
|
||||
"--hidden-import",
|
||||
"pedalboard",
|
||||
"--hidden-import",
|
||||
"chatterbox",
|
||||
"--hidden-import",
|
||||
"chatterbox.tts_turbo",
|
||||
"--hidden-import",
|
||||
"chatterbox.mtl_tts",
|
||||
"--hidden-import",
|
||||
"backend.backends.chatterbox_backend",
|
||||
"--hidden-import",
|
||||
"backend.backends.chatterbox_turbo_backend",
|
||||
"--hidden-import",
|
||||
"backend.backends.luxtts_backend",
|
||||
"--hidden-import",
|
||||
"zipvoice",
|
||||
"--hidden-import",
|
||||
"zipvoice.luxvoice",
|
||||
"--collect-all",
|
||||
"zipvoice",
|
||||
"--collect-all",
|
||||
"linacodec",
|
||||
"--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",
|
||||
"--copy-metadata",
|
||||
"requests",
|
||||
"--copy-metadata",
|
||||
"transformers",
|
||||
"--copy-metadata",
|
||||
"huggingface-hub",
|
||||
"--copy-metadata",
|
||||
"tokenizers",
|
||||
"--copy-metadata",
|
||||
"safetensors",
|
||||
"--copy-metadata",
|
||||
"tqdm",
|
||||
"--hidden-import",
|
||||
"requests",
|
||||
"--collect-submodules",
|
||||
"qwen_tts",
|
||||
"--collect-data",
|
||||
"qwen_tts",
|
||||
# Fix for pkg_resources and jaraco namespace packages
|
||||
"--hidden-import",
|
||||
"pkg_resources.extern",
|
||||
"--collect-submodules",
|
||||
"jaraco",
|
||||
# inflect uses typeguard @typechecked which calls inspect.getsource()
|
||||
# at import time — needs .py source files, not just .pyc bytecode
|
||||
"--collect-all",
|
||||
"inflect",
|
||||
# perth ships pretrained watermark model files (hparams.yaml, .pth.tar)
|
||||
# in perth/perth_net/pretrained/ — needed by chatterbox at runtime
|
||||
"--collect-all",
|
||||
"perth",
|
||||
# piper_phonemize ships espeak-ng-data/ (phoneme tables, language dicts)
|
||||
# needed by LuxTTS for text-to-phoneme conversion
|
||||
"--collect-all",
|
||||
"piper_phonemize",
|
||||
]
|
||||
)
|
||||
|
||||
# Add CUDA-specific hidden imports
|
||||
if cuda:
|
||||
print("Building with CUDA support")
|
||||
args.extend([
|
||||
'--hidden-import', 'torch.cuda',
|
||||
'--hidden-import', 'torch.backends.cudnn',
|
||||
])
|
||||
args.extend(
|
||||
[
|
||||
"--hidden-import",
|
||||
"torch.cuda",
|
||||
"--hidden-import",
|
||||
"torch.backends.cudnn",
|
||||
]
|
||||
)
|
||||
else:
|
||||
# Exclude NVIDIA CUDA packages from CPU-only builds to keep binary small.
|
||||
# When building from a venv with CUDA torch installed, PyInstaller would
|
||||
# bundle ~3GB of NVIDIA shared libraries. We exclude both the Python
|
||||
# modules and the binary DLLs.
|
||||
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',
|
||||
"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])
|
||||
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',
|
||||
])
|
||||
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")
|
||||
|
||||
dist_dir = str(backend_dir / 'dist')
|
||||
build_dir = str(backend_dir / 'build')
|
||||
dist_dir = str(backend_dir / "dist")
|
||||
build_dir = str(backend_dir / "build")
|
||||
|
||||
args.extend([
|
||||
'--distpath', dist_dir,
|
||||
'--workpath', build_dir,
|
||||
'--noconfirm',
|
||||
'--clean',
|
||||
])
|
||||
args.extend(
|
||||
[
|
||||
"--distpath",
|
||||
dist_dir,
|
||||
"--workpath",
|
||||
build_dir,
|
||||
"--noconfirm",
|
||||
"--clean",
|
||||
]
|
||||
)
|
||||
|
||||
# Change to backend directory
|
||||
os.chdir(backend_dir)
|
||||
|
||||
|
||||
# For CPU builds on Windows, ensure we're using CPU-only torch.
|
||||
# If CUDA torch is installed (local dev), swap to CPU torch before building,
|
||||
# then restore CUDA torch after. This prevents PyInstaller from bundling
|
||||
@@ -184,17 +278,28 @@ def build_server(cuda=False):
|
||||
restore_cuda = False
|
||||
if not cuda and platform.system() == "Windows":
|
||||
import subprocess
|
||||
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-c", "import torch; print(torch.version.cuda or '')"],
|
||||
capture_output=True, text=True
|
||||
[sys.executable, "-c", "import torch; print(torch.version.cuda or '')"], capture_output=True, text=True
|
||||
)
|
||||
has_cuda_torch = bool(result.stdout.strip())
|
||||
if has_cuda_torch:
|
||||
print("CUDA torch detected — installing CPU torch for CPU build...")
|
||||
subprocess.run(
|
||||
[sys.executable, "-m", "pip", "install", "torch", "torchvision", "torchaudio",
|
||||
"--index-url", "https://download.pytorch.org/whl/cpu", "--force-reinstall", "-q"],
|
||||
check=True
|
||||
[
|
||||
sys.executable,
|
||||
"-m",
|
||||
"pip",
|
||||
"install",
|
||||
"torch",
|
||||
"torchvision",
|
||||
"torchaudio",
|
||||
"--index-url",
|
||||
"https://download.pytorch.org/whl/cpu",
|
||||
"--force-reinstall",
|
||||
"-q",
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
restore_cuda = True
|
||||
|
||||
@@ -206,55 +311,77 @@ def build_server(cuda=False):
|
||||
if restore_cuda:
|
||||
print("Restoring CUDA torch...")
|
||||
import subprocess
|
||||
|
||||
subprocess.run(
|
||||
[sys.executable, "-m", "pip", "install", "torch", "torchvision", "torchaudio",
|
||||
"--index-url", "https://download.pytorch.org/whl/cu126", "--force-reinstall", "-q"],
|
||||
check=True
|
||||
[
|
||||
sys.executable,
|
||||
"-m",
|
||||
"pip",
|
||||
"install",
|
||||
"torch",
|
||||
"torchvision",
|
||||
"torchaudio",
|
||||
"--index-url",
|
||||
"https://download.pytorch.org/whl/cu126",
|
||||
"--force-reinstall",
|
||||
"-q",
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
|
||||
|
||||
print(f"Binary built in {backend_dir / 'dist' / binary_name}")
|
||||
|
||||
|
||||
def _get_cuda_dll_excludes():
|
||||
"""Get list of CUDA DLL filenames to exclude from CPU builds.
|
||||
|
||||
|
||||
When building locally with CUDA torch installed, PyInstaller bundles ~3GB of
|
||||
CUDA DLLs from torch/lib/. Returns a list of DLL filenames to exclude.
|
||||
"""
|
||||
try:
|
||||
import torch
|
||||
torch_lib = Path(torch.__file__).parent / 'lib'
|
||||
|
||||
torch_lib = Path(torch.__file__).parent / "lib"
|
||||
except ImportError:
|
||||
return []
|
||||
|
||||
|
||||
cuda_prefixes = (
|
||||
'torch_cuda', 'cublas', 'cublasLt', 'cudnn', 'cusparse', 'cufft',
|
||||
'cusolver', 'cusolverMg', 'curand', 'nvrtc', 'nvJitLink', 'nccl',
|
||||
'nvperf', 'nvrtc-builtins',
|
||||
"torch_cuda",
|
||||
"cublas",
|
||||
"cublasLt",
|
||||
"cudnn",
|
||||
"cusparse",
|
||||
"cufft",
|
||||
"cusolver",
|
||||
"cusolverMg",
|
||||
"curand",
|
||||
"nvrtc",
|
||||
"nvJitLink",
|
||||
"nccl",
|
||||
"nvperf",
|
||||
"nvrtc-builtins",
|
||||
)
|
||||
|
||||
|
||||
exclude_dlls = []
|
||||
if torch_lib.exists():
|
||||
for f in torch_lib.iterdir():
|
||||
if f.suffix == '.dll' and any(f.name.startswith(p) for p in cuda_prefixes):
|
||||
if f.suffix == ".dll" and any(f.name.startswith(p) for p in cuda_prefixes):
|
||||
exclude_dlls.append(f.name)
|
||||
|
||||
|
||||
if exclude_dlls:
|
||||
total_mb = sum(
|
||||
(torch_lib / dll).stat().st_size
|
||||
for dll in exclude_dlls
|
||||
if (torch_lib / dll).exists()
|
||||
) / 1024 / 1024
|
||||
total_mb = (
|
||||
sum((torch_lib / dll).stat().st_size for dll in exclude_dlls if (torch_lib / dll).exists()) / 1024 / 1024
|
||||
)
|
||||
print(f"CPU build: will exclude {len(exclude_dlls)} CUDA DLLs ({total_mb:.0f} MB)")
|
||||
|
||||
|
||||
return exclude_dlls
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Build voicebox-server binary")
|
||||
parser.add_argument(
|
||||
'--cuda',
|
||||
action='store_true',
|
||||
"--cuda",
|
||||
action="store_true",
|
||||
help="Build CUDA-enabled binary (voicebox-server-cuda)",
|
||||
)
|
||||
cli_args = parser.parse_args()
|
||||
|
||||
Reference in New Issue
Block a user