Compare commits
2 Commits
4af97a68f2
...
8d91087f85
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d91087f85 | ||
|
|
bef9eaf19b |
@@ -1,6 +1,6 @@
|
|||||||
# Windows 검증 — Gitea Actions
|
# Windows 검증 — Gitea Actions
|
||||||
#
|
#
|
||||||
# 이 저장소에 push 하면 Windows 러너가 자동으로
|
# main 에 push 하거나 Actions 탭에서 수동 실행하면 Windows 러너가 자동으로
|
||||||
# 1) 테스트를 돌리고
|
# 1) 테스트를 돌리고
|
||||||
# 2) 포터블 exe 를 빌드하고
|
# 2) 포터블 exe 를 빌드하고
|
||||||
# 3) 앱이 실제로 뜨는지 확인한 뒤
|
# 3) 앱이 실제로 뜨는지 확인한 뒤
|
||||||
@@ -25,15 +25,21 @@ jobs:
|
|||||||
# Gitea 는 runs-on 이 어떤 러너 라벨과도 안 맞으면 작업을 실패시키지 않고
|
# Gitea 는 runs-on 이 어떤 러너 라벨과도 안 맞으면 작업을 실패시키지 않고
|
||||||
# 기본 ubuntu 컨테이너에서 돌린다. 라벨을 잘못 등록하면 Windows 검증이
|
# 기본 ubuntu 컨테이너에서 돌린다. 라벨을 잘못 등록하면 Windows 검증이
|
||||||
# 조용히 리눅스에서 돌아가 "통과했다"는 착각을 준다. 그래서 먼저 막는다.
|
# 조용히 리눅스에서 돌아가 "통과했다"는 착각을 준다. 그래서 먼저 막는다.
|
||||||
|
#
|
||||||
|
# bash 가 아니라 pwsh 를 쓴다. Windows 에 Git Bash 가 깔려 있어도
|
||||||
|
# bash.exe 는 기본 PATH 에 없어서 (Git\cmd 만 등록된다) 첫 단계부터
|
||||||
|
# "bash not found" 로 죽는다. 라벨을 잘못 걸어 리눅스 컨테이너로
|
||||||
|
# 떨어진 경우에는 pwsh 가 없어서 역시 실패하므로, 어느 쪽이든
|
||||||
|
# 조용히 통과하는 일은 없다.
|
||||||
- name: Windows 에서 돌고 있는지 확인
|
- name: Windows 에서 돌고 있는지 확인
|
||||||
shell: bash
|
shell: pwsh
|
||||||
run: |
|
run: |
|
||||||
echo "RUNNER_OS=$RUNNER_OS"
|
Write-Host "RUNNER_OS=$env:RUNNER_OS"
|
||||||
if [ "$RUNNER_OS" != "Windows" ]; then
|
if ($env:RUNNER_OS -ne "Windows") {
|
||||||
echo "::error::Windows 러너가 아닙니다 ($RUNNER_OS)."
|
Write-Host "::error::Windows 러너가 아닙니다 ($env:RUNNER_OS)."
|
||||||
echo "::error::러너를 'windows:host' 라벨로 등록했는지 확인하세요."
|
Write-Host "::error::러너를 'windows:host' 라벨로 등록했는지 확인하세요."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
}
|
||||||
|
|
||||||
- name: 소스 받기
|
- name: 소스 받기
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -69,6 +75,16 @@ jobs:
|
|||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: .\.venv-ci\Scripts\python packaging\windows_smoke.py
|
run: .\.venv-ci\Scripts\python packaging\windows_smoke.py
|
||||||
|
|
||||||
|
# 부품이 다 통과해도 이어 붙이면 안 되는 경우가 있다. 실제 음성 파일을
|
||||||
|
# 넣어 한국어 자막이 나오는지 끝까지 확인한다. CPU 로 돌므로 GPU 없는
|
||||||
|
# 서버 VM 에서도 그대로 된다. 모델은 처음 한 번만 받고 이후 캐시를 쓴다.
|
||||||
|
- name: 전 파이프라인 (오디오 -> 자막)
|
||||||
|
shell: pwsh
|
||||||
|
timeout-minutes: 30
|
||||||
|
env:
|
||||||
|
PYTHONPATH: src
|
||||||
|
run: .\.venv-ci\Scripts\python packaging\windows_smoke.py --pipeline
|
||||||
|
|
||||||
- name: 프로그램별 캡처 보조 프로그램 빌드
|
- name: 프로그램별 캡처 보조 프로그램 빌드
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
continue-on-error: true # VS Build Tools 가 없는 러너도 있을 수 있다
|
continue-on-error: true # VS Build Tools 가 없는 러너도 있을 수 있다
|
||||||
|
|||||||
@@ -4,7 +4,64 @@
|
|||||||
오디오 캡처, 전역 단축키, 모니터 열거, exe 빌드가 전부 여기에 해당합니다.
|
오디오 캡처, 전역 단축키, 모니터 열거, exe 빌드가 전부 여기에 해당합니다.
|
||||||
그래서 "리눅스에서 다 통과했는데 윈도우에서 안 되더라"가 나올 수 있습니다.
|
그래서 "리눅스에서 다 통과했는데 윈도우에서 안 되더라"가 나올 수 있습니다.
|
||||||
|
|
||||||
Windows 러너를 하나 붙이면 push할 때마다 이 경로들이 자동으로 검증됩니다.
|
Windows 러너를 하나 붙이면 `main` 에 push할 때(또는 Actions 탭에서 수동
|
||||||
|
실행할 때) 이 경로들이 자동으로 검증됩니다.
|
||||||
|
|
||||||
|
## 어디에 올릴까 — 서버 Windows VM 권장
|
||||||
|
|
||||||
|
개인 PC를 안 쓰고 서버에 올려 완전 자동화하는 게 가장 깔끔합니다.
|
||||||
|
**GPU가 없어도 됩니다.** 실제로 확인해봤습니다.
|
||||||
|
|
||||||
|
| 검증 항목 | GPU 필요? | GPU 없는 서버 VM |
|
||||||
|
|---|---|---|
|
||||||
|
| 단위 테스트 189개 | ❌ | ✅ |
|
||||||
|
| WASAPI 장치·프로그램 열거 | ❌ | ✅ |
|
||||||
|
| 전역 단축키 | ❌ | ✅ |
|
||||||
|
| 모니터 열거·자막 배치 | ❌ | ✅ |
|
||||||
|
| 포터블 exe 빌드·기동 | ❌ | ✅ |
|
||||||
|
| **오디오 → 한국어 자막 (전 과정)** | ❌ CPU로 됨 | ✅ |
|
||||||
|
| CUDA 인식·VRAM 상한 | ✅ | ⚠️ 경고만 (빌드 통과) |
|
||||||
|
|
||||||
|
GPU가 필요한 건 마지막 한 줄뿐이고, 그건 **이 서버(.9, 리눅스)에 RTX 5050이
|
||||||
|
물려 있어 이미 거기서 검증**하고 있습니다. GPU는 한 VM만 독점할 수 있으니
|
||||||
|
Windows VM에는 안 주는 게 맞습니다.
|
||||||
|
|
||||||
|
### Proxmox에 Windows VM 만들기
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Proxmox 호스트에서 (VMID 는 비어 있는 번호로)
|
||||||
|
qm create 920 --name win-ci --memory 8192 --cores 4 --net0 virtio,bridge=vmbr0 --scsihw virtio-scsi-single --scsi0 local-lvm:80 --ostype win11 --machine q35 --bios ovmf --efidisk0 local-lvm:1,efitype=4m,pre-enrolled-keys=1 --tpmstate0 local-lvm:1,version=v2.0 --ide2 local:iso/Win11.iso,media=cdrom --ide0 local:iso/virtio-win.iso,media=cdrom --audio0 device=ich9-intel-hda,driver=none --vga std
|
||||||
|
```
|
||||||
|
|
||||||
|
RAM 8GB / 디스크 80GB면 충분합니다(모델 캐시 포함). 아래 둘을 꼭 넣으세요.
|
||||||
|
|
||||||
|
- `--audio0 ...` — **없으면 오디오 장치가 하나도 안 잡혀** WASAPI 점검이 실패합니다
|
||||||
|
- `--vga std` — 화면이 있어야 자막 오버레이를 그리고 스크린샷을 찍습니다
|
||||||
|
|
||||||
|
### VM 안에서 꼭 해야 하는 두 가지
|
||||||
|
|
||||||
|
**1. 자동 로그온** — 러너가 서비스로 돌면 데스크톱 세션이 없어 GUI 테스트와
|
||||||
|
스크린샷이 전부 실패합니다. 자동 로그온 + 로그온 시 실행이어야 합니다.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# 관리자 PowerShell (VM 안이므로 부담 적음)
|
||||||
|
$k = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
|
||||||
|
Set-ItemProperty $k AutoAdminLogon 1
|
||||||
|
Set-ItemProperty $k DefaultUserName "ci"
|
||||||
|
Set-ItemProperty $k DefaultPassword "<비밀번호>"
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. 화면 꺼짐·잠금 끄기** — 잠기면 GUI 테스트가 실패합니다.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powercfg /change monitor-timeout-ac 0
|
||||||
|
powercfg /change standby-timeout-ac 0
|
||||||
|
```
|
||||||
|
|
||||||
|
그 다음은 아래 일반 설치 절차와 같습니다. 러너는 **서비스가 아니라 로그온
|
||||||
|
예약 작업**으로 등록하세요(아래 "항상 켜두고 싶다면" 참고).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 무엇이 자동으로 확인되나
|
## 무엇이 자동으로 확인되나
|
||||||
|
|
||||||
@@ -30,11 +87,19 @@ Windows 러너를 하나 붙이면 push할 때마다 이 경로들이 자동으
|
|||||||
### 1. 필요한 것 설치
|
### 1. 필요한 것 설치
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
|
winget install Git.Git # 소스 받기 (checkout) 에 필요
|
||||||
|
winget install Microsoft.PowerShell # 워크플로가 pwsh 7 을 씁니다
|
||||||
winget install Python.Python.3.12
|
winget install Python.Python.3.12
|
||||||
winget install Kitware.CMake
|
winget install Kitware.CMake
|
||||||
winget install Microsoft.VisualStudio.2022.BuildTools # C++ 데스크톱 워크로드 선택
|
winget install Microsoft.VisualStudio.2022.BuildTools # C++ 데스크톱 워크로드 선택
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`Git.Git` 과 `Microsoft.PowerShell` 은 빠뜨리기 쉬운데 둘 다 필수입니다.
|
||||||
|
Git 이 없으면 소스를 못 받고, Windows 에 기본으로 깔린 PowerShell 은 5.1 이라
|
||||||
|
워크플로가 쓰는 `pwsh`(7.x) 가 없으면 단계마다 실패합니다.
|
||||||
|
설치 후 새 PowerShell 창을 열어 `git --version`, `pwsh --version` 이
|
||||||
|
나오는지 확인하세요.
|
||||||
|
|
||||||
### 2. Gitea 러너 내려받기
|
### 2. Gitea 러너 내려받기
|
||||||
|
|
||||||
<https://gitea.com/gitea/act_runner/releases> 에서
|
<https://gitea.com/gitea/act_runner/releases> 에서
|
||||||
|
|||||||
@@ -160,6 +160,76 @@ def run_source_checks() -> None:
|
|||||||
return "스크린샷 2장 저장 (artifacts/)"
|
return "스크린샷 2장 저장 (artifacts/)"
|
||||||
|
|
||||||
|
|
||||||
|
# --- 전 파이프라인 점검 -----------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def run_pipeline_check(timeout_s: int = 900) -> None:
|
||||||
|
"""오디오 파일 하나를 넣어 한국어 자막이 나오는지 끝까지 본다.
|
||||||
|
|
||||||
|
개별 부품이 다 통과해도 이어 붙이면 안 되는 경우가 있다. 이 점검이
|
||||||
|
통과하면 "음성인식 -> 번역 -> 자막" 이 실제로 동작한다는 뜻이다.
|
||||||
|
|
||||||
|
GPU 없이 CPU 로 돌린다. 서버의 GPU 없는 Windows VM 에서도 그대로 된다.
|
||||||
|
모델은 처음 한 번만 받고 그 뒤로는 캐시를 쓴다.
|
||||||
|
"""
|
||||||
|
print(f"\n=== 전 파이프라인 (오디오 -> 자막), 플랫폼: {sys.platform} ===")
|
||||||
|
|
||||||
|
@check("오디오 파일 -> 한국어 자막")
|
||||||
|
def _pipeline():
|
||||||
|
from livesub.config import AppConfig
|
||||||
|
from livesub.core.engine import TranslationEngine
|
||||||
|
|
||||||
|
clip = ROOT / "tests" / "fixtures" / "en_callout_16k.wav"
|
||||||
|
if not clip.is_file():
|
||||||
|
raise RuntimeError(f"테스트 음성이 없습니다: {clip}")
|
||||||
|
|
||||||
|
cfg = AppConfig()
|
||||||
|
cfg.models.tier = "lightning" # CI 에서는 가장 가벼운 티어로 충분하다
|
||||||
|
cfg.models.device = "cpu" # GPU 없는 VM 에서도 돌아야 한다
|
||||||
|
cfg.models.preload_on_start = True
|
||||||
|
cfg.audio.backend = "file"
|
||||||
|
cfg.audio.target_process_name = str(clip)
|
||||||
|
cfg.audio.silence_ms = 400
|
||||||
|
cfg.performance.game_mode = False
|
||||||
|
|
||||||
|
lines: list = []
|
||||||
|
errors: list[str] = []
|
||||||
|
|
||||||
|
def on_status(status) -> None:
|
||||||
|
if status.state.value == "error":
|
||||||
|
errors.append(status.message)
|
||||||
|
|
||||||
|
engine = TranslationEngine(cfg, on_line=lines.append, on_status=on_status)
|
||||||
|
engine.start()
|
||||||
|
deadline = time.time() + timeout_s
|
||||||
|
try:
|
||||||
|
while time.time() < deadline and not errors:
|
||||||
|
if any(line.is_final for line in lines):
|
||||||
|
break
|
||||||
|
time.sleep(0.5)
|
||||||
|
finally:
|
||||||
|
engine.shutdown()
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
raise RuntimeError(f"엔진 오류: {errors[0]}")
|
||||||
|
finals = [line for line in lines if line.is_final]
|
||||||
|
if not finals:
|
||||||
|
raise RuntimeError(f"{timeout_s}초 안에 자막이 나오지 않았습니다")
|
||||||
|
|
||||||
|
line = finals[0]
|
||||||
|
if "enemy" not in line.source_text.lower():
|
||||||
|
raise RuntimeError(f"음성인식이 빗나갔습니다: {line.source_text!r}")
|
||||||
|
if not any("\uac00" <= ch <= "\ud7a3" for ch in line.translated_text):
|
||||||
|
raise RuntimeError(f"한국어 자막이 아닙니다: {line.translated_text!r}")
|
||||||
|
|
||||||
|
ARTIFACTS.mkdir(exist_ok=True)
|
||||||
|
(ARTIFACTS / "pipeline.txt").write_text(
|
||||||
|
f"원문: {line.source_text}\n자막: {line.translated_text}\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
return f"{line.source_text!r} -> {line.translated_text!r}"
|
||||||
|
|
||||||
|
|
||||||
# --- 빌드된 exe 점검 --------------------------------------------------------
|
# --- 빌드된 exe 점검 --------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
@@ -195,10 +265,16 @@ def run_exe_check(exe: Path) -> None:
|
|||||||
def main() -> int:
|
def main() -> int:
|
||||||
parser = argparse.ArgumentParser(description="Windows 전용 점검")
|
parser = argparse.ArgumentParser(description="Windows 전용 점검")
|
||||||
parser.add_argument("--exe", type=Path, help="빌드된 exe 를 점검")
|
parser.add_argument("--exe", type=Path, help="빌드된 exe 를 점검")
|
||||||
|
parser.add_argument(
|
||||||
|
"--pipeline", action="store_true",
|
||||||
|
help="오디오 파일로 전 파이프라인 점검 (모델을 내려받으므로 느립니다)",
|
||||||
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.exe:
|
if args.exe:
|
||||||
run_exe_check(args.exe)
|
run_exe_check(args.exe)
|
||||||
|
elif args.pipeline:
|
||||||
|
run_pipeline_check()
|
||||||
else:
|
else:
|
||||||
run_source_checks()
|
run_source_checks()
|
||||||
|
|
||||||
|
|||||||
BIN
tests/fixtures/en_callout_16k.wav
vendored
Normal file
BIN
tests/fixtures/en_callout_16k.wav
vendored
Normal file
Binary file not shown.
Reference in New Issue
Block a user