Spaces:
Sleeping
Sleeping
| # -*- 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', | |
| ) | |