OmniVoice-Studio / backend.spec
LΓͺ Phi Nam
Deploy to HF Space
94004a2
Raw
History Blame Contribute Delete
9.64 kB
# -*- mode: python ; coding: utf-8 -*-
# PyInstaller spec for OmniVoice Studio backend.
#
# Produces a one-folder bundle at dist/omnivoice-backend/ that Tauri launches
# as a sidecar binary. Kept intentionally permissive with collect_all(...)
# on the heavy ML deps because PyInstaller's static analysis misses their
# runtime-imported submodules, C extensions, and data files.
#
# Cross-platform: targets mac-ARM, mac-Intel, Linux x64, Windows x64. mlx
# deps are gated on mac-ARM (sys.platform=='darwin' + machine=='arm64') so
# PyInstaller on other hosts doesn't blow up trying to find mlx wheels.
#
# Run: uv run pyinstaller backend.spec --noconfirm --clean
import platform
import sys
from PyInstaller.utils.hooks import collect_data_files, collect_all, collect_submodules
IS_MAC_ARM = sys.platform == "darwin" and platform.machine() == "arm64"
datas = []
binaries = []
hiddenimports = [
# Web stack
'uvicorn', 'uvicorn.logging', 'uvicorn.loops', 'uvicorn.loops.auto',
'uvicorn.protocols', 'uvicorn.protocols.http', 'uvicorn.protocols.http.auto',
'uvicorn.protocols.websockets', 'uvicorn.protocols.websockets.auto',
'uvicorn.lifespan', 'uvicorn.lifespan.on',
'fastapi', 'fastapi.responses', 'starlette',
'multipart',
# Core
'uuid', 'asyncio',
# Audio / ML
'torch', 'torchaudio', 'soundfile', 'scipy', 'numpy',
'numpy.random._pickle',
# Cross-platform primary ASR β€” WhisperX (faster-whisper + wav2vec2
# alignment) is the default on every platform. faster-whisper is the
# transcription engine; WhisperX adds forced alignment for Β±10-30 ms
# word timing, which directly improves dub lip-sync. Both backends are
# registered in asr_backend.py; the user can switch via Settings.
'whisperx', 'whisperx.alignment', 'whisperx.asr', 'whisperx.diarize',
'whisperx.vad', 'whisperx.audio', 'whisperx.utils',
'faster_whisper', 'faster_whisper.transcribe', 'faster_whisper.audio',
'faster_whisper.utils', 'faster_whisper.tokenizer', 'faster_whisper.vad',
'ctranslate2',
# Lightweight English TTS tier β€” ONNX-based, cross-platform. The ONNX
# Runtime wheels ship platform-specific .so/.dll/.dylib which collect_all
# picks up; the kittentts Python package is pure Python but has a couple
# of asset files the bundler needs to include.
'kittentts', 'onnxruntime',
# Pipeline
'yt_dlp', 'demucs', 'demucs.separate',
# OmniVoice's own package
'omnivoice', 'omnivoice.models', 'omnivoice.models.omnivoice',
]
if IS_MAC_ARM:
# MLX Whisper on Apple Silicon (optional speedup path). mlx's pure-Python
# submodules (nn, utils, …) are imported lazily by mlx_whisper at
# transcribe time and the plain dep tracer misses them. We deliberately
# do NOT collect_all() mlx because that double-registers mlx.core with
# nanobind and the binary aborts on the first mlx.core touch.
hiddenimports.append('mlx_whisper')
# mlx-audio engine multiplexer β€” Kokoro / CSM / Dia / Qwen3-TTS /
# Chatterbox / MeloTTS / OuteTTS / … β€” gives mac-ARM users a rich
# engine picker. Like mlx_whisper it's mac-ARM-only; also like
# mlx_whisper we list it here but avoid collect_all() because it
# depends on the same nanobind-registered mlx.core.
hiddenimports += [
'mlx_audio', 'mlx_audio.tts', 'mlx_audio.tts.utils',
'mlx_audio.tts.models', 'mlx_audio.tts.generate',
'mlx_audio.stt', 'mlx_audio.codec',
]
# Note: we deliberately DON'T enumerate mlx submodules here. Any variant of
# `collect_submodules('mlx')` or `collect_all('mlx')` β€” even filtered to
# exclude mlx.core β€” reliably re-triggers the nanobind duplicate-key error
# the first time anything imports mlx.core ("refusing to add duplicate key
# 'cpu' to enumeration mlx.core.DeviceType"). Shipping without mlx in the
# frozen bundle leaves mlx-whisper unavailable; asr_backend falls back to
# pytorch-whisper (slower but functional on Apple Silicon). Revisit once
# we have a minimal repro or a PyInstaller hook specifically for mlx.
# The nuclear option on heavy ML libs β€” pull every submodule, C ext, and
# data file. Cost: bigger bundle. Benefit: we don't ship a binary that
# ImportErrors the first time a user hits a code path.
# Note: 'mlx' is intentionally NOT in this list. Calling collect_all('mlx')
# alongside collect_all('mlx_whisper') causes the nanobind binding init to
# run twice in the frozen bundle, crashing with
# "Critical nanobind error: refusing to add duplicate key 'cpu'
# to enumeration 'mlx.core.DeviceType'!"
# the first time anything imports mlx.core. mlx_whisper already depends on
# mlx and PyInstaller's dep tracer pulls the needed mlx submodules + the .so.
_collect_pkgs = [
'torch', 'torchaudio', 'soundfile', 'scipy', 'numpy',
'omnivoice', 'demucs', 'yt_dlp', 'fastapi', 'uvicorn',
# Primary cross-platform ASR. collect_all pulls CTranslate2's bundled
# .so/.dylib/.dll plus its compiled kernel data. WhisperX ships its own
# pure-Python code + some asset files (e.g. language metadata).
'whisperx', 'faster_whisper', 'ctranslate2',
# ONNX-based lightweight TTS. onnxruntime's collect_all pulls the
# platform-appropriate .so/.dll/.dylib + CUDA providers when present.
'kittentts', 'onnxruntime',
]
if IS_MAC_ARM:
# Only attempt mlx_whisper collection on mac-ARM β€” no wheels exist for
# Linux/Windows/mac-Intel, so collect_all would fail on CI for those.
_collect_pkgs.append('mlx_whisper')
for pkg in _collect_pkgs:
try:
tmp_datas, tmp_binaries, tmp_hidden = collect_all(pkg)
datas += tmp_datas
binaries += tmp_binaries
hiddenimports += tmp_hidden
except Exception as e: # noqa: BLE001
print(f"[backend.spec] collect_all({pkg!r}) skipped: {e}")
# Include the backend's own modules as data so imports like
# `api.routers.dub_generate` resolve inside the frozen bundle.
datas += [
('backend/api', 'api'),
('backend/core', 'core'),
('backend/services', 'services'),
('backend/schemas', 'schemas'),
('backend/migrations', 'migrations'),
]
a = Analysis(
['backend/main.py'],
pathex=['backend', '.'],
binaries=binaries,
datas=datas,
hiddenimports=hiddenimports,
hookspath=[],
hooksconfig={},
runtime_hooks=[
'backend/hooks/pyi_rth_numpy_compat.py',
'backend/hooks/pyi_rth_torch_compiler_disable.py',
],
excludes=[
# Desktop-only bloat the frozen backend never uses.
'tkinter', 'matplotlib', 'PIL.ImageQt', 'PyQt5', 'PyQt6',
# CUDA / NVIDIA wheels on every platform β€” we ship CPU-only inference
# for the desktop app. Models download on first run via HF cache, and
# GPU use is surfaced only when a user-installed driver is detected
# at runtime. Excluding these saves ~2 GB per bundle, which is what
# keeps Linux .deb / Windows MSI under GH Releases' 2 GB asset cap.
'nvidia', 'nvidia.cublas', 'nvidia.cudnn', 'nvidia.cuda_runtime',
'nvidia.cuda_nvrtc', 'nvidia.nccl', 'nvidia.nvtx',
'nvidia.curand', 'nvidia.cusolver', 'nvidia.cusparse',
'nvidia.cufft', 'nvidia.cuda_cupti', 'nvidia.cusparselt',
'nvidia.nvjitlink', 'nvidia.cufile',
'triton', 'flash_attn',
# Torch internals we never invoke at inference time β€” distributed
# training, compile, FX tracing, tensorboard, testing helpers. These
# pull hundreds of MB of Python source + transitive deps.
'torch.distributed', 'torch._dynamo', 'torch._inductor',
'torch._export', 'torch.testing', 'torch.utils.tensorboard',
'torch.utils.benchmark', 'torch.fx.experimental',
'torch._functorch', 'torch.ao', 'torch.onnx',
# torchaudio prototype / deprecated β€” nothing in the backend touches
# these; removing saves tens of MB and silences the deprecation log
# noise on startup.
'torchaudio.prototype', 'torchaudio.models.hifigan',
# Heavy optional deps that are in pyproject.toml but the Studio
# backend never imports (verified with `grep ^import`). Excluding
# keeps them out of the frozen bundle; nothing on the runtime path
# breaks.
'gradio', 'gradio_client', 'tensorboardX', 'webdataset',
's3prl', 'funasr', 'pedalboard',
# Test / example trees that get swept up by collect_all.
'scipy.special.tests', 'scipy.tests', 'numpy.f2py.tests',
'numpy.tests', 'numpy.testing.tests',
],
noarchive=False,
# optimize=2 compiles the embedded stdlib + site-packages with -OO,
# stripping assert statements + docstrings. Saves ~50-80 MB on a bundle
# this size. Runtime impact is negligible because we never inspect
# docstrings at runtime.
optimize=2,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='omnivoice-backend',
debug=False,
bootloader_ignore_signals=False,
# strip=True removes debug symbols from ELF/Mach-O binaries (no-op on
# Windows since MSVC doesn't emit symbols in the same way). Saves
# 10-30% on native libraries like libtorch_cpu.so (~300 MB β†’ ~220 MB).
strip=True,
upx=False, # UPX often corrupts ML native libs β€” disabled.
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
coll = COLLECT(
exe,
a.binaries,
a.datas,
strip=True,
upx=False,
upx_exclude=[],
name='omnivoice-backend',
)