Skip to main content
Pre-release

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.

Metadata

Release files for ocr-batch 0.1.0b2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ocr-batch 0.1.0b2
File Size Uploaded Provenance
ocr_batch-0.1.0b2.tar.gz 18.8 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for ocr-batch 0.1.0b2
File Interpreter ABI Platform
ocr_batch-0.1.0b2-py3-none-any.whl Python 3 none any Details

Total release size: 41.9 kB

Release files / ocr_batch-0.1.0b2.tar.gz

Download URL ocr_batch-0.1.0b2.tar.gz
Size 18.8 kB
Tags Source
SHA-256 checksum
How to use checksums
f6cad6e6b60ac648acde7ff771347bbd03c7cd853dafb9e20f8a2d2a449ca361
BLAKE2b-256 checksum
How to use checksums
8741988a42f3ebedf9fdbf8569547c37620b309d599aa8f384b5dfca5b1f0187
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 27, 2026.

Transparency log

Release files / ocr_batch-0.1.0b2-py3-none-any.whl

Download URL ocr_batch-0.1.0b2-py3-none-any.whl
Size 23.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b1bbb2100b458675c5f6256e212e111be31b87ab5c25f7c5030c5955eb5438ae
BLAKE2b-256 checksum
How to use checksums
8665ff9c9c3a793e405538b24e702545f7e9f9589d2ba5356a9657e4fd9be0c9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 27, 2026.

Transparency log

Security for ocr-batch 0.1.0b2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for ocr_batch-0.1.0b2.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 27, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page