Files
live-app-translator/packaging/livesub.spec
EJClaw 03a2ca50c0 fix: PyInstaller 가 webrtcvad 훅에서 죽어 포터블 exe 가 안 나오던 문제
Windows 포터블 빌드가 dist\LiveSub.exe 를 만들지 못했다. 원인은
pyinstaller-hooks-contrib 의 hook-webrtcvad.py 가 copy_metadata('webrtcvad')
를 부르는 것이다. 우리가 설치하는 배포 이름은 webrtcvad-wheels 라
(원본 webrtcvad 는 Windows 휠이 없어 MSVC 컴파일이 필요하다)
PackageNotFoundError 가 나고, PyInstaller 는 훅 import 실패를
ImportErrorWhenRunningHook 으로 올려 빌드를 통째로 중단한다.

packaging/pyinstaller-hooks/hook-webrtcvad.py 로 같은 이름의 훅을 덮어써
실제 배포 이름으로 메타데이터를 수집한다. spec 의 hookspath 에 등록했으므로
build-portable.ps1, .gitea 워크플로, VM 자체 검증이 모두 같은 경로를 탄다.

리눅스에서 최소 재현으로 확인: 훅 없이는 같은 ImportErrorWhenRunningHook,
훅을 넣으면 "Processing standard module hook ... from packaging/pyinstaller-hooks"
로 우리 훅이 선택되고 빌드 성공, 만들어진 바이너리에서 webrtcvad.Vad(2) 생성까지 통과.
2026-09-25 23:38:34 +09:00

99 lines
3.9 KiB
Python

# -*- 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,
)