From 1a87ec6677aa353efdc154a9c02f2718c622b0de Mon Sep 17 00:00:00 2001 From: EJClaw Date: Mon, 21 Sep 2026 10:47:11 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20Hearo=201=EC=B0=A8=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=20=E2=80=94=20=ED=94=84=EB=A1=9C=EA=B7=B8=EB=9E=A8=20=EC=86=8C?= =?UTF-8?q?=EB=A6=AC=20=EC=8B=A4=EC=8B=9C=EA=B0=84=20=EB=B2=88=EC=97=AD=20?= =?UTF-8?q?=EC=9E=90=EB=A7=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 프로그램별 오디오를 캡처해 로컬 GPU에서 음성인식→번역하고 화면 위 자막으로 보여주는 데스크톱 앱. 한/영/일/중 4개 언어. 구성 - audio: WASAPI 프로그램별 캡처(C++ 보조 프로그램) + 장치 루프백 폴백, 적응형 VAD 발화 분할 - models: 속도~품질 5단계 티어, faster-whisper + CTranslate2/LLM 2백엔드, 용어집(플레이스홀더 보호 + 프롬프트 주입) - core: Qt 비의존 파이프라인 엔진 (캡처/분할/추론 3스레드, 큐 연결) - ui: 사이드바 5화면 + 무테두리 항상위 자막 오버레이, 자체 다크 테마 모델 선정 근거는 docs/MODELS.md, 추가학습 가능 여부와 방법은 docs/FINETUNING.md 참고. 검증: pytest 39개 통과 (GPU·오디오 장치 없이 실행), ruff clean Co-Authored-By: Claude Opus 4.7 --- .gitignore | 31 +++ LICENSE | 21 ++ README.md | 169 +++++++++++- docs/ARCHITECTURE.md | 91 +++++++ docs/FINETUNING.md | 137 ++++++++++ docs/MODELS.md | 81 ++++++ docs/images/glossary.png | Bin 0 -> 62031 bytes docs/images/home.png | Bin 0 -> 54442 bytes docs/images/models.png | Bin 0 -> 136945 bytes docs/images/overlay.png | Bin 0 -> 27024 bytes docs/images/subtitle.png | Bin 0 -> 68601 bytes native/process_loopback/CMakeLists.txt | 22 ++ native/process_loopback/build.ps1 | 21 ++ native/process_loopback/main.cpp | 229 ++++++++++++++++ pyproject.toml | 66 +++++ requirements.txt | 22 ++ scripts/finetune_mt.py | 248 ++++++++++++++++++ src/hearo/__init__.py | 6 + src/hearo/__main__.py | 4 + src/hearo/app.py | 53 ++++ src/hearo/audio/__init__.py | 72 +++++ src/hearo/audio/base.py | 146 +++++++++++ src/hearo/audio/file_source.py | 66 +++++ src/hearo/audio/process_loopback.py | 168 ++++++++++++ src/hearo/audio/segmenter.py | 188 ++++++++++++++ src/hearo/audio/wasapi_loopback.py | 95 +++++++ src/hearo/config.py | 140 ++++++++++ src/hearo/constants.py | 52 ++++ src/hearo/core/__init__.py | 6 + src/hearo/core/engine.py | 259 ++++++++++++++++++ src/hearo/core/events.py | 45 ++++ src/hearo/ui/__init__.py | 6 + src/hearo/ui/main_window.py | 258 ++++++++++++++++++ src/hearo/ui/overlay.py | 346 +++++++++++++++++++++++++ src/hearo/ui/pages/__init__.py | 7 + src/hearo/ui/pages/glossary_page.py | 256 ++++++++++++++++++ src/hearo/ui/pages/home.py | 226 ++++++++++++++++ src/hearo/ui/pages/models_page.py | 221 ++++++++++++++++ src/hearo/ui/pages/settings_page.py | 176 +++++++++++++ src/hearo/ui/pages/subtitle_page.py | 216 +++++++++++++++ src/hearo/ui/theme.py | 209 +++++++++++++++ src/hearo/ui/widgets/__init__.py | 3 + src/hearo/ui/widgets/common.py | 136 ++++++++++ tests/test_config.py | 58 +++++ tests/test_glossary.py | 75 ++++++ tests/test_pipeline.py | 129 +++++++++ tests/test_segmenter.py | 78 ++++++ tests/test_tiers.py | 36 +++ tests/test_ui_smoke.py | 102 ++++++++ 49 files changed, 4975 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/FINETUNING.md create mode 100644 docs/MODELS.md create mode 100644 docs/images/glossary.png create mode 100644 docs/images/home.png create mode 100644 docs/images/models.png create mode 100644 docs/images/overlay.png create mode 100644 docs/images/subtitle.png create mode 100644 native/process_loopback/CMakeLists.txt create mode 100644 native/process_loopback/build.ps1 create mode 100644 native/process_loopback/main.cpp create mode 100644 pyproject.toml create mode 100644 requirements.txt create mode 100644 scripts/finetune_mt.py create mode 100644 src/hearo/__init__.py create mode 100644 src/hearo/__main__.py create mode 100644 src/hearo/app.py create mode 100644 src/hearo/audio/__init__.py create mode 100644 src/hearo/audio/base.py create mode 100644 src/hearo/audio/file_source.py create mode 100644 src/hearo/audio/process_loopback.py create mode 100644 src/hearo/audio/segmenter.py create mode 100644 src/hearo/audio/wasapi_loopback.py create mode 100644 src/hearo/config.py create mode 100644 src/hearo/constants.py create mode 100644 src/hearo/core/__init__.py create mode 100644 src/hearo/core/engine.py create mode 100644 src/hearo/core/events.py create mode 100644 src/hearo/ui/__init__.py create mode 100644 src/hearo/ui/main_window.py create mode 100644 src/hearo/ui/overlay.py create mode 100644 src/hearo/ui/pages/__init__.py create mode 100644 src/hearo/ui/pages/glossary_page.py create mode 100644 src/hearo/ui/pages/home.py create mode 100644 src/hearo/ui/pages/models_page.py create mode 100644 src/hearo/ui/pages/settings_page.py create mode 100644 src/hearo/ui/pages/subtitle_page.py create mode 100644 src/hearo/ui/theme.py create mode 100644 src/hearo/ui/widgets/__init__.py create mode 100644 src/hearo/ui/widgets/common.py create mode 100644 tests/test_config.py create mode 100644 tests/test_glossary.py create mode 100644 tests/test_pipeline.py create mode 100644 tests/test_segmenter.py create mode 100644 tests/test_tiers.py create mode 100644 tests/test_ui_smoke.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..112cc04 --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +# Python +__pycache__/ +*.py[cod] +*.egg-info/ +build/ +dist/ +.venv/ +venv/ +.pytest_cache/ +.ruff_cache/ + +# 네이티브 빌드 산출물 +native/process_loopback/build/ +src/hearo/resources/bin/*.exe +src/hearo/resources/bin/*.pdb + +# 모델 캐시 / 학습 산출물 / 사용자 데이터 +models/ +adapters/ +data/ +*.jsonl +config.json +glossary.json +transcripts/ +*.log + +# 에디터 +.vscode/ +.idea/ +*.swp +.DS_Store diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1cbd2f4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 tkrmagid + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index a191238..c0acdd0 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,169 @@ -# live-app-translator +# Hearo — 듣고, 바로 이해하다 +특정 프로그램에서 나오는 소리를 실시간으로 받아 번역해 자막으로 보여줍니다. +번역은 전부 **내 컴퓨터의 GPU에서** 돌아갑니다. 인터넷도, API 키도 필요 없습니다. + +한국어 · English · 日本語 · 中文 사이를 번역합니다. + +![홈 화면](docs/images/home.png) + +--- + +## 무엇을 하는 프로그램인가 + +게임을 하는데 영어 음성만 나올 때, 해외 방송을 보는데 자막이 없을 때, +그 프로그램을 골라서 `번역 시작`만 누르면 화면 아래에 한국어 자막이 뜹니다. + +``` +게임 소리 → 음성인식 → 번역 → 화면 위 자막 + (Whisper) (Seed-X / NLLB) +``` + +![자막 오버레이](docs/images/overlay.png) + +## 주요 기능 + +- **프로그램 단위 소리 캡처** — 게임 소리만 받고 디스코드 음성은 안 받습니다 +- **5단계 품질 선택** — 속도 우선(0.6초)부터 품질 우선까지, GPU 사양에 맞춰 고릅니다 +- **자막 자유 설정** — 글꼴·크기·색·외곽선·투명도·위치·줄 수 +- **용어집** — 게임 고유명사를 원하는 번역으로 고정. 학습 없이 즉시 적용 +- **추가학습** — 게임/방송 말투로 번역 모델을 LoRA 학습시킬 수 있습니다 +- **완전 로컬** — 음성이 외부로 나가지 않습니다 + +--- + +## 설치 + +### 1. 사전 준비 + +- Windows 10 (2004 이상) 또는 Windows 11 +- NVIDIA GPU (권장 VRAM 6GB 이상) + 최신 드라이버 +- Python 3.10 ~ 3.12 + +### 2. PyTorch (CUDA 빌드) 먼저 + +CPU 버전이 깔리면 GPU를 못 씁니다. 반드시 이 순서로 설치하세요. + +```powershell +py -3.12 -m venv .venv +.venv\Scripts\activate + +# Blackwell(RTX 50 시리즈)은 cu128, 그 이전 세대는 cu124 +pip install torch --index-url https://download.pytorch.org/whl/cu128 +``` + +### 3. 나머지 설치 + +```powershell +pip install -r requirements.txt +pip install -e . +``` + +### 4. 프로그램별 캡처 켜기 (선택, 권장) + +이걸 빌드하지 않으면 출력 장치 전체 소리를 받습니다 (다른 앱 소리도 섞임). + +```powershell +# Visual Studio 2022 Build Tools (C++ 데스크톱) + CMake 필요 +winget install Microsoft.VisualStudio.2022.BuildTools +winget install Kitware.CMake + +powershell -ExecutionPolicy Bypass -File native\process_loopback\build.ps1 +``` + +### 5. 실행 + +```powershell +hearo +``` + +또는 `python -m hearo` + +--- + +## 처음 쓸 때 + +1. **모델** 화면에서 티어를 고릅니다. VRAM 8GB면 `3. 균형`, 10GB 이상이면 `4. 정밀` +2. **홈** 화면에서 소리를 받아올 프로그램을 고릅니다 +3. 원본 언어(자동 감지 권장)와 번역할 언어를 고릅니다 +4. `번역 시작` + +모델은 처음 한 번만 자동으로 내려받습니다 (2~17GB, 티어에 따라 다름). +저장 위치는 `%APPDATA%\Hearo\models` 입니다. + +--- + +## 모델 티어 + +| # | 이름 | VRAM | 지연 | 설명 | +|---|---|---|---|---| +| 1 | 번개 | 2GB | 0.6초 | 속도 최우선. 저사양 GPU | +| 2 | 신속 | 4GB | 0.9초 | 인식률은 최상급, 번역만 경량 | +| 3 | **균형** | 6GB | 1.2초 | 기본값. 8GB GPU에 가장 무난 | +| 4 | **정밀** | 10GB | 2.0초 | **추천.** 현 시점 최고 번역 품질 | +| 5 | 극한 | 16GB | 3.0초 | 추가학습(LoRA)에 최적 | + +![모델 화면](docs/images/models.png) + +어떤 모델을 왜 골랐는지는 [docs/MODELS.md](docs/MODELS.md)에 정리했습니다. + +--- + +## 용어집 + +게임 고유명사가 이상하게 번역될 때 씁니다. **학습이 필요 없고 즉시 적용됩니다.** + +`용어집` 화면에서 직접 입력하거나 CSV로 가져오세요. + +```csv +원문 용어,한국어 역어,English 역어,日本語 역어,中文 역어,메모 +Nexus,넥서스,Nexus,ネクサス,基地,LoL +baron,바론,Baron,バロン,男爵,LoL +``` + +게임 하나당 200~500개만 등록해도 체감 품질이 크게 달라집니다. + +## 추가학습 + +말투나 문장 구조까지 바꾸고 싶다면 LoRA로 학습시킬 수 있습니다. +언어쌍당 문장 1,000~3,000개가 필요합니다. + +```powershell +pip install -e ".[finetune]" +python scripts\finetune_mt.py --data data\game.jsonl --tier ultimate --output .\adapters\game-ko +``` + +데이터를 어떻게 모으고 어느 모델을 학습시켜야 하는지는 +[docs/FINETUNING.md](docs/FINETUNING.md)를 참고하세요. + +--- + +## 자막 창 조작 + +| 동작 | 결과 | +|---|---| +| 드래그 | 위치 이동 | +| 우하단 모서리 드래그 | 크기 조절 | +| 마우스 휠 | 글자 크기 | +| 우클릭 | 잠금 / 클릭 통과 / 항상 위에 / 숨기기 | + +전체화면 게임에서 자막이 안 보이면 게임을 **테두리 없는 창 모드**로 바꾸세요. +독점 전체화면(exclusive fullscreen)에서는 어떤 오버레이도 표시되지 않습니다. + +--- + +## 개발 + +```bash +pip install -e ".[dev]" +pytest # GPU·오디오 장치 없이 실행됩니다 +ruff check src tests scripts +``` + +구조는 [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)를 보세요. + +## 라이선스 + +MIT. 사용하는 모델은 각자의 라이선스를 따릅니다 +(Whisper: MIT, NLLB: CC-BY-NC, Seed-X: OpenMDW, Qwen3: Apache-2.0). +**NLLB는 비상업적 이용만 허용됩니다.** 상업적으로 쓰려면 4~5티어를 사용하세요. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..ddbd859 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,91 @@ +# 구조 + +## 데이터 흐름 + +``` +[캡처 스레드] [분할 스레드] [추론 스레드] [GUI 스레드] +hearo_capture.exe → Segmenter (VAD) → Whisper → 번역 모델 → 자막 오버레이 + 또는 WASAPI 발화 단위로 절단 용어집 적용 컨트롤 창 로그 + ↓ ↓ ↓ ↑ + 오디오 큐 구간 큐(최대 32) TranslationLine Qt Signal + (16kHz mono f32) 확정 / 중간결과 +``` + +세 워커 스레드는 모두 큐로만 연결된다. 어느 한 단계가 밀려도 나머지가 멈추지 않는다. +큐가 가득 차면 **오래된 오디오를 버린다** — 실시간 자막에서는 밀린 소리보다 지금 나는 +소리가 항상 더 가치 있기 때문이다. + +## 패키지 + +``` +src/hearo/ + constants.py 전역 상수, 언어 표, 경로 + config.py 설정 dataclass + JSON 영속화 + app.py 진입점 + + audio/ + base.py CaptureBackend 추상 클래스, 리샘플링 + process_loopback.py 프로그램별 캡처 (네이티브 보조 프로그램 구동) + wasapi_loopback.py 출력 장치 전체 캡처 (PyAudioWPatch) + file_source.py WAV 재생 (테스트·데모용) + segmenter.py 적응형 VAD로 발화 구간 절단 + + models/ + tiers.py 5단계 티어 정의 + asr.py faster-whisper 래퍼 + translator.py CTranslate2 / Transformers 두 백엔드 + glossary.py 용어집 (플레이스홀더 보호 + 프롬프트 주입) + manager.py 티어별 모델 쌍 로드·해제, GPU 탐지 + + core/ + engine.py 파이프라인 오케스트레이션 (Qt 비의존) + events.py TranslationLine, EngineStatus + + ui/ + theme.py 디자인 토큰 + QSS + overlay.py 자막 창 + 공용 렌더링 함수 + main_window.py 사이드바 + 페이지 스택 + pages/ 홈 / 모델 / 자막 / 용어집 / 설정 + widgets/ Card, StatusPill, LevelMeter + +native/process_loopback/ WASAPI process loopback C++ 보조 프로그램 +scripts/finetune_mt.py LoRA 추가학습 +``` + +## 설계 판단 + +### 캡처를 별도 실행 파일로 뺀 이유 + +WASAPI의 `ActivateAudioInterfaceAsync` + `AUDIOCLIENT_ACTIVATION_TYPE_PROCESS_LOOPBACK` +은 COM 비동기 콜백을 요구한다. ctypes로 COM vtable을 흉내 내는 것보다 C++ 200줄이 +훨씬 안전하고 디버깅이 쉽다. 파이프로 생 PCM만 주고받으므로 인터페이스도 단순하다. + +보조 프로그램이 없으면 `capture_capabilities()` 가 이를 알려주고 장치 루프백으로 +자동 폴백한다. 빌드 없이도 프로그램은 동작한다. + +### 중간 결과는 번역하지 않는다 + +말하는 도중에도 인식 결과를 흐리게 보여주면 체감 지연이 크게 줄어든다. 하지만 +매번 번역까지 돌리면 GPU가 몇 배로 바빠지고, 문장이 완성되기 전 번역은 어차피 +틀린다. 그래서 **중간 결과는 원문만, 확정 문장만 번역**한다. + +### UI 코드와 렌더링 코드 분리 + +자막 설정 미리보기와 실제 오버레이가 `paint_subtitle()` 하나를 공유한다. +QSS로 미리보기를 흉내 내면 `text-shadow` 미지원 같은 이유로 실제와 어긋난다. + +### 엔진은 Qt를 모른다 + +`core/engine.py` 는 콜백만 받는다. Qt 의존은 `MainWindow` 가 콜백을 Signal로 +다시 던지는 지점에만 있다. 덕분에 GPU도 오디오 장치도 없는 환경에서 파이프라인 +전체를 테스트할 수 있다 (`tests/test_pipeline.py`). + +## 테스트 + +`pytest` 전체가 GPU·오디오 장치 없이 돈다. + +- `test_segmenter.py` — 합성 사인파로 VAD 절단 검증 +- `test_pipeline.py` — 가짜 캡처/인식/번역으로 엔진 종단 검증 +- `test_glossary.py` — 최장일치, 단어경계, 왕복 변환 +- `test_config.py` — 스키마 변경 내성 +- `test_ui_smoke.py` — offscreen 렌더링으로 화면 생성 및 자막 픽셀 검증 diff --git a/docs/FINETUNING.md b/docs/FINETUNING.md new file mode 100644 index 0000000..27b7219 --- /dev/null +++ b/docs/FINETUNING.md @@ -0,0 +1,137 @@ +# 게임·방송 용어 추가학습 + +## 결론부터 + +**가능합니다.** 다만 대부분의 경우 **추가학습보다 용어집이 먼저입니다.** + +| 방법 | 준비 | 효과가 나타나는 시점 | 무엇에 좋은가 | +|---|---|---|---| +| **1. 용어집** (구현 완료) | 단어 목록만 | **즉시** | 고유명사, 스킬명, 아이템명, 캐릭터명 | +| **2. LoRA 추가학습** | 문장 쌍 1,000~3,000개 | 30분~3시간 학습 | 말투, 문장 구조, 도메인 어조 | +| **3. 풀 파인튜닝** | 문장 쌍 10,000개 이상 | 수 시간~하루 | 번역 스타일 전면 교체 | + +용어집과 추가학습은 **경쟁 관계가 아니라 보완 관계**입니다. 둘 다 켜는 게 가장 좋습니다. + +--- + +## 1. 용어집 — 먼저 이것부터 + +`용어집` 화면에서 "원문 → 각 언어 역어"를 등록하면 끝입니다. 학습 없이 바로 적용됩니다. + +동작 방식은 번역 백엔드에 따라 다릅니다. + +- **NLLB 계열(1~3티어)** — 등록 단어를 `⟦0⟧` 같은 토큰으로 바꿔치기해 모델이 아예 + 건드리지 못하게 한 뒤, 번역이 끝나면 지정한 역어로 되돌립니다. 100% 보장됩니다. +- **LLM 계열(4~5티어)** — 그 문장에 실제로 나온 용어만 골라 프롬프트에 + "이 용어는 이렇게 옮겨라"로 넣어줍니다. 조사·어미까지 문맥에 맞게 붙습니다. + +CSV로 한 번에 가져올 수 있습니다. + +```csv +원문 용어,한국어 역어,English 역어,日本語 역어,中文 역어,메모 +Nexus,넥서스,Nexus,ネクサス,基地,LoL +baron,바론,Baron,バロン,男爵,LoL +ult,궁,ultimate,アルティメット,大招,궁극기 +gg,잘 싸웠다,gg,gg,打得好, +``` + +**게임 하나당 200~500개만 등록해도 체감 품질이 크게 올라갑니다.** 추가학습으로 +같은 효과를 내려면 훨씬 많은 데이터가 필요합니다. 용어집을 먼저 채우세요. + +--- + +## 2. LoRA 추가학습 — 용어집으로 안 되는 것 + +용어집은 "이 단어를 저 단어로"만 고칩니다. 아래는 못 잡습니다. + +- 말투 — "You're getting rolled" → "탈탈 털리고 있네" (직역 아닌 게임 말투) +- 생략된 주어·목적어 복원 — 게임 음성에는 생략이 많습니다 +- 방송 특유의 감탄·리액션 어조 + +이건 문장 쌍으로 학습시켜야 합니다. + +### 필요한 데이터 양 + +공개 연구 기준으로, + +- 언어쌍당 **약 650~1,000 문장**만으로도 LoRA 도메인 적응이 동작합니다. +- 언어쌍당 **2,000 문장 / 15~20 epoch** 수준에서 chrF++ 평균 **+6.1점** 개선이 보고됩니다. + +즉 **언어쌍당 1,000~3,000 문장이 현실적인 목표**입니다. 4개 언어 전부가 아니라, +실제로 많이 쓰는 방향(예: 영어→한국어) 하나만 먼저 하는 게 효율적입니다. + +### 데이터 만드는 법 + +1. **게임 공식 현지화 자산** — 한국어판이 있는 게임의 자막/UI 텍스트. 품질이 가장 좋습니다. +2. **자막 파일 쌍** — 같은 영상의 영어 자막 + 한국어 자막(.srt)을 시간축으로 정렬. +3. **이 프로그램의 기록** — `설정 > 번역 기록을 파일로 남기기`를 켜두면 + `%APPDATA%/Hearo/transcripts/` 에 "원문 / 번역" 쌍이 쌓입니다. + **틀린 번역만 손으로 고쳐서** 학습 데이터로 쓰는 게 가장 현실적인 경로입니다. +4. 위키·커뮤니티 용어 사전 — 용어집으로 쓰는 게 더 낫습니다. 문장이 아니므로. + +형식은 JSONL 한 줄에 한 쌍입니다. + +```jsonl +{"source": "Enemy missing from mid", "target": "미드 실종", "source_lang": "en", "target_lang": "ko"} +{"source": "I'll take baron", "target": "바론 내가 먹을게", "source_lang": "en", "target_lang": "ko"} +``` + +### 학습 실행 + +```bash +pip install -e ".[finetune]" + +python scripts/finetune_mt.py \ + --data data/game_terms.jsonl \ + --tier ultimate \ + --output ./adapters/game-ko \ + --epochs 3 +``` + +끝나면 `모델` 화면의 **추가학습 어댑터** 칸에 `./adapters/game-ko` 를 넣으면 적용됩니다. + +### 티어별 난이도 + +| 티어 | 번역 모델 | 방식 | 학습에 필요한 VRAM | 난이도 | +|---|---|---|---|---| +| 1~2 | NLLB-600M | 풀 파인튜닝 가능 | ~8GB | ★ 쉬움 | +| 3 | NLLB-1.3B | LoRA | ~10GB | ★★ | +| 4 | Seed-X-PPO-7B | LoRA (권장 안 함) | ~16GB | ★★★ 까다로움 | +| 5 | **Qwen3-8B** | **LoRA** | **~16GB** | **★ 쉬움 (권장)** | + +**4티어 Seed-X는 추가학습 대상으로 권하지 않습니다.** 이미 PPO(강화학습)까지 마친 +모델이라 그 위에 SFT를 얹으면 기존 번역 품질이 무너지기 쉽습니다 (catastrophic forgetting). +Int4 양자화 가중치라 학습 자체도 번거롭습니다. + +**추가학습을 할 거라면 5티어(Qwen3-8B)**, **VRAM이 부족하면 1~2티어(NLLB-600M 풀 파인튜닝)** +로 가는 게 맞습니다. + +### 권장 하이퍼파라미터 + +연구에서 널리 쓰이는 설정입니다. `scripts/finetune_mt.py` 의 기본값이기도 합니다. + +``` +LoRA rank r = 16, alpha = 32, dropout = 0.05 +대상 모듈: attention 의 q_proj, v_proj +learning rate = 2e-4 (LoRA) / 5e-5 (풀 파인튜닝) +epochs = 3 (데이터 2,000개 이상) / 10~20 (수백 개) +``` + +--- + +## 3. 어느 것부터 할지 + +``` +1주차 용어집 300개 등록 → 이것만으로 충분한지 확인 +2주차 번역 기록 켜고 실사용, 틀린 문장 수집 +3주차 고친 문장 1,000개 모이면 LoRA 학습 +``` + +**용어집만으로 만족스러우면 추가학습은 안 해도 됩니다.** 실제로 고유명사 오역이 +체감 불만의 대부분입니다. + +## 참고 + +- [Fine-Tuning NLLB-200 with LoRA on a 650-Sentence Corpus](https://medium.com/@meinnps/fine-tuning-nllb-200-with-lora-on-a-650-sentence-turkmen-english-corpus-082f68bdec71) +- [SemiAdapt / SemiLoRA: Efficient Domain Adaptation for Low-Resource MT (arXiv)](https://arxiv.org/pdf/2510.18725) +- [How to fine-tune a NLLB-200 model](https://cointegrated.medium.com/how-to-fine-tune-a-nllb-200-model-for-translating-a-new-language-a37fc706b865) diff --git a/docs/MODELS.md b/docs/MODELS.md new file mode 100644 index 0000000..91967c6 --- /dev/null +++ b/docs/MODELS.md @@ -0,0 +1,81 @@ +# 모델 선정 근거 + +## 왜 두 단계인가 + +"소리 → 번역"을 한 모델로 끝내는 방법도 있다. Whisper에는 `translate` 태스크가 있고 +Voxtral·Qwen-Omni 같은 음성 LLM도 있다. 하지만 이 프로그램에는 맞지 않는다. + +- Whisper의 `translate`는 **X → 영어만** 된다. 한국어로 받아볼 수 없다. +- 음성 LLM 한 방에 처리하면 번역만 따로 교체하거나 추가학습시킬 수 없다. +- 용어집을 꽂아 넣을 지점이 사라진다. + +그래서 **음성인식(ASR) → 번역(MT)** 2단 구조로 간다. 두 단계를 따로 고를 수 있어서 +"인식은 최고급, 번역은 경량" 같은 조합이 가능하고, 이게 실제로 가장 가성비가 좋다. + +## 음성인식 + +| 후보 | 판단 | +|---|---| +| **Whisper large-v3-turbo** | 디코더를 32층 → 4층으로 줄여 large-v3 대비 약 6배 빠르면서 정확도 손실은 1~2%. 809M 파라미터, int8이면 VRAM 1.6GB. **한/영/일/중 모두 지원.** | +| Whisper large-v3 | 가장 정확하지만 느리다. 최상위 티어에만. | +| distil-whisper | 빠르지만 **영어 전용**이라 탈락. | +| NVIDIA Parakeet | 실시간성은 최고지만 **영어 전용**이라 탈락. | + +실행은 `faster-whisper`(CTranslate2)로 한다. 순정 `openai-whisper` 대비 4배 빠르고 +VRAM을 절반만 쓴다. + +→ **2~4티어의 기본은 large-v3-turbo.** 4개 언어를 모두 지원하면서 실시간을 만족하는 +유일한 지점이다. + +## 번역 + +| 후보 | VRAM | 판단 | +|---|---|---| +| **Seed-X-PPO-7B** (ByteDance) | ~5.6GB (Int4) | 28개 언어 **번역 전용**으로 학습된 7B. 자체 평가에서 Gemma3-27B, Llama4-Scout, Qwen3-235B를 앞서고 사람 평가에서 GPT-4o·Claude-3.5·Gemini-2.5-Pro와 대등. 한/영/일/중이 모두 주력 언어. | +| **Qwen3-8B** | ~11GB (fp16) | 범용 LLM이라 Seed-X보다 기본 번역은 약간 아래. 대신 **추가학습(LoRA) 생태계가 가장 두껍고**, 프롬프트 지시를 잘 따른다. | +| **NLLB-200-distilled 600M / 1.3B** | 0.8~2.9GB | 200개 언어 seq2seq. 구어체는 LLM보다 딱딱하지만 압도적으로 빠르고 가볍다. 게다가 **풀 파인튜닝이 소비자 GPU에서 된다.** | +| Gemma 3 12B | ~8GB | 140개 언어로 넓게 강하지만, 한/영/일/중만 필요한 우리에게는 Seed-X가 더 낫다. | +| opus-mt (Helsinki) | ~0.3GB | 언어쌍마다 모델이 따로라 4×3=12개를 관리해야 한다. 티어 구조와 안 맞아 탈락. | + +## 다섯 티어 + +| # | 이름 | 음성인식 | 번역 | VRAM | 지연 | +|---|---|---|---|---|---| +| 1 | 번개 | whisper-small (int8) | NLLB-600M (int8) | ~2GB | 0.6초 | +| 2 | 신속 | large-v3-turbo (int8) | NLLB-600M (fp16) | ~4GB | 0.9초 | +| 3 | **균형** (기본) | large-v3-turbo (fp16) | NLLB-1.3B (fp16) | ~6GB | 1.2초 | +| 4 | **정밀** (추천) | large-v3-turbo (fp16) | Seed-X-PPO-7B (Int4) | ~10GB | 2.0초 | +| 5 | 극한 | large-v3 (fp16) | Qwen3-8B (fp16) + LoRA | ~16GB | 3.0초 | + +## 결론: 무엇을 고를 것인가 + +**추가학습을 안 한다면 → 4티어 "정밀"이 최선이다.** +Seed-X는 번역만 하도록 만들어진 모델이라 7B치고 품질이 비정상적으로 좋다. +게임 대사처럼 짧고 구어체인 문장에서 NLLB 계열과 체감 차이가 크다. + +**추가학습을 전제로 하면 → 5티어 "극한"(Qwen3-8B + LoRA)으로 간다.** +이유는 세 가지다. + +1. Seed-X는 이미 PPO(강화학습)로 조율이 끝난 모델이다. 그 위에 다시 SFT를 얹으면 + 기존 품질이 깨지기 쉽다. 반면 Qwen3-8B는 instruct 베이스라 추가 학습을 전제로 만들어졌다. +2. peft / unsloth / TRL 등 LoRA 도구가 Qwen 계열에 가장 잘 맞춰져 있다. +3. 용어집을 프롬프트로 직접 지시할 수 있어 "학습 + 프롬프트" 이중으로 용어를 잡을 수 있다. + +**VRAM이 8GB 이하라면 → 3티어 "균형".** 그리고 이 경우의 추가학습은 +NLLB-1.3B를 **풀 파인튜닝**하는 쪽이 오히려 유리하다 (docs/FINETUNING.md 참고). + +## 주의 — 벤치마크를 곧이곧대로 믿지 말 것 + +위 비교는 공개 벤치마크와 모델 카드에 근거한 것이고, **실제 게임/방송 음성에서의 +체감 품질은 다를 수 있다.** 특히 Seed-X의 우위는 자체 발표 수치에 크게 기대고 있다. +그래서 프로그램에 5개 티어를 모두 넣었다. 실제 쓰는 콘텐츠로 3·4·5티어를 직접 +번갈아 써보고 정하는 것이 가장 정확하다. + +## 참고 + +- [Best open source STT model in 2026 (benchmarks)](https://northflank.com/blog/best-open-source-speech-to-text-stt-model-in-2026-benchmarks) +- [Whisper Large-v3 vs Turbo: Speed, WER & Cost](https://vexascribe.com/whisper-large-v3-vs-turbo) +- [Seed-X: Building Strong Multilingual Translation LLM with 7B Parameters (arXiv)](https://arxiv.org/html/2507.13618v1) +- [ByteDance-Seed/Seed-X-PPO-7B (Hugging Face)](https://huggingface.co/ByteDance-Seed/Seed-X-PPO-7B) +- [Local translation benchmark 2026 (cctrans)](https://github.com/kargnas/cctrans/blob/main/docs/local-translation-benchmark-2026.md) +- [CTranslate2 지원 모델](https://opennmt.net/CTranslate2/guides/transformers.html) diff --git a/docs/images/glossary.png b/docs/images/glossary.png new file mode 100644 index 0000000000000000000000000000000000000000..4686285702e57a034159f78dda152a114297da7c GIT binary patch literal 62031 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 literal 0 HcmV?d00001 diff --git a/docs/images/home.png b/docs/images/home.png new file mode 100644 index 0000000000000000000000000000000000000000..655bb1661af85ef39ad447e79657bfc24c75ba7c GIT binary patch literal 54442 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%0A+dq%Am-qXu&wHHNWYB|YC8EWpWyA5DJ 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 z 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 z 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 CLvPUl literal 0 HcmV?d00001 diff --git a/docs/images/subtitle.png b/docs/images/subtitle.png new file mode 100644 index 0000000000000000000000000000000000000000..3faf3add33d75d0dd8ff982081ae451f37cc96cf GIT binary patch literal 68601 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�C%fSd$?Tp5YH@v`Bl_V>WP|_E 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

)+!Vt7M4#>V`OFB?a6J`o|0Va>?5eF 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 literal 0 HcmV?d00001 diff --git a/native/process_loopback/CMakeLists.txt b/native/process_loopback/CMakeLists.txt new file mode 100644 index 0000000..e15dfff --- /dev/null +++ b/native/process_loopback/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.21) +project(hearo_capture LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_executable(hearo_capture main.cpp) + +if(MSVC) + target_compile_options(hearo_capture PRIVATE /W3 /permissive- /EHsc) + target_compile_definitions(hearo_capture PRIVATE _WIN32_WINNT=0x0A00 NOMINMAX) +endif() + +target_link_libraries(hearo_capture PRIVATE ole32 mmdevapi) + +# 빌드 결과를 파이썬 패키지가 찾는 위치로 복사한다. +add_custom_command(TARGET hearo_capture POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory + "${CMAKE_SOURCE_DIR}/../../src/hearo/resources/bin" + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "$" + "${CMAKE_SOURCE_DIR}/../../src/hearo/resources/bin/") diff --git a/native/process_loopback/build.ps1 b/native/process_loopback/build.ps1 new file mode 100644 index 0000000..7b99a44 --- /dev/null +++ b/native/process_loopback/build.ps1 @@ -0,0 +1,21 @@ +# 프로그램별 오디오 캡처 보조 프로그램 빌드 (Windows 전용) +# +# 필요: Visual Studio 2022 Build Tools (C++ 데스크톱 워크로드) + CMake +# winget install Microsoft.VisualStudio.2022.BuildTools +# winget install Kitware.CMake +# +# 사용: powershell -ExecutionPolicy Bypass -File native\process_loopback\build.ps1 + +$ErrorActionPreference = "Stop" +$here = Split-Path -Parent $MyInvocation.MyCommand.Path +$build = Join-Path $here "build" + +cmake -S $here -B $build -A x64 +cmake --build $build --config Release + +$out = Join-Path $here "..\..\src\hearo\resources\bin\hearo_capture.exe" +if (Test-Path $out) { + Write-Host "빌드 완료: $out" -ForegroundColor Green +} else { + Write-Error "빌드는 끝났지만 결과물을 찾지 못했습니다." +} diff --git a/native/process_loopback/main.cpp b/native/process_loopback/main.cpp new file mode 100644 index 0000000..4cbc99a --- /dev/null +++ b/native/process_loopback/main.cpp @@ -0,0 +1,229 @@ +// hearo_capture.exe — 특정 프로세스(와 그 자식 프로세스)의 렌더 오디오만 +// 뽑아내 stdout 으로 흘려보내는 보조 프로그램. +// +// 출력 포맷: 16000Hz / mono / 32-bit float, 헤더 없는 생 PCM. +// 사용법: hearo_capture.exe --pid 1234 [--exclude] +// --exclude 를 주면 해당 프로세스 트리를 "제외한" 나머지 소리를 받는다. +// +// Windows 10 2004(build 19041) 이상에서 동작하는 WASAPI process loopback API를 +// 사용한다. Microsoft 의 ApplicationLoopback 샘플이 기반이다. + +#define WIN32_LEAN_AND_MEAN +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#pragma comment(lib, "ole32.lib") +#pragma comment(lib, "mmdevapi.lib") + +using Microsoft::WRL::ComPtr; +using Microsoft::WRL::RuntimeClass; +using Microsoft::WRL::RuntimeClassFlags; +using Microsoft::WRL::ClassicCom; +using Microsoft::WRL::FtmBase; + +namespace { + +constexpr int kSampleRate = 16000; +constexpr int kChannels = 1; + +std::atomic g_stop{false}; + +BOOL WINAPI ConsoleHandler(DWORD type) { + if (type == CTRL_C_EVENT || type == CTRL_BREAK_EVENT || type == CTRL_CLOSE_EVENT) { + g_stop = true; + return TRUE; + } + return FALSE; +} + +// ActivateAudioInterfaceAsync 완료 콜백. 결과를 이벤트로 넘겨준다. +class ActivationHandler + : public RuntimeClass, + IActivateAudioInterfaceCompletionHandler> { +public: + explicit ActivationHandler(HANDLE done) : done_(done) {} + + STDMETHODIMP ActivateCompleted(IActivateAudioInterfaceAsyncOperation* op) override { + HRESULT activate_hr = S_OK; + ComPtr unknown; + HRESULT hr = op->GetActivateResult(&activate_hr, &unknown); + if (SUCCEEDED(hr) && SUCCEEDED(activate_hr)) { + unknown.As(&client_); + } + result_ = FAILED(hr) ? hr : activate_hr; + SetEvent(done_); + return S_OK; + } + + HRESULT result() const { return result_; } + ComPtr client() const { return client_; } + +private: + HANDLE done_; + HRESULT result_ = E_FAIL; + ComPtr client_; +}; + +void Fail(const char* what, HRESULT hr) { + std::fprintf(stderr, "[hearo_capture] %s (hr=0x%08lX)\n", what, static_cast(hr)); +} + +int Run(DWORD pid, bool exclude) { + HANDLE activated = CreateEventW(nullptr, FALSE, FALSE, nullptr); + HANDLE sample_ready = CreateEventW(nullptr, FALSE, FALSE, nullptr); + if (!activated || !sample_ready) { + Fail("이벤트 생성 실패", HRESULT_FROM_WIN32(GetLastError())); + return 1; + } + + AUDIOCLIENT_ACTIVATION_PARAMS params{}; + params.ActivationType = AUDIOCLIENT_ACTIVATION_TYPE_PROCESS_LOOPBACK; + params.ProcessLoopbackParams.TargetProcessId = pid; + params.ProcessLoopbackParams.ProcessLoopbackMode = + exclude ? PROCESS_LOOPBACK_MODE_EXCLUDE_TARGET_PROCESS_TREE + : PROCESS_LOOPBACK_MODE_INCLUDE_TARGET_PROCESS_TREE; + + PROPVARIANT activate_params{}; + activate_params.vt = VT_BLOB; + activate_params.blob.cbSize = sizeof(params); + activate_params.blob.pBlobData = reinterpret_cast(¶ms); + + auto handler = Microsoft::WRL::Make(activated); + ComPtr async_op; + HRESULT hr = ActivateAudioInterfaceAsync(VIRTUAL_AUDIO_DEVICE_PROCESS_LOOPBACK, + __uuidof(IAudioClient), &activate_params, + handler.Get(), &async_op); + if (FAILED(hr)) { + Fail("ActivateAudioInterfaceAsync 실패", hr); + return 2; + } + if (WaitForSingleObject(activated, 5000) != WAIT_OBJECT_0 || FAILED(handler->result())) { + Fail("오디오 인터페이스 활성화 실패 (Windows 10 2004 이상 필요)", handler->result()); + return 3; + } + + ComPtr client = handler->client(); + if (!client) { + Fail("IAudioClient 를 받지 못했습니다", E_POINTER); + return 3; + } + + // process loopback 경로에는 GetMixFormat 이 없으므로 포맷을 직접 지정한다. + WAVEFORMATEX format{}; + format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; + format.nChannels = kChannels; + format.nSamplesPerSec = kSampleRate; + format.wBitsPerSample = 32; + format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8; + format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; + format.cbSize = 0; + + constexpr REFERENCE_TIME kBufferDuration = 2 * 10'000'000LL; // 200ms + hr = client->Initialize(AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + kBufferDuration, 0, &format, nullptr); + if (FAILED(hr)) { + Fail("IAudioClient::Initialize 실패", hr); + return 4; + } + + ComPtr capture; + hr = client->GetService(IID_PPV_ARGS(&capture)); + if (FAILED(hr)) { + Fail("IAudioCaptureClient 획득 실패", hr); + return 4; + } + hr = client->SetEventHandle(sample_ready); + if (FAILED(hr)) { + Fail("SetEventHandle 실패", hr); + return 4; + } + hr = client->Start(); + if (FAILED(hr)) { + Fail("스트림 시작 실패", hr); + return 4; + } + + std::fprintf(stderr, "[hearo_capture] pid=%lu 캡처 시작 (%dHz mono f32)\n", + static_cast(pid), kSampleRate); + std::fflush(stderr); + + while (!g_stop) { + // 무음 구간에서는 이벤트가 오지 않으므로 타임아웃으로 빠져나와 종료 여부를 확인한다. + DWORD wait = WaitForSingleObject(sample_ready, 200); + if (wait == WAIT_TIMEOUT) continue; + if (wait != WAIT_OBJECT_0) break; + + UINT32 packet = 0; + while (SUCCEEDED(capture->GetNextPacketSize(&packet)) && packet > 0) { + BYTE* data = nullptr; + UINT32 frames = 0; + DWORD flags = 0; + hr = capture->GetBuffer(&data, &frames, &flags, nullptr, nullptr); + if (FAILED(hr)) break; + + const size_t bytes = static_cast(frames) * format.nBlockAlign; + if (flags & AUDCLNT_BUFFERFLAGS_SILENT) { + static thread_local std::vector zeros; + zeros.assign(bytes, 0); + std::fwrite(zeros.data(), 1, bytes, stdout); + } else if (data && bytes) { + std::fwrite(data, 1, bytes, stdout); + } + std::fflush(stdout); + capture->ReleaseBuffer(frames); + + if (std::ferror(stdout)) { // 부모 프로세스가 파이프를 닫음 + g_stop = true; + break; + } + } + } + + client->Stop(); + CloseHandle(activated); + CloseHandle(sample_ready); + return 0; +} + +} // namespace + +int main(int argc, char** argv) { + DWORD pid = 0; + bool exclude = false; + for (int i = 1; i < argc; ++i) { + if (std::strcmp(argv[i], "--pid") == 0 && i + 1 < argc) { + pid = static_cast(std::strtoul(argv[++i], nullptr, 10)); + } else if (std::strcmp(argv[i], "--exclude") == 0) { + exclude = true; + } + } + if (pid == 0) { + std::fprintf(stderr, "사용법: hearo_capture.exe --pid [--exclude]\n"); + return 64; + } + + _setmode(_fileno(stdout), _O_BINARY); + SetConsoleCtrlHandler(ConsoleHandler, TRUE); + + HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + if (FAILED(hr)) { + Fail("CoInitializeEx 실패", hr); + return 1; + } + int rc = Run(pid, exclude); + CoUninitialize(); + return rc; +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..02e64c6 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,66 @@ +[build-system] +requires = ["setuptools>=68", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "hearo" +version = "0.1.0" +description = "프로그램 소리를 실시간으로 듣고 번역해 자막으로 보여주는 도구" +readme = "README.md" +requires-python = ">=3.10" +license = { text = "MIT" } +authors = [{ name = "tkrmagid" }] + +dependencies = [ + "PySide6>=6.6", + "numpy>=1.24", +] + +[project.optional-dependencies] +# GPU 추론 (실사용에 필요). torch 는 CUDA 빌드를 따로 설치해야 한다 — README 참고. +gpu = [ + "faster-whisper>=1.0", + "ctranslate2>=4.4", + "transformers>=4.44", + "huggingface-hub>=0.24", + "sentencepiece>=0.2", +] +# Windows 오디오 캡처 +windows = [ + "PyAudioWPatch>=0.2.12.7", + "pycaw>=20240210", + "comtypes>=1.4", + "psutil>=5.9", +] +# 더 정확한 발화 구간 판정 (없어도 동작) +vad = ["webrtcvad-wheels>=2.0.14"] +# 추가학습 (LoRA) +finetune = [ + "peft>=0.12", + "datasets>=2.20", + "accelerate>=0.33", + "bitsandbytes>=0.43; platform_system != 'Darwin'", +] +dev = ["pytest>=8.0", "ruff>=0.6"] + +[project.scripts] +hearo = "hearo.app:main" + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.setuptools.package-data] +hearo = ["resources/bin/*"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +pythonpath = ["src"] + +[tool.ruff] +line-length = 100 +target-version = "py310" +src = ["src", "tests"] + +[tool.ruff.lint] +select = ["E", "F", "W", "I", "UP", "B", "SIM"] +ignore = ["E501", "B008"] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..775af4c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,22 @@ +# 기본 실행에 필요한 최소 구성 +PySide6>=6.6 +numpy>=1.24 + +# --- GPU 추론 --------------------------------------------------------- +# torch 는 반드시 CUDA 빌드로 먼저 설치하세요 (README 참고): +# pip install torch --index-url https://download.pytorch.org/whl/cu128 +faster-whisper>=1.0 +ctranslate2>=4.4 +transformers>=4.44 +huggingface-hub>=0.24 +sentencepiece>=0.2 +accelerate>=0.33 + +# --- 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 diff --git a/scripts/finetune_mt.py b/scripts/finetune_mt.py new file mode 100644 index 0000000..7120798 --- /dev/null +++ b/scripts/finetune_mt.py @@ -0,0 +1,248 @@ +#!/usr/bin/env python3 +"""번역 모델 추가학습 (게임·방송 용어). + +사용법: + python scripts/finetune_mt.py --data data/game.jsonl --tier ultimate \ + --output ./adapters/game-ko --epochs 3 + +데이터 형식 (JSONL, 한 줄에 한 쌍): + {"source": "...", "target": "...", "source_lang": "en", "target_lang": "ko"} + +자세한 배경은 docs/FINETUNING.md 참고. +""" + +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) + +from hearo.constants import LANGUAGES, models_dir # noqa: E402 +from hearo.models.tiers import MTBackend, get_tier, ordered_tiers # noqa: E402 + +#: NLLB(seq2seq)와 Qwen(causal LM)에서 공통으로 존재하는 attention 투영 이름 +LORA_TARGETS = ["q_proj", "v_proj"] + + +def load_pairs(path: Path) -> list[dict]: + pairs = [] + with path.open(encoding="utf-8") as fh: + for lineno, raw in enumerate(fh, 1): + raw = raw.strip() + if not raw: + continue + try: + item = json.loads(raw) + except json.JSONDecodeError as exc: + raise SystemExit(f"{path}:{lineno} JSON 오류: {exc}") from exc + for key in ("source", "target"): + if not item.get(key): + raise SystemExit(f"{path}:{lineno} '{key}' 가 비어 있습니다") + item.setdefault("source_lang", "en") + item.setdefault("target_lang", "ko") + for key in ("source_lang", "target_lang"): + if item[key] not in LANGUAGES: + raise SystemExit(f"{path}:{lineno} 지원하지 않는 언어: {item[key]}") + pairs.append(item) + if not pairs: + raise SystemExit(f"{path} 에 학습 데이터가 없습니다") + return pairs + + +def build_prompt(item: dict) -> tuple[str, str]: + """LLM 학습용 (프롬프트, 정답) 쌍. translator.py 의 추론 프롬프트와 맞춰야 한다.""" + src = LANGUAGES[item["source_lang"]]["english"] + tgt = LANGUAGES[item["target_lang"]]["english"] + prompt = ( + f"Translate the following {src} text into {tgt}. " + "It is a live spoken line from a game or broadcast, so keep the tone " + "casual and natural. Output only the translation.\n\n" + f"{src}: {item['source']}\n{tgt}:" + ) + return prompt, " " + item["target"] + + +def train_llm(pairs, repo, output: Path, epochs: int, lr: float, rank: int, batch: int): + import torch + from datasets import Dataset + from peft import LoraConfig, get_peft_model + from transformers import ( + AutoModelForCausalLM, + AutoTokenizer, + DataCollatorForLanguageModeling, + Trainer, + TrainingArguments, + ) + + tokenizer = AutoTokenizer.from_pretrained(repo, cache_dir=str(models_dir())) + if tokenizer.pad_token is None: + tokenizer.pad_token = tokenizer.eos_token + + def encode(item): + prompt, answer = build_prompt(item) + prompt_ids = tokenizer(prompt, add_special_tokens=False)["input_ids"] + answer_ids = tokenizer(answer, add_special_tokens=False)["input_ids"] + answer_ids.append(tokenizer.eos_token_id) + ids = (prompt_ids + answer_ids)[:512] + # 프롬프트 부분은 손실에서 제외해야 '번역 결과'만 학습된다. + labels = ([-100] * len(prompt_ids) + answer_ids)[:512] + return {"input_ids": ids, "labels": labels, "attention_mask": [1] * len(ids)} + + dataset = Dataset.from_list([encode(p) for p in pairs]) + + model = AutoModelForCausalLM.from_pretrained( + repo, + cache_dir=str(models_dir()), + torch_dtype=torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float16, + device_map="auto", + ) + model.enable_input_require_grads() + model = get_peft_model( + model, + LoraConfig( + r=rank, + lora_alpha=rank * 2, + lora_dropout=0.05, + bias="none", + task_type="CAUSAL_LM", + target_modules=LORA_TARGETS, + ), + ) + model.print_trainable_parameters() + + Trainer( + model=model, + args=TrainingArguments( + output_dir=str(output / "checkpoints"), + num_train_epochs=epochs, + per_device_train_batch_size=batch, + gradient_accumulation_steps=max(1, 8 // batch), + learning_rate=lr, + warmup_ratio=0.05, + logging_steps=20, + save_strategy="no", + gradient_checkpointing=True, + report_to=[], + ), + train_dataset=dataset, + data_collator=DataCollatorForLanguageModeling(tokenizer, mlm=False), + ).train() + + model.save_pretrained(output) + tokenizer.save_pretrained(output) + + +def train_seq2seq(pairs, repo, output: Path, epochs: int, lr: float, rank: int, batch: int): + """NLLB 계열. CTranslate2 변환 모델이 아니라 원본 HF 모델로 학습해야 한다.""" + from datasets import Dataset + from peft import LoraConfig, get_peft_model + from transformers import ( + AutoModelForSeq2SeqLM, + AutoTokenizer, + DataCollatorForSeq2Seq, + Seq2SeqTrainer, + Seq2SeqTrainingArguments, + ) + + tokenizer = AutoTokenizer.from_pretrained(repo, cache_dir=str(models_dir())) + + def encode(item): + tokenizer.src_lang = LANGUAGES[item["source_lang"]]["nllb"] + tokenizer.tgt_lang = LANGUAGES[item["target_lang"]]["nllb"] + batch_enc = tokenizer( + item["source"], text_target=item["target"], truncation=True, max_length=256 + ) + return dict(batch_enc) + + dataset = Dataset.from_list([encode(p) for p in pairs]) + model = AutoModelForSeq2SeqLM.from_pretrained(repo, cache_dir=str(models_dir())) + model = get_peft_model( + model, + LoraConfig( + r=rank, + lora_alpha=rank * 2, + lora_dropout=0.05, + bias="none", + task_type="SEQ_2_SEQ_LM", + target_modules=LORA_TARGETS, + ), + ) + model.print_trainable_parameters() + + Seq2SeqTrainer( + model=model, + args=Seq2SeqTrainingArguments( + output_dir=str(output / "checkpoints"), + num_train_epochs=epochs, + per_device_train_batch_size=batch, + learning_rate=lr, + warmup_ratio=0.05, + logging_steps=20, + save_strategy="no", + report_to=[], + ), + train_dataset=dataset, + data_collator=DataCollatorForSeq2Seq(tokenizer, model=model), + ).train() + + model.save_pretrained(output) + tokenizer.save_pretrained(output) + + +def main() -> int: + parser = argparse.ArgumentParser(description="번역 모델 LoRA 추가학습") + parser.add_argument("--data", required=True, type=Path, help="JSONL 학습 데이터") + parser.add_argument( + "--tier", + default="ultimate", + choices=[t.key for t in ordered_tiers()], + help="어느 티어의 번역 모델을 학습할지 (기본: ultimate)", + ) + parser.add_argument("--output", required=True, type=Path, help="어댑터 저장 폴더") + parser.add_argument("--epochs", type=int, default=3) + parser.add_argument("--lr", type=float, default=2e-4) + parser.add_argument("--rank", type=int, default=16) + parser.add_argument("--batch", type=int, default=2) + parser.add_argument( + "--base-model", + default="", + help="티어 기본값 대신 쓸 HF 모델 (NLLB는 CT2 변환본이 아닌 원본을 지정해야 함)", + ) + args = parser.parse_args() + + tier = get_tier(args.tier) + pairs = load_pairs(args.data) + args.output.mkdir(parents=True, exist_ok=True) + + if tier.mt.finetune_ease >= 3: + print( + f"경고: '{tier.name}' 티어의 번역 모델은 추가학습에 적합하지 않습니다.\n" + " docs/FINETUNING.md 참고 — 'ultimate' 티어를 권장합니다.\n", + file=sys.stderr, + ) + + if tier.mt.backend is MTBackend.TRANSFORMERS: + repo = args.base_model or tier.mt.repo + print(f"LLM LoRA 학습: {repo} · {len(pairs)}쌍 · {args.epochs} epoch") + train_llm(pairs, repo, args.output, args.epochs, args.lr, args.rank, args.batch) + else: + # CT2 변환본에는 학습에 필요한 가중치가 없으므로 원본 리포로 바꾼다. + repo = args.base_model or _hf_original(tier.mt.repo) + print(f"seq2seq LoRA 학습: {repo} · {len(pairs)}쌍 · {args.epochs} epoch") + train_seq2seq(pairs, repo, args.output, args.epochs, args.lr, args.rank, args.batch) + + print(f"\n완료. '모델' 화면의 추가학습 어댑터 칸에 입력하세요:\n {args.output.resolve()}") + return 0 + + +def _hf_original(ct2_repo: str) -> str: + """CTranslate2 변환 리포 이름에서 원본 facebook/ 리포를 추정한다.""" + name = ct2_repo.split("/")[-1].replace("-ctranslate2", "") + return f"facebook/{name}" + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/hearo/__init__.py b/src/hearo/__init__.py new file mode 100644 index 0000000..7c1194e --- /dev/null +++ b/src/hearo/__init__.py @@ -0,0 +1,6 @@ +"""Hearo — 프로그램 소리를 실시간으로 듣고 번역해 자막으로 보여주는 도구.""" + +from .constants import APP_NAME, APP_SLOGAN, APP_VERSION + +__all__ = ["APP_NAME", "APP_SLOGAN", "APP_VERSION", "__version__"] +__version__ = APP_VERSION diff --git a/src/hearo/__main__.py b/src/hearo/__main__.py new file mode 100644 index 0000000..c58ddac --- /dev/null +++ b/src/hearo/__main__.py @@ -0,0 +1,4 @@ +from .app import main + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/hearo/app.py b/src/hearo/app.py new file mode 100644 index 0000000..3a8f4c4 --- /dev/null +++ b/src/hearo/app.py @@ -0,0 +1,53 @@ +"""애플리케이션 진입점.""" + +from __future__ import annotations + +import contextlib +import logging +import sys + +from .config import AppConfig +from .constants import APP_NAME, ORG_NAME, user_data_dir + + +def setup_logging(verbose: bool = False) -> None: + log_file = user_data_dir() / "hearo.log" + handlers: list[logging.Handler] = [logging.StreamHandler(sys.stderr)] + with contextlib.suppress(OSError): + handlers.append(logging.FileHandler(log_file, encoding="utf-8")) + logging.basicConfig( + level=logging.DEBUG if verbose else logging.INFO, + format="%(asctime)s %(levelname)-7s %(name)s: %(message)s", + handlers=handlers, + force=True, + ) + # 모델 라이브러리들이 INFO로 너무 시끄럽다. + for noisy in ("urllib3", "filelock", "huggingface_hub", "transformers"): + logging.getLogger(noisy).setLevel(logging.WARNING) + + +def main(argv: list[str] | None = None) -> int: + argv = list(sys.argv if argv is None else argv) + verbose = "--verbose" in argv or "-v" in argv + setup_logging(verbose) + + from PySide6.QtCore import Qt + from PySide6.QtWidgets import QApplication + + from .ui import MainWindow + + QApplication.setAttribute(Qt.ApplicationAttribute.AA_UseHighDpiPixmaps, True) + app = QApplication(argv) + app.setApplicationName(APP_NAME) + app.setOrganizationName(ORG_NAME) + app.setQuitOnLastWindowClosed(True) + + config = AppConfig.load() + window = MainWindow(config) + if not config.start_minimized: + window.show() + return app.exec() + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/hearo/audio/__init__.py b/src/hearo/audio/__init__.py new file mode 100644 index 0000000..412989e --- /dev/null +++ b/src/hearo/audio/__init__.py @@ -0,0 +1,72 @@ +"""오디오 캡처 레이어.""" + +from __future__ import annotations + +import logging + +from ..config import AudioConfig +from .base import AudioSource, CaptureBackend, CaptureError +from .file_source import FileCapture +from .process_loopback import ProcessLoopbackCapture +from .segmenter import Segment, Segmenter, SegmenterConfig +from .wasapi_loopback import WasapiLoopbackCapture + +log = logging.getLogger(__name__) + +__all__ = [ + "AudioSource", + "CaptureBackend", + "CaptureError", + "FileCapture", + "ProcessLoopbackCapture", + "Segment", + "Segmenter", + "SegmenterConfig", + "WasapiLoopbackCapture", + "capture_capabilities", + "create_capture", + "list_sources", +] + + +def capture_capabilities() -> dict[str, bool]: + """현재 환경에서 어떤 캡처가 가능한지.""" + return { + "process": ProcessLoopbackCapture.available(), + "device": WasapiLoopbackCapture.available(), + "file": True, + } + + +def list_sources() -> list[AudioSource]: + """프로그램 + 출력 장치를 한 목록으로.""" + sources: list[AudioSource] = [] + if ProcessLoopbackCapture.available(): + sources.extend(ProcessLoopbackCapture.list_sources()) + sources.extend(WasapiLoopbackCapture.list_sources()) + return sources + + +def create_capture(config: AudioConfig) -> CaptureBackend: + """설정에 맞는 캡처 백엔드를 만든다. 불가능하면 장치 루프백으로 폴백.""" + backend = config.backend + if backend == "file": + return FileCapture(config.target_process_name) + + want_process = backend in ("process", "auto") and config.target_pid > 0 + if want_process: + if ProcessLoopbackCapture.available(): + return ProcessLoopbackCapture(config.target_pid) + if backend == "process": + raise CaptureError( + "프로그램별 캡처를 쓰려면 hearo_capture.exe 가 필요합니다. " + "native/process_loopback/build.ps1 로 빌드하거나 '출력 장치 전체'를 선택하세요." + ) + log.warning("프로그램별 캡처를 쓸 수 없어 장치 루프백으로 전환합니다.") + + if not WasapiLoopbackCapture.available(): + raise CaptureError( + "이 환경에서는 오디오 캡처를 할 수 없습니다. " + "Windows에서 PyAudioWPatch 설치 여부를 확인하세요." + ) + return WasapiLoopbackCapture(config.device_index) diff --git a/src/hearo/audio/base.py b/src/hearo/audio/base.py new file mode 100644 index 0000000..8150811 --- /dev/null +++ b/src/hearo/audio/base.py @@ -0,0 +1,146 @@ +"""오디오 캡처 백엔드 공통 인터페이스. + +모든 백엔드는 16kHz / mono / float32(-1.0~1.0) 프레임을 큐로 흘려보낸다. +리샘플링과 다운믹스는 각 백엔드가 책임진다. +""" + +from __future__ import annotations + +import abc +import queue +import threading +from dataclasses import dataclass + +import numpy as np + +from ..constants import SAMPLE_RATE + + +@dataclass(frozen=True) +class AudioSource: + """UI에 노출되는 캡처 대상 한 줄.""" + + kind: str # "process" | "device" + identifier: str # pid 문자열 또는 장치 인덱스 + label: str + detail: str = "" + icon_path: str = "" + + @property + def pid(self) -> int: + return int(self.identifier) if self.kind == "process" else 0 + + @property + def device_index(self) -> int: + return int(self.identifier) if self.kind == "device" else -1 + + +class CaptureError(RuntimeError): + """캡처를 시작할 수 없을 때.""" + + +class CaptureBackend(abc.ABC): + """오디오 캡처 백엔드 베이스.""" + + name = "base" + #: 특정 프로그램 소리만 분리해서 받을 수 있는가 + per_process = False + + def __init__(self, max_queue_frames: int = 400) -> None: + self._queue: queue.Queue[np.ndarray] = queue.Queue(maxsize=max_queue_frames) + self._stop = threading.Event() + self._thread: threading.Thread | None = None + self._error: Exception | None = None + + # --- 하위 클래스가 구현 ------------------------------------------- + @abc.abstractmethod + def _run(self) -> None: + """블로킹 캡처 루프. self._stop 이 set 될 때까지 _emit() 호출.""" + + @staticmethod + @abc.abstractmethod + def available() -> bool: + """현재 환경에서 이 백엔드를 쓸 수 있는가.""" + + @staticmethod + @abc.abstractmethod + def list_sources() -> list[AudioSource]: + """선택 가능한 캡처 대상 목록.""" + + # --- 공통 동작 ----------------------------------------------------- + def start(self) -> None: + if self._thread and self._thread.is_alive(): + return + self._stop.clear() + self._error = None + self._thread = threading.Thread( + target=self._thread_main, name=f"capture-{self.name}", daemon=True + ) + self._thread.start() + + def _thread_main(self) -> None: + try: + self._run() + except Exception as exc: # noqa: BLE001 - 워커 스레드 경계 + self._error = exc + + def stop(self, timeout: float = 2.0) -> None: + self._stop.set() + if self._thread: + self._thread.join(timeout=timeout) + self._thread = None + + @property + def running(self) -> bool: + return bool(self._thread and self._thread.is_alive()) + + @property + def error(self) -> Exception | None: + return self._error + + def _emit(self, frame: np.ndarray) -> None: + """캡처 프레임 투입. 큐가 가득 차면 가장 오래된 프레임을 버린다. + + 실시간 자막에서는 밀린 오디오보다 최신 오디오가 항상 더 가치 있다. + """ + try: + self._queue.put_nowait(frame) + except queue.Full: + try: + self._queue.get_nowait() + self._queue.put_nowait(frame) + except (queue.Empty, queue.Full): + pass + + def read(self, timeout: float = 0.5) -> np.ndarray | None: + try: + return self._queue.get(timeout=timeout) + except queue.Empty: + return None + + def drain(self) -> None: + while not self._queue.empty(): + try: + self._queue.get_nowait() + except queue.Empty: + break + + +def to_mono_16k(data: np.ndarray, channels: int, src_rate: int) -> np.ndarray: + """인터리브된 float32 PCM을 16kHz 모노로 변환.""" + if data.size == 0: + return data.astype(np.float32, copy=False) + audio = data.astype(np.float32, copy=False) + if channels > 1: + usable = (audio.size // channels) * channels + audio = audio[:usable].reshape(-1, channels).mean(axis=1) + if src_rate != SAMPLE_RATE and audio.size: + # 선형 보간 리샘플. 음성인식 입력으로는 충분한 품질이며 의존성이 없다. + duration = audio.size / src_rate + target_len = max(1, int(duration * SAMPLE_RATE)) + audio = np.interp( + np.linspace(0.0, audio.size - 1, target_len, dtype=np.float64), + np.arange(audio.size, dtype=np.float64), + audio, + ).astype(np.float32) + return audio diff --git a/src/hearo/audio/file_source.py b/src/hearo/audio/file_source.py new file mode 100644 index 0000000..22a943d --- /dev/null +++ b/src/hearo/audio/file_source.py @@ -0,0 +1,66 @@ +"""파일/합성 오디오 캡처 백엔드. + +실제 오디오 장치 없이 파이프라인 전체를 돌려볼 수 있게 해준다. +개발·테스트·데모용이며 어느 OS에서나 동작한다. +""" + +from __future__ import annotations + +import time +import wave +from pathlib import Path + +import numpy as np + +from ..constants import FRAME_SAMPLES, SAMPLE_RATE +from .base import AudioSource, CaptureBackend, CaptureError, to_mono_16k + + +class FileCapture(CaptureBackend): + """WAV 파일을 실시간 속도로 재생하듯 흘려보낸다.""" + + name = "file" + per_process = False + + def __init__(self, path: str | Path, loop: bool = False, realtime: bool = True) -> None: + super().__init__() + self.path = Path(path) + self.loop = loop + self.realtime = realtime + + @staticmethod + def available() -> bool: + return True + + @staticmethod + def list_sources() -> list[AudioSource]: + return [] + + def _read_wav(self) -> np.ndarray: + if not self.path.is_file(): + raise CaptureError(f"오디오 파일을 찾을 수 없습니다: {self.path}") + with wave.open(str(self.path), "rb") as wf: + channels = wf.getnchannels() + rate = wf.getframerate() + width = wf.getsampwidth() + raw = wf.readframes(wf.getnframes()) + if width == 2: + data = np.frombuffer(raw, dtype=np.int16).astype(np.float32) / 32768.0 + elif width == 4: + data = np.frombuffer(raw, dtype=np.float32) + else: + raise CaptureError(f"지원하지 않는 샘플 폭입니다: {width * 8}bit") + return to_mono_16k(data, channels, rate) + + def _run(self) -> None: + audio = self._read_wav() + chunk = FRAME_SAMPLES * 5 # 100ms + while not self._stop.is_set(): + for start in range(0, audio.size, chunk): + if self._stop.is_set(): + return + self._emit(audio[start : start + chunk].copy()) + if self.realtime: + time.sleep(chunk / SAMPLE_RATE) + if not self.loop: + return diff --git a/src/hearo/audio/process_loopback.py b/src/hearo/audio/process_loopback.py new file mode 100644 index 0000000..31ab0e0 --- /dev/null +++ b/src/hearo/audio/process_loopback.py @@ -0,0 +1,168 @@ +"""프로그램별 오디오 캡처 (Windows WASAPI process loopback). + +`native/process_loopback` 에서 빌드한 `hearo_capture.exe` 를 자식 프로세스로 띄우고 +stdout 으로 흘러나오는 16kHz mono float32 PCM 을 그대로 읽는다. + +보조 프로그램이 없으면 `available()` 이 False 를 돌려주고, 상위 레이어는 +장치 루프백으로 자동 폴백한다. +""" + +from __future__ import annotations + +import logging +import subprocess +import sys +from pathlib import Path + +import numpy as np + +from ..constants import FRAME_SAMPLES +from .base import AudioSource, CaptureBackend, CaptureError + +log = logging.getLogger(__name__) + +HELPER_NAME = "hearo_capture.exe" +READ_BYTES = FRAME_SAMPLES * 4 * 5 # 100ms 분량 + + +def helper_path() -> Path | None: + """보조 프로그램 위치. 패키지 내부 → 저장소 빌드 폴더 순으로 찾는다.""" + candidates = [ + Path(__file__).resolve().parent.parent / "resources" / "bin" / HELPER_NAME, + Path(__file__).resolve().parents[3] + / "native" + / "process_loopback" + / "build" + / "Release" + / HELPER_NAME, + ] + for path in candidates: + if path.is_file(): + return path + return None + + +class ProcessLoopbackCapture(CaptureBackend): + name = "process" + per_process = True + + def __init__(self, pid: int, exclude: bool = False) -> None: + super().__init__() + self.pid = pid + self.exclude = exclude + self._proc: subprocess.Popen[bytes] | None = None + + @staticmethod + def available() -> bool: + return sys.platform == "win32" and helper_path() is not None + + @staticmethod + def list_sources() -> list[AudioSource]: + """현재 소리를 내고 있는(또는 낼 수 있는) 프로세스 목록.""" + if sys.platform != "win32": + return [] + try: + from pycaw.pycaw import AudioUtilities + except ImportError: + log.debug("pycaw 미설치 — 프로세스 목록을 만들 수 없습니다.") + return [] + + seen: dict[int, AudioSource] = {} + for session in AudioUtilities.GetAllSessions(): + proc = session.Process + if proc is None or proc.pid in seen: + continue + try: + name = proc.name() + except Exception: # noqa: BLE001 - 종료된 프로세스 + continue + title = _window_title(proc.pid) or name + seen[proc.pid] = AudioSource( + kind="process", + identifier=str(proc.pid), + label=title, + detail=f"{name} (PID {proc.pid})", + ) + return sorted(seen.values(), key=lambda s: s.label.lower()) + + def _run(self) -> None: # pragma: no cover - Windows 전용 + exe = helper_path() + if exe is None: + raise CaptureError( + f"{HELPER_NAME} 를 찾지 못했습니다. " + "native/process_loopback/build.ps1 로 먼저 빌드하세요." + ) + cmd = [str(exe), "--pid", str(self.pid)] + if self.exclude: + cmd.append("--exclude") + + self._proc = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + bufsize=0, + creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0), + ) + assert self._proc.stdout is not None + try: + while not self._stop.is_set(): + raw = self._proc.stdout.read(READ_BYTES) + if not raw: + code = self._proc.poll() + err = b"" + if self._proc.stderr is not None: + err = self._proc.stderr.read() or b"" + raise CaptureError( + f"캡처 보조 프로그램이 종료되었습니다(코드 {code}): " + f"{err.decode('utf-8', 'replace').strip()}" + ) + # 4바이트 정렬이 깨지면 float32 해석이 밀린다. + usable = len(raw) - (len(raw) % 4) + self._emit(np.frombuffer(raw[:usable], dtype=np.float32).copy()) + finally: + self._terminate() + + def _terminate(self) -> None: + proc, self._proc = self._proc, None + if proc is None: + return + if proc.poll() is None: + proc.terminate() + try: + proc.wait(timeout=2) + except subprocess.TimeoutExpired: + proc.kill() + for pipe in (proc.stdout, proc.stderr): + if pipe is not None: + pipe.close() + + +def _window_title(pid: int) -> str: + """해당 PID의 최상위 창 제목. 실패하면 빈 문자열.""" + if sys.platform != "win32": + return "" + try: + import ctypes + from ctypes import wintypes + + user32 = ctypes.windll.user32 + result: list[str] = [] + + @ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM) + def callback(hwnd, _lparam): + owner = wintypes.DWORD() + user32.GetWindowThreadProcessId(hwnd, ctypes.byref(owner)) + if owner.value != pid or not user32.IsWindowVisible(hwnd): + return True + length = user32.GetWindowTextLengthW(hwnd) + if length <= 0: + return True + buf = ctypes.create_unicode_buffer(length + 1) + user32.GetWindowTextW(hwnd, buf, length + 1) + result.append(buf.value) + return False + + user32.EnumWindows(callback, 0) + return result[0] if result else "" + except Exception: # noqa: BLE001 - 창 제목은 부가 정보일 뿐 + return "" diff --git a/src/hearo/audio/segmenter.py b/src/hearo/audio/segmenter.py new file mode 100644 index 0000000..b31d084 --- /dev/null +++ b/src/hearo/audio/segmenter.py @@ -0,0 +1,188 @@ +"""발화 구간 분할기. + +캡처된 연속 오디오를 "한 문장" 단위로 잘라 음성인식에 넘긴다. +외부 의존성 없이 동작하도록 적응형 노이즈 플로어 기반 에너지 VAD를 쓴다. +webrtcvad 가 설치돼 있으면 더 정확한 판정을 위해 함께 사용한다. +""" + +from __future__ import annotations + +import time +from dataclasses import dataclass + +import numpy as np + +from ..constants import FRAME_MS, FRAME_SAMPLES, SAMPLE_RATE + +try: # pragma: no cover - 선택 의존성 + import webrtcvad +except ImportError: # pragma: no cover + webrtcvad = None # type: ignore[assignment] + + +@dataclass +class Segment: + """음성인식에 넘길 오디오 한 덩어리.""" + + audio: np.ndarray + started_at: float + ended_at: float + is_final: bool = True + + @property + def duration_s(self) -> float: + return self.audio.size / SAMPLE_RATE + + +@dataclass +class SegmenterConfig: + silence_ms: int = 600 + min_segment_ms: int = 400 + max_segment_ms: int = 12_000 + partial_interval_ms: int = 900 + #: 노이즈 플로어 대비 몇 배 이상이면 발화로 볼지 + speech_ratio: float = 3.0 + #: 절대 무음 판정 하한 (RMS) + absolute_floor: float = 2e-4 + vad_aggressiveness: int = 2 + #: 발화 시작 앞쪽에 붙일 여유분 (앞 음절 잘림 방지) + lead_in_ms: int = 240 + + +class Segmenter: + """프레임을 받아 발화 구간을 뱉는 상태 기계. + + `push()` 는 확정 구간(is_final=True) 또는 중간 결과(is_final=False)를 + 돌려주거나, 아직 내보낼 게 없으면 None 을 돌려준다. + """ + + def __init__(self, config: SegmenterConfig | None = None) -> None: + self.config = config or SegmenterConfig() + self._vad = None + if webrtcvad is not None: + self._vad = webrtcvad.Vad(max(0, min(3, self.config.vad_aggressiveness))) + + self._buffer: list[np.ndarray] = [] + self._lead_in: list[np.ndarray] = [] + self._pending = np.zeros(0, dtype=np.float32) + self._noise_floor = 1e-3 + self._silence_ms = 0 + self._speech_ms = 0 + self._in_speech = False + self._started_at = 0.0 + self._last_partial_ms = 0 + self._lead_in_frames = max(1, self.config.lead_in_ms // FRAME_MS) + + # --- 입력 ---------------------------------------------------------- + def push(self, chunk: np.ndarray) -> list[Segment]: + """임의 길이의 오디오를 넣고 완성된 구간 목록을 받는다.""" + out: list[Segment] = [] + if chunk.size: + self._pending = ( + chunk.astype(np.float32, copy=False) + if self._pending.size == 0 + else np.concatenate((self._pending, chunk)) + ) + while self._pending.size >= FRAME_SAMPLES: + frame = self._pending[:FRAME_SAMPLES] + self._pending = self._pending[FRAME_SAMPLES:] + segment = self._push_frame(frame) + if segment is not None: + out.append(segment) + return out + + def flush(self) -> Segment | None: + """남은 버퍼를 강제로 확정 구간으로 만든다 (캡처 종료 시).""" + if not self._in_speech: + return None + return self._finish() + + def reset(self) -> None: + """발화 상태만 초기화한다. + + `_pending` 은 건드리지 않는다. 아직 프레임으로 쪼개지 못한 '입력' 이라 + 여기서 버리면 한 구간을 확정한 직후의 오디오가 통째로 사라진다. + """ + self._buffer.clear() + self._lead_in.clear() + self._silence_ms = 0 + self._speech_ms = 0 + self._in_speech = False + self._last_partial_ms = 0 + + # --- 내부 ---------------------------------------------------------- + def _push_frame(self, frame: np.ndarray) -> Segment | None: + speech = self._is_speech(frame) + cfg = self.config + + if not self._in_speech: + self._lead_in.append(frame) + if len(self._lead_in) > self._lead_in_frames: + self._lead_in.pop(0) + if speech: + self._in_speech = True + self._started_at = time.monotonic() + self._buffer = list(self._lead_in) + self._lead_in = [] + self._silence_ms = 0 + self._speech_ms = FRAME_MS + self._last_partial_ms = 0 + return None + + self._buffer.append(frame) + self._speech_ms += FRAME_MS + self._silence_ms = 0 if speech else self._silence_ms + FRAME_MS + + buffered_ms = len(self._buffer) * FRAME_MS + if self._silence_ms >= cfg.silence_ms: + if buffered_ms - self._silence_ms < cfg.min_segment_ms: + self.reset() # 잡음 한 번 튄 것 — 버린다 + return None + return self._finish() + + if buffered_ms >= cfg.max_segment_ms: + return self._finish() + + if cfg.partial_interval_ms > 0: + since = buffered_ms - self._last_partial_ms + if since >= cfg.partial_interval_ms and buffered_ms >= cfg.min_segment_ms: + self._last_partial_ms = buffered_ms + return Segment( + audio=np.concatenate(self._buffer), + started_at=self._started_at, + ended_at=time.monotonic(), + is_final=False, + ) + return None + + def _finish(self) -> Segment: + audio = np.concatenate(self._buffer) if self._buffer else np.zeros(0, np.float32) + segment = Segment( + audio=audio, + started_at=self._started_at, + ended_at=time.monotonic(), + is_final=True, + ) + self.reset() + return segment + + def _is_speech(self, frame: np.ndarray) -> bool: + rms = float(np.sqrt(np.mean(np.square(frame), dtype=np.float64))) + cfg = self.config + + if rms < cfg.absolute_floor: + self._noise_floor = min(self._noise_floor, max(rms, 1e-6)) + return False + + energetic = rms > max(self._noise_floor * cfg.speech_ratio, cfg.absolute_floor) + if not energetic: + # 무음 구간에서만 노이즈 플로어를 천천히 따라가게 한다. + self._noise_floor = 0.95 * self._noise_floor + 0.05 * rms + + if self._vad is None: + return energetic + try: + pcm16 = (np.clip(frame, -1.0, 1.0) * 32767.0).astype(np.int16).tobytes() + return energetic and self._vad.is_speech(pcm16, SAMPLE_RATE) + except Exception: # noqa: BLE001 - webrtcvad 는 프레임 길이에 민감 + return energetic diff --git a/src/hearo/audio/wasapi_loopback.py b/src/hearo/audio/wasapi_loopback.py new file mode 100644 index 0000000..ec02e88 --- /dev/null +++ b/src/hearo/audio/wasapi_loopback.py @@ -0,0 +1,95 @@ +"""WASAPI 장치 루프백 캡처 (Windows). + +출력 장치 전체의 소리를 받는다. 프로그램별 분리는 안 되지만 의존성이 +`PyAudioWPatch` 하나뿐이라 어디서나 바로 동작하는 기본 백엔드다. +""" + +from __future__ import annotations + +import contextlib +import logging + +import numpy as np + +from ..constants import FRAME_SAMPLES +from .base import AudioSource, CaptureBackend, CaptureError, to_mono_16k + +log = logging.getLogger(__name__) + +try: # pragma: no cover - 플랫폼 의존 + import pyaudiowpatch as pyaudio +except ImportError: # pragma: no cover + pyaudio = None # type: ignore[assignment] + + +class WasapiLoopbackCapture(CaptureBackend): + name = "device" + per_process = False + + def __init__(self, device_index: int = -1) -> None: + super().__init__() + self.device_index = device_index + + @staticmethod + def available() -> bool: + return pyaudio is not None + + @staticmethod + def list_sources() -> list[AudioSource]: + if pyaudio is None: + return [] + sources: list[AudioSource] = [] + pa = pyaudio.PyAudio() + try: + default_index = -1 + with contextlib.suppress(OSError, LookupError): + default_index = pa.get_default_wasapi_loopback()["index"] + for info in pa.get_loopback_device_info_generator(): + idx = int(info["index"]) + sources.append( + AudioSource( + kind="device", + identifier=str(idx), + label=str(info["name"]).replace(" [Loopback]", ""), + detail="기본 출력 장치" if idx == default_index else "출력 장치", + ) + ) + finally: + pa.terminate() + return sources + + def _resolve_device(self, pa) -> dict: + if self.device_index >= 0: + return pa.get_device_info_by_index(self.device_index) + return pa.get_default_wasapi_loopback() + + def _run(self) -> None: # pragma: no cover - 실제 오디오 장치 필요 + if pyaudio is None: + raise CaptureError("PyAudioWPatch가 설치되지 않았습니다.") + pa = pyaudio.PyAudio() + stream = None + try: + info = self._resolve_device(pa) + if info is None: + raise CaptureError("루프백 장치를 찾지 못했습니다.") + rate = int(info["defaultSampleRate"]) + channels = int(info["maxInputChannels"]) or 2 + chunk = max(FRAME_SAMPLES, int(rate * 0.02)) + stream = pa.open( + format=pyaudio.paFloat32, + channels=channels, + rate=rate, + input=True, + input_device_index=int(info["index"]), + frames_per_buffer=chunk, + ) + log.info("장치 루프백 캡처 시작: %s (%dHz, %dch)", info["name"], rate, channels) + while not self._stop.is_set(): + raw = stream.read(chunk, exception_on_overflow=False) + data = np.frombuffer(raw, dtype=np.float32) + self._emit(to_mono_16k(data, channels, rate)) + finally: + if stream is not None: + stream.stop_stream() + stream.close() + pa.terminate() diff --git a/src/hearo/config.py b/src/hearo/config.py new file mode 100644 index 0000000..a99c0b5 --- /dev/null +++ b/src/hearo/config.py @@ -0,0 +1,140 @@ +"""설정 모델과 JSON 영속화.""" + +from __future__ import annotations + +import json +import logging +from dataclasses import asdict, dataclass, field, fields, is_dataclass +from pathlib import Path +from typing import Any + +from .constants import DEFAULT_TIER, user_data_dir + +log = logging.getLogger(__name__) + +CONFIG_PATH = user_data_dir() / "config.json" + + +@dataclass +class SubtitleStyle: + """자막 텍스트 표현 설정.""" + + font_family: str = "Pretendard" + font_size: int = 34 + bold: bool = True + text_color: str = "#FFFFFF" + source_text_color: str = "#9AA4B2" + outline_color: str = "#000000" + outline_width: int = 3 + background_color: str = "#000000" + background_opacity: int = 55 # 0-100 + line_spacing: int = 6 + max_lines: int = 2 + show_source: bool = False # 원문 동시 표시 + align: str = "center" # left | center | right + fade_out_ms: int = 4000 # 0이면 계속 유지 + + +@dataclass +class OverlayConfig: + """자막 창(오버레이) 위치/동작.""" + + x: int = -1 # -1 = 화면 하단 중앙 자동 배치 + y: int = -1 + width: int = 1100 + height: int = 200 + always_on_top: bool = True + click_through: bool = False + locked: bool = False + visible: bool = True + + +@dataclass +class AudioConfig: + backend: str = "auto" # auto | process | device | file + target_pid: int = 0 + target_process_name: str = "" + device_index: int = -1 # -1 = 기본 출력 장치 루프백 + input_gain: float = 1.0 + vad_aggressiveness: int = 2 # 0-3 + silence_ms: int = 600 # 이 시간만큼 조용하면 한 문장 종료 + min_segment_ms: int = 400 + max_segment_ms: int = 12_000 + partial_interval_ms: int = 900 # 중간 결과 갱신 주기, 0이면 끔 + + +@dataclass +class ModelConfig: + tier: str = DEFAULT_TIER + device: str = "cuda" # cuda | cpu + device_index: int = 0 + source_lang: str = "auto" # auto 또는 언어코드 + target_lang: str = "ko" + preload_on_start: bool = True + lora_adapter_path: str = "" # 추가학습 어댑터 (비어있으면 미사용) + + +@dataclass +class GlossaryConfig: + enabled: bool = True + path: str = "" # 비어있으면 user_data_dir()/glossary.json + case_sensitive: bool = False + + +@dataclass +class AppConfig: + audio: AudioConfig = field(default_factory=AudioConfig) + models: ModelConfig = field(default_factory=ModelConfig) + subtitle: SubtitleStyle = field(default_factory=SubtitleStyle) + overlay: OverlayConfig = field(default_factory=OverlayConfig) + glossary: GlossaryConfig = field(default_factory=GlossaryConfig) + theme: str = "dark" # dark | light + start_minimized: bool = False + log_transcripts: bool = True + + # --- 영속화 --------------------------------------------------------- + def to_dict(self) -> dict[str, Any]: + return asdict(self) + + def save(self, path: Path | None = None) -> Path: + target = path or CONFIG_PATH + target.parent.mkdir(parents=True, exist_ok=True) + tmp = target.with_suffix(".json.tmp") + tmp.write_text( + json.dumps(self.to_dict(), ensure_ascii=False, indent=2), encoding="utf-8" + ) + tmp.replace(target) + return target + + @classmethod + def load(cls, path: Path | None = None) -> AppConfig: + target = path or CONFIG_PATH + if not target.exists(): + return cls() + try: + raw = json.loads(target.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as exc: + log.warning("설정 파일을 읽지 못해 기본값을 사용합니다: %s", exc) + return cls() + return _from_dict(cls, raw) + + +def _from_dict(cls: type, raw: Any) -> Any: + """알 수 없는 키는 버리고 누락된 키는 기본값으로 채우는 관대한 역직렬화. + + 기본값 인스턴스를 먼저 만든 뒤 알고 있는 필드만 덮어쓴다. 덕분에 + 설정 스키마가 바뀌어도 사용자의 기존 config.json이 깨지지 않는다. + """ + instance = cls() + if not isinstance(raw, dict): + return instance + known = {f.name for f in fields(cls)} + for key, value in raw.items(): + if key not in known: + continue + current = getattr(instance, key) + if is_dataclass(current) and not isinstance(current, type): + setattr(instance, key, _from_dict(type(current), value)) + else: + setattr(instance, key, value) + return instance diff --git a/src/hearo/constants.py b/src/hearo/constants.py new file mode 100644 index 0000000..90b0fa3 --- /dev/null +++ b/src/hearo/constants.py @@ -0,0 +1,52 @@ +"""전역 상수. 제품명 변경은 이 파일만 고치면 됩니다.""" + +from __future__ import annotations + +from pathlib import Path + +APP_NAME = "Hearo" +APP_NAME_KO = "히어로" +APP_SLOGAN = "듣고, 바로 이해하다" +APP_VERSION = "0.1.0" +ORG_NAME = "tkrmagid" + +# 지원 언어 (1차: 4개) +LANGUAGES: dict[str, dict[str, str]] = { + "ko": {"label": "한국어", "english": "Korean", "nllb": "kor_Hang", "flag": "KO"}, + "en": {"label": "English", "english": "English", "nllb": "eng_Latn", "flag": "EN"}, + "ja": {"label": "日本語", "english": "Japanese", "nllb": "jpn_Jpan", "flag": "JA"}, + "zh": {"label": "中文", "english": "Chinese", "nllb": "zho_Hans", "flag": "ZH"}, +} +LANGUAGE_CODES = tuple(LANGUAGES.keys()) + +# 기본 모델 품질 티어. 실제 티어 정의는 models/tiers.py 에 있지만, +# config 가 models 패키지를 import 하면 순환 의존이 생기므로 키만 여기에 둔다. +DEFAULT_TIER = "balance" + +# 오디오 파이프라인 규격 +SAMPLE_RATE = 16_000 +CHANNELS = 1 +FRAME_MS = 20 +FRAME_SAMPLES = SAMPLE_RATE * FRAME_MS // 1000 + + +def user_data_dir() -> Path: + """설정/로그/모델 캐시를 두는 사용자 디렉터리.""" + import os + import sys + + if sys.platform == "win32": + base = Path(os.environ.get("APPDATA", Path.home() / "AppData" / "Roaming")) + elif sys.platform == "darwin": + base = Path.home() / "Library" / "Application Support" + else: + base = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config")) + path = base / APP_NAME + path.mkdir(parents=True, exist_ok=True) + return path + + +def models_dir() -> Path: + path = user_data_dir() / "models" + path.mkdir(parents=True, exist_ok=True) + return path diff --git a/src/hearo/core/__init__.py b/src/hearo/core/__init__.py new file mode 100644 index 0000000..4074d95 --- /dev/null +++ b/src/hearo/core/__init__.py @@ -0,0 +1,6 @@ +"""파이프라인 코어.""" + +from .engine import TranslationEngine +from .events import EngineState, EngineStatus, TranslationLine + +__all__ = ["EngineState", "EngineStatus", "TranslationEngine", "TranslationLine"] diff --git a/src/hearo/core/engine.py b/src/hearo/core/engine.py new file mode 100644 index 0000000..f569f05 --- /dev/null +++ b/src/hearo/core/engine.py @@ -0,0 +1,259 @@ +"""실시간 번역 파이프라인 오케스트레이션. + + 캡처 스레드 → [오디오 큐] → 분할 스레드 → [구간 큐] → 인식/번역 워커 → 콜백 + +UI 프레임워크에 의존하지 않는다. Qt 쪽은 콜백을 시그널로 다시 던져준다. +""" + +from __future__ import annotations + +import logging +import queue +import threading +import time +from collections.abc import Callable + +import numpy as np + +from ..audio import ( + CaptureBackend, + CaptureError, + Segment, + Segmenter, + SegmenterConfig, + create_capture, +) +from ..config import AppConfig +from ..constants import LANGUAGE_CODES, user_data_dir +from ..models import Glossary, ModelManager +from .events import EngineState, EngineStatus, TranslationLine + +log = logging.getLogger(__name__) + +StatusCallback = Callable[[EngineStatus], None] +LineCallback = Callable[[TranslationLine], None] + + +class TranslationEngine: + def __init__( + self, + config: AppConfig, + on_line: LineCallback | None = None, + on_status: StatusCallback | None = None, + ) -> None: + self.config = config + self._on_line = on_line + self._on_status = on_status + + self.models = ModelManager(config.models) + self.glossary = self._load_glossary() + + self._capture: CaptureBackend | None = None + self._segments: queue.Queue[Segment] = queue.Queue(maxsize=32) + self._threads: list[threading.Thread] = [] + self._stop = threading.Event() + self._status = EngineStatus() + self._level = 0.0 + #: 인식 정확도를 높이기 위해 직전 확정 문장을 프롬프트로 넘긴다. + self._last_final_text = "" + + # --- 수명주기 ------------------------------------------------------- + @property + def status(self) -> EngineStatus: + return self._status + + @property + def running(self) -> bool: + return self._status.state in (EngineState.RUNNING, EngineState.LOADING) + + def start(self) -> None: + if self.running: + return + self._stop.clear() + self._emit_status(EngineState.LOADING, "모델을 준비하고 있습니다…", 0.0) + + thread = threading.Thread(target=self._bootstrap, name="engine-boot", daemon=True) + thread.start() + self._threads = [thread] + + def stop(self) -> None: + if self._status.state is EngineState.IDLE: + return + self._emit_status(EngineState.STOPPING, "정지 중…") + self._stop.set() + if self._capture is not None: + self._capture.stop() + self._capture = None + for t in self._threads: + if t is not threading.current_thread(): + t.join(timeout=3) + self._threads = [] + self._drain_segments() + self._emit_status(EngineState.IDLE, "대기 중") + + def shutdown(self) -> None: + self.stop() + self.models.unload() + + def reload_glossary(self) -> None: + self.glossary = self._load_glossary() + + # --- 내부 스레드 ---------------------------------------------------- + def _bootstrap(self) -> None: + try: + if self.config.models.preload_on_start: + self.models.preload( + lambda msg, pct: self._emit_status(EngineState.LOADING, msg, pct) + ) + self._capture = create_capture(self.config.audio) + self._capture.start() + + workers = [ + threading.Thread(target=self._segment_loop, name="engine-segment", daemon=True), + threading.Thread(target=self._infer_loop, name="engine-infer", daemon=True), + ] + for w in workers: + w.start() + self._threads.extend(workers) + + kind = "프로그램별 캡처" if self._capture.per_process else "출력 장치 전체" + self._emit_status(EngineState.RUNNING, f"듣는 중 · {kind}", 1.0) + except (CaptureError, OSError, RuntimeError, ImportError) as exc: + log.exception("엔진 시작 실패") + self._emit_status(EngineState.ERROR, str(exc)) + + def _segment_loop(self) -> None: + cfg = self.config.audio + segmenter = Segmenter( + SegmenterConfig( + silence_ms=cfg.silence_ms, + min_segment_ms=cfg.min_segment_ms, + max_segment_ms=cfg.max_segment_ms, + partial_interval_ms=cfg.partial_interval_ms, + vad_aggressiveness=cfg.vad_aggressiveness, + ) + ) + while not self._stop.is_set(): + capture = self._capture + if capture is None: + break + chunk = capture.read(timeout=0.3) + if chunk is None: + if capture.error is not None: + self._emit_status(EngineState.ERROR, str(capture.error)) + return + continue + + if cfg.input_gain != 1.0: + chunk = np.clip(chunk * cfg.input_gain, -1.0, 1.0) + self._level = float(np.sqrt(np.mean(np.square(chunk, dtype=np.float64)))) + + for segment in segmenter.push(chunk): + self._enqueue(segment) + + leftover = segmenter.flush() + if leftover is not None: + self._enqueue(leftover) + + def _enqueue(self, segment: Segment) -> None: + try: + self._segments.put_nowait(segment) + except queue.Full: + # 밀렸다면 중간 결과부터 버린다. 확정 문장은 지켜야 한다. + if not segment.is_final: + return + try: + self._segments.get_nowait() + self._segments.put_nowait(segment) + except (queue.Empty, queue.Full): + pass + + def _infer_loop(self) -> None: + recognizer = self.models.recognizer() + translator = self.models.translator() + mcfg = self.config.models + + while not self._stop.is_set(): + try: + segment = self._segments.get(timeout=0.3) + except queue.Empty: + continue + + # 처리 중 더 새로운 확정 구간이 들어왔다면 오래된 중간 결과는 버린다. + if not segment.is_final and not self._segments.empty(): + continue + + try: + self._process(segment, recognizer, translator, mcfg) + except Exception as exc: # noqa: BLE001 - 한 문장 실패로 엔진이 죽으면 안 된다 + log.warning("구간 처리 실패: %s", exc, exc_info=log.isEnabledFor(logging.DEBUG)) + + def _process(self, segment: Segment, recognizer, translator, mcfg) -> None: + source_lang = None if mcfg.source_lang == "auto" else mcfg.source_lang + + t0 = time.perf_counter() + transcript = recognizer.transcribe( + segment.audio, + language=source_lang, + fast=not segment.is_final, + prompt=self._last_final_text if segment.is_final else "", + ) + asr_ms = int((time.perf_counter() - t0) * 1000) + if not transcript.text: + return + + detected = transcript.language if transcript.language in LANGUAGE_CODES else mcfg.target_lang + target = mcfg.target_lang + + translated = "" + mt_ms = 0 + if segment.is_final: + # 중간 결과는 원문만 흘려보낸다. 번역은 확정 문장에만 돌려 GPU를 아낀다. + t1 = time.perf_counter() + translated = translator.translate( + transcript.text, + detected, + target, + self.glossary if self.config.glossary.enabled else None, + ) + mt_ms = int((time.perf_counter() - t1) * 1000) + self._last_final_text = transcript.text + + line = TranslationLine( + source_text=transcript.text, + translated_text=translated, + source_lang=detected, + target_lang=target, + is_final=segment.is_final, + latency_s=time.monotonic() - segment.ended_at, + asr_ms=asr_ms, + mt_ms=mt_ms, + ) + if self._on_line: + self._on_line(line) + self._status.level = self._level + self._status.queue_depth = self._segments.qsize() + + # --- 유틸 ----------------------------------------------------------- + def _load_glossary(self) -> Glossary: + cfg = self.config.glossary + path = cfg.path or str(user_data_dir() / "glossary.json") + return Glossary.load(path, cfg.case_sensitive) + + def _drain_segments(self) -> None: + while not self._segments.empty(): + try: + self._segments.get_nowait() + except queue.Empty: + break + + def _emit_status(self, state: EngineState, message: str, progress: float | None = None) -> None: + self._status = EngineStatus( + state=state, + message=message, + progress=self._status.progress if progress is None else progress, + level=self._level, + queue_depth=self._segments.qsize(), + ) + if self._on_status: + self._on_status(self._status) diff --git a/src/hearo/core/events.py b/src/hearo/core/events.py new file mode 100644 index 0000000..4349134 --- /dev/null +++ b/src/hearo/core/events.py @@ -0,0 +1,45 @@ +"""엔진이 UI로 올려보내는 이벤트 타입.""" + +from __future__ import annotations + +import time +from dataclasses import dataclass, field +from enum import Enum + + +class EngineState(str, Enum): + IDLE = "idle" + LOADING = "loading" + RUNNING = "running" + STOPPING = "stopping" + ERROR = "error" + + +@dataclass +class TranslationLine: + """화면에 뿌릴 자막 한 줄.""" + + source_text: str + translated_text: str + source_lang: str + target_lang: str + is_final: bool = True + created_at: float = field(default_factory=time.time) + #: 발화가 끝난 시점부터 번역이 나오기까지 걸린 시간(초) + latency_s: float = 0.0 + asr_ms: int = 0 + mt_ms: int = 0 + + @property + def display(self) -> str: + return self.translated_text or self.source_text + + +@dataclass +class EngineStatus: + state: EngineState = EngineState.IDLE + message: str = "" + progress: float = 0.0 + #: 최근 입력 오디오 레벨 (0.0 ~ 1.0), VU 미터용 + level: float = 0.0 + queue_depth: int = 0 diff --git a/src/hearo/ui/__init__.py b/src/hearo/ui/__init__.py new file mode 100644 index 0000000..6b6c13b --- /dev/null +++ b/src/hearo/ui/__init__.py @@ -0,0 +1,6 @@ +"""Qt UI 레이어.""" + +from .main_window import MainWindow +from .overlay import SubtitleOverlay, SubtitlePreview + +__all__ = ["MainWindow", "SubtitleOverlay", "SubtitlePreview"] diff --git a/src/hearo/ui/main_window.py b/src/hearo/ui/main_window.py new file mode 100644 index 0000000..40ec4ef --- /dev/null +++ b/src/hearo/ui/main_window.py @@ -0,0 +1,258 @@ +"""메인 컨트롤 창 — 사이드바 + 페이지 스택. + +디자인 의도: 자막은 게임 위 오버레이가 전담하고, 이 창은 '설정과 상태'만 +담당한다. 그래서 두 창이 서로 방해하지 않는다. +""" + +from __future__ import annotations + +import logging +from datetime import datetime + +from PySide6.QtCore import Qt, QTimer, Signal +from PySide6.QtGui import QCloseEvent +from PySide6.QtWidgets import ( + QButtonGroup, + QFrame, + QHBoxLayout, + QLabel, + QMainWindow, + QMessageBox, + QPushButton, + QScrollArea, + QStackedWidget, + QVBoxLayout, + QWidget, +) + +from ..config import AppConfig +from ..constants import APP_NAME, APP_SLOGAN, APP_VERSION, user_data_dir +from ..core import EngineState, EngineStatus, TranslationEngine, TranslationLine +from .overlay import SubtitleOverlay +from .pages import GlossaryPage, HomePage, ModelsPage, SettingsPage, SubtitlePage +from .theme import SPACING, palette, stylesheet + +log = logging.getLogger(__name__) + +NAV = [ + ("홈", "실시간 번역"), + ("모델", "품질 티어"), + ("자막", "표시 설정"), + ("용어집", "고유명사 고정"), + ("설정", "세부 조정"), +] + + +class MainWindow(QMainWindow): + #: 엔진 콜백은 워커 스레드에서 오므로 시그널로 GUI 스레드에 넘긴다. + line_received = Signal(object) + status_received = Signal(object) + + def __init__(self, config: AppConfig) -> None: + super().__init__() + self.config = config + self.setWindowTitle(f"{APP_NAME} · {APP_SLOGAN}") + self.resize(1060, 820) + + self.engine = TranslationEngine( + config, + on_line=self.line_received.emit, + on_status=self.status_received.emit, + ) + self.overlay = SubtitleOverlay(config.subtitle, config.overlay) + self._transcript_file = None + + self._build_ui() + self._connect() + + self._save_timer = QTimer(self) + self._save_timer.setSingleShot(True) + self._save_timer.setInterval(800) # 입력 중 계속 쓰지 않도록 묶어서 저장 + self._save_timer.timeout.connect(lambda: self.config.save()) + + # --- UI ------------------------------------------------------------- + def _build_ui(self) -> None: + self.setStyleSheet(stylesheet(self.config.theme)) + + central = QWidget() + layout = QHBoxLayout(central) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + layout.addWidget(self._build_sidebar()) + + self.stack = QStackedWidget() + self.home_page = HomePage(self.config) + self.models_page = ModelsPage(self.config) + self.subtitle_page = SubtitlePage(self.config) + self.glossary_page = GlossaryPage(self.config) + self.settings_page = SettingsPage(self.config) + for page in ( + self.home_page, + self.models_page, + self.subtitle_page, + self.glossary_page, + self.settings_page, + ): + self.stack.addWidget(_scrollable(page)) + layout.addWidget(self.stack, 1) + self.setCentralWidget(central) + + def _build_sidebar(self) -> QFrame: + p = palette(self.config.theme) + bar = QFrame() + bar.setObjectName("Sidebar") + bar.setFixedWidth(212) + + layout = QVBoxLayout(bar) + layout.setContentsMargins(14, 22, 14, 18) + layout.setSpacing(6) + + brand = QLabel(APP_NAME) + brand.setStyleSheet( + f"font-size: 24px; font-weight: 800; color: {p.accent}; padding-left: 6px;" + ) + layout.addWidget(brand) + tag = QLabel(APP_SLOGAN) + tag.setObjectName("Hint") + tag.setStyleSheet(f"color: {p.text_dim}; padding-left: 7px;") + layout.addWidget(tag) + layout.addSpacing(SPACING + 8) + + self._nav_group = QButtonGroup(self) + self._nav_group.setExclusive(True) + for index, (title, hint) in enumerate(NAV): + button = QPushButton(title) + button.setObjectName("NavButton") + button.setCheckable(True) + button.setToolTip(hint) + button.setCursor(Qt.CursorShape.PointingHandCursor) + self._nav_group.addButton(button, index) + layout.addWidget(button) + self._nav_group.button(0).setChecked(True) + self._nav_group.idClicked.connect(self._goto_page) + + layout.addStretch(1) + version = QLabel(f"v{APP_VERSION}") + version.setObjectName("Hint") + version.setAlignment(Qt.AlignmentFlag.AlignCenter) + layout.addWidget(version) + return bar + + def _connect(self) -> None: + self.line_received.connect(self._on_line) + self.status_received.connect(self._on_status) + + self.home_page.start_requested.connect(self.start_engine) + self.home_page.stop_requested.connect(self.stop_engine) + self.home_page.config_changed.connect(self._schedule_save) + + self.models_page.tier_changed.connect(self._on_tier_changed) + self.models_page.config_changed.connect(self._schedule_save) + + self.subtitle_page.style_changed.connect(self._on_style_changed) + self.subtitle_page.overlay_toggled.connect(self._on_overlay_toggled) + + self.glossary_page.glossary_changed.connect(self._on_glossary_changed) + + self.settings_page.config_changed.connect(self._schedule_save) + self.settings_page.restart_needed.connect(self._on_restart_needed) + + self.overlay.geometry_changed.connect(lambda *_: self._schedule_save()) + self.overlay.closed.connect( + lambda: self.subtitle_page.overlay_check.setChecked(False) + ) + + # --- 엔진 ----------------------------------------------------------- + def start_engine(self) -> None: + if self.config.overlay.visible: + self.overlay.show() + self.config.save() + self.engine.start() + + def stop_engine(self) -> None: + self.engine.stop() + self.overlay.clear() + self._close_transcript() + + def _on_tier_changed(self, tier_key: str) -> None: + was_running = self.engine.running + if was_running: + self.engine.stop() + self.engine.models.switch_tier(tier_key) + if was_running: + self.engine.start() + + def _on_restart_needed(self) -> None: + if self.engine.running: + self.engine.stop() + self.engine.start() + + # --- 이벤트 --------------------------------------------------------- + def _on_line(self, line: TranslationLine) -> None: + self.overlay.set_line(line.translated_text, line.source_text, line.is_final) + self.home_page.on_line(line) + if line.is_final and self.config.log_transcripts: + self._write_transcript(line) + + def _on_status(self, status: EngineStatus) -> None: + self.home_page.on_status(status) + if status.state is EngineState.ERROR: + QMessageBox.warning(self, "오류", status.message) + + def _on_style_changed(self) -> None: + self.overlay.update() + self._schedule_save() + + def _on_overlay_toggled(self, visible: bool) -> None: + self.config.overlay.visible = visible + self.overlay.setVisible(visible) + self._schedule_save() + + def _on_glossary_changed(self) -> None: + self.engine.reload_glossary() + self._schedule_save() + + def _goto_page(self, index: int) -> None: + self.stack.setCurrentIndex(index) + + def _schedule_save(self) -> None: + self._save_timer.start() + + # --- 기록 ----------------------------------------------------------- + def _write_transcript(self, line: TranslationLine) -> None: + if self._transcript_file is None: + folder = user_data_dir() / "transcripts" + folder.mkdir(parents=True, exist_ok=True) + name = datetime.now().strftime("%Y%m%d-%H%M%S.txt") + # 세션 내내 열어두는 핸들이다. _close_transcript() 에서 닫는다. + self._transcript_file = open(folder / name, "a", encoding="utf-8") # noqa: SIM115 + stamp = datetime.now().strftime("%H:%M:%S") + self._transcript_file.write( + f"[{stamp}] {line.source_text}\n {line.translated_text}\n" + ) + self._transcript_file.flush() + + def _close_transcript(self) -> None: + if self._transcript_file is not None: + self._transcript_file.close() + self._transcript_file = None + + # --- 종료 ----------------------------------------------------------- + def closeEvent(self, event: QCloseEvent) -> None: # noqa: N802 + self.config.save() + self.engine.shutdown() + self._close_transcript() + self.overlay.close() + super().closeEvent(event) + + +def _scrollable(widget: QWidget) -> QScrollArea: + area = QScrollArea() + area.setWidgetResizable(True) + area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + holder = QWidget() + layout = QVBoxLayout(holder) + layout.setContentsMargins(26, 24, 26, 24) + layout.addWidget(widget) + area.setWidget(holder) + return area diff --git a/src/hearo/ui/overlay.py b/src/hearo/ui/overlay.py new file mode 100644 index 0000000..5470b82 --- /dev/null +++ b/src/hearo/ui/overlay.py @@ -0,0 +1,346 @@ +"""자막 오버레이 창. + +게임/영상 위에 얹는 무테두리 반투명 창. 기본 동작은 유튜브 자막과 같은 +'외곽선 있는 큰 흰 글씨'로, 어떤 배경 위에서도 읽히는 것을 최우선으로 한다. + +조작: + * 드래그 — 위치 이동 + * 우하단 모서리 — 크기 조절 + * 휠 — 글자 크기 조절 + * 우클릭 — 잠금 / 클릭 통과 / 숨기기 메뉴 +""" + +from __future__ import annotations + +from PySide6.QtCore import QPoint, QRect, Qt, QTimer, Signal +from PySide6.QtGui import ( + QAction, + QColor, + QFont, + QFontMetrics, + QPainter, + QPainterPath, + QPen, +) +from PySide6.QtWidgets import QApplication, QMenu, QWidget + +from ..config import OverlayConfig, SubtitleStyle + +GRIP = 18 # 우하단 리사이즈 핸들 크기 + + +def paint_subtitle( + painter: QPainter, + rect: QRect, + st: SubtitleStyle, + translated: str, + source: str = "", + partial: str = "", +) -> None: + """자막 한 화면을 그린다. 오버레이와 설정 미리보기가 이 함수를 공유한다.""" + painter.setRenderHint(QPainter.RenderHint.Antialiasing, True) + painter.setRenderHint(QPainter.RenderHint.TextAntialiasing, True) + + if st.background_opacity > 0: + bg = QColor(st.background_color) + bg.setAlpha(int(255 * st.background_opacity / 100)) + painter.setBrush(bg) + painter.setPen(Qt.PenStyle.NoPen) + painter.drawRoundedRect(rect, 12, 12) + + rows: list[tuple[str, QColor, int]] = [] + if st.show_source and source and translated: + rows.append((source, QColor(st.source_text_color), int(st.font_size * 0.62))) + main = translated or partial + if main: + color = QColor(st.text_color) + if not translated: # 아직 확정 전 — 흐리게 + color.setAlpha(170) + rows.append((main, color, st.font_size)) + if not rows: + return + + margin = 18 + available = rect.width() - margin * 2 + blocks: list[tuple[list[str], QColor, QFont]] = [] + total_h = 0 + for text, color, size in rows: + font = QFont(st.font_family, size) + font.setBold(st.bold) + lines = _wrap(text, QFontMetrics(font), available, st.max_lines) + blocks.append((lines, color, font)) + total_h += len(lines) * (QFontMetrics(font).height() + st.line_spacing) + + y = rect.y() + max(margin, (rect.height() - total_h) // 2) + for lines, color, font in blocks: + painter.setFont(font) + fm = QFontMetrics(font) + for line in lines: + width = fm.horizontalAdvance(line) + if st.align == "left": + x = rect.x() + margin + elif st.align == "right": + x = rect.right() - margin - width + else: + x = rect.x() + (rect.width() - width) // 2 + _draw_outlined_text(painter, x, y + fm.ascent(), line, color, font, st) + y += fm.height() + st.line_spacing + + +def _draw_outlined_text(painter, x, y, text, color, font, st: SubtitleStyle) -> None: + """외곽선 있는 글자. + + 스트로크는 글자 윤곽선 '가운데'에 그려지므로 절반이 글자 안쪽을 파먹는다. + 한글처럼 획이 얇은 글꼴은 그대로 두면 글자가 통째로 외곽선 색이 된다. + 그래서 스트로크를 먼저 깔고 그 위에 글자를 채우는 2패스로 그린다. + """ + if st.outline_width <= 0: + painter.setPen(color) + painter.setBrush(Qt.BrushStyle.NoBrush) + painter.drawText(x, y, text) + return + + path = QPainterPath() + path.addText(x, y, font, text) + + pen = QPen(QColor(st.outline_color)) + pen.setWidth(st.outline_width * 2) # 절반이 안쪽으로 먹히므로 2배 + pen.setJoinStyle(Qt.PenJoinStyle.RoundJoin) + pen.setCapStyle(Qt.PenCapStyle.RoundCap) + painter.setPen(pen) + painter.setBrush(Qt.BrushStyle.NoBrush) + painter.drawPath(path) + + painter.setPen(Qt.PenStyle.NoPen) + painter.setBrush(color) + painter.drawPath(path) + + +class SubtitlePreview(QWidget): + """설정 화면에서 쓰는 미리보기. 실제 자막과 같은 코드로 그린다.""" + + def __init__(self, style: SubtitleStyle, parent: QWidget | None = None): + super().__init__(parent) + self.style_cfg = style + self.translated = "" + self.source = "" + self.setMinimumHeight(140) + + def set_sample(self, translated: str, source: str) -> None: + self.translated = translated + self.source = source + self.update() + + def paintEvent(self, event) -> None: # noqa: N802 + painter = QPainter(self) + paint_subtitle( + painter, self.rect(), self.style_cfg, self.translated, self.source + ) + + +class SubtitleOverlay(QWidget): + """항상 위에 떠 있는 자막 창.""" + + geometry_changed = Signal(int, int, int, int) + closed = Signal() + + def __init__(self, style: SubtitleStyle, overlay: OverlayConfig) -> None: + super().__init__(None) + self.style_cfg = style + self.overlay_cfg = overlay + + self._final_text = "" + self._partial_text = "" + self._source_text = "" + self._drag_from: QPoint | None = None + self._resize_from: QRect | None = None + + self._fade = QTimer(self) + self._fade.setSingleShot(True) + self._fade.timeout.connect(self._clear_text) + + self.setWindowFlags( + Qt.WindowType.FramelessWindowHint + | Qt.WindowType.Tool + | Qt.WindowType.WindowStaysOnTopHint + ) + self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, True) + self.setMinimumSize(320, 90) + self.apply_config() + + # --- 외부 API ------------------------------------------------------- + def apply_config(self) -> None: + cfg = self.overlay_cfg + flags = self.windowFlags() + flags = ( + flags | Qt.WindowType.WindowStaysOnTopHint + if cfg.always_on_top + else flags & ~Qt.WindowType.WindowStaysOnTopHint + ) + self.setWindowFlags(flags) + self.setAttribute( + Qt.WidgetAttribute.WA_TransparentForMouseEvents, cfg.click_through + ) + + if cfg.x < 0 or cfg.y < 0: + screen = QApplication.primaryScreen() + area = screen.availableGeometry() if screen else QRect(0, 0, 1920, 1080) + cfg.width = min(cfg.width, area.width() - 80) + cfg.x = area.x() + (area.width() - cfg.width) // 2 + cfg.y = area.y() + area.height() - cfg.height - 90 + self.setGeometry(cfg.x, cfg.y, cfg.width, cfg.height) + if cfg.visible: + self.show() + self.update() + + def set_line(self, translated: str, source: str = "", is_final: bool = True) -> None: + if is_final: + self._final_text = translated or source + self._partial_text = "" + self._source_text = source + else: + self._partial_text = source + self._restart_fade() + self.update() + + def clear(self) -> None: + self._clear_text() + + # --- 그리기 --------------------------------------------------------- + def paintEvent(self, event) -> None: # noqa: N802 - Qt 규약 + painter = QPainter(self) + paint_subtitle( + painter, + self.rect(), + self.style_cfg, + self._final_text, + self._source_text, + self._partial_text, + ) + if not (self._final_text or self._partial_text) and not self.overlay_cfg.locked: + self._draw_placeholder(painter) + self._draw_grip(painter) + + def _draw_placeholder(self, painter) -> None: + painter.setPen(QColor(255, 255, 255, 90)) + font = QFont(self.style_cfg.font_family, 13) + painter.setFont(font) + painter.drawText( + self.rect(), + Qt.AlignmentFlag.AlignCenter, + "자막이 여기에 표시됩니다\n드래그로 이동 · 휠로 크기 · 우클릭으로 메뉴", + ) + + def _draw_grip(self, painter) -> None: + if self.overlay_cfg.locked or self.overlay_cfg.click_through: + return + painter.setPen(QPen(QColor(255, 255, 255, 110), 2)) + w, h = self.width(), self.height() + for offset in (5, 10, 15): + painter.drawLine(w - offset, h - 4, w - 4, h - offset) + + # --- 마우스 --------------------------------------------------------- + def mousePressEvent(self, event) -> None: # noqa: N802 + if self.overlay_cfg.locked or event.button() != Qt.MouseButton.LeftButton: + return + pos = event.position().toPoint() + if pos.x() > self.width() - GRIP and pos.y() > self.height() - GRIP: + self._resize_from = QRect(event.globalPosition().toPoint(), self.size()) + else: + self._drag_from = event.globalPosition().toPoint() - self.pos() + + def mouseMoveEvent(self, event) -> None: # noqa: N802 + if self._resize_from is not None: + delta = event.globalPosition().toPoint() - self._resize_from.topLeft() + self.resize( + max(self.minimumWidth(), self._resize_from.width() + delta.x()), + max(self.minimumHeight(), self._resize_from.height() + delta.y()), + ) + elif self._drag_from is not None: + self.move(event.globalPosition().toPoint() - self._drag_from) + + def mouseReleaseEvent(self, event) -> None: # noqa: N802 + if self._drag_from is not None or self._resize_from is not None: + self._drag_from = None + self._resize_from = None + g = self.geometry() + self.overlay_cfg.x, self.overlay_cfg.y = g.x(), g.y() + self.overlay_cfg.width, self.overlay_cfg.height = g.width(), g.height() + self.geometry_changed.emit(g.x(), g.y(), g.width(), g.height()) + + def wheelEvent(self, event) -> None: # noqa: N802 + if self.overlay_cfg.locked: + return + step = 2 if event.angleDelta().y() > 0 else -2 + self.style_cfg.font_size = max(12, min(120, self.style_cfg.font_size + step)) + self.update() + + def contextMenuEvent(self, event) -> None: # noqa: N802 + menu = QMenu(self) + cfg = self.overlay_cfg + + lock = QAction("위치 잠금", self, checkable=True, checked=cfg.locked) + lock.toggled.connect(self._set_locked) + menu.addAction(lock) + + through = QAction("클릭 통과", self, checkable=True, checked=cfg.click_through) + through.toggled.connect(self._set_click_through) + menu.addAction(through) + + top = QAction("항상 위에", self, checkable=True, checked=cfg.always_on_top) + top.toggled.connect(self._set_always_on_top) + menu.addAction(top) + + menu.addSeparator() + menu.addAction("자막 지우기", self.clear) + menu.addAction("자막 창 숨기기", self.hide_overlay) + menu.exec(event.globalPos()) + + # --- 메뉴 동작 ------------------------------------------------------ + def _set_locked(self, value: bool) -> None: + self.overlay_cfg.locked = value + self.update() + + def _set_click_through(self, value: bool) -> None: + self.overlay_cfg.click_through = value + self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, value) + self.update() + + def _set_always_on_top(self, value: bool) -> None: + self.overlay_cfg.always_on_top = value + self.apply_config() + + def hide_overlay(self) -> None: + self.overlay_cfg.visible = False + self.hide() + self.closed.emit() + + def _restart_fade(self) -> None: + self._fade.stop() + if self.style_cfg.fade_out_ms > 0: + self._fade.start(self.style_cfg.fade_out_ms) + + def _clear_text(self) -> None: + self._final_text = "" + self._partial_text = "" + self._source_text = "" + self.update() + + +def _wrap(text: str, fm: QFontMetrics, width: int, max_lines: int) -> list[str]: + """단어 단위 줄바꿈. 마지막 줄이 넘치면 앞부분을 버리고 최신 내용을 남긴다.""" + if width <= 0 or not text: + return [text] if text else [] + lines: list[str] = [] + current = "" + for word in text.split(): + candidate = f"{current} {word}".strip() + if fm.horizontalAdvance(candidate) <= width or not current: + current = candidate + else: + lines.append(current) + current = word + if current: + lines.append(current) + # 실시간 자막에서는 앞이 아니라 뒤(최신)를 보여주는 게 맞다. + return lines[-max_lines:] if max_lines > 0 else lines diff --git a/src/hearo/ui/pages/__init__.py b/src/hearo/ui/pages/__init__.py new file mode 100644 index 0000000..1a2d952 --- /dev/null +++ b/src/hearo/ui/pages/__init__.py @@ -0,0 +1,7 @@ +from .glossary_page import GlossaryPage +from .home import HomePage +from .models_page import ModelsPage +from .settings_page import SettingsPage +from .subtitle_page import SubtitlePage + +__all__ = ["GlossaryPage", "HomePage", "ModelsPage", "SettingsPage", "SubtitlePage"] diff --git a/src/hearo/ui/pages/glossary_page.py b/src/hearo/ui/pages/glossary_page.py new file mode 100644 index 0000000..b201011 --- /dev/null +++ b/src/hearo/ui/pages/glossary_page.py @@ -0,0 +1,256 @@ +"""용어집 — 게임·방송 고유명사를 원하는 번역으로 고정한다.""" + +from __future__ import annotations + +import csv +from pathlib import Path + +from PySide6.QtCore import Qt, Signal +from PySide6.QtWidgets import ( + QAbstractItemView, + QCheckBox, + QFileDialog, + QHBoxLayout, + QHeaderView, + QLabel, + QLineEdit, + QMessageBox, + QPushButton, + QTableWidget, + QTableWidgetItem, + QVBoxLayout, + QWidget, +) + +from ...config import AppConfig +from ...constants import LANGUAGE_CODES, LANGUAGES, user_data_dir +from ...models import Glossary, GlossaryEntry +from ..theme import SPACING +from ..widgets import Card, PageHeader + +COLUMNS = ["원문 용어", *[f"{LANGUAGES[c]['label']} 역어" for c in LANGUAGE_CODES], "메모"] + + +class GlossaryPage(QWidget): + glossary_changed = Signal() + + def __init__(self, config: AppConfig, parent: QWidget | None = None): + super().__init__(parent) + self.config = config + self.path = Path(config.glossary.path or user_data_dir() / "glossary.json") + self.glossary = Glossary.load(self.path, config.glossary.case_sensitive) + + root = QVBoxLayout(self) + root.setContentsMargins(0, 0, 0, 0) + root.setSpacing(SPACING + 4) + root.addWidget( + PageHeader( + "용어집", + "여기 등록한 단어는 번역 모델이 마음대로 바꾸지 못하고 지정한 역어로 고정됩니다. " + "모델을 추가로 학습시키지 않아도 바로 적용됩니다.", + ) + ) + root.addWidget(self._build_options_card()) + root.addWidget(self._build_table_card(), 1) + self._reload_table() + + # --- 구성 ----------------------------------------------------------- + def _build_options_card(self) -> Card: + card = Card() + row = QWidget() + layout = QHBoxLayout(row) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(10) + + self.enabled_check = QCheckBox("용어집 사용") + self.enabled_check.setChecked(self.config.glossary.enabled) + self.enabled_check.toggled.connect(self._on_enabled) + layout.addWidget(self.enabled_check) + + self.case_check = QCheckBox("대소문자 구분") + self.case_check.setChecked(self.config.glossary.case_sensitive) + self.case_check.toggled.connect(self._on_case) + layout.addWidget(self.case_check) + layout.addStretch(1) + + for label, slot in ( + ("CSV 가져오기", self._import_csv), + ("CSV 내보내기", self._export_csv), + ): + button = QPushButton(label) + button.clicked.connect(slot) + layout.addWidget(button) + + card.add(row) + return card + + def _build_table_card(self) -> Card: + card = Card( + "등록된 용어", + "역어를 비워두면 그 언어에서는 규칙을 적용하지 않습니다. " + "CSV 형식: 원문,한국어,English,日本語,中文,메모", + ) + + search_row = QWidget() + search_layout = QHBoxLayout(search_row) + search_layout.setContentsMargins(0, 0, 0, 0) + search_layout.setSpacing(10) + self.search = QLineEdit() + self.search.setPlaceholderText("용어 검색…") + self.search.textChanged.connect(self._filter) + search_layout.addWidget(self.search, 1) + for label, slot in (("행 추가", self._add_row), ("선택 삭제", self._delete_rows)): + button = QPushButton(label) + button.clicked.connect(slot) + search_layout.addWidget(button) + card.add(search_row) + + self.table = QTableWidget(0, len(COLUMNS)) + self.table.setHorizontalHeaderLabels(COLUMNS) + self.table.verticalHeader().setVisible(False) + self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) + self.table.horizontalHeader().setSectionResizeMode( + QHeaderView.ResizeMode.Stretch + ) + self.table.itemChanged.connect(self._on_item_changed) + card.add(self.table) + + self.count_label = card.add( + _hint(f"{len(self.glossary)}개 등록됨") + ) + return card + + # --- 데이터 --------------------------------------------------------- + def _reload_table(self) -> None: + self.table.blockSignals(True) + self.table.setRowCount(0) + for entry in self.glossary.entries: + self._append_row(entry) + self.table.blockSignals(False) + self.count_label.setText(f"{len(self.glossary)}개 등록됨") + + def _append_row(self, entry: GlossaryEntry) -> None: + row = self.table.rowCount() + self.table.insertRow(row) + values = [entry.source, *[entry.target_for(c) for c in LANGUAGE_CODES], entry.note] + for col, value in enumerate(values): + self.table.setItem(row, col, QTableWidgetItem(value)) + + def _collect(self) -> list[GlossaryEntry]: + entries: list[GlossaryEntry] = [] + for row in range(self.table.rowCount()): + source_item = self.table.item(row, 0) + source = source_item.text().strip() if source_item else "" + if not source: + continue + targets: dict[str, str] = {} + for i, code in enumerate(LANGUAGE_CODES, start=1): + item = self.table.item(row, i) + text = item.text().strip() if item else "" + if text: + targets[code] = text + note_item = self.table.item(row, len(COLUMNS) - 1) + entries.append( + GlossaryEntry( + source=source, + targets=targets, + note=note_item.text().strip() if note_item else "", + ) + ) + return entries + + def _save(self) -> None: + self.glossary = Glossary(self._collect(), self.config.glossary.case_sensitive) + self.glossary.save(self.path) + self.count_label.setText(f"{len(self.glossary)}개 등록됨") + self.glossary_changed.emit() + + # --- 동작 ----------------------------------------------------------- + def _on_item_changed(self, *_) -> None: + self._save() + + def _on_enabled(self, value: bool) -> None: + self.config.glossary.enabled = value + self.glossary_changed.emit() + + def _on_case(self, value: bool) -> None: + self.config.glossary.case_sensitive = value + self._save() + + def _add_row(self) -> None: + self.table.blockSignals(True) + self._append_row(GlossaryEntry(source="")) + self.table.blockSignals(False) + self.table.editItem(self.table.item(self.table.rowCount() - 1, 0)) + + def _delete_rows(self) -> None: + rows = sorted({i.row() for i in self.table.selectedIndexes()}, reverse=True) + if not rows: + return + self.table.blockSignals(True) + for row in rows: + self.table.removeRow(row) + self.table.blockSignals(False) + self._save() + + def _filter(self, text: str) -> None: + needle = text.strip().casefold() + for row in range(self.table.rowCount()): + haystack = " ".join( + (self.table.item(row, c).text() if self.table.item(row, c) else "") + for c in range(self.table.columnCount()) + ).casefold() + self.table.setRowHidden(row, bool(needle) and needle not in haystack) + + def _import_csv(self) -> None: + path, _ = QFileDialog.getOpenFileName(self, "CSV 가져오기", "", "CSV (*.csv)") + if not path: + return + try: + with open(path, encoding="utf-8-sig", newline="") as fh: + rows = [r for r in csv.reader(fh) if r and r[0].strip()] + except OSError as exc: + QMessageBox.warning(self, "가져오기 실패", str(exc)) + return + + # 첫 줄이 헤더처럼 보이면 건너뛴다. + if rows and rows[0][0].strip() in ("원문", "원문 용어", "source"): + rows = rows[1:] + + added = 0 + for row in rows: + cells = [c.strip() for c in row] + [""] * (len(COLUMNS) - len(row)) + targets = { + code: cells[i] + for i, code in enumerate(LANGUAGE_CODES, start=1) + if cells[i] + } + self.glossary.add( + GlossaryEntry(source=cells[0], targets=targets, note=cells[len(COLUMNS) - 1]) + ) + added += 1 + self.glossary.save(self.path) + self._reload_table() + self.glossary_changed.emit() + QMessageBox.information(self, "가져오기 완료", f"{added}개 용어를 반영했습니다.") + + def _export_csv(self) -> None: + path, _ = QFileDialog.getSaveFileName( + self, "CSV 내보내기", "glossary.csv", "CSV (*.csv)" + ) + if not path: + return + with open(path, "w", encoding="utf-8-sig", newline="") as fh: + writer = csv.writer(fh) + writer.writerow(COLUMNS) + for entry in self.glossary.entries: + writer.writerow( + [entry.source, *[entry.target_for(c) for c in LANGUAGE_CODES], entry.note] + ) + + +def _hint(text: str) -> QLabel: + label = QLabel(text) + label.setObjectName("Hint") + label.setAlignment(Qt.AlignmentFlag.AlignRight) + return label diff --git a/src/hearo/ui/pages/home.py b/src/hearo/ui/pages/home.py new file mode 100644 index 0000000..12f0fd3 --- /dev/null +++ b/src/hearo/ui/pages/home.py @@ -0,0 +1,226 @@ +"""홈 — 대상 선택, 언어, 시작/정지, 실시간 로그.""" + +from __future__ import annotations + +from PySide6.QtCore import Qt, Signal +from PySide6.QtWidgets import ( + QComboBox, + QHBoxLayout, + QLabel, + QListWidget, + QListWidgetItem, + QPushButton, + QVBoxLayout, + QWidget, +) + +from ...audio import capture_capabilities, list_sources +from ...config import AppConfig +from ...constants import LANGUAGES +from ...core.events import EngineState, EngineStatus, TranslationLine +from ..theme import SPACING, palette +from ..widgets import Card, LevelMeter, PageHeader, StatusPill + +MAX_LOG_ROWS = 300 + + +class HomePage(QWidget): + start_requested = Signal() + stop_requested = Signal() + config_changed = Signal() + + def __init__(self, config: AppConfig, parent: QWidget | None = None): + super().__init__(parent) + self.config = config + self._sources: list = [] + + root = QVBoxLayout(self) + root.setContentsMargins(0, 0, 0, 0) + root.setSpacing(SPACING + 4) + root.addWidget( + PageHeader("실시간 번역", "소리를 받아올 프로그램과 번역할 언어를 고르세요.") + ) + + root.addWidget(self._build_source_card()) + root.addWidget(self._build_control_card()) + root.addWidget(self._build_log_card(), 1) + + self.refresh_sources() + + # --- 구성 ----------------------------------------------------------- + def _build_source_card(self) -> Card: + card = Card("소리 받아올 곳", "") + self.capability_label = QLabel() + self.capability_label.setObjectName("Hint") + self.capability_label.setWordWrap(True) + card.add(self.capability_label) + + row = QWidget() + layout = QHBoxLayout(row) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(10) + + self.source_combo = QComboBox() + self.source_combo.setMinimumWidth(360) + self.source_combo.currentIndexChanged.connect(self._on_source_changed) + layout.addWidget(self.source_combo, 1) + + refresh = QPushButton("새로고침") + refresh.clicked.connect(self.refresh_sources) + layout.addWidget(refresh) + card.add(row) + return card + + def _build_control_card(self) -> Card: + card = Card() + row = QWidget() + layout = QHBoxLayout(row) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(12) + + self.source_lang = QComboBox() + self.source_lang.addItem("자동 감지", "auto") + for code, meta in LANGUAGES.items(): + self.source_lang.addItem(meta["label"], code) + self.source_lang.setCurrentIndex( + max(0, self.source_lang.findData(self.config.models.source_lang)) + ) + self.source_lang.currentIndexChanged.connect(self._on_lang_changed) + + self.target_lang = QComboBox() + for code, meta in LANGUAGES.items(): + self.target_lang.addItem(meta["label"], code) + self.target_lang.setCurrentIndex( + max(0, self.target_lang.findData(self.config.models.target_lang)) + ) + self.target_lang.currentIndexChanged.connect(self._on_lang_changed) + + layout.addWidget(QLabel("원본")) + layout.addWidget(self.source_lang) + arrow = QLabel("→") + arrow.setStyleSheet(f"color: {palette('dark').accent}; font-size: 18px;") + layout.addWidget(arrow) + layout.addWidget(QLabel("번역")) + layout.addWidget(self.target_lang) + layout.addStretch(1) + + self.status_pill = StatusPill() + layout.addWidget(self.status_pill) + + self.toggle_button = QPushButton("번역 시작") + self.toggle_button.setObjectName("Primary") + self.toggle_button.setMinimumWidth(140) + self.toggle_button.clicked.connect(self._on_toggle) + layout.addWidget(self.toggle_button) + + card.add(row) + self.level_meter = LevelMeter() + card.add(self.level_meter) + return card + + def _build_log_card(self) -> Card: + card = Card("최근 자막", "확정된 문장이 위로 쌓입니다. 지연 시간도 함께 보여줍니다.") + self.log_list = QListWidget() + self.log_list.setWordWrap(True) + self.log_list.setAlternatingRowColors(False) + card.add(self.log_list) + return card + + # --- 동작 ----------------------------------------------------------- + def refresh_sources(self) -> None: + caps = capture_capabilities() + self._sources = list_sources() + + self.source_combo.blockSignals(True) + self.source_combo.clear() + for src in self._sources: + prefix = "🎮" if src.kind == "process" else "🔊" + self.source_combo.addItem(f"{prefix} {src.label} — {src.detail}", src) + if not self._sources: + self.source_combo.addItem("사용 가능한 오디오 대상이 없습니다", None) + self._select_saved_source() + self.source_combo.blockSignals(False) + self._on_source_changed() + + if caps["process"]: + msg = "프로그램별 캡처 사용 가능 — 선택한 프로그램의 소리만 정확히 받아옵니다." + elif caps["device"]: + msg = ( + "프로그램별 캡처 보조 프로그램(hearo_capture.exe)이 없어 " + "출력 장치 전체 소리를 받습니다. native/process_loopback/build.ps1 로 빌드하면 " + "프로그램 단위로 분리됩니다." + ) + else: + msg = "이 환경에서는 오디오 캡처를 할 수 없습니다. Windows에서 실행하세요." + self.capability_label.setText(msg) + + def _select_saved_source(self) -> None: + cfg = self.config.audio + for i, src in enumerate(self._sources): + if cfg.backend == "process" and src.kind == "process" and src.pid == cfg.target_pid: + self.source_combo.setCurrentIndex(i) + return + if cfg.backend == "device" and src.kind == "device" and src.device_index == cfg.device_index: + self.source_combo.setCurrentIndex(i) + return + + def _on_source_changed(self, *_) -> None: + src = self.source_combo.currentData() + cfg = self.config.audio + if src is None: + return + if src.kind == "process": + cfg.backend = "process" + cfg.target_pid = src.pid + cfg.target_process_name = src.label + else: + cfg.backend = "device" + cfg.device_index = src.device_index + cfg.target_pid = 0 + self.config_changed.emit() + + def _on_lang_changed(self, *_) -> None: + self.config.models.source_lang = self.source_lang.currentData() + self.config.models.target_lang = self.target_lang.currentData() + self.config_changed.emit() + + def _on_toggle(self) -> None: + if self.toggle_button.property("running"): + self.stop_requested.emit() + else: + self.start_requested.emit() + + # --- 엔진 연동 ------------------------------------------------------ + def on_status(self, status: EngineStatus) -> None: + running = status.state in (EngineState.RUNNING, EngineState.LOADING) + self.toggle_button.setProperty("running", running) + self.toggle_button.setText("번역 정지" if running else "번역 시작") + self.toggle_button.setObjectName("Danger" if running else "Primary") + self.toggle_button.style().polish(self.toggle_button) + + kind = { + EngineState.RUNNING: "ok", + EngineState.LOADING: "busy", + EngineState.STOPPING: "busy", + EngineState.ERROR: "error", + }.get(status.state, "idle") + self.status_pill.set_status(status.message or status.state.value, kind) + self.level_meter.set_level(status.level) + + for widget in (self.source_combo, self.source_lang, self.target_lang): + widget.setEnabled(not running) + + def on_line(self, line: TranslationLine) -> None: + if not line.is_final: + return + item = QListWidgetItem( + f"{line.translated_text}\n" + f" {line.source_text} · {line.latency_s:.1f}초 " + f"(인식 {line.asr_ms}ms · 번역 {line.mt_ms}ms)" + ) + item.setTextAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter) + item.setToolTip(line.source_text) + self.log_list.insertItem(0, item) + while self.log_list.count() > MAX_LOG_ROWS: + self.log_list.takeItem(self.log_list.count() - 1) + self.log_list.scrollToTop() diff --git a/src/hearo/ui/pages/models_page.py b/src/hearo/ui/pages/models_page.py new file mode 100644 index 0000000..4185874 --- /dev/null +++ b/src/hearo/ui/pages/models_page.py @@ -0,0 +1,221 @@ +"""모델 — 5단계 품질 티어 선택과 GPU 상태.""" + +from __future__ import annotations + +from PySide6.QtCore import Qt, Signal +from PySide6.QtWidgets import ( + QButtonGroup, + QComboBox, + QFrame, + QHBoxLayout, + QLabel, + QLineEdit, + QPushButton, + QRadioButton, + QVBoxLayout, + QWidget, +) + +from ...config import AppConfig +from ...models import Tier, detect_gpu, ordered_tiers +from ..theme import SPACING, palette +from ..widgets import Card, PageHeader + + +class TierCard(QFrame): + """티어 하나를 라디오 버튼 카드로.""" + + def __init__(self, tier: Tier, gpu_vram_gb: float, parent: QWidget | None = None): + super().__init__(parent) + self.tier = tier + self.setObjectName("Card") + p = palette("dark") + + layout = QHBoxLayout(self) + layout.setContentsMargins(16, 14, 16, 14) + layout.setSpacing(14) + + self.radio = QRadioButton() + layout.addWidget(self.radio, 0, Qt.AlignmentFlag.AlignTop) + + text = QVBoxLayout() + text.setSpacing(3) + + head = QHBoxLayout() + head.setSpacing(8) + name = QLabel(f"{tier.order}. {tier.name}") + name.setStyleSheet("font-size: 16px; font-weight: 700;") + head.addWidget(name) + if tier.recommended: + head.addWidget(_badge("추천", p.accent)) + if tier.best_after_finetune: + head.addWidget(_badge("추가학습 최적", p.success)) + if gpu_vram_gb and gpu_vram_gb < tier.min_vram_gb: + head.addWidget(_badge(f"VRAM {tier.min_vram_gb:g}GB 필요", p.warning)) + head.addStretch(1) + text.addLayout(head) + + tagline = QLabel(tier.tagline) + tagline.setObjectName("Subtitle") + text.addWidget(tagline) + + spec = QLabel( + f"예상 지연 {tier.approx_latency_s:g}초 · 권장 VRAM {tier.min_vram_gb:g}GB " + f"· 내려받기 약 {tier.download_gb:g}GB" + ) + spec.setObjectName("Hint") + text.addWidget(spec) + + if tier.notes: + note = QLabel(tier.notes) + note.setObjectName("Hint") + note.setWordWrap(True) + text.addWidget(note) + + layout.addLayout(text, 1) + + def set_selected(self, selected: bool) -> None: + self.setObjectName("CardSelected" if selected else "Card") + self.style().unpolish(self) + self.style().polish(self) + + +class ModelsPage(QWidget): + tier_changed = Signal(str) + config_changed = Signal() + + def __init__(self, config: AppConfig, parent: QWidget | None = None): + super().__init__(parent) + self.config = config + self._cards: list[TierCard] = [] + + root = QVBoxLayout(self) + root.setContentsMargins(0, 0, 0, 0) + root.setSpacing(SPACING + 4) + root.addWidget( + PageHeader( + "모델", + "속도 우선(1)부터 품질 우선(5)까지 다섯 단계입니다. " + "모델은 처음 사용할 때 자동으로 내려받습니다.", + ) + ) + + root.addWidget(self._build_gpu_card()) + + self._group = QButtonGroup(self) + self._group.setExclusive(True) + for tier in ordered_tiers(): + card = TierCard(tier, self._gpu_vram_gb) + self._group.addButton(card.radio, tier.order) + card.radio.toggled.connect( + lambda checked, t=tier: self._on_tier_selected(t) if checked else None + ) + self._cards.append(card) + root.addWidget(card) + + root.addWidget(self._build_lora_card()) + root.addStretch(1) + self._apply_selection(config.models.tier) + + # --- 구성 ----------------------------------------------------------- + def _build_gpu_card(self) -> Card: + gpu = detect_gpu() + self._gpu_vram_gb = gpu.total_vram_mb / 1024 if gpu.available else 0.0 + p = palette("dark") + + card = Card("실행 환경") + row = QWidget() + layout = QHBoxLayout(row) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(12) + + if gpu.available: + text = ( + f"GPU: {gpu.name} · VRAM {gpu.total_vram_mb / 1024:.1f}GB " + f"(여유 {gpu.free_vram_mb / 1024:.1f}GB)" + ) + color = p.success + else: + text = f"GPU를 쓸 수 없습니다 — {gpu.reason} (CPU로도 동작하지만 많이 느립니다)" + color = p.warning + info = QLabel(text) + info.setStyleSheet(f"color: {color}; font-weight: 600;") + info.setWordWrap(True) + layout.addWidget(info, 1) + + self.device_combo = QComboBox() + self.device_combo.addItem("GPU (CUDA)", "cuda") + self.device_combo.addItem("CPU", "cpu") + self.device_combo.setCurrentIndex( + max(0, self.device_combo.findData(self.config.models.device)) + ) + self.device_combo.setEnabled(gpu.available) + self.device_combo.currentIndexChanged.connect(self._on_device_changed) + layout.addWidget(self.device_combo) + + card.add(row) + return card + + def _build_lora_card(self) -> Card: + card = Card( + "추가학습 어댑터 (선택)", + "게임·방송 용어로 따로 학습시킨 LoRA 어댑터 폴더를 지정하면 번역 모델에 얹습니다. " + "scripts/finetune_mt.py 로 만들 수 있습니다.", + ) + row = QWidget() + layout = QHBoxLayout(row) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(10) + + self.lora_edit = QLineEdit(self.config.models.lora_adapter_path) + self.lora_edit.setPlaceholderText("비워두면 사용하지 않습니다") + self.lora_edit.editingFinished.connect(self._on_lora_changed) + layout.addWidget(self.lora_edit, 1) + + browse = QPushButton("폴더 선택") + browse.clicked.connect(self._browse_lora) + layout.addWidget(browse) + + card.add(row) + return card + + # --- 동작 ----------------------------------------------------------- + def _apply_selection(self, tier_key: str) -> None: + for card in self._cards: + selected = card.tier.key == tier_key + card.radio.setChecked(selected) + card.set_selected(selected) + + def _on_tier_selected(self, tier: Tier) -> None: + if self.config.models.tier == tier.key: + return + self.config.models.tier = tier.key + for card in self._cards: + card.set_selected(card.tier.key == tier.key) + self.tier_changed.emit(tier.key) + self.config_changed.emit() + + def _on_device_changed(self) -> None: + self.config.models.device = self.device_combo.currentData() + self.config_changed.emit() + + def _on_lora_changed(self) -> None: + self.config.models.lora_adapter_path = self.lora_edit.text().strip() + self.config_changed.emit() + + def _browse_lora(self) -> None: + from PySide6.QtWidgets import QFileDialog + + path = QFileDialog.getExistingDirectory(self, "LoRA 어댑터 폴더 선택") + if path: + self.lora_edit.setText(path) + self._on_lora_changed() + + +def _badge(text: str, color: str) -> QLabel: + label = QLabel(text) + label.setStyleSheet( + f"color: {color}; border: 1px solid {color}; border-radius: 9px;" + f"padding: 1px 8px; font-size: 11px; font-weight: 700;" + ) + return label diff --git a/src/hearo/ui/pages/settings_page.py b/src/hearo/ui/pages/settings_page.py new file mode 100644 index 0000000..9432832 --- /dev/null +++ b/src/hearo/ui/pages/settings_page.py @@ -0,0 +1,176 @@ +"""설정 — 인식 민감도, 응답 속도, 기타 동작.""" + +from __future__ import annotations + +from PySide6.QtCore import Qt, Signal +from PySide6.QtWidgets import ( + QCheckBox, + QComboBox, + QHBoxLayout, + QLabel, + QPushButton, + QSlider, + QSpinBox, + QVBoxLayout, + QWidget, +) + +from ...config import AppConfig +from ...constants import APP_NAME, APP_SLOGAN, APP_VERSION, user_data_dir +from ..theme import SPACING +from ..widgets import Card, PageHeader + + +class SettingsPage(QWidget): + config_changed = Signal() + #: 오디오 파이프라인 설정이 바뀌어 엔진 재시작이 필요할 때 + restart_needed = Signal() + + def __init__(self, config: AppConfig, parent: QWidget | None = None): + super().__init__(parent) + self.config = config + + root = QVBoxLayout(self) + root.setContentsMargins(0, 0, 0, 0) + root.setSpacing(SPACING + 4) + root.addWidget(PageHeader("설정", "인식 민감도와 응답 속도를 조절합니다.")) + + root.addWidget(self._build_audio_card()) + root.addWidget(self._build_behavior_card()) + root.addWidget(self._build_about_card()) + root.addStretch(1) + + # --- 구성 ----------------------------------------------------------- + def _build_audio_card(self) -> Card: + cfg = self.config.audio + card = Card( + "음성 인식", + "소리가 작아 인식이 안 되면 입력 증폭을 올리고, 주변 소음까지 잡히면 " + "민감도를 낮추세요. 값을 바꾸면 다음 시작부터 적용됩니다.", + ) + form = card.add_form() + + self.gain_slider, gain_row = _slider_row( + int(cfg.input_gain * 100), 25, 400, "%", self._on_audio_changed + ) + form.addRow("입력 증폭", gain_row) + + self.vad_combo = QComboBox() + for label, value in ( + ("낮음 — 작은 소리도 잡음", 0), + ("보통", 1), + ("높음 (기본)", 2), + ("매우 높음 — 또렷한 말만", 3), + ): + self.vad_combo.addItem(label, value) + self.vad_combo.setCurrentIndex(max(0, self.vad_combo.findData(cfg.vad_aggressiveness))) + self.vad_combo.currentIndexChanged.connect(self._on_audio_changed) + form.addRow("잡음 억제", self.vad_combo) + + self.silence_spin = _spin(cfg.silence_ms, 200, 2000, 50, " ms", self._on_audio_changed) + self.silence_spin.setToolTip( + "이 시간만큼 조용하면 한 문장이 끝난 것으로 봅니다. " + "짧게 하면 자막이 빨리 뜨지만 문장이 자주 끊깁니다." + ) + form.addRow("문장 끊는 침묵", self.silence_spin) + + self.max_spin = _spin(cfg.max_segment_ms, 3000, 30_000, 1000, " ms", self._on_audio_changed) + self.max_spin.setToolTip("말이 계속 이어져도 이 길이에서 강제로 끊습니다.") + form.addRow("문장 최대 길이", self.max_spin) + + self.partial_spin = _spin( + cfg.partial_interval_ms, 0, 3000, 100, " ms", self._on_audio_changed + ) + self.partial_spin.setToolTip( + "말하는 도중에도 인식 중인 내용을 흐리게 미리 보여줍니다. " + "0이면 끕니다. GPU 부하가 조금 늘어납니다." + ) + form.addRow("중간 결과 갱신", self.partial_spin) + return card + + def _build_behavior_card(self) -> Card: + card = Card("동작") + form = card.add_form() + + self.theme_combo = QComboBox() + self.theme_combo.addItem("어두운 테마", "dark") + self.theme_combo.addItem("밝은 테마", "light") + self.theme_combo.setCurrentIndex(max(0, self.theme_combo.findData(self.config.theme))) + self.theme_combo.currentIndexChanged.connect(self._on_behavior_changed) + form.addRow("테마", self.theme_combo) + + self.preload_check = QCheckBox("시작할 때 모델을 미리 올려두기") + self.preload_check.setChecked(self.config.models.preload_on_start) + self.preload_check.setToolTip( + "끄면 프로그램이 빨리 뜨지만 첫 문장 번역이 오래 걸립니다." + ) + self.preload_check.toggled.connect(self._on_behavior_changed) + form.addRow("", self.preload_check) + + self.log_check = QCheckBox("번역 기록을 파일로 남기기") + self.log_check.setChecked(self.config.log_transcripts) + self.log_check.toggled.connect(self._on_behavior_changed) + form.addRow("", self.log_check) + + open_folder = QPushButton("설정 폴더 열기") + open_folder.clicked.connect(self._open_folder) + form.addRow("", open_folder) + return card + + def _build_about_card(self) -> Card: + card = Card(f"{APP_NAME} v{APP_VERSION}", APP_SLOGAN) + path = QLabel(f"설정 위치: {user_data_dir()}") + path.setObjectName("Hint") + path.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) + path.setWordWrap(True) + card.add(path) + return card + + # --- 동작 ----------------------------------------------------------- + def _on_audio_changed(self, *_) -> None: + cfg = self.config.audio + cfg.input_gain = self.gain_slider.value() / 100 + cfg.vad_aggressiveness = self.vad_combo.currentData() + cfg.silence_ms = self.silence_spin.value() + cfg.max_segment_ms = self.max_spin.value() + cfg.partial_interval_ms = self.partial_spin.value() + self.config_changed.emit() + self.restart_needed.emit() + + def _on_behavior_changed(self, *_) -> None: + self.config.theme = self.theme_combo.currentData() + self.config.models.preload_on_start = self.preload_check.isChecked() + self.config.log_transcripts = self.log_check.isChecked() + self.config_changed.emit() + + def _open_folder(self) -> None: + from PySide6.QtCore import QUrl + from PySide6.QtGui import QDesktopServices + + QDesktopServices.openUrl(QUrl.fromLocalFile(str(user_data_dir()))) + + +def _spin(value, low, high, step, suffix, slot) -> QSpinBox: + spin = QSpinBox() + spin.setRange(low, high) + spin.setSingleStep(step) + spin.setSuffix(suffix) + spin.setValue(value) + spin.valueChanged.connect(slot) + return spin + + +def _slider_row(value, low, high, suffix, slot) -> tuple[QSlider, QWidget]: + row = QWidget() + layout = QHBoxLayout(row) + layout.setContentsMargins(0, 0, 0, 0) + slider = QSlider(Qt.Orientation.Horizontal) + slider.setRange(low, high) + slider.setValue(value) + label = QLabel(f"{value}{suffix}") + label.setMinimumWidth(48) + slider.valueChanged.connect(lambda v: label.setText(f"{v}{suffix}")) + slider.valueChanged.connect(slot) + layout.addWidget(slider, 1) + layout.addWidget(label) + return slider, row diff --git a/src/hearo/ui/pages/subtitle_page.py b/src/hearo/ui/pages/subtitle_page.py new file mode 100644 index 0000000..f70d9dd --- /dev/null +++ b/src/hearo/ui/pages/subtitle_page.py @@ -0,0 +1,216 @@ +"""자막 — 글꼴·색·크기 등 표시 설정과 실시간 미리보기.""" + +from __future__ import annotations + +from PySide6.QtCore import Qt, Signal +from PySide6.QtGui import QColor, QFont, QFontDatabase +from PySide6.QtWidgets import ( + QCheckBox, + QColorDialog, + QComboBox, + QFontComboBox, + QHBoxLayout, + QLabel, + QPushButton, + QSlider, + QSpinBox, + QVBoxLayout, + QWidget, +) + +from ...config import AppConfig +from ..overlay import SubtitlePreview +from ..theme import SPACING +from ..widgets import Card, PageHeader + +PREVIEW_TEXT = "적이 왼쪽에서 들어온다, 지금 바로 빠져!" +PREVIEW_SOURCE = "Enemy coming from the left, fall back now!" + + +class ColorButton(QPushButton): + color_picked = Signal(str) + + def __init__(self, color: str, parent: QWidget | None = None): + super().__init__(parent) + self.setFixedSize(52, 30) + self.setCursor(Qt.CursorShape.PointingHandCursor) + self.set_color(color) + self.clicked.connect(self._pick) + + @property + def color(self) -> str: + return self._color + + def set_color(self, color: str) -> None: + self._color = color + self.setStyleSheet( + f"background: {color}; border: 1px solid #3A4152; border-radius: 6px;" + ) + + def _pick(self) -> None: + chosen = QColorDialog.getColor(QColor(self._color), self, "색 선택") + if chosen.isValid(): + self.set_color(chosen.name()) + self.color_picked.emit(chosen.name()) + + +class SubtitlePage(QWidget): + style_changed = Signal() + overlay_toggled = Signal(bool) + + def __init__(self, config: AppConfig, parent: QWidget | None = None): + super().__init__(parent) + self.config = config + + root = QVBoxLayout(self) + root.setContentsMargins(0, 0, 0, 0) + root.setSpacing(SPACING + 4) + root.addWidget( + PageHeader("자막", "자막 창에 표시될 글씨의 모양을 정합니다. 아래에서 바로 확인하세요.") + ) + + root.addWidget(self._build_preview_card()) + root.addWidget(self._build_text_card()) + root.addWidget(self._build_layout_card()) + root.addStretch(1) + + self._sync_preview() + + # --- 구성 ----------------------------------------------------------- + def _build_preview_card(self) -> Card: + card = Card("미리보기", "실제 자막 창과 같은 방식으로 그립니다.") + self.preview = SubtitlePreview(self.config.subtitle) + self.preview.set_sample(PREVIEW_TEXT, PREVIEW_SOURCE) + card.add(self.preview) + return card + + def _build_text_card(self) -> Card: + st = self.config.subtitle + card = Card("글씨") + form = card.add_form() + + self.font_combo = QFontComboBox() + available = set(QFontDatabase.families()) + self.font_combo.setCurrentFont( + QFont(st.font_family if st.font_family in available else "Segoe UI") + ) + self.font_combo.currentFontChanged.connect(self._on_font_changed) + form.addRow("글꼴", self.font_combo) + + self.size_spin = QSpinBox() + self.size_spin.setRange(12, 120) + self.size_spin.setSuffix(" px") + self.size_spin.setValue(st.font_size) + self.size_spin.valueChanged.connect(self._on_change) + form.addRow("크기", self.size_spin) + + self.bold_check = QCheckBox("굵게") + self.bold_check.setChecked(st.bold) + self.bold_check.toggled.connect(self._on_change) + form.addRow("", self.bold_check) + + self.text_color = ColorButton(st.text_color) + self.text_color.color_picked.connect(self._on_change) + form.addRow("번역문 색", self.text_color) + + self.source_color = ColorButton(st.source_text_color) + self.source_color.color_picked.connect(self._on_change) + form.addRow("원문 색", self.source_color) + + self.outline_color = ColorButton(st.outline_color) + self.outline_color.color_picked.connect(self._on_change) + form.addRow("외곽선 색", self.outline_color) + + self.outline_spin = QSpinBox() + self.outline_spin.setRange(0, 8) + self.outline_spin.setSuffix(" px") + self.outline_spin.setValue(st.outline_width) + self.outline_spin.setToolTip("배경이 밝든 어둡든 글씨가 읽히게 해줍니다. 0이면 끕니다.") + self.outline_spin.valueChanged.connect(self._on_change) + form.addRow("외곽선 두께", self.outline_spin) + return card + + def _build_layout_card(self) -> Card: + st = self.config.subtitle + card = Card("배치와 배경") + form = card.add_form() + + self.bg_color = ColorButton(st.background_color) + self.bg_color.color_picked.connect(self._on_change) + form.addRow("배경 색", self.bg_color) + + opacity_row = QWidget() + layout = QHBoxLayout(opacity_row) + layout.setContentsMargins(0, 0, 0, 0) + self.opacity_slider = QSlider(Qt.Orientation.Horizontal) + self.opacity_slider.setRange(0, 100) + self.opacity_slider.setValue(st.background_opacity) + self.opacity_slider.valueChanged.connect(self._on_change) + self.opacity_value = QLabel(f"{st.background_opacity}%") + self.opacity_value.setMinimumWidth(42) + layout.addWidget(self.opacity_slider, 1) + layout.addWidget(self.opacity_value) + form.addRow("배경 투명도", opacity_row) + + self.align_combo = QComboBox() + for label, value in (("가운데", "center"), ("왼쪽", "left"), ("오른쪽", "right")): + self.align_combo.addItem(label, value) + self.align_combo.setCurrentIndex(max(0, self.align_combo.findData(st.align))) + self.align_combo.currentIndexChanged.connect(self._on_change) + form.addRow("정렬", self.align_combo) + + self.lines_spin = QSpinBox() + self.lines_spin.setRange(1, 5) + self.lines_spin.setSuffix(" 줄") + self.lines_spin.setValue(st.max_lines) + self.lines_spin.valueChanged.connect(self._on_change) + form.addRow("최대 줄 수", self.lines_spin) + + self.fade_spin = QSpinBox() + self.fade_spin.setRange(0, 30_000) + self.fade_spin.setSingleStep(500) + self.fade_spin.setSuffix(" ms") + self.fade_spin.setValue(st.fade_out_ms) + self.fade_spin.setToolTip("이 시간이 지나면 자막을 지웁니다. 0이면 계속 남겨둡니다.") + self.fade_spin.valueChanged.connect(self._on_change) + form.addRow("자동 지우기", self.fade_spin) + + self.show_source_check = QCheckBox("원문도 같이 표시") + self.show_source_check.setChecked(st.show_source) + self.show_source_check.toggled.connect(self._on_change) + form.addRow("", self.show_source_check) + + self.overlay_check = QCheckBox("자막 창 보이기") + self.overlay_check.setChecked(self.config.overlay.visible) + self.overlay_check.toggled.connect(self.overlay_toggled.emit) + form.addRow("", self.overlay_check) + return card + + # --- 동작 ----------------------------------------------------------- + def _on_font_changed(self, font: QFont) -> None: + self.config.subtitle.font_family = font.family() + self._on_change() + + def _on_change(self, *_) -> None: + st = self.config.subtitle + st.font_size = self.size_spin.value() + st.bold = self.bold_check.isChecked() + st.text_color = self.text_color.color + st.source_text_color = self.source_color.color + st.outline_color = self.outline_color.color + st.outline_width = self.outline_spin.value() + st.background_color = self.bg_color.color + st.background_opacity = self.opacity_slider.value() + st.align = self.align_combo.currentData() + st.max_lines = self.lines_spin.value() + st.fade_out_ms = self.fade_spin.value() + st.show_source = self.show_source_check.isChecked() + + self.opacity_value.setText(f"{st.background_opacity}%") + self._sync_preview() + self.style_changed.emit() + + def _sync_preview(self) -> None: + # 미리보기는 설정 객체를 그대로 참조하므로 다시 그리기만 하면 된다. + self.preview.setMinimumHeight(max(140, self.config.subtitle.font_size * 4)) + self.preview.update() diff --git a/src/hearo/ui/theme.py b/src/hearo/ui/theme.py new file mode 100644 index 0000000..a0d6eb4 --- /dev/null +++ b/src/hearo/ui/theme.py @@ -0,0 +1,209 @@ +"""디자인 토큰과 QSS 스타일시트. + +의도적으로 외부 테마 패키지를 쓰지 않는다. 자막 오버레이가 반투명·무테두리라 +플랫폼 기본 테마와 섞이면 오히려 지저분해지고, 색/간격을 한곳에서 관리하는 편이 +설정 화면의 '미리보기'와 실제 자막을 일치시키기에도 좋다. +""" + +from __future__ import annotations + +from dataclasses import dataclass + + +@dataclass(frozen=True) +class Palette: + bg: str + surface: str + surface_alt: str + border: str + text: str + text_dim: str + accent: str + accent_hover: str + accent_text: str + success: str + warning: str + danger: str + + +DARK = Palette( + bg="#0F1117", + surface="#171A22", + surface_alt="#1E222C", + border="#2A2F3C", + text="#E7EAF0", + text_dim="#8B93A5", + accent="#5B8CFF", + accent_hover="#7AA2FF", + accent_text="#0B0E14", + success="#3DD68C", + warning="#F5B84E", + danger="#F2555A", +) + +LIGHT = Palette( + bg="#F4F6FA", + surface="#FFFFFF", + surface_alt="#EDF0F6", + border="#D9DEE9", + text="#131722", + text_dim="#5C6578", + accent="#3B6EF0", + accent_hover="#2A5AD6", + accent_text="#FFFFFF", + success="#17A46A", + warning="#C9821B", + danger="#D63A3F", +) + +RADIUS = 10 +SPACING = 12 + + +def palette(theme: str) -> Palette: + return LIGHT if theme == "light" else DARK + + +def stylesheet(theme: str = "dark") -> str: + p = palette(theme) + return f""" + QWidget {{ + background: {p.bg}; + color: {p.text}; + font-family: "Pretendard", "Segoe UI Variable", "Segoe UI", "Malgun Gothic", sans-serif; + font-size: 14px; + }} + /* 라벨류는 부모(카드/사이드바) 배경이 비쳐야 한다. 지정하지 않으면 + QWidget 규칙의 창 배경색을 상속해 카드 위에 검은 박스가 생긴다. */ + QLabel, QCheckBox, QRadioButton, QScrollArea > QWidget > QWidget {{ + background: transparent; + }} + QLabel#Title {{ font-size: 22px; font-weight: 700; }} + QLabel#Subtitle {{ font-size: 13px; color: {p.text_dim}; }} + QLabel#SectionLabel {{ font-size: 12px; font-weight: 700; color: {p.text_dim}; + letter-spacing: 1px; }} + QLabel#Hint {{ font-size: 12px; color: {p.text_dim}; }} + + /* --- 사이드바 --- */ + QFrame#Sidebar {{ + background: {p.surface}; + border-right: 1px solid {p.border}; + }} + QPushButton#NavButton {{ + background: transparent; + border: none; + border-radius: {RADIUS}px; + padding: 11px 14px; + text-align: left; + color: {p.text_dim}; + font-size: 14px; + }} + QPushButton#NavButton:hover {{ background: {p.surface_alt}; color: {p.text}; }} + QPushButton#NavButton:checked {{ + background: {p.accent}; + color: {p.accent_text}; + font-weight: 700; + }} + + /* --- 카드 --- */ + QFrame#Card {{ + background: {p.surface}; + border: 1px solid {p.border}; + border-radius: {RADIUS}px; + }} + QFrame#CardSelected {{ + background: {p.surface}; + border: 2px solid {p.accent}; + border-radius: {RADIUS}px; + }} + + /* --- 입력 위젯 --- */ + QComboBox, QLineEdit, QSpinBox, QPlainTextEdit, QTextEdit, QListWidget, QTableWidget {{ + background: {p.surface_alt}; + border: 1px solid {p.border}; + border-radius: 8px; + padding: 7px 10px; + selection-background-color: {p.accent}; + selection-color: {p.accent_text}; + }} + QComboBox:focus, QLineEdit:focus, QSpinBox:focus, QPlainTextEdit:focus {{ + border: 1px solid {p.accent}; + }} + QComboBox::drop-down {{ border: none; width: 22px; }} + QComboBox QAbstractItemView {{ + background: {p.surface_alt}; + border: 1px solid {p.border}; + selection-background-color: {p.accent}; + outline: none; + }} + QHeaderView::section {{ + background: {p.surface}; + color: {p.text_dim}; + border: none; + border-bottom: 1px solid {p.border}; + padding: 7px; + font-weight: 600; + }} + QTableWidget {{ gridline-color: {p.border}; }} + + /* --- 버튼 --- */ + QPushButton {{ + background: {p.surface_alt}; + border: 1px solid {p.border}; + border-radius: 8px; + padding: 8px 16px; + color: {p.text}; + }} + QPushButton:hover {{ border-color: {p.accent}; }} + QPushButton:disabled {{ color: {p.text_dim}; border-color: {p.border}; }} + QPushButton#Primary {{ + background: {p.accent}; + color: {p.accent_text}; + border: none; + font-weight: 700; + padding: 11px 22px; + }} + QPushButton#Primary:hover {{ background: {p.accent_hover}; }} + QPushButton#Primary:disabled {{ background: {p.border}; color: {p.text_dim}; }} + QPushButton#Danger {{ + background: {p.danger}; + color: #FFFFFF; + border: none; + font-weight: 700; + padding: 11px 22px; + }} + + /* --- 기타 --- */ + QSlider::groove:horizontal {{ + height: 4px; background: {p.border}; border-radius: 2px; + }} + QSlider::handle:horizontal {{ + width: 16px; height: 16px; margin: -6px 0; + background: {p.accent}; border-radius: 8px; + }} + QSlider::sub-page:horizontal {{ background: {p.accent}; border-radius: 2px; }} + QProgressBar {{ + background: {p.surface_alt}; border: none; border-radius: 4px; + height: 6px; text-align: center; color: transparent; + }} + QProgressBar::chunk {{ background: {p.accent}; border-radius: 4px; }} + QCheckBox::indicator, QRadioButton::indicator {{ + width: 17px; height: 17px; + border: 1px solid {p.border}; border-radius: 4px; + background: {p.surface_alt}; + }} + QCheckBox::indicator:checked, QRadioButton::indicator:checked {{ + background: {p.accent}; border-color: {p.accent}; + }} + QScrollBar:vertical {{ background: transparent; width: 10px; margin: 0; }} + QScrollBar::handle:vertical {{ + background: {p.border}; border-radius: 5px; min-height: 30px; + }} + QScrollBar::handle:vertical:hover {{ background: {p.text_dim}; }} + QScrollBar::add-line, QScrollBar::sub-line {{ height: 0; }} + QScrollArea {{ border: none; }} + QToolTip {{ + background: {p.surface_alt}; color: {p.text}; + border: 1px solid {p.border}; padding: 6px; border-radius: 6px; + }} + """ diff --git a/src/hearo/ui/widgets/__init__.py b/src/hearo/ui/widgets/__init__.py new file mode 100644 index 0000000..d9644f8 --- /dev/null +++ b/src/hearo/ui/widgets/__init__.py @@ -0,0 +1,3 @@ +from .common import Card, LevelMeter, PageHeader, StatusPill, labelled_row + +__all__ = ["Card", "LevelMeter", "PageHeader", "StatusPill", "labelled_row"] diff --git a/src/hearo/ui/widgets/common.py b/src/hearo/ui/widgets/common.py new file mode 100644 index 0000000..27796e9 --- /dev/null +++ b/src/hearo/ui/widgets/common.py @@ -0,0 +1,136 @@ +"""여러 화면에서 재사용하는 작은 위젯들.""" + +from __future__ import annotations + +from PySide6.QtCore import Qt +from PySide6.QtGui import QColor, QPainter +from PySide6.QtWidgets import ( + QFormLayout, + QFrame, + QHBoxLayout, + QLabel, + QSizePolicy, + QVBoxLayout, + QWidget, +) + +from ..theme import SPACING, palette + + +class Card(QFrame): + """제목 + 본문 레이아웃을 가진 카드 컨테이너.""" + + def __init__(self, title: str = "", subtitle: str = "", parent: QWidget | None = None): + super().__init__(parent) + self.setObjectName("Card") + outer = QVBoxLayout(self) + outer.setContentsMargins(SPACING + 4, SPACING + 4, SPACING + 4, SPACING + 4) + outer.setSpacing(SPACING) + + if title: + label = QLabel(title) + label.setStyleSheet("font-size: 15px; font-weight: 700;") + outer.addWidget(label) + if subtitle: + hint = QLabel(subtitle) + hint.setObjectName("Hint") + hint.setWordWrap(True) + outer.addWidget(hint) + + self.body = QVBoxLayout() + self.body.setSpacing(SPACING) + outer.addLayout(self.body) + + def add(self, widget: QWidget) -> QWidget: + self.body.addWidget(widget) + return widget + + def add_form(self) -> QFormLayout: + form = QFormLayout() + form.setSpacing(10) + form.setLabelAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter) + form.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.ExpandingFieldsGrow) + self.body.addLayout(form) + return form + + +class PageHeader(QWidget): + def __init__(self, title: str, subtitle: str = "", parent: QWidget | None = None): + super().__init__(parent) + layout = QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(4) + head = QLabel(title) + head.setObjectName("Title") + layout.addWidget(head) + if subtitle: + sub = QLabel(subtitle) + sub.setObjectName("Subtitle") + sub.setWordWrap(True) + layout.addWidget(sub) + + +class StatusPill(QLabel): + """상태를 색 점 + 문구로 보여주는 배지.""" + + def __init__(self, parent: QWidget | None = None): + super().__init__(parent) + self._color = "#8B93A5" + self.setContentsMargins(10, 6, 12, 6) + self.set_status("대기 중", "idle") + + def set_status(self, text: str, kind: str = "idle") -> None: + p = palette("dark") + self._color = { + "idle": p.text_dim, + "busy": p.warning, + "ok": p.success, + "error": p.danger, + }.get(kind, p.text_dim) + self.setText(f"● {text}") + self.setStyleSheet( + f"color: {self._color}; background: {p.surface_alt};" + f"border-radius: 13px; padding: 6px 14px; font-weight: 600;" + ) + + +class LevelMeter(QWidget): + """입력 오디오 레벨 바. 소리가 잡히는지 한눈에 확인하는 용도.""" + + def __init__(self, parent: QWidget | None = None): + super().__init__(parent) + self._level = 0.0 + self._peak = 0.0 + self.setFixedHeight(8) + self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) + + def set_level(self, value: float) -> None: + # RMS를 그대로 그리면 거의 안 움직여서 제곱근으로 펴준다. + self._level = min(1.0, max(0.0, value) ** 0.5 * 2.2) + self._peak = max(self._level, self._peak * 0.92) + self.update() + + def paintEvent(self, event) -> None: # noqa: N802 + p = palette("dark") + painter = QPainter(self) + painter.setRenderHint(QPainter.RenderHint.Antialiasing) + painter.setPen(Qt.PenStyle.NoPen) + painter.setBrush(QColor(p.surface_alt)) + painter.drawRoundedRect(self.rect(), 4, 4) + if self._level > 0.01: + color = QColor(p.danger if self._level > 0.92 else p.success) + rect = self.rect().adjusted(0, 0, -int(self.width() * (1 - self._level)), 0) + painter.setBrush(color) + painter.drawRoundedRect(rect, 4, 4) + + +def labelled_row(label: str, widget: QWidget) -> QWidget: + row = QWidget() + layout = QHBoxLayout(row) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(10) + text = QLabel(label) + text.setMinimumWidth(110) + layout.addWidget(text) + layout.addWidget(widget, 1) + return row diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 0000000..6548d06 --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,58 @@ +import json + +from hearo.config import AppConfig +from hearo.models.tiers import DEFAULT_TIER + + +def test_defaults_are_sane(): + cfg = AppConfig() + assert cfg.models.tier == DEFAULT_TIER + assert cfg.models.target_lang == "ko" + assert cfg.subtitle.font_size > 0 + assert cfg.audio.silence_ms > 0 + + +def test_save_load_roundtrip(tmp_path): + path = tmp_path / "config.json" + cfg = AppConfig() + cfg.subtitle.font_size = 52 + cfg.subtitle.text_color = "#FF00AA" + cfg.models.tier = "precision" + cfg.audio.target_pid = 4242 + cfg.save(path) + + loaded = AppConfig.load(path) + assert loaded.subtitle.font_size == 52 + assert loaded.subtitle.text_color == "#FF00AA" + assert loaded.models.tier == "precision" + assert loaded.audio.target_pid == 4242 + + +def test_missing_file_gives_defaults(tmp_path): + assert AppConfig.load(tmp_path / "nope.json").models.tier == DEFAULT_TIER + + +def test_corrupt_file_gives_defaults(tmp_path): + path = tmp_path / "config.json" + path.write_text("{ this is not json", encoding="utf-8") + assert AppConfig.load(path).models.tier == DEFAULT_TIER + + +def test_unknown_keys_are_ignored_and_missing_keys_defaulted(tmp_path): + """스키마가 바뀌어도 사용자의 기존 설정이 깨지면 안 된다.""" + path = tmp_path / "config.json" + path.write_text( + json.dumps( + { + "theme": "light", + "removed_option": True, + "subtitle": {"font_size": 40, "gone_field": 1}, + } + ), + encoding="utf-8", + ) + cfg = AppConfig.load(path) + assert cfg.theme == "light" + assert cfg.subtitle.font_size == 40 + assert cfg.subtitle.bold is True # 기본값 유지 + assert not hasattr(cfg, "removed_option") diff --git a/tests/test_glossary.py b/tests/test_glossary.py new file mode 100644 index 0000000..1662f01 --- /dev/null +++ b/tests/test_glossary.py @@ -0,0 +1,75 @@ +from hearo.models.glossary import Glossary, GlossaryEntry + + +def make() -> Glossary: + return Glossary( + [ + GlossaryEntry("headshot", {"ko": "헤드샷"}), + GlossaryEntry("head", {"ko": "머리"}), + GlossaryEntry("Nexus", {"ko": "넥서스", "ja": "ネクサス"}), + ] + ) + + +def test_protect_and_restore_roundtrip(): + g = make() + protected, repl = g.protect("Nice headshot on the Nexus", "ko") + assert "headshot" not in protected + assert "Nexus" not in protected + assert repl == ["헤드샷", "넥서스"] + # 번역 모델이 플레이스홀더를 그대로 통과시켰다고 가정 + assert Glossary.restore(protected, repl) == "Nice 헤드샷 on the 넥서스" + + +def test_longest_match_wins(): + """'headshot'이 'head'에 먼저 잡아먹히면 안 된다.""" + g = make() + _, repl = g.protect("headshot", "ko") + assert repl == ["헤드샷"] + + +def test_word_boundary_for_latin_terms(): + g = make() + _, repl = g.protect("overheated", "ko") + assert repl == [] + + +def test_missing_target_language_is_left_alone(): + g = make() + protected, repl = g.protect("headshot", "ja") + assert protected == "headshot" + assert repl == [] + + +def test_prompt_hint_only_includes_present_terms(): + g = make() + hint = g.prompt_hint("push to the Nexus", "ko") + assert "Nexus -> 넥서스" in hint + assert "headshot" not in hint + assert g.prompt_hint("nothing here", "ko") == "" + + +def test_case_insensitive_by_default(): + g = make() + _, repl = g.protect("NEXUS down", "ko") + assert repl == ["넥서스"] + + +def test_save_and_load(tmp_path): + path = tmp_path / "glossary.json" + make().save(path) + loaded = Glossary.load(path) + assert len(loaded) == 3 + assert loaded.entries[2].target_for("ja") == "ネクサス" + + +def test_add_replaces_existing_entry(): + g = make() + g.add(GlossaryEntry("Nexus", {"ko": "본진"})) + assert len(g) == 3 + _, repl = g.protect("Nexus", "ko") + assert repl == ["본진"] + + +def test_restore_ignores_out_of_range_placeholder(): + assert Glossary.restore("a ⟦5⟧ b", ["x"]) == "a b" diff --git a/tests/test_pipeline.py b/tests/test_pipeline.py new file mode 100644 index 0000000..fab33c1 --- /dev/null +++ b/tests/test_pipeline.py @@ -0,0 +1,129 @@ +"""엔진 파이프라인을 가짜 모델로 끝까지 돌려본다 (GPU/오디오 장치 불필요).""" + +from __future__ import annotations + +import time + +import numpy as np +import pytest + +from hearo.audio.base import CaptureBackend, to_mono_16k +from hearo.config import AppConfig +from hearo.constants import SAMPLE_RATE +from hearo.core.engine import TranslationEngine +from hearo.models.asr import Transcript + + +class FakeCapture(CaptureBackend): + """말-침묵-말-침묵 패턴을 즉시 흘려보낸다.""" + + name = "fake" + per_process = True + + @staticmethod + def available() -> bool: + return True + + @staticmethod + def list_sources(): + return [] + + def _run(self) -> None: + def tone(ms, amp=0.35): + n = SAMPLE_RATE * ms // 1000 + t = np.arange(n, dtype=np.float32) / SAMPLE_RATE + return (np.sin(2 * np.pi * 240 * t) * amp).astype(np.float32) + + pattern = np.concatenate( + [np.zeros(SAMPLE_RATE // 5, np.float32), tone(900), + np.zeros(SAMPLE_RATE, np.float32)] + ) + while not self._stop.is_set(): + for i in range(0, pattern.size, 1600): + if self._stop.is_set(): + return + self._emit(pattern[i : i + 1600].copy()) + time.sleep(0.005) + + +class FakeRecognizer: + loaded = True + + def load(self): + pass + + def unload(self): + pass + + def transcribe(self, audio, language=None, fast=False, prompt=""): + return Transcript(text="hello world", language="en", confidence=0.99) + + +class FakeTranslator: + loaded = True + + def load(self): + pass + + def unload(self): + pass + + def translate(self, text, source_lang, target_lang, glossary=None): + return f"[{target_lang}] {text}" + + +@pytest.fixture +def engine(tmp_path, monkeypatch): + config = AppConfig() + config.models.preload_on_start = False + config.glossary.path = str(tmp_path / "glossary.json") + config.audio.partial_interval_ms = 0 + config.audio.silence_ms = 300 + config.audio.min_segment_ms = 200 + + lines = [] + eng = TranslationEngine(config, on_line=lines.append) + monkeypatch.setattr(eng.models, "recognizer", lambda: FakeRecognizer()) + monkeypatch.setattr(eng.models, "translator", lambda: FakeTranslator()) + monkeypatch.setattr("hearo.core.engine.create_capture", lambda cfg: FakeCapture()) + eng.lines = lines + yield eng + eng.shutdown() + + +def test_engine_produces_translated_lines(engine): + engine.start() + deadline = time.time() + 12 + while time.time() < deadline and not engine.lines: + time.sleep(0.05) + engine.stop() + + assert engine.lines, "12초 안에 자막이 한 줄도 나오지 않았습니다" + line = engine.lines[0] + assert line.source_text == "hello world" + assert line.translated_text == "[ko] hello world" + assert line.source_lang == "en" + assert line.is_final + assert line.latency_s >= 0 + + +def test_stop_is_idempotent(engine): + engine.start() + time.sleep(0.3) + engine.stop() + engine.stop() + assert not engine.running + + +def test_to_mono_16k_downmixes_and_resamples(): + stereo_48k = np.tile(np.array([1.0, -1.0], dtype=np.float32), 48_000) + out = to_mono_16k(stereo_48k, channels=2, src_rate=48_000) + assert out.dtype == np.float32 + assert abs(out.size - 16_000) <= 2 # 1초 분량 + assert np.allclose(out, 0.0, atol=1e-6) # L+R 이 상쇄 + + +def test_to_mono_16k_passthrough_when_already_correct(): + mono = np.linspace(-1, 1, 16_000, dtype=np.float32) + out = to_mono_16k(mono, channels=1, src_rate=16_000) + assert np.array_equal(out, mono) diff --git a/tests/test_segmenter.py b/tests/test_segmenter.py new file mode 100644 index 0000000..973bda2 --- /dev/null +++ b/tests/test_segmenter.py @@ -0,0 +1,78 @@ +import numpy as np + +from hearo.audio.segmenter import Segmenter, SegmenterConfig +from hearo.constants import SAMPLE_RATE + + +def tone(ms: int, amplitude: float = 0.3) -> np.ndarray: + n = SAMPLE_RATE * ms // 1000 + t = np.arange(n, dtype=np.float32) / SAMPLE_RATE + return (np.sin(2 * np.pi * 220 * t) * amplitude).astype(np.float32) + + +def silence(ms: int) -> np.ndarray: + return np.zeros(SAMPLE_RATE * ms // 1000, dtype=np.float32) + + +def config(**kw) -> SegmenterConfig: + base = dict(silence_ms=300, min_segment_ms=200, max_segment_ms=5000, + partial_interval_ms=0) + base.update(kw) + return SegmenterConfig(**base) + + +def test_speech_then_silence_emits_one_final_segment(): + seg = Segmenter(config()) + out = seg.push(silence(200)) + assert out == [] + out += seg.push(tone(800)) + out += seg.push(silence(600)) + finals = [s for s in out if s.is_final] + assert len(finals) == 1 + assert 0.7 <= finals[0].duration_s <= 1.8 + + +def test_short_blip_is_discarded(): + seg = Segmenter(config(min_segment_ms=500)) + out = seg.push(tone(60)) + seg.push(silence(600)) + assert out == [] + + +def test_max_length_forces_a_cut(): + seg = Segmenter(config(max_segment_ms=1000)) + out = seg.push(tone(3000)) + assert len([s for s in out if s.is_final]) >= 2 + + +def test_partial_results_are_emitted_while_speaking(): + seg = Segmenter(config(partial_interval_ms=300)) + out = seg.push(tone(1500)) + partials = [s for s in out if not s.is_final] + assert len(partials) >= 2 + # 중간 결과는 누적된다 + assert partials[-1].duration_s > partials[0].duration_s + + +def test_flush_returns_pending_speech(): + seg = Segmenter(config()) + assert seg.push(tone(700)) == [] # 아직 침묵이 안 왔으니 확정 없음 + leftover = seg.flush() + assert leftover is not None and leftover.is_final + assert seg.flush() is None + + +def test_continuous_silence_never_emits(): + seg = Segmenter(config()) + for _ in range(10): + assert seg.push(silence(200)) == [] + + +def test_lead_in_is_prepended(): + """발화 직전 오디오가 붙어 첫 음절이 잘리지 않아야 한다.""" + seg = Segmenter(config(lead_in_ms=200)) + seg.push(silence(400)) + out = seg.push(tone(600)) + seg.push(silence(600)) + finals = [s for s in out if s.is_final] + assert len(finals) == 1 + # 0.6초 발화 + 최대 0.2초 리드인 + assert finals[0].duration_s > 0.6 diff --git a/tests/test_tiers.py b/tests/test_tiers.py new file mode 100644 index 0000000..beeb4d5 --- /dev/null +++ b/tests/test_tiers.py @@ -0,0 +1,36 @@ +from hearo.models.tiers import DEFAULT_TIER, TIERS, MTBackend, get_tier, ordered_tiers + + +def test_exactly_five_tiers_ordered_by_quality(): + tiers = ordered_tiers() + assert len(tiers) == 5 + assert [t.order for t in tiers] == [1, 2, 3, 4, 5] + + +def test_latency_and_vram_increase_monotonically(): + tiers = ordered_tiers() + latencies = [t.approx_latency_s for t in tiers] + vram = [t.min_vram_gb for t in tiers] + assert latencies == sorted(latencies) + assert vram == sorted(vram) + + +def test_exactly_one_recommended_and_one_finetune_pick(): + assert sum(t.recommended for t in TIERS.values()) == 1 + assert sum(t.best_after_finetune for t in TIERS.values()) == 1 + + +def test_llm_backends_support_prompt_glossary(): + for tier in TIERS.values(): + if tier.mt.backend is MTBackend.TRANSFORMERS: + assert tier.mt.supports_prompt_glossary + else: + assert not tier.mt.supports_prompt_glossary + + +def test_unknown_key_falls_back_to_default(): + assert get_tier("does-not-exist").key == DEFAULT_TIER + + +def test_default_tier_exists(): + assert DEFAULT_TIER in TIERS diff --git a/tests/test_ui_smoke.py b/tests/test_ui_smoke.py new file mode 100644 index 0000000..ea22275 --- /dev/null +++ b/tests/test_ui_smoke.py @@ -0,0 +1,102 @@ +"""UI가 실제로 만들어지고 자막이 그려지는지 확인한다 (offscreen 렌더링).""" + +from __future__ import annotations + +import os + +import pytest + +pytest.importorskip("PySide6") +os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") + +from PySide6.QtGui import QImage, QPainter # noqa: E402 +from PySide6.QtWidgets import QApplication # noqa: E402 + +from hearo.config import AppConfig # noqa: E402 +from hearo.core.events import EngineState, EngineStatus, TranslationLine # noqa: E402 +from hearo.ui.main_window import MainWindow # noqa: E402 +from hearo.ui.overlay import paint_subtitle # noqa: E402 + + +@pytest.fixture(scope="session") +def qapp(): + app = QApplication.instance() or QApplication([]) + yield app + + +@pytest.fixture +def window(qapp, tmp_path, monkeypatch): + monkeypatch.setattr("hearo.constants.user_data_dir", lambda: tmp_path) + config = AppConfig() + config.models.preload_on_start = False + config.glossary.path = str(tmp_path / "glossary.json") + win = MainWindow(config) + yield win + win.engine.shutdown() + win.overlay.close() + win.close() + + +def test_main_window_builds_all_pages(window): + assert window.stack.count() == 5 + + +def test_navigation_switches_pages(window): + for index in range(5): + window._goto_page(index) + assert window.stack.currentIndex() == index + + +def test_status_updates_toggle_button(window): + window._on_status(EngineStatus(EngineState.RUNNING, "듣는 중")) + assert window.home_page.toggle_button.text() == "번역 정지" + window._on_status(EngineStatus(EngineState.IDLE, "대기 중")) + assert window.home_page.toggle_button.text() == "번역 시작" + + +def test_final_line_reaches_overlay_and_log(window): + window.config.log_transcripts = False + window._on_line( + TranslationLine("hello", "안녕하세요", "en", "ko", is_final=True, latency_s=1.2) + ) + assert window.overlay._final_text == "안녕하세요" + assert window.home_page.log_list.count() == 1 + + +def test_partial_line_is_not_logged(window): + window.config.log_transcripts = False + window._on_line(TranslationLine("hel", "", "en", "ko", is_final=False)) + assert window.home_page.log_list.count() == 0 + + +def test_tier_selection_updates_config(window): + window.models_page._on_tier_selected(window.models_page._cards[3].tier) + assert window.config.models.tier == "precision" + + +def test_subtitle_style_edit_propagates(window): + window.subtitle_page.size_spin.setValue(60) + assert window.config.subtitle.font_size == 60 + + +def test_paint_subtitle_actually_draws_pixels(qapp): + """자막 글씨가 투명 이미지 위에 실제로 그려지는지 (빈 화면이면 실패). + + 배경 상자가 픽셀을 채워버리면 글씨 유무를 알 수 없으므로 배경은 끄고 잰다. + """ + st = AppConfig().subtitle + st.background_opacity = 0 + image = QImage(900, 200, QImage.Format.Format_ARGB32) + image.fill(0) + + painter = QPainter(image) + paint_subtitle(painter, image.rect(), st, "안녕하세요 반갑습니다", "Hello there") + painter.end() + + non_transparent = sum( + 1 + for y in range(0, image.height(), 4) + for x in range(0, image.width(), 4) + if image.pixelColor(x, y).alpha() > 0 + ) + assert non_transparent > 100