feat: 존댓말/반말 모드, 포터블 exe 빌드, AI 로고/배너

말투 (존댓말 기본, 반말 선택)
- 원문이 실제로 존댓말이면 반말 모드여도 존댓말을 지킨다. 상대가 정중하게
  말했는데 자막이 반말이면 뉘앙스가 뒤집히기 때문.
- 영어·중국어는 문법적 높임이 없으므로 항상 고른 모드를 따른다.
  "please" 를 존댓말 근거로 삼으면 오탐이 많아 쓰지 않았다.
- 낮춤 변환은 한글 자모를 분해해 실제 활용 규칙(모음조화 + 축약)을 구현했다.
  오+아->와, 지+어->져, 하+어->해, 았/었 뒤는 항상 어, 치겠습니다->칠게.
  어미를 나열하는 방식보다 훨씬 넓게 맞는다.
- 변환 방향은 존댓말->반말 한쪽만 한다. 번역 모델의 한국어 출력이 이미
  격식체라 존댓말 모드는 손댈 필요가 없고, 반대 방향은 훨씬 자주 틀린다.
- 지시문을 이해하는 Qwen3 에는 프롬프트로도 전달한다. Seed-X 는 지시문을
  못 알아듣는 모델이라 후처리로만 맞춘다.

포터블 exe
- PyInstaller 명세 + 빌드 스크립트. torch 를 의도적으로 제외했다.
  torch+CUDA 만 2.5GB 라 onefile 로 묶으면 실행할 때마다 그걸 임시폴더에
  푸느라 1분 넘게 걸려 쓸 수 없다.
- 음성인식(faster-whisper)도 번역(NLLB)도 CTranslate2 위에서 돌아 torch 가
  필요 없다. 덕분에 1~3티어는 그대로 다 되고 exe 는 3GB -> 500MB 가 된다.
- 4~5티어는 못 쓰므로 tier_availability() 로 판정해 모델 화면에 '사용 불가'와
  이유를 표시한다. torch 없는 환경에서 앱 전체가 뜨는 것을 확인했다.

AI 이미지
- SDXL-turbo 로 아이콘/배경 생성 (로컬 GPU, 피크 VRAM 1.9GB).
- 글자는 AI 가 제대로 못 쓰므로 아트만 AI 로 만들고 타이포그래피는
  정확히 렌더링해 합성했다. icon.ico 는 16~256px 멀티해상도.

검증: pytest 163개 통과 (말투 53개 신규), ruff clean

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
EJClaw
2026-09-23 01:06:23 +09:00
parent e4a048c8d5
commit 71436843d8
18 changed files with 525 additions and 7 deletions

View File

@@ -0,0 +1,51 @@
# 포터블 LiveSub.exe 빌드 (Windows 전용)
#
# powershell -ExecutionPolicy Bypass -File packaging\build-portable.ps1
#
# 결과: dist\LiveSub.exe — 설치 없이 이 파일 하나만 있으면 실행됩니다.
# 모델은 exe 에 없고 첫 실행 때 %APPDATA%\LiveSub\models 로 내려받습니다.
$ErrorActionPreference = "Stop"
$root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
Push-Location $root
try {
$venv = Join-Path $root ".venv-portable"
if (-not (Test-Path $venv)) {
Write-Host "포터블 전용 가상환경을 만듭니다 (torch 없이)..." -ForegroundColor Cyan
py -3.12 -m venv $venv
}
$py = Join-Path $venv "Scripts\python.exe"
& $py -m pip install --upgrade pip --quiet
& $py -m pip install -r packaging\requirements-portable.txt
# torch 가 섞여 들어가면 exe 가 3GB 로 불어난다. 빌드 전에 확인한다.
& $py -c "import importlib.util,sys; sys.exit(1 if importlib.util.find_spec('torch') else 0)"
if ($LASTEXITCODE -ne 0) {
Write-Error "이 환경에 torch 가 있습니다. 포터블 빌드는 torch 없는 별도 venv 에서 하세요."
}
# 프로세스별 캡처 보조 프로그램이 있으면 같이 담긴다 (없어도 빌드는 된다).
$capture = Join-Path $root "src\livesub\resources\bin\livesub_capture.exe"
if (-not (Test-Path $capture)) {
Write-Warning "livesub_capture.exe 가 없습니다. 포터블은 '출력 장치 전체' 캡처만 됩니다."
Write-Warning "프로그램별 캡처를 넣으려면 native\process_loopback\build.ps1 을 먼저 실행하세요."
}
Remove-Item -Recurse -Force build, dist -ErrorAction SilentlyContinue
& $py -m PyInstaller packaging\livesub.spec --noconfirm
$out = Join-Path $root "dist\LiveSub.exe"
if (Test-Path $out) {
$mb = [math]::Round((Get-Item $out).Length / 1MB, 1)
Write-Host ""
Write-Host "빌드 완료: $out ($mb MB)" -ForegroundColor Green
Write-Host "이 파일 하나만 복사하면 됩니다. 설치 필요 없습니다." -ForegroundColor Green
} else {
Write-Error "빌드는 끝났지만 dist\LiveSub.exe 를 찾지 못했습니다."
}
}
finally {
Pop-Location
}

89
packaging/livesub.spec Normal file
View File

@@ -0,0 +1,89 @@
# -*- 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티어는 그대로 다 동작한다. 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",
"huggingface_hub",
]
# 넣으면 용량만 키우고 쓰지 않는 것들. torch 계열은 의도적으로 제외한다.
excludes = [
"torch", "torchvision", "torchaudio", "transformers", "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,
hookspath=[],
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,
)

View File

@@ -0,0 +1,25 @@
# 포터블 exe 전용 의존성 — torch 를 넣지 않는다.
#
# 음성인식(faster-whisper)도 번역(NLLB)도 CTranslate2 위에서 돌고
# CTranslate2 는 torch 를 요구하지 않는다. 덕분에 exe 가 3GB -> 500MB 가
# 된다. 대신 4~5티어(Seed-X / Qwen3)는 빠지며 앱이 그렇게 표시한다.
#
# 4~5티어까지 쓰려면 포터블이 아니라 일반 설치(requirements.txt)를 쓰세요.
PySide6>=6.6
numpy>=1.24
faster-whisper>=1.0
ctranslate2>=4.4
tokenizers>=0.19
sentencepiece>=0.2
huggingface-hub>=0.24
# Windows 오디오 캡처
PyAudioWPatch>=0.2.12.7; sys_platform == "win32"
pycaw>=20240210; sys_platform == "win32"
comtypes>=1.4; sys_platform == "win32"
psutil>=5.9; sys_platform == "win32"
webrtcvad-wheels>=2.0.14
pyinstaller>=6.6