Metadata-Version: 2.4
Name: lemon-manga-translator
Version: 0.1.0
Summary: Koharu-inspired manga translation pipeline (JA->KO) in Python
Keywords: manga,translation,japanese,korean,ocr,inpainting
Author: LemonDouble
Author-email: LemonDouble <contact@lemondouble.com>
License-Expression: GPL-3.0-only
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Multimedia :: Graphics
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Dist: numpy
Requires-Dist: pillow
Requires-Dist: opencv-python-headless
Requires-Dist: scikit-image>=0.25
Requires-Dist: httpx
Requires-Dist: onnxruntime>=1.17
Requires-Dist: huggingface-hub>=0.20
Requires-Python: >=3.11
Project-URL: Homepage, https://github.com/LemonDouble/lemon-manga-translator
Project-URL: Repository, https://github.com/LemonDouble/lemon-manga-translator
Project-URL: Issues, https://github.com/LemonDouble/lemon-manga-translator/issues
Description-Content-Type: text/markdown

# lemon-manga-translator

일본어 만화 페이지를 한국어로 번역하는 파이프라인입니다. 학습된 검출 모델로 글자와 말풍선을 찾고, Vision LLM 한 번의 호출로 글자 묶음·원문·번역을 받은 뒤, 배치할 수 있는 영역의 원문만 지우고 한국어를 그립니다.

```
글자·말풍선 검출 → Vision LLM 읽기·번역 → 배치 검증 → 원문 삭제 → 한국어 합성
```

## 사용법

```python
from lemon_manga_translator import MangaTranslator
from PIL import Image

translator = MangaTranslator(
    openrouter_api_key="sk-or-...",
    openrouter_model="google/gemini-3.1-flash-lite",
    device="cpu",              # "cuda"면 onnxruntime 대신 onnxruntime-gpu를 설치해야 합니다.
    mask_background=False,     # True면 LLM에 보내는 페이지에서 글자 영역 밖을 흰색으로 가립니다.
)
translator.prepare()  # HuggingFace에서 모델을 내려받아 캐시합니다.

result = translator.translate(Image.open("page.jpg"))
result.image.save("output.png")
```

- 결과는 `TranslationResult(image, vision_results, vision_error)`입니다. `vision_error`는 LLM 호출이 2회 모두 실패했을 때만 채워지며, 이때 원문을 그대로 남깁니다.
- Vision 호출 설정(temperature `0`, reasoning `minimal`, 최대 2회 시도, 요청당 120초 제한)은 검수한 값으로 고정되어 있습니다. 프롬프트는 [`prompts/vision_system.txt`](lemon_manga_translator/prompts/vision_system.txt)입니다.
- 대사·내레이션·제목·이름표·표지판을 번역합니다. 효과음·포장 상품명·저작권 표시·읽을 수 없는 글자, 그리고 번역에 가나나 기호만 남은 경우는 원문을 보존합니다.
- `mask_background=True`는 액션 장면처럼 LLM 콘텐츠 필터에 걸리는 페이지를 위한 옵션입니다. 말풍선 확대 이미지는 가리지 않습니다.
- Docker 이미지에서는 빌드 단계에서 `MangaTranslator.pre_download()`를 실행해 모델을 미리 캐시합니다.

### 진행 상황과 중간 결과

```python
result = translator.translate(image, on_progress=print, on_stage_done=handle)
```

- `on_progress`는 단계를 시작할 때 `PipelineStage`(`detect` → `translate` → `inpaint` → `render`)로 호출됩니다. 글자가 없는 페이지는 `detect`에서 끝납니다.
- `on_stage_done`은 단계가 끝날 때 `StageResult`로 호출됩니다.

| 단계 | 채워지는 필드 |
|------|------|
| `DETECT` | `text_blocks`. 자동 번역에서는 LLM이 글자를 묶은 뒤에 보고합니다. |
| `TRANSLATE` | `vision_results` (`block_index`, `no_translate`, `ko_translation`) |
| `INPAINT` | `image` (원문을 지운 이미지) |
| `RENDER` | `image` (최종 이미지) |

### 번역을 고쳐 다시 그리기

```python
result = translator.translate(image, translations=edited_results, text_boxes=previous_boxes)
```

- `translations`를 넘기면 LLM을 호출하지 않고 그 번역으로 다시 그립니다. `no_translate=True`인 블록은 원문을 남기고, `no_translate=False`에 빈 번역을 넘기면 원문만 지웁니다.
- 같은 인스턴스는 최근 64개 이미지의 글자 묶음을 기억합니다. 캐시가 만료되었거나 프로세스가 재시작되었다면 이전 `DETECT`의 bbox를 `text_boxes`로 함께 넘겨야 하며, 둘 다 없으면 `ValueError`가 발생합니다.
- `text_boxes`에서 좌표를 바꾸거나 추가한 박스는 그 사각형 전체를 삭제·배치 영역으로 사용합니다. 좌표가 같은 박스는 검출 mask를 유지합니다.

### 디버그 산출물

`translate(image, debug_dir="debug/page_001")`로 단계별 산출물을 저장합니다. rude-image-translator가 이 파일 이름으로 S3에 올리고 관리자 화면에 표시합니다.

| 파일 | 내용 |
|------|------|
| `00_input.png`, `00_params.json` | 입력 이미지, 모델과 `mask_background` |
| `02_bboxes.png`, `02_text_blocks.json` | LLM이 묶은 뒤의 글자 블록과 검출 점수 |
| `05a_annotated.png` | LLM에 보낸 번호 박스 이미지 |
| `05_vision.json`, `05_cost.json` | 블록별 번역 결과, 토큰 사용량과 비용(원) |
| `03c_mask_filtered_overlay.png` | 실제로 지운 영역 |
| `04_inpainted.png` | 원문을 지운 이미지 |
| `05b_render_bboxes.png` | 번역 블록의 bbox(빨강)와 배치 영역(초록) |
| `06_final.png` | 최종 결과 |

로컬 이미지는 다음 스크립트로 번역해 볼 수 있습니다.

```bash
uv run --env-file .env python scripts/translate.py page.jpg pages/ --out-dir out
```

## 개발

```bash
uv sync
uv run pytest
```

- `v*` 태그를 push하면 GitHub Actions가 PyPI에 배포합니다.

## 모델·폰트 출처

| 구성 요소 | 출처 | 라이선스 |
|----------|------|---------|
| 글자·말풍선 검출 | [mayocream/koharu-layout-rfdetr-seg-2xl-1152](https://huggingface.co/mayocream/koharu-layout-rfdetr-seg-2xl-1152)의 ONNX 변환본 [ShiniShiho/koharu-layout-rfdetr-seg-2xl-1152-onnx](https://huggingface.co/ShiniShiho/koharu-layout-rfdetr-seg-2xl-1152-onnx) | `other` (Manga109 학술 용도 데이터로 학습) |
| AOT 인페인팅 | [manga-image-translator beta-0.3](https://github.com/zyddnys/manga-image-translator/releases/tag/beta-0.3)의 체크포인트를 ONNX로 변환해 [lemondouble/lemon-manga-translator](https://huggingface.co/lemondouble/lemon-manga-translator)에 배포 | [GPL-3.0](https://github.com/zyddnys/manga-image-translator/blob/main/LICENSE) |
| `assets/fonts/Jua-Regular.ttf` | [Google Fonts: Jua](https://fonts.google.com/specimen/Jua) | [SIL OFL 1.1](https://openfontlicense.org/) |
| `assets/fonts/PretendardJP-Regular.otf` | [orioncactus/pretendard](https://github.com/orioncactus/pretendard) | [SIL OFL 1.1](https://github.com/orioncactus/pretendard/blob/main/LICENSE) |

## License

[GPL-3.0](LICENSE)
