From 926910177de037e0c6ad891139daa4700aec61f4 Mon Sep 17 00:00:00 2001 From: Josh Knapp Date: Fri, 26 Dec 2025 11:01:43 -0800 Subject: [PATCH] Fix Windows build: Use collect_all for FastAPI packages - On Windows, PyInstaller wasn't properly bundling FastAPI dependencies - Added platform-specific collection using PyInstaller.utils.hooks.collect_all - Only applies aggressive collection on Windows to keep Linux builds stable - Collects all submodules and data files for: fastapi, starlette, pydantic, pydantic_core, anyio, uvicorn, websockets, h11 - Linux builds remain unchanged and continue to work as before Fixes: ModuleNotFoundError: No module named 'fastapi' on Windows executable --- local-transcription.spec | 145 +++++++++++++++++++++++---------------- 1 file changed, 84 insertions(+), 61 deletions(-) diff --git a/local-transcription.spec b/local-transcription.spec index 3f5d2b1..8749a1f 100644 --- a/local-transcription.spec +++ b/local-transcription.spec @@ -10,75 +10,98 @@ block_cipher = None # Determine if we're on Windows is_windows = sys.platform == 'win32' +# Import PyInstaller utilities (only needed on Windows for FastAPI fix) +if is_windows: + from PyInstaller.utils.hooks import collect_all + # Find faster_whisper assets folder import faster_whisper faster_whisper_path = os.path.dirname(faster_whisper.__file__) vad_assets_path = os.path.join(faster_whisper_path, 'assets') +# Base configuration +binaries = [] +datas = [ + ('config/default_config.yaml', 'config'), + (vad_assets_path, 'faster_whisper/assets'), # Include VAD model +] +hiddenimports = [ + 'PySide6.QtCore', + 'PySide6.QtWidgets', + 'PySide6.QtGui', + 'faster_whisper', + 'faster_whisper.transcribe', + 'faster_whisper.vad', + 'ctranslate2', + 'sounddevice', + 'noisereduce', + 'webrtcvad', + 'scipy', + 'scipy.signal', + 'numpy', + # FastAPI and dependencies + 'fastapi', + 'fastapi.routing', + 'fastapi.responses', + 'starlette', + 'starlette.applications', + 'starlette.routing', + 'starlette.responses', + 'starlette.websockets', + 'starlette.middleware', + 'starlette.middleware.cors', + 'pydantic', + 'pydantic.fields', + 'pydantic.main', + 'anyio', + 'anyio._backends', + 'anyio._backends._asyncio', + 'sniffio', + # Uvicorn and dependencies + 'uvicorn', + 'uvicorn.logging', + 'uvicorn.loops', + 'uvicorn.loops.auto', + 'uvicorn.protocols', + 'uvicorn.protocols.http', + 'uvicorn.protocols.http.auto', + 'uvicorn.protocols.http.h11_impl', + 'uvicorn.protocols.websockets', + 'uvicorn.protocols.websockets.auto', + 'uvicorn.protocols.websockets.wsproto_impl', + 'uvicorn.lifespan', + 'uvicorn.lifespan.on', + 'h11', + 'websockets', + 'websockets.legacy', + 'websockets.legacy.server', + # Requests (for server sync) + 'requests', + 'urllib3', + 'certifi', + 'charset_normalizer', +] + +# On Windows, use more aggressive collection for FastAPI packages +# This fixes import errors on Windows while keeping Linux builds stable +if is_windows: + print("Windows build: Using collect_all for FastAPI packages...") + for package in ['fastapi', 'starlette', 'pydantic', 'pydantic_core', 'anyio', 'uvicorn', 'websockets', 'h11']: + try: + tmp_datas, tmp_binaries, tmp_hiddenimports = collect_all(package) + datas += tmp_datas + binaries += tmp_binaries + hiddenimports += tmp_hiddenimports + print(f" ✓ Collected {package}") + except Exception as e: + print(f" ⚠ Warning: Could not collect {package}: {e}") + a = Analysis( ['main.py'], pathex=[], - binaries=[], - datas=[ - ('config/default_config.yaml', 'config'), - (vad_assets_path, 'faster_whisper/assets'), # Include VAD model - ], - hiddenimports=[ - 'PySide6.QtCore', - 'PySide6.QtWidgets', - 'PySide6.QtGui', - 'faster_whisper', - 'faster_whisper.transcribe', - 'faster_whisper.vad', - 'ctranslate2', - 'sounddevice', - 'noisereduce', - 'webrtcvad', - 'scipy', - 'scipy.signal', - 'numpy', - # FastAPI and dependencies - 'fastapi', - 'fastapi.routing', - 'fastapi.responses', - 'starlette', - 'starlette.applications', - 'starlette.routing', - 'starlette.responses', - 'starlette.websockets', - 'starlette.middleware', - 'starlette.middleware.cors', - 'pydantic', - 'pydantic.fields', - 'pydantic.main', - 'anyio', - 'anyio._backends', - 'anyio._backends._asyncio', - 'sniffio', - # Uvicorn and dependencies - 'uvicorn', - 'uvicorn.logging', - 'uvicorn.loops', - 'uvicorn.loops.auto', - 'uvicorn.protocols', - 'uvicorn.protocols.http', - 'uvicorn.protocols.http.auto', - 'uvicorn.protocols.http.h11_impl', - 'uvicorn.protocols.websockets', - 'uvicorn.protocols.websockets.auto', - 'uvicorn.protocols.websockets.wsproto_impl', - 'uvicorn.lifespan', - 'uvicorn.lifespan.on', - 'h11', - 'websockets', - 'websockets.legacy', - 'websockets.legacy.server', - # Requests (for server sync) - 'requests', - 'urllib3', - 'certifi', - 'charset_normalizer', - ], + binaries=binaries, + datas=datas, + hiddenimports=hiddenimports, hookspath=[], hooksconfig={}, runtime_hooks=[],