This release is a pre-release and may not be stable for production use.
ocr-batch
Batch OCR a directory tree of PDFs two ways:
- locally, with PyMuPDF, extracting whatever text layer each PDF already has (fast, free, exact for born-digital documents);
- remotely, with the Mistral asynchronous batch OCR API (discounted, handles scans and handwriting).
Both runs use the same page separators, so the two renderings of a document can be diffed line by line.
Install
Requires Python 3.14+.
With uv, no checkout needed:
uvx --from git+https://github.com/nidrissi/ocr-batch ocr-batch run ./applications ./ocr-output
Or from a clone:
git clone https://github.com/nidrissi/ocr-batch
cd ocr-batch
uv sync
uv run ocr-batch run ./applications ./ocr-output
Export your Mistral API key before running either command (or pass --api-key):
export MISTRAL_API_KEY=...
Usage
ocr-batch run ./applications ./ocr-output
run is the whole pipeline: it starts local extraction, uploads the PDFs,
creates the batch job(s), waits for them, downloads and splits the results, and
deletes the uploaded originals. The individual stages are also available, and
submitted jobs are resumable because their ids and uploaded file ids are kept in
<output_dir>/_state.json as they arrive:
| Command | What it does |
|---|---|
ocr-batch submit IN OUT |
Local extraction, uploads, batch job creation. Records job ids and uploaded file ids. |
ocr-batch status OUT |
One-shot progress report for a submitted run. |
ocr-batch fetch OUT |
Waits (--no-wait to fail fast), downloads results, writes per-PDF outputs, cleans up. Idempotent. |
ocr-batch cleanup OUT |
Deletes the uploaded originals from Mistral. Safe to re-run. |
ocr-batch run IN OUT |
submit + fetch. |
If a run is interrupted, the job keeps going on Mistral's side; pick it back up
with ocr-batch fetch OUT. Recorded live jobs are not submitted twice: submit
refuses to start while a job from a previous run is still live, and skips
documents whose complete outputs already exist (--force overrides both).
Output layout
For applications/nested/smith.cv.final.pdf, under OUT/:
nested/smith.cv.final.native.txt local PyMuPDF text, page-separated
nested/smith.cv.final.ocr.md Mistral OCR markdown, page-separated
nested/smith.cv.final.ocr.json raw per-document OCR response
_manifest.json custom_id -> relative path, hash, page count
_state.json resumable run state (job ids, upload ids)
_mistral_batch_results.jsonl raw batch output as downloaded
_mistral_batch_errors.jsonl per-request errors, when the batch reports any
Every suffix is appended to the full name, so smith.cv.final.pdf and
smith.pdf never collide. Sources that would collide (a.pdf and a.PDF)
are refused before anything is uploaded.
Useful flags
--no-ocr / --no-native run only one of the two extraction paths
--force redo work whose outputs already exist
--jobs N local extraction processes (default: one per CPU)
--upload-workers N concurrent uploads (default: 8)
--batch-size N requests per batch job (default: 500)
--timeout-hours N batch job timeout (default: 24)
--no-include-blocks smaller responses: no per-block bounding boxes
--confidence-granularity ... none | page | word | block (default: block)
--keep-remote keep the uploaded originals on Mistral
--cancel-on-interrupt cancel the job on Ctrl-C instead of leaving it running
Exit codes: 0 success, 1 error, 3 finished with per-document failures,
130 interrupted.
Data handling
The PDFs are uploaded to Mistral (purpose: ocr, visibility: user) and made
readable to the batch job through a signed URL valid for up to 168 hours. Once a
job is terminal, the CLI attempts deletion even if fetching or splitting its
results fails; ocr-batch cleanup re-runs any deletion that failed.
As a backstop for a crash between an upload and its id reaching disk, uploads
also carry a server-side expiry (job timeout + 24h by default).
Mistral Annotations are deliberately not used: no document_annotation_format
and no bbox_annotation_format are ever sent.
Development
$ uv run pytest
$ uv run ruff check . && uv run ruff format --check .
$ uv run pyright
The test suite drives the whole pipeline through a fake client and never touches the network.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ocr_batch-0.1.0b2.tar.gz.
File metadata
- Download URL: ocr_batch-0.1.0b2.tar.gz
- Upload date:
- Size: 18.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f6cad6e6b60ac648acde7ff771347bbd03c7cd853dafb9e20f8a2d2a449ca361
|
|
| MD5 |
8eed12949ae191aff2eef798abb7b08c
|
|
| BLAKE2b-256 |
8741988a42f3ebedf9fdbf8569547c37620b309d599aa8f384b5dfca5b1f0187
|
Provenance
The following attestation bundles were made for ocr_batch-0.1.0b2.tar.gz:
Publisher:
release.yml on nidrissi/ocr-batch
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ocr_batch-0.1.0b2.tar.gz -
Subject digest:
f6cad6e6b60ac648acde7ff771347bbd03c7cd853dafb9e20f8a2d2a449ca361 - Sigstore transparency entry: 2613772562
- Sigstore integration time:
-
Permalink:
nidrissi/ocr-batch@46014c4485da79a4be4a5e881751542e1540b5b0 -
Branch / Tag:
refs/tags/v0.1.0b2 - Owner: https://github.com/nidrissi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@46014c4485da79a4be4a5e881751542e1540b5b0 -
Trigger Event:
release
-
Statement type:
File details
Details for the file ocr_batch-0.1.0b2-py3-none-any.whl.
File metadata
- Download URL: ocr_batch-0.1.0b2-py3-none-any.whl
- Upload date:
- Size: 23.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b1bbb2100b458675c5f6256e212e111be31b87ab5c25f7c5030c5955eb5438ae
|
|
| MD5 |
092205f22e26c45a3fb4863263db3ec2
|
|
| BLAKE2b-256 |
8665ff9c9c3a793e405538b24e702545f7e9f9589d2ba5356a9657e4fd9be0c9
|
Provenance
The following attestation bundles were made for ocr_batch-0.1.0b2-py3-none-any.whl:
Publisher:
release.yml on nidrissi/ocr-batch
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ocr_batch-0.1.0b2-py3-none-any.whl -
Subject digest:
b1bbb2100b458675c5f6256e212e111be31b87ab5c25f7c5030c5955eb5438ae - Sigstore transparency entry: 2613772596
- Sigstore integration time:
-
Permalink:
nidrissi/ocr-batch@46014c4485da79a4be4a5e881751542e1540b5b0 -
Branch / Tag:
refs/tags/v0.1.0b2 - Owner: https://github.com/nidrissi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@46014c4485da79a4be4a5e881751542e1540b5b0 -
Trigger Event:
release
-
Statement type: