# -*- mode: python ; coding: utf-8 -*- """포터블 LiveSub 빌드 명세 (PyInstaller). 설치 없이 exe 하나만 두고 실행하는 것이 목표다. 다만 그냥 전부 넣으면 안 된다. torch+CUDA 만 2.5GB 라 --onefile 로 묶으면 **실행할 때마다** 그 2.5GB 를 임시폴더에 풀어야 해서 시작에 1분 넘게 걸린다. 그래서 포터블 빌드는 torch 를 뺀다. 음성인식(faster-whisper)과 번역 (NLLB) 모두 CTranslate2 위에서 돌아가고 CTranslate2 는 torch 를 요구하지 않기 때문에, 1~3티어는 그대로 다 동작한다. 다만 NLLB 토크나이저는 transformers 의 AutoTokenizer 를 쓰므로 transformers 자체는 넣어야 한다. transformers 는 torch 가 없으면 "토크나이저/설정만 사용 가능" 모드로 뜨며, 우리가 쓰는 용도가 정확히 그것이다 (실측 확인). 4~5티어(Seed-X / Qwen3)만 빠지며, 앱이 '모델' 화면에서 그 사실을 표시한다. 빌드: pip install -r packaging/requirements-portable.txt pyinstaller pyinstaller packaging/livesub.spec --noconfirm 결과: dist/LiveSub.exe (약 400~600MB, 첫 실행 20~40초 / 이후 10초 내외) 모델은 exe 에 넣지 않는다. 처음 쓸 때 %APPDATA%\\LiveSub\\models 로 내려받으므로 exe 를 USB 로 옮겨도 모델은 그 PC 에 남는다. """ from pathlib import Path from PyInstaller.utils.hooks import collect_dynamic_libs BLOCK_CIPHER = None ROOT = Path(SPECPATH).parent # noqa: F821 - PyInstaller 가 주입 SRC = ROOT / "src" # 게임 용어집 JSON 과 (빌드돼 있다면) 프로세스 캡처 보조 프로그램을 함께 담는다. datas = [(str(SRC / "livesub" / "resources" / "glossaries"), "livesub/resources/glossaries")] capture_exe = SRC / "livesub" / "resources" / "bin" / "livesub_capture.exe" if capture_exe.is_file(): datas.append((str(capture_exe), "livesub/resources/bin")) # CTranslate2 는 CUDA/cuDNN DLL 을 곁에 두고 로드한다. 자동 수집에서 빠지기 쉽다. binaries = collect_dynamic_libs("ctranslate2") hiddenimports = [ "livesub.audio.process_loopback", "livesub.audio.wasapi_loopback", "ctranslate2", "faster_whisper", "tokenizers", "sentencepiece", "transformers", "transformers.models.nllb", "huggingface_hub", ] # 넣으면 용량만 키우고 쓰지 않는 것들. torch 계열은 의도적으로 제외한다. excludes = [ # transformers 는 제외하지 않는다. NLLB 토크나이저(AutoTokenizer)가 필요하고, # torch 없이 토크나이저 전용으로 동작하는 것을 실제로 확인했다. "torch", "torchvision", "torchaudio", "peft", "accelerate", "datasets", "bitsandbytes", "autoawq", "matplotlib", "scipy", "pandas", "IPython", "notebook", "tkinter", "PySide6.QtWebEngineCore", "PySide6.Qt3DCore", "PySide6.QtCharts", "PySide6.QtDataVisualization", "PySide6.QtMultimedia", "PySide6.QtQuick", "PySide6.QtQml", "PySide6.QtPdf", "PySide6.QtDesigner", ] a = Analysis( # noqa: F821 [str(SRC / "livesub" / "__main__.py")], pathex=[str(SRC)], binaries=binaries, datas=datas, hiddenimports=hiddenimports, # contrib 훅이 빌드를 중단시키는 것을 덮어쓴다 (hook-webrtcvad.py 주석 참고). hookspath=[str(ROOT / "packaging" / "pyinstaller-hooks")], runtime_hooks=[], excludes=excludes, noarchive=False, ) pyz = PYZ(a.pure, a.zipped_data, cipher=BLOCK_CIPHER) # noqa: F821 exe = EXE( # noqa: F821 pyz, a.scripts, a.binaries, a.datas, [], name="LiveSub", debug=False, bootloader_ignore_signals=False, strip=False, upx=False, # UPX 로 압축하면 백신이 오탐하는 일이 잦다 runtime_tmpdir=None, console=False, # 콘솔 창 없이 GUI 로만 icon=str(ROOT / "src" / "livesub" / "resources" / "icon.ico"), version=None, )