update
This commit is contained in:
+76
-17
@@ -12,7 +12,16 @@ and cross-page continuity context:
|
||||
| Pure-OCR alternative | `google_vision_ocr.py --mode novel` | `google_vision_ocr.py --mode manga`, `local_mangaocr_ocr.py` |
|
||||
| Layout assumed | Dense running prose, read top-to-bottom then right-to-left | Speech bubbles / narration boxes / SFX scattered across panels |
|
||||
| Output | Continuous transcribed text per page | Numbered list, one entry per bubble, in manga reading order |
|
||||
| Translation | — | `--translate`, with glossary + cross-page context |
|
||||
| Translation | `--translate`, with glossary + cross-page context | `--translate`, with glossary + cross-page context |
|
||||
|
||||
`novel_ocr.py` and `manga_ocr_llm.py` share their translate/state/logging
|
||||
infrastructure via [`common.py`](common.py) — the only thing genuinely
|
||||
different between them is layout (reading order, output format), which is
|
||||
exactly why they're still two scripts rather than split along an
|
||||
OCR-vs-translate axis: translating manga still needs to know about panels
|
||||
and bubble types, and translating a novel would still want the same
|
||||
glossary/context machinery, so the layout split is what actually avoids
|
||||
duplicated logic.
|
||||
|
||||
For a light novel, this is step 1 of the pipeline — step 2 is
|
||||
[`epub_builder`](../epub_builder/README.md), which turns the resulting
|
||||
@@ -25,9 +34,12 @@ targets prose light novels and doesn't lay out manga pages.
|
||||
Reads each page with a multimodal LLM via any OpenAI-compatible API
|
||||
(OpenRouter, a direct provider endpoint, a self-hosted proxy, etc. —
|
||||
including Gemini, GPT-4V-class models, or anything else exposed through
|
||||
such an endpoint) and transcribes the vertical Japanese prose. Kept
|
||||
deliberately simple — transcription only, no translate/glossary/context —
|
||||
since that's genuinely all a novel page needs.
|
||||
such an endpoint) and transcribes the vertical Japanese prose — or
|
||||
translates it directly with `--translate` (same glossary/context
|
||||
machinery as `manga_ocr_llm.py`, via the shared [`common.py`](common.py)).
|
||||
Kept deliberately simpler than `manga_ocr_llm.py` on the transcription
|
||||
side — no bubble types/reading-order logic — since a novel page is one
|
||||
linear column of prose, not scattered panels.
|
||||
|
||||
```bash
|
||||
pip install openai pillow natsort tqdm
|
||||
@@ -39,32 +51,57 @@ pip install openai pillow natsort tqdm
|
||||
identifier. This same file is shared with `manga_ocr_llm.py` and the
|
||||
`epub_builder` module. `temperature`/`max_tokens`/`top_p`/
|
||||
`reasoning_effort` are optional — see [API parameters](#api-parameters).
|
||||
2. Optionally edit `prompt_novel.txt` — plain English text, no need to
|
||||
touch any code to tweak the instructions given to the model.
|
||||
2. Optionally edit `prompt_novel.txt` (transcription) or
|
||||
`prompt_novel_translate.txt` (`--translate`) — plain English text, no
|
||||
need to touch any code to tweak the instructions given to the model.
|
||||
|
||||
```bash
|
||||
python novel_ocr.py --input ./pages --output ./out
|
||||
|
||||
# ...or translate directly instead of transcribing:
|
||||
python novel_ocr.py --input ./pages --output ./out --translate --target-lang Russian
|
||||
```
|
||||
|
||||
- `--input`: folder with scanned page images (jpg/png/...), named so that
|
||||
alphabetical sorting matches page order (natural sort is used, so
|
||||
`page2.jpg` and `page10.jpg` sort correctly too).
|
||||
- `--output`: folder for results. Creates `pages_txt/<name>.txt` (one file
|
||||
per page) plus a `combined.md` preview of the whole run.
|
||||
- `--output`: folder for results. Creates `pages_txt/<name>.txt` (one
|
||||
file per page), a `combined.md` preview of the whole run, and a
|
||||
`state.json` manifest (see below).
|
||||
- If interrupted, just re-run with the same `--output` — pages that already
|
||||
have a `.txt` file are skipped, so nothing already done gets re-sent
|
||||
(and re-billed).
|
||||
- A page that fails to OCR (network error, rate limit, etc.) does **not**
|
||||
get an empty file written for it, specifically so the next run retries it
|
||||
instead of silently treating it as done.
|
||||
- A page that fails to OCR (network error, rate limit, truncated/empty
|
||||
response, etc.) does **not** get a file written for it, specifically so
|
||||
the next run retries it instead of silently treating it as done.
|
||||
- A page the model judges to be empty (illustration-only, blank, or a
|
||||
cover/technical page) gets a file containing exactly the literal text
|
||||
`[NO_TEXT]` — this is a deliberate marker, not an OCR failure. The
|
||||
`epub_builder` module knows to treat it as "no text on this page".
|
||||
- `--pages "3,7-12,20"` limits the run to just those pages (1-based
|
||||
positions in the sorted page list) — combine with `--redo` to re-OCR/
|
||||
re-translate just a few pages, e.g. after tweaking the prompt or
|
||||
glossary, without redoing the whole book.
|
||||
- `--redo` re-runs pages even if a `.txt` already exists for them.
|
||||
- `--start-page N` skips everything before page N entirely (not just a
|
||||
relabelling — pages before it are never sent).
|
||||
- `--no-state` ignores `state.json` and falls back to plain "file exists =
|
||||
done" behaviour.
|
||||
- `--sleep N` adds a delay (seconds) between requests if you're hitting
|
||||
rate limits.
|
||||
- `--config` overrides the config.json path (defaults to the repo root).
|
||||
- `--prompt-file` overrides the prompt file (defaults to `prompt_novel.txt`).
|
||||
- `--prompt-file` overrides the prompt file (defaults to `prompt_novel.txt`,
|
||||
or `prompt_novel_translate.txt` with `--translate`).
|
||||
|
||||
### `state.json`
|
||||
|
||||
Both `novel_ocr.py` and `manga_ocr_llm.py` write a `state.json` manifest to
|
||||
`--output`, recording (per page) the model, a hash of the exact prompt
|
||||
used, token usage, and a timestamp. This is what makes `--redo`/`--pages`
|
||||
targeted re-runs possible, and it's also how the script notices (and warns
|
||||
about, non-fatally) pages that were transcribed/translated under a prompt
|
||||
or glossary that's since changed — it won't re-OCR them on its own, but
|
||||
tells you to consider `--redo` for them.
|
||||
|
||||
## `manga_ocr_llm.py` (recommended for manga)
|
||||
|
||||
@@ -100,7 +137,8 @@ Output format is a numbered list per page:
|
||||
|
||||
where entries are ordered manga-style (panels right-to-left top-to-bottom;
|
||||
bubbles within a panel the same way), and TYPE is one of DIALOGUE,
|
||||
THOUGHT, NARRATION, or SFX. Same `--input`/`--output`/`--sleep`/`--config`/
|
||||
THOUGHT, NARRATION, or SFX. Same `--input`/`--output`/`--pages`/`--redo`/
|
||||
`--start-page`/`--no-state`/`--sleep`/`--config`/
|
||||
`--prompt-file` conventions as `novel_ocr.py`.
|
||||
|
||||
### Translation (`--translate`)
|
||||
@@ -114,16 +152,24 @@ THOUGHT, NARRATION, or SFX. Same `--input`/`--output`/`--sleep`/`--config`/
|
||||
- Use a different `--output` folder than your transcription run — both
|
||||
write to `pages_txt/<name>.txt`, and the script skips a page whose
|
||||
output file already exists, so reusing the same folder would just
|
||||
return the old transcription instead of translating.
|
||||
return the old transcription instead of translating. (`--redo` re-runs
|
||||
it in place if you'd rather overwrite than use a separate folder.)
|
||||
- Everything under Glossary/Continuity context/Lookahead context below
|
||||
applies identically to `novel_ocr.py --translate` — the `--glossary`/
|
||||
`--context-pages`/`--target-lang` flags are the exact same flags,
|
||||
implemented once in [`common.py`](common.py). `--context-pages-ahead`
|
||||
is manga-only for now (see that section for why light novel prose
|
||||
doesn't need it as much).
|
||||
|
||||
### Glossary
|
||||
|
||||
For a consistent translation of recurring names and terms, copy
|
||||
[`glossary.example.md`](glossary.example.md) to `glossary.md` right here in
|
||||
the `ocr/` folder (next to the scripts, not with your page images — a
|
||||
single stable file you keep building up across books/chapters, which also
|
||||
makes it a natural fit for an editing UI later). It's auto-detected and
|
||||
sent along with every `--translate` request, e.g.:
|
||||
single stable file you keep building up across books/chapters, shared by
|
||||
both `novel_ocr.py` and `manga_ocr_llm.py`, and a natural fit for an
|
||||
editing UI later). It's auto-detected and sent along with every
|
||||
`--translate` request, e.g.:
|
||||
|
||||
```markdown
|
||||
## Names
|
||||
@@ -257,6 +303,19 @@ with the OpenAI-compatible API the other scripts use):
|
||||
{ "vision": { "credentials": "/path/to/service-account-key.json" } }
|
||||
```
|
||||
|
||||
## System/user prompt split (caching)
|
||||
|
||||
For `novel_ocr.py` and `manga_ocr_llm.py`, every request is sent as a
|
||||
`system` message (the format instructions, and, with `--translate`, the
|
||||
glossary — identical on every page of a run) plus a `user` message (the
|
||||
page image, and, with `--context-pages`/`--context-pages-ahead`, that
|
||||
page's sliding-window context). Most OpenAI-compatible providers cache a
|
||||
repeated `system` prefix, so a 300-page book isn't re-billed/re-processed
|
||||
for the same instructions on every single page — only the truly per-page
|
||||
part (image + context) is new each time. This is implemented once in
|
||||
`common.py`'s `ocr_image()`/`build_system_prompt()`/`build_context_block()`
|
||||
and used identically by both scripts.
|
||||
|
||||
## Request/response logs
|
||||
|
||||
`novel_ocr.py`, `manga_ocr_llm.py`, and `google_vision_ocr.py` each write
|
||||
|
||||
Reference in New Issue
Block a user