""" scripts/build-sidecar.py faster-whisper sidecar를 PyInstaller로 빌드한다. 사용법: pip install pyinstaller python scripts/build-sidecar.py 출력: sidecar-dist/sidecar.exe (단일 디렉토리 모드) """ import subprocess import sys import shutil from pathlib import Path PROJECT_ROOT = Path(__file__).parent.parent SIDECAR_DIR = PROJECT_ROOT / "sidecar" MAIN_PY = SIDECAR_DIR / "main.py" OUTPUT_DIR = PROJECT_ROOT / "sidecar-dist" def check_prerequisites(): """필수 도구가 설치되어 있는지 확인한다.""" # PyInstaller try: import PyInstaller # noqa: F401 except ImportError: print("PyInstaller가 설치되어 있지 않습니다.") print("실행: pip install pyinstaller") sys.exit(1) # faster-whisper try: import faster_whisper # noqa: F401 except ImportError: print("faster-whisper가 설치되어 있지 않습니다.") print("실행: pip install -r sidecar/requirements.txt") sys.exit(1) if not MAIN_PY.exists(): print(f"sidecar 소스를 찾을 수 없습니다: {MAIN_PY}") sys.exit(1) def build(): """PyInstaller로 sidecar를 빌드한다.""" print("=" * 60) print("D3RO-VOICE Sidecar 빌드 시작") print("=" * 60) # 기존 빌드 정리 if OUTPUT_DIR.exists(): shutil.rmtree(OUTPUT_DIR) # PyInstaller 실행 — onedir 모드 (onefile보다 시작이 빠름) cmd = [ sys.executable, "-m", "PyInstaller", "--name", "sidecar", "--distpath", str(OUTPUT_DIR), "--workpath", str(PROJECT_ROOT / "build" / "sidecar-build"), "--specpath", str(PROJECT_ROOT / "build"), # onedir 모드 (단일 디렉토리) "--noconfirm", "--clean", # hidden imports (PyInstaller가 자동 감지 못하는 것) "--hidden-import", "faster_whisper", "--hidden-import", "ctranslate2", "--hidden-import", "huggingface_hub", "--hidden-import", "tokenizers", "--hidden-import", "uvicorn.logging", "--hidden-import", "uvicorn.protocols.http", "--hidden-import", "uvicorn.protocols.http.auto", "--hidden-import", "uvicorn.protocols.http.h11_impl", "--hidden-import", "uvicorn.protocols.websockets", "--hidden-import", "uvicorn.protocols.websockets.auto", "--hidden-import", "uvicorn.lifespan", "--hidden-import", "uvicorn.lifespan.on", "--hidden-import", "uvicorn.lifespan.off", # 콘솔 없음 (Windows) "--noconsole", str(MAIN_PY), ] print(f"\n실행 명령:\n {' '.join(cmd)}\n") result = subprocess.run(cmd, cwd=str(PROJECT_ROOT)) if result.returncode != 0: print(f"\nPyInstaller 빌드 실패 (exit code: {result.returncode})") sys.exit(1) # 빌드 결과 확인 exe_path = OUTPUT_DIR / "sidecar" / "sidecar.exe" if exe_path.exists(): size_mb = exe_path.stat().st_size / (1024 * 1024) print(f"\n빌드 성공!") print(f" 경로: {exe_path}") print(f" 크기: {size_mb:.1f} MB") # 전체 디렉토리 크기 total_size = sum(f.stat().st_size for f in (OUTPUT_DIR / "sidecar").rglob("*") if f.is_file()) total_mb = total_size / (1024 * 1024) print(f" 전체 디렉토리: {total_mb:.1f} MB") else: print(f"\n빌드 출력을 찾을 수 없습니다: {exe_path}") # onedir 모드에서는 디렉토리 내부에 exe가 있음 for exe in OUTPUT_DIR.rglob("*.exe"): print(f" 발견: {exe}") print("\n빌드 완료!") print(f"electron-builder에서 이 경로를 extraResources로 지정하세요:") print(f" from: sidecar-dist/sidecar/") print(f" to: sidecar/") if __name__ == "__main__": check_prerequisites() build()