manga-translator 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- manga_translator-0.3.0/LICENSE +21 -0
- manga_translator-0.3.0/PKG-INFO +356 -0
- manga_translator-0.3.0/README.md +325 -0
- manga_translator-0.3.0/balloon_detector.py +84 -0
- manga_translator-0.3.0/llm_corrector.py +342 -0
- manga_translator-0.3.0/manga_bubbles.py +258 -0
- manga_translator-0.3.0/manga_models/LICENSE +202 -0
- manga_translator-0.3.0/manga_models/NOTICE +20 -0
- manga_translator-0.3.0/manga_models/__init__.py +1 -0
- manga_translator-0.3.0/manga_models/bubble.onnx +0 -0
- manga_translator-0.3.0/manga_translate.py +288 -0
- manga_translator-0.3.0/manga_translator.egg-info/PKG-INFO +356 -0
- manga_translator-0.3.0/manga_translator.egg-info/SOURCES.txt +22 -0
- manga_translator-0.3.0/manga_translator.egg-info/dependency_links.txt +1 -0
- manga_translator-0.3.0/manga_translator.egg-info/entry_points.txt +3 -0
- manga_translator-0.3.0/manga_translator.egg-info/requires.txt +6 -0
- manga_translator-0.3.0/manga_translator.egg-info/top_level.txt +6 -0
- manga_translator-0.3.0/offline_mt.py +197 -0
- manga_translator-0.3.0/pyproject.toml +43 -0
- manga_translator-0.3.0/setup.cfg +4 -0
- manga_translator-0.3.0/tests/test_bubbles.py +136 -0
- manga_translator-0.3.0/tests/test_llm_corrector.py +393 -0
- manga_translator-0.3.0/tests/test_manga_translate.py +134 -0
- manga_translator-0.3.0/tests/test_offline_mt.py +304 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Indra Gunanda
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: manga-translator
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Offline-first manga translation CLI with optional LLM post-editing
|
|
5
|
+
Author: Indra Gunanda
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/igun997/manga-translator
|
|
8
|
+
Project-URL: Repository, https://github.com/igun997/manga-translator
|
|
9
|
+
Project-URL: Issues, https://github.com/igun997/manga-translator/issues
|
|
10
|
+
Keywords: manga,comic,translation,ocr,tesseract,argos,offline
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
20
|
+
Classifier: Topic :: Text Processing :: Linguistic
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: numpy>=1.26
|
|
25
|
+
Requires-Dist: opencv-python-headless>=4.11
|
|
26
|
+
Requires-Dist: Pillow>=10
|
|
27
|
+
Requires-Dist: argostranslate<2,>=1.11
|
|
28
|
+
Requires-Dist: PyYAML>=6
|
|
29
|
+
Requires-Dist: onnxruntime<2,>=1.17
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# manga-translator
|
|
33
|
+
|
|
34
|
+
[](https://github.com/igun997/manga-translator/actions/workflows/ci.yml)
|
|
35
|
+
[](https://pypi.org/project/manga-translator/)
|
|
36
|
+
[](https://pypi.org/project/manga-translator/)
|
|
37
|
+
[](LICENSE)
|
|
38
|
+
|
|
39
|
+
Translate a folder of manga pages, or a few selected pages, from any supported
|
|
40
|
+
language to any other. The CLI finds speech balloons and light narration boxes,
|
|
41
|
+
reads them with Tesseract, translates with locally installed Argos models, and
|
|
42
|
+
reletters the page in your target font.
|
|
43
|
+
|
|
44
|
+
- **Offline by default.** Detection, OCR and translation all run on your
|
|
45
|
+
machine. Nothing is uploaded, and no model is downloaded during translation.
|
|
46
|
+
- **LLM post-editing is optional.** An OpenAI-compatible model can polish the
|
|
47
|
+
offline drafts when you ask for it with `--llm`; otherwise no LLM credential
|
|
48
|
+
is read and no network request is made.
|
|
49
|
+
- **Nothing is overwritten blind.** Outputs go to a separate directory, and every
|
|
50
|
+
region that fails to translate or fit is reported instead of clipped.
|
|
51
|
+
|
|
52
|
+
Two commands ship in the package: `manga-translate` (detect → OCR → translate →
|
|
53
|
+
reletter) and `manga-bubbles` (analyze and reletter by hand, no translation).
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Requirements
|
|
58
|
+
|
|
59
|
+
| Requirement | Why | Notes |
|
|
60
|
+
| --- | --- | --- |
|
|
61
|
+
| Python 3.10+ | runtime | |
|
|
62
|
+
| Tesseract OCR | reads the original text | plus a language pack **per source language** |
|
|
63
|
+
| Argos package for your language pair | offline translation | installed once by `manga-translate models` |
|
|
64
|
+
| A font with your **target** script | reletters the page | CJK needs a CJK font; Latin/Cyrillic/Greek work with DejaVu |
|
|
65
|
+
|
|
66
|
+
OCR language packs, Argos translation packages and target fonts are three
|
|
67
|
+
independent things. Changing the target language usually means installing a
|
|
68
|
+
font; changing the source language usually means installing an OCR pack.
|
|
69
|
+
|
|
70
|
+
## Installation
|
|
71
|
+
|
|
72
|
+
### 1. System packages (Debian/Ubuntu)
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
sudo apt-get install tesseract-ocr tesseract-ocr-eng # English source
|
|
76
|
+
# Japanese source also needs:
|
|
77
|
+
# tesseract-ocr-jpn tesseract-ocr-jpn-vert
|
|
78
|
+
# A CJK font is only needed when the TARGET is Japanese/Chinese/Korean:
|
|
79
|
+
# fonts-noto-cjk
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 2. The CLI
|
|
83
|
+
|
|
84
|
+
With `pip`:
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
python3 -m venv .venv
|
|
88
|
+
.venv/bin/python -m pip install -e .
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
With [uv](https://docs.astral.sh/uv/) (faster, and what CI uses):
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
uv venv .venv
|
|
95
|
+
uv pip install --python .venv/bin/python -e .
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Or straight from PyPI, once published:
|
|
99
|
+
|
|
100
|
+
```sh
|
|
101
|
+
pip install manga-translator
|
|
102
|
+
# or
|
|
103
|
+
uv tool install manga-translator
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
> `argostranslate` depends on PyTorch. On a GPU-less machine, install the CPU
|
|
107
|
+
> build first to avoid pulling several gigabytes of CUDA wheels:
|
|
108
|
+
> ```sh
|
|
109
|
+
> uv pip install --python .venv/bin/python torch --index-url https://download.pytorch.org/whl/cpu
|
|
110
|
+
> uv pip install --python .venv/bin/python -e .
|
|
111
|
+
> ```
|
|
112
|
+
|
|
113
|
+
Verify:
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
.venv/bin/manga-translate --help
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Usage
|
|
120
|
+
|
|
121
|
+
### Step 1 — install the translation route (once, needs internet)
|
|
122
|
+
|
|
123
|
+
Translation needs an Argos package for the pair you want. `models` resolves the
|
|
124
|
+
shortest route, direct or through a pivot such as Japanese → English →
|
|
125
|
+
Indonesian, and downloads only the missing packages:
|
|
126
|
+
|
|
127
|
+
```sh
|
|
128
|
+
.venv/bin/manga-translate models --source ja --target id
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
```text
|
|
132
|
+
Installed offline route: ja->en, en->id
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
This is the **only** command that touches the network. Run it again for each new
|
|
136
|
+
pair; it is a no-op when the route is already installed.
|
|
137
|
+
|
|
138
|
+
### Step 2 — translate
|
|
139
|
+
|
|
140
|
+
```sh
|
|
141
|
+
# A whole folder, subfolders included
|
|
142
|
+
.venv/bin/manga-translate translate ./pages --source ja --target id --output ./translated-id
|
|
143
|
+
|
|
144
|
+
# Just a few pages
|
|
145
|
+
.venv/bin/manga-translate translate cover.jpeg page-01.jpeg \
|
|
146
|
+
--source ja --target id --output ./translated-id
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Outputs are PNGs under `--output` (default `translated-TARGET`), keeping the
|
|
150
|
+
input folder's relative paths. Existing outputs are left alone unless you pass
|
|
151
|
+
`--overwrite`.
|
|
152
|
+
|
|
153
|
+
### Language codes
|
|
154
|
+
|
|
155
|
+
`--source` / `--target` are **Argos** codes; `--ocr-lang` is a **Tesseract**
|
|
156
|
+
code. The Tesseract code is inferred from `--source` for the common languages,
|
|
157
|
+
and can always be overridden:
|
|
158
|
+
|
|
159
|
+
| | | |
|
|
160
|
+
| --- | --- | --- |
|
|
161
|
+
| `ja` → `jpn` | `en` → `eng` | `id` → `ind` |
|
|
162
|
+
| `zh` → `chi_sim` | `ko` → `kor` | `es` → `spa` |
|
|
163
|
+
|
|
164
|
+
```sh
|
|
165
|
+
# Vertical Japanese lettering
|
|
166
|
+
.venv/bin/manga-translate translate ./pages --source ja --target id \
|
|
167
|
+
--ocr-lang jpn_vert --psm 5
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Use `--font /path/to/font.ttf` whenever the default target font cannot render
|
|
171
|
+
your script; Japanese/Chinese/Korean default to
|
|
172
|
+
`/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc` and fail with an
|
|
173
|
+
actionable message if it is absent.
|
|
174
|
+
|
|
175
|
+
### Reading the report
|
|
176
|
+
|
|
177
|
+
Every run writes `translation-report.json` next to the images:
|
|
178
|
+
|
|
179
|
+
```json
|
|
180
|
+
{
|
|
181
|
+
"source": "en", "target": "id", "llm": false,
|
|
182
|
+
"pages": [{
|
|
183
|
+
"input": "pages/panel-1.jpeg",
|
|
184
|
+
"output": "translated-id/panel-1.png",
|
|
185
|
+
"status": "ok",
|
|
186
|
+
"regions": [{
|
|
187
|
+
"id": 1,
|
|
188
|
+
"bbox": [576, 4, 856, 436],
|
|
189
|
+
"source": "AT THE MOMENT OF MY DEATH, ...",
|
|
190
|
+
"translation": "Pada saat kematianku, ...",
|
|
191
|
+
"status": "translated"
|
|
192
|
+
}]
|
|
193
|
+
}]
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
| Status | Meaning |
|
|
198
|
+
| --- | --- |
|
|
199
|
+
| `translated` | OCR'd, translated and reletted |
|
|
200
|
+
| `offline_fallback` | LLM text did not fit; the offline draft was used instead |
|
|
201
|
+
| `untranslated` | translator returned nothing new; the balloon was left untouched |
|
|
202
|
+
| `unfittable` | translation cannot fit at the minimum font size; nothing erased |
|
|
203
|
+
| `no_text` | detector found no text in this region |
|
|
204
|
+
| `failed` | OCR or render error; artwork untouched |
|
|
205
|
+
|
|
206
|
+
Page statuses are `ok`, `partial` (any region failed) or `no_text`. The command
|
|
207
|
+
exits `0` when every page is `ok`, `1` when any page is `partial`/`failed`, and
|
|
208
|
+
`2` on a configuration error. **Always eyeball the output**: OCR, detection and
|
|
209
|
+
inpainting are all approximate.
|
|
210
|
+
|
|
211
|
+
### Missed a balloon? Annotate it
|
|
212
|
+
|
|
213
|
+
Open the image, read the coordinates, and add a region the detectors did not
|
|
214
|
+
find. Give points *inside* the balloon outline:
|
|
215
|
+
|
|
216
|
+
```json
|
|
217
|
+
{"panel-1.jpeg": [{"polygon": [[903, 1276], [948, 1281], [998, 1298], [999, 1362], [986, 1465]]}]}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
```sh
|
|
221
|
+
.venv/bin/manga-translate translate panel-1.jpeg --source en --target id \
|
|
222
|
+
--regions regions.json --output ./translated-id
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Keys are the input filename, or the relative path inside a folder input. A
|
|
226
|
+
`bbox: [left, top, right, bottom]` works instead of a polygon. Keep the shape
|
|
227
|
+
inset: it masks both the erase step and the fitted text, so a polygon that
|
|
228
|
+
spans artwork can erase art.
|
|
229
|
+
|
|
230
|
+
### Optional: LLM post-editing
|
|
231
|
+
|
|
232
|
+
The LLM receives the recognized source text and its offline draft — **not the
|
|
233
|
+
image** — and rewrites the draft into more natural target language. It never
|
|
234
|
+
replaces a translation that does not fit; the offline draft is used then.
|
|
235
|
+
|
|
236
|
+
```sh
|
|
237
|
+
.venv/bin/manga-translate translate ./pages --source ja --target id \
|
|
238
|
+
--llm --provider netra --model deepseek/deepseek-v4-flash-0731 \
|
|
239
|
+
--output ./translated-id
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Credentials, in order of priority:
|
|
243
|
+
|
|
244
|
+
1. **A provider file.** Defaults to `~/.omp/agent/models.yml`:
|
|
245
|
+
|
|
246
|
+
```yaml
|
|
247
|
+
providers:
|
|
248
|
+
netra:
|
|
249
|
+
baseUrl: https://your-endpoint/v1
|
|
250
|
+
apiKey: sk-...
|
|
251
|
+
models:
|
|
252
|
+
- id: deepseek/deepseek-v4-flash-0731
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Point elsewhere with `--models-file ./models.yml`; pick an entry with
|
|
256
|
+
`--provider` and `--model`.
|
|
257
|
+
|
|
258
|
+
2. **Environment variables**, which skip the file entirely and are convenient
|
|
259
|
+
for CI:
|
|
260
|
+
|
|
261
|
+
| Variable | Purpose |
|
|
262
|
+
| --- | --- |
|
|
263
|
+
| `MANGA_LLM_BASE_URL` | OpenAI-compatible base URL |
|
|
264
|
+
| `MANGA_LLM_API_KEY` | key for any provider |
|
|
265
|
+
| `MANGA_LLM_API_KEY_<PROVIDER>` | key for one provider, e.g. `MANGA_LLM_API_KEY_NETRA` |
|
|
266
|
+
| `MANGA_LLM_MODEL` | model id |
|
|
267
|
+
| `OPENAI_API_KEY` | last-resort fallback |
|
|
268
|
+
|
|
269
|
+
Remote endpoints must be `https`; plain `http` is accepted only for loopback.
|
|
270
|
+
`--llm` makes network requests and may cost money. Without it, no key is read,
|
|
271
|
+
no config file is opened, and no request is made.
|
|
272
|
+
|
|
273
|
+
### Manual relettering without translation
|
|
274
|
+
|
|
275
|
+
`manga-bubbles` finds balloons and gives each an ID you can translate by hand.
|
|
276
|
+
|
|
277
|
+
```sh
|
|
278
|
+
.venv/bin/manga-bubbles analyze panel-1.jpeg > detections.json
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
```json
|
|
282
|
+
[{"id": 1, "bbox": [576, 4, 856, 436], "text": "I WANT TO DO IT!", "words": [ ... ]}]
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Write a JSON map of only the IDs you want changed, then apply it:
|
|
286
|
+
|
|
287
|
+
```json
|
|
288
|
+
{"1": "I WILL DO IT TODAY, EVEN IF IT TAKES ALL DAY!"}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
```sh
|
|
292
|
+
.venv/bin/manga-bubbles replace panel-1.jpeg translations.json out.png \
|
|
293
|
+
--font /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Use the same `--lang` and `--psm` for `analyze` and `replace` so the IDs line
|
|
297
|
+
up. Balloons you do not list, and everything outside the balloon interiors,
|
|
298
|
+
stay pixel-identical. Text too long to fit raises an error rather than clipping.
|
|
299
|
+
|
|
300
|
+
## How detection works
|
|
301
|
+
|
|
302
|
+
1. **Bright-region geometry** finds enclosed balloons and light rectangular
|
|
303
|
+
narration boxes by thresholding, filling, and filtering on shape.
|
|
304
|
+
2. **A bundled ONNX balloon segmenter** adds balloons the geometric pass cannot
|
|
305
|
+
see — notably open balloons whose white interior flows into the page
|
|
306
|
+
background. Its mask is inset by a few pixels before OCR, so lettering
|
|
307
|
+
sitting outside the outline is never read as dialogue.
|
|
308
|
+
3. Overlapping proposals are deduplicated, so a balloon is never OCR'd twice.
|
|
309
|
+
|
|
310
|
+
The segmenter is `huyvux3005/manga109-segmentation-bubble` (Apache-2.0),
|
|
311
|
+
exported to a fixed 1600×1600 ONNX graph and run with `onnxruntime` only — no
|
|
312
|
+
Ultralytics install, no download at run time. See `manga_models/NOTICE` and
|
|
313
|
+
`manga_models/LICENSE`.
|
|
314
|
+
|
|
315
|
+
## Scope and limits
|
|
316
|
+
|
|
317
|
+
- Targets speech balloons and light narration boxes. Dark balloons, heavy
|
|
318
|
+
screentone, and unusual shapes may be missed — annotate them with `--regions`.
|
|
319
|
+
- Erasure covers OCR word boxes, so stylized lettering or punctuation the OCR
|
|
320
|
+
missed can need manual retouching.
|
|
321
|
+
- Right-to-left and vertical reading order is not reconstructed for you; the
|
|
322
|
+
report lists regions top-to-bottom, left-to-right.
|
|
323
|
+
- Detection is not a general artwork segmenter, and no OCR result is guaranteed.
|
|
324
|
+
- Inspect before publishing anything.
|
|
325
|
+
|
|
326
|
+
## Development
|
|
327
|
+
|
|
328
|
+
```sh
|
|
329
|
+
uv venv .venv
|
|
330
|
+
uv pip install --python .venv/bin/python -e .
|
|
331
|
+
uv pip install --python .venv/bin/python "numpy>=1.26" "opencv-python-headless>=4.11" "Pillow>=10" "PyYAML>=6" "onnxruntime>=1.17,<2"
|
|
332
|
+
|
|
333
|
+
.venv/bin/python -m unittest discover -s tests -v
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Tests need Tesseract with the `eng` pack, DejaVu fonts, and the bundled
|
|
337
|
+
`manga_models/bubble.onnx` (it ships in the wheel, so an editable install has
|
|
338
|
+
it). The suite is deterministic, runs offline, and draws its own pages with
|
|
339
|
+
Pillow — including the open balloon whose interior joins the page background,
|
|
340
|
+
which geometry alone cannot separate. No third-party manga page is redistributed
|
|
341
|
+
with this project.
|
|
342
|
+
|
|
343
|
+
`examples/` is gitignored: it is the place for your own scans.
|
|
344
|
+
|
|
345
|
+
## License
|
|
346
|
+
|
|
347
|
+
MIT — see [LICENSE](LICENSE). The bundled balloon model weights are Apache-2.0;
|
|
348
|
+
see [manga_models/NOTICE](manga_models/NOTICE).
|
|
349
|
+
|
|
350
|
+
### Research basis
|
|
351
|
+
|
|
352
|
+
- [OpenCV contour extraction](https://docs.opencv.org/4.13.0/df/d0d/tutorial_find_contours.html): contour-derived interior masks.
|
|
353
|
+
- [Pillow text measurement and drawing](https://pillow.readthedocs.io/en/stable/reference/ImageDraw.html): measured line layout and mask rendering.
|
|
354
|
+
- [huyvux3005/manga109-segmentation-bubble](https://huggingface.co/huyvux3005/manga109-segmentation-bubble) (Apache-2.0): bundled YOLO11n balloon segmenter, exported to ONNX.
|
|
355
|
+
- [Dubray and Laubrock, speech balloon segmentation](https://arxiv.org/abs/1902.08137): open balloons need learned segmentation rather than thresholding alone.
|
|
356
|
+
- [comic-text-detector](https://github.com/dmMaze/comic-text-detector): learned text masks as an alternative to OCR rectangles; its GPL-3.0 code is not included.
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
# manga-translator
|
|
2
|
+
|
|
3
|
+
[](https://github.com/igun997/manga-translator/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/manga-translator/)
|
|
5
|
+
[](https://pypi.org/project/manga-translator/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
Translate a folder of manga pages, or a few selected pages, from any supported
|
|
9
|
+
language to any other. The CLI finds speech balloons and light narration boxes,
|
|
10
|
+
reads them with Tesseract, translates with locally installed Argos models, and
|
|
11
|
+
reletters the page in your target font.
|
|
12
|
+
|
|
13
|
+
- **Offline by default.** Detection, OCR and translation all run on your
|
|
14
|
+
machine. Nothing is uploaded, and no model is downloaded during translation.
|
|
15
|
+
- **LLM post-editing is optional.** An OpenAI-compatible model can polish the
|
|
16
|
+
offline drafts when you ask for it with `--llm`; otherwise no LLM credential
|
|
17
|
+
is read and no network request is made.
|
|
18
|
+
- **Nothing is overwritten blind.** Outputs go to a separate directory, and every
|
|
19
|
+
region that fails to translate or fit is reported instead of clipped.
|
|
20
|
+
|
|
21
|
+
Two commands ship in the package: `manga-translate` (detect → OCR → translate →
|
|
22
|
+
reletter) and `manga-bubbles` (analyze and reletter by hand, no translation).
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Requirements
|
|
27
|
+
|
|
28
|
+
| Requirement | Why | Notes |
|
|
29
|
+
| --- | --- | --- |
|
|
30
|
+
| Python 3.10+ | runtime | |
|
|
31
|
+
| Tesseract OCR | reads the original text | plus a language pack **per source language** |
|
|
32
|
+
| Argos package for your language pair | offline translation | installed once by `manga-translate models` |
|
|
33
|
+
| A font with your **target** script | reletters the page | CJK needs a CJK font; Latin/Cyrillic/Greek work with DejaVu |
|
|
34
|
+
|
|
35
|
+
OCR language packs, Argos translation packages and target fonts are three
|
|
36
|
+
independent things. Changing the target language usually means installing a
|
|
37
|
+
font; changing the source language usually means installing an OCR pack.
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
### 1. System packages (Debian/Ubuntu)
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
sudo apt-get install tesseract-ocr tesseract-ocr-eng # English source
|
|
45
|
+
# Japanese source also needs:
|
|
46
|
+
# tesseract-ocr-jpn tesseract-ocr-jpn-vert
|
|
47
|
+
# A CJK font is only needed when the TARGET is Japanese/Chinese/Korean:
|
|
48
|
+
# fonts-noto-cjk
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 2. The CLI
|
|
52
|
+
|
|
53
|
+
With `pip`:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
python3 -m venv .venv
|
|
57
|
+
.venv/bin/python -m pip install -e .
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
With [uv](https://docs.astral.sh/uv/) (faster, and what CI uses):
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
uv venv .venv
|
|
64
|
+
uv pip install --python .venv/bin/python -e .
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Or straight from PyPI, once published:
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
pip install manga-translator
|
|
71
|
+
# or
|
|
72
|
+
uv tool install manga-translator
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
> `argostranslate` depends on PyTorch. On a GPU-less machine, install the CPU
|
|
76
|
+
> build first to avoid pulling several gigabytes of CUDA wheels:
|
|
77
|
+
> ```sh
|
|
78
|
+
> uv pip install --python .venv/bin/python torch --index-url https://download.pytorch.org/whl/cpu
|
|
79
|
+
> uv pip install --python .venv/bin/python -e .
|
|
80
|
+
> ```
|
|
81
|
+
|
|
82
|
+
Verify:
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
.venv/bin/manga-translate --help
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Usage
|
|
89
|
+
|
|
90
|
+
### Step 1 — install the translation route (once, needs internet)
|
|
91
|
+
|
|
92
|
+
Translation needs an Argos package for the pair you want. `models` resolves the
|
|
93
|
+
shortest route, direct or through a pivot such as Japanese → English →
|
|
94
|
+
Indonesian, and downloads only the missing packages:
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
.venv/bin/manga-translate models --source ja --target id
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
```text
|
|
101
|
+
Installed offline route: ja->en, en->id
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
This is the **only** command that touches the network. Run it again for each new
|
|
105
|
+
pair; it is a no-op when the route is already installed.
|
|
106
|
+
|
|
107
|
+
### Step 2 — translate
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
# A whole folder, subfolders included
|
|
111
|
+
.venv/bin/manga-translate translate ./pages --source ja --target id --output ./translated-id
|
|
112
|
+
|
|
113
|
+
# Just a few pages
|
|
114
|
+
.venv/bin/manga-translate translate cover.jpeg page-01.jpeg \
|
|
115
|
+
--source ja --target id --output ./translated-id
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Outputs are PNGs under `--output` (default `translated-TARGET`), keeping the
|
|
119
|
+
input folder's relative paths. Existing outputs are left alone unless you pass
|
|
120
|
+
`--overwrite`.
|
|
121
|
+
|
|
122
|
+
### Language codes
|
|
123
|
+
|
|
124
|
+
`--source` / `--target` are **Argos** codes; `--ocr-lang` is a **Tesseract**
|
|
125
|
+
code. The Tesseract code is inferred from `--source` for the common languages,
|
|
126
|
+
and can always be overridden:
|
|
127
|
+
|
|
128
|
+
| | | |
|
|
129
|
+
| --- | --- | --- |
|
|
130
|
+
| `ja` → `jpn` | `en` → `eng` | `id` → `ind` |
|
|
131
|
+
| `zh` → `chi_sim` | `ko` → `kor` | `es` → `spa` |
|
|
132
|
+
|
|
133
|
+
```sh
|
|
134
|
+
# Vertical Japanese lettering
|
|
135
|
+
.venv/bin/manga-translate translate ./pages --source ja --target id \
|
|
136
|
+
--ocr-lang jpn_vert --psm 5
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Use `--font /path/to/font.ttf` whenever the default target font cannot render
|
|
140
|
+
your script; Japanese/Chinese/Korean default to
|
|
141
|
+
`/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc` and fail with an
|
|
142
|
+
actionable message if it is absent.
|
|
143
|
+
|
|
144
|
+
### Reading the report
|
|
145
|
+
|
|
146
|
+
Every run writes `translation-report.json` next to the images:
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"source": "en", "target": "id", "llm": false,
|
|
151
|
+
"pages": [{
|
|
152
|
+
"input": "pages/panel-1.jpeg",
|
|
153
|
+
"output": "translated-id/panel-1.png",
|
|
154
|
+
"status": "ok",
|
|
155
|
+
"regions": [{
|
|
156
|
+
"id": 1,
|
|
157
|
+
"bbox": [576, 4, 856, 436],
|
|
158
|
+
"source": "AT THE MOMENT OF MY DEATH, ...",
|
|
159
|
+
"translation": "Pada saat kematianku, ...",
|
|
160
|
+
"status": "translated"
|
|
161
|
+
}]
|
|
162
|
+
}]
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
| Status | Meaning |
|
|
167
|
+
| --- | --- |
|
|
168
|
+
| `translated` | OCR'd, translated and reletted |
|
|
169
|
+
| `offline_fallback` | LLM text did not fit; the offline draft was used instead |
|
|
170
|
+
| `untranslated` | translator returned nothing new; the balloon was left untouched |
|
|
171
|
+
| `unfittable` | translation cannot fit at the minimum font size; nothing erased |
|
|
172
|
+
| `no_text` | detector found no text in this region |
|
|
173
|
+
| `failed` | OCR or render error; artwork untouched |
|
|
174
|
+
|
|
175
|
+
Page statuses are `ok`, `partial` (any region failed) or `no_text`. The command
|
|
176
|
+
exits `0` when every page is `ok`, `1` when any page is `partial`/`failed`, and
|
|
177
|
+
`2` on a configuration error. **Always eyeball the output**: OCR, detection and
|
|
178
|
+
inpainting are all approximate.
|
|
179
|
+
|
|
180
|
+
### Missed a balloon? Annotate it
|
|
181
|
+
|
|
182
|
+
Open the image, read the coordinates, and add a region the detectors did not
|
|
183
|
+
find. Give points *inside* the balloon outline:
|
|
184
|
+
|
|
185
|
+
```json
|
|
186
|
+
{"panel-1.jpeg": [{"polygon": [[903, 1276], [948, 1281], [998, 1298], [999, 1362], [986, 1465]]}]}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
```sh
|
|
190
|
+
.venv/bin/manga-translate translate panel-1.jpeg --source en --target id \
|
|
191
|
+
--regions regions.json --output ./translated-id
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Keys are the input filename, or the relative path inside a folder input. A
|
|
195
|
+
`bbox: [left, top, right, bottom]` works instead of a polygon. Keep the shape
|
|
196
|
+
inset: it masks both the erase step and the fitted text, so a polygon that
|
|
197
|
+
spans artwork can erase art.
|
|
198
|
+
|
|
199
|
+
### Optional: LLM post-editing
|
|
200
|
+
|
|
201
|
+
The LLM receives the recognized source text and its offline draft — **not the
|
|
202
|
+
image** — and rewrites the draft into more natural target language. It never
|
|
203
|
+
replaces a translation that does not fit; the offline draft is used then.
|
|
204
|
+
|
|
205
|
+
```sh
|
|
206
|
+
.venv/bin/manga-translate translate ./pages --source ja --target id \
|
|
207
|
+
--llm --provider netra --model deepseek/deepseek-v4-flash-0731 \
|
|
208
|
+
--output ./translated-id
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Credentials, in order of priority:
|
|
212
|
+
|
|
213
|
+
1. **A provider file.** Defaults to `~/.omp/agent/models.yml`:
|
|
214
|
+
|
|
215
|
+
```yaml
|
|
216
|
+
providers:
|
|
217
|
+
netra:
|
|
218
|
+
baseUrl: https://your-endpoint/v1
|
|
219
|
+
apiKey: sk-...
|
|
220
|
+
models:
|
|
221
|
+
- id: deepseek/deepseek-v4-flash-0731
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Point elsewhere with `--models-file ./models.yml`; pick an entry with
|
|
225
|
+
`--provider` and `--model`.
|
|
226
|
+
|
|
227
|
+
2. **Environment variables**, which skip the file entirely and are convenient
|
|
228
|
+
for CI:
|
|
229
|
+
|
|
230
|
+
| Variable | Purpose |
|
|
231
|
+
| --- | --- |
|
|
232
|
+
| `MANGA_LLM_BASE_URL` | OpenAI-compatible base URL |
|
|
233
|
+
| `MANGA_LLM_API_KEY` | key for any provider |
|
|
234
|
+
| `MANGA_LLM_API_KEY_<PROVIDER>` | key for one provider, e.g. `MANGA_LLM_API_KEY_NETRA` |
|
|
235
|
+
| `MANGA_LLM_MODEL` | model id |
|
|
236
|
+
| `OPENAI_API_KEY` | last-resort fallback |
|
|
237
|
+
|
|
238
|
+
Remote endpoints must be `https`; plain `http` is accepted only for loopback.
|
|
239
|
+
`--llm` makes network requests and may cost money. Without it, no key is read,
|
|
240
|
+
no config file is opened, and no request is made.
|
|
241
|
+
|
|
242
|
+
### Manual relettering without translation
|
|
243
|
+
|
|
244
|
+
`manga-bubbles` finds balloons and gives each an ID you can translate by hand.
|
|
245
|
+
|
|
246
|
+
```sh
|
|
247
|
+
.venv/bin/manga-bubbles analyze panel-1.jpeg > detections.json
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
```json
|
|
251
|
+
[{"id": 1, "bbox": [576, 4, 856, 436], "text": "I WANT TO DO IT!", "words": [ ... ]}]
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Write a JSON map of only the IDs you want changed, then apply it:
|
|
255
|
+
|
|
256
|
+
```json
|
|
257
|
+
{"1": "I WILL DO IT TODAY, EVEN IF IT TAKES ALL DAY!"}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
```sh
|
|
261
|
+
.venv/bin/manga-bubbles replace panel-1.jpeg translations.json out.png \
|
|
262
|
+
--font /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Use the same `--lang` and `--psm` for `analyze` and `replace` so the IDs line
|
|
266
|
+
up. Balloons you do not list, and everything outside the balloon interiors,
|
|
267
|
+
stay pixel-identical. Text too long to fit raises an error rather than clipping.
|
|
268
|
+
|
|
269
|
+
## How detection works
|
|
270
|
+
|
|
271
|
+
1. **Bright-region geometry** finds enclosed balloons and light rectangular
|
|
272
|
+
narration boxes by thresholding, filling, and filtering on shape.
|
|
273
|
+
2. **A bundled ONNX balloon segmenter** adds balloons the geometric pass cannot
|
|
274
|
+
see — notably open balloons whose white interior flows into the page
|
|
275
|
+
background. Its mask is inset by a few pixels before OCR, so lettering
|
|
276
|
+
sitting outside the outline is never read as dialogue.
|
|
277
|
+
3. Overlapping proposals are deduplicated, so a balloon is never OCR'd twice.
|
|
278
|
+
|
|
279
|
+
The segmenter is `huyvux3005/manga109-segmentation-bubble` (Apache-2.0),
|
|
280
|
+
exported to a fixed 1600×1600 ONNX graph and run with `onnxruntime` only — no
|
|
281
|
+
Ultralytics install, no download at run time. See `manga_models/NOTICE` and
|
|
282
|
+
`manga_models/LICENSE`.
|
|
283
|
+
|
|
284
|
+
## Scope and limits
|
|
285
|
+
|
|
286
|
+
- Targets speech balloons and light narration boxes. Dark balloons, heavy
|
|
287
|
+
screentone, and unusual shapes may be missed — annotate them with `--regions`.
|
|
288
|
+
- Erasure covers OCR word boxes, so stylized lettering or punctuation the OCR
|
|
289
|
+
missed can need manual retouching.
|
|
290
|
+
- Right-to-left and vertical reading order is not reconstructed for you; the
|
|
291
|
+
report lists regions top-to-bottom, left-to-right.
|
|
292
|
+
- Detection is not a general artwork segmenter, and no OCR result is guaranteed.
|
|
293
|
+
- Inspect before publishing anything.
|
|
294
|
+
|
|
295
|
+
## Development
|
|
296
|
+
|
|
297
|
+
```sh
|
|
298
|
+
uv venv .venv
|
|
299
|
+
uv pip install --python .venv/bin/python -e .
|
|
300
|
+
uv pip install --python .venv/bin/python "numpy>=1.26" "opencv-python-headless>=4.11" "Pillow>=10" "PyYAML>=6" "onnxruntime>=1.17,<2"
|
|
301
|
+
|
|
302
|
+
.venv/bin/python -m unittest discover -s tests -v
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Tests need Tesseract with the `eng` pack, DejaVu fonts, and the bundled
|
|
306
|
+
`manga_models/bubble.onnx` (it ships in the wheel, so an editable install has
|
|
307
|
+
it). The suite is deterministic, runs offline, and draws its own pages with
|
|
308
|
+
Pillow — including the open balloon whose interior joins the page background,
|
|
309
|
+
which geometry alone cannot separate. No third-party manga page is redistributed
|
|
310
|
+
with this project.
|
|
311
|
+
|
|
312
|
+
`examples/` is gitignored: it is the place for your own scans.
|
|
313
|
+
|
|
314
|
+
## License
|
|
315
|
+
|
|
316
|
+
MIT — see [LICENSE](LICENSE). The bundled balloon model weights are Apache-2.0;
|
|
317
|
+
see [manga_models/NOTICE](manga_models/NOTICE).
|
|
318
|
+
|
|
319
|
+
### Research basis
|
|
320
|
+
|
|
321
|
+
- [OpenCV contour extraction](https://docs.opencv.org/4.13.0/df/d0d/tutorial_find_contours.html): contour-derived interior masks.
|
|
322
|
+
- [Pillow text measurement and drawing](https://pillow.readthedocs.io/en/stable/reference/ImageDraw.html): measured line layout and mask rendering.
|
|
323
|
+
- [huyvux3005/manga109-segmentation-bubble](https://huggingface.co/huyvux3005/manga109-segmentation-bubble) (Apache-2.0): bundled YOLO11n balloon segmenter, exported to ONNX.
|
|
324
|
+
- [Dubray and Laubrock, speech balloon segmentation](https://arxiv.org/abs/1902.08137): open balloons need learned segmentation rather than thresholding alone.
|
|
325
|
+
- [comic-text-detector](https://github.com/dmMaze/comic-text-detector): learned text masks as an alternative to OCR rectangles; its GPL-3.0 code is not included.
|