# engine.spec — PyInstaller config cho daw_engine (sidecar Python) # Chay: pyinstaller engine.spec --clean --noconfirm (Windows/Linux/macOS) # -*- mode: python ; coding: utf-8 -*- import os from PyInstaller.utils.hooks import collect_dynamic_libs, collect_data_files # Native DLL cho pedalboard va soundfile (Windows .pyd/.dll, Linux .so) binaries = collect_dynamic_libs('pedalboard') binaries += collect_dynamic_libs('soundfile') # Root tuyet doi cua thu muc chua spec — dung cho cac datas KHONG nam trong # package 'app' (vd thu muc md/). Cac assets cua app (static/templates/models) # duoc bundle qua collect_data_files('app'). _SPEC_ROOT = os.path.abspath(SPECPATH) # ⚠️ GOC ROOT CUA MOI LOI 'app\\static does not exist' (gap 3 lan): # lenh `pyinstaller engine.spec` (entry-point script) KHONG them CWD vao # sys.path (chi `python -m PyInstaller` moi them). collect_data_files('app') # import package qua sys.path -> khong thay 'app' -> tra ve [] AM THAM -> # bundle thieu static/templates -> exe crash luc chay. Fix: chen _SPEC_ROOT # vao sys.path TRUOC khi collect de import 'app' luon hoạt dong. import sys as _sys if _SPEC_ROOT not in _sys.path: _sys.path.insert(0, _SPEC_ROOT) # Assets cua app: bundle QUA IMPORT SYSTEM (collect_data_files) — an toan nhat. # Loai tru storage (57MB soundfonts/uploads — vo ich, config.py da chuyen # storage sang %APPDATA%\\SonicForgeDAW khi frozen) va __pycache__. datas = collect_data_files('app', excludes=['**/storage/**', '**/__pycache__/**', '**/*.pyc']) # Fallback cuoi cung: neu collect_data_files van tra ve rong (phong moi truong # hop ky la), dung datas TINH absolute — tinh huong xau nhat van co du assets. if not datas: print("WARN: collect_data_files('app') tra ve rong - dung datas tinh absolute") datas = [ (os.path.join(_SPEC_ROOT, 'app', 'templates'), 'app/templates'), # index.html, favicon.svg (os.path.join(_SPEC_ROOT, 'app', 'static'), 'app/static'), # js/css/processors (os.path.join(_SPEC_ROOT, 'app', 'models'), 'app/models'), # project_schema.json (projects.py) ] datas += [ (os.path.join(_SPEC_ROOT, 'md'), 'md'), # /ai-prompt-generator (doc, ngoai package app) ] # ══════════════════════════════════════════════════════════════════════════ # TOI UU BUNDLE (SonicForgeStudio 1.1): 409MB -> ~120MB # ────────────────────────────────────────────────────────────────────────── # 1. librosa/numba/llvmlite (~171MB) + scikit-learn (~17MB) da DUOC LOAI BO # khoi code (app/core/audio_features.py thay the, numpy/scipy/soundfile). # 2. celery/kombu/billiard/redis (~40MB) KHONG bundle — desktop chay task # eager dong bo, khong can broker (app/api/v1/tasks.py da lazy + fallback). # 3. scipy: KHONG con quet toan bo site-packages/scipy (truoc day bundle ca # scipy.stats/sparse/optimize/linalg ~48MB). Chi quet scipy.signal — goi # lazy-import noi bo cua no van duoc bat day du (scipy.signal.windows, # _savitzky_golay, _spectral_py... duoc import bang ten ben trong ham). # scipy.signal la goi DUY NHAT con duoc app dung (sub_tab_dsp, # render_engine, audio_features). # ══════════════════════════════════════════════════════════════════════════ import importlib.util as _ilu import glob as _glob def _scan_pkg_modules(pkg_name: str): """Scan filesystem cua 1 package con (khong import, khong walk) -> bat moi module .py/.pyd/.so -> hiddenimports day du, tranh lazy-import miss.""" _spec = _ilu.find_spec(pkg_name) if _spec is None or _spec.origin is None: print(f"WARN: khong tim thay package '{pkg_name}' - bo qua scan") return [] _pkg_dir = os.path.dirname(os.path.abspath(_spec.origin)) _out = [] for _ext in ('*.py', '*.pyd', '*.so'): for _f in _glob.glob(os.path.join(_pkg_dir, '**', _ext), recursive=True): _rel = os.path.relpath(_f, _pkg_dir) _base = os.path.basename(_rel).split('.')[0] # bo .cpython-312-x86_64... .so _sub = os.path.dirname(_rel).replace(os.sep, '.') _mod = (pkg_name + '.' + _sub + '.' + _base) if _sub else (pkg_name + '.' + _base) if _mod not in _out: _out.append(_mod) return _out _scipy_signal_hidden = _scan_pkg_modules('scipy.signal') # Uvicorn lazy-load loop/protocol theo ten (string) -> hiddenimport tinh. _hidden = [ 'uvicorn.logging', 'uvicorn.loops', 'uvicorn.loops.auto', 'uvicorn.protocols', 'uvicorn.protocols.http', 'uvicorn.protocols.http.auto', 'uvicorn.protocols.websockets', 'uvicorn.protocols.websockets.auto', 'pedalboard', 'soundfile', 'sf2utils', 'mido.backends.rtmidi', ] + _scipy_signal_hidden # Khoa khong bundle: loai toan bo cay nang khong con duoc dung. _excludes = [ 'tkinter', # libs da thay the (audio_features.py) 'librosa', 'numba', 'llvmlite', 'sklearn', 'scikit-learn', 'joblib', 'threadpoolctl', 'audioread', 'lazy_loader', 'soxr', # celery/redis chi dung cho server (Docker), khong cho desktop 'celery', 'kombu', 'billiard', 'vine', 'amqp', 'redis', 'click_didyoumean', 'click_plugins', 'click_repl', # LUU Y: KHONG exclude 'click' — uvicorn.main import click (CLI parser)! 'dateutil', 'pytz', 'tzdata', 'msgpack', 'yaml', # khong dung trong desktop 'matplotlib', 'pandas', 'IPython', 'jupyter', 'pytest', 'setuptools', # keo vao nham boi hooks_contrib (app khong import bao gio) 'PIL', 'Pillow', 'cairosvg', 'zstandard', 'imageio', ] a = Analysis( ['desktop_engine.py'], pathex=[_SPEC_ROOT], binaries=binaries, datas=datas, hiddenimports=_hidden, hookspath=[], hooksconfig={}, runtime_hooks=[], excludes=_excludes, win_no_prefer_redirects=False, win_private_assemblies=False, cipher=None, noarchive=False, ) pyz = PYZ(a.pure, a.zipped_data, cipher=None) exe = EXE( pyz, a.scripts, [], exclude_binaries=True, name='daw_engine', debug=False, bootloader_ignore_signals=False, strip=False, upx=True, upx_exclude=[], runtime_tmpdir=None, console=False, # True khi debug (xem log truc tiep), False cho production # Icon exe = favicon cua app (app/templates/favicon.svg -> render PNG -> ICO, # sinh boi tools/gen_favicon_ico.py). Cung nguon voi icon hien thi trong app. icon='src-tauri/icons/favicon.ico', ) # ONEDIR (khong phai onefile): exe 700MB+ onefile phai giai nen toan bo vao # %TEMP% moi lan chay -> load RAT CHAM tren Windows. Onedir chay truc tiep tu # thu muc (bundle qua Tauri resources), khoi dong gan nhu tuc thi. coll = COLLECT( exe, a.binaries, a.zipfiles, a.datas, name='daw_engine', )