Files
local-transcription/local-transcription.spec
jknapp 9b7f2e1d69 Fix enum34 error by excluding it in PyInstaller spec
The previous approach of uninstalling enum34 before PyInstaller didn't
work because 'uv run' re-syncs dependencies. The proper solution is to
exclude enum34 directly in the PyInstaller spec file.

Changes:
- Added hooks/hook-enum34.py: Custom PyInstaller hook to exclude enum34
- Updated local-transcription.spec:
  - Added 'hooks' to hookspath
  - Added 'enum34' to excludes list
- Updated build.sh and build.bat:
  - Removed enum34 uninstall step (no longer needed)
  - Added comment explaining enum34 is excluded in spec

Why this works:
- PyInstaller's excludes list prevents enum34 from being bundled
- The custom hook provides documentation and explicit exclusion
- enum34 can remain installed in venv (won't break anything)
- Works regardless of 'uv run' re-syncing dependencies

enum34 is an obsolete Python 2.7/3.3 backport that's incompatible with
PyInstaller and unnecessary on Python 3.4+ (enum is in stdlib).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-28 19:32:23 -08:00

173 lines
4.7 KiB
Python

# -*- mode: python ; coding: utf-8 -*-
"""PyInstaller spec file for Local Transcription app."""
import sys
from pathlib import Path
import os
block_cipher = None
# Determine if we're on Windows
is_windows = sys.platform == 'win32'
# Import PyInstaller utilities
from PyInstaller.utils.hooks import collect_submodules, collect_data_files
# 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',
'scipy',
'scipy.signal',
'numpy',
# RealtimeSTT and its dependencies
'RealtimeSTT',
'RealtimeSTT.audio_recorder',
'webrtcvad',
'webrtcvad_wheels',
'silero_vad',
'torch',
'torch.nn',
'torch.nn.functional',
'torchaudio',
'onnxruntime',
'onnxruntime.capi',
'onnxruntime.capi.onnxruntime_pybind11_state',
'pyaudio',
'halo', # RealtimeSTT progress indicator
'colorama', # Terminal colors (used by halo)
# 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',
]
# Collect all submodules for FastAPI and related packages
# This approach is more reliable than collect_all() which has design flaws
# Particularly important for pydantic which uses compiled cpython extensions
print("Collecting submodules for FastAPI packages...")
for package in ['fastapi', 'starlette', 'pydantic', 'pydantic_core', 'anyio', 'uvicorn', 'websockets', 'h11', 'httptools', 'uvloop']:
try:
submodules = collect_submodules(package)
hiddenimports += submodules
print(f" ✓ Collected {len(submodules)} submodules from {package}")
except Exception as e:
print(f" ⚠ Warning: Could not collect {package}: {e}")
# Collect data files for packages that need them
for package in ['fastapi', 'starlette', 'pydantic', 'uvicorn']:
try:
data_files = collect_data_files(package)
if data_files:
datas += data_files
print(f" ✓ Collected {len(data_files)} data files from {package}")
except Exception as e:
pass # Not all packages have data files
# Add critical pydantic dependencies that may be missed
hiddenimports += [
'colorsys', 'decimal', 'json', 'ipaddress', 'pathlib', 'uuid',
'email.message', 'typing_extensions',
]
a = Analysis(
['main.py'],
pathex=[],
binaries=binaries,
datas=datas,
hiddenimports=hiddenimports,
hookspath=['hooks'], # Add hooks directory for custom PyInstaller hooks
hooksconfig={},
runtime_hooks=[],
excludes=['enum34'], # Exclude enum34 - incompatible with PyInstaller and Python 3.4+
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='LocalTranscription',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=False, # Hide console window for GUI application
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon='LocalTranscription.ico' if is_windows else 'LocalTranscription.icns', # Platform-specific icon
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='LocalTranscription',
)