lmocr
lmocr is a Rust CLI that converts PDFs and images to Markdown using an OpenRouter multimodal model.
The tool is optimized for real OCR work, not toy demos:
- input types: PDFs, single images (PNG, JPEG, WebP, GIF, TIFF, BMP), and folders of images treated as multi-page documents
- per-page processing with retries and resumable caching
- continuity-aware stitching across page boundaries
- deterministic cleanup for running headers, footers, and page numbers
- blank-page detection to avoid hallucinated content
- extracted-text fast path (
pdftotext) for text-based PDFs
Install
# needed for PDF inputs only (macOS); image inputs have no external dependencies
# build + install to ~/.cargo/bin
Setup
# configure once
# or set per-run
Usage
Run lmocr -h for the full option list.
Presets
| Preset | Description |
|---|---|
markdown |
Default. Faithful transcription — preserves all structure, tables as HTML, poetry in <pre> blocks. |
audiobook |
Linearized for text-to-speech. Strips tables, images, headers, footers, page numbers, and footnotes. Expands abbreviations and merges hyphenated line breaks. |
search |
Keyword-dense output for search indexing. Keeps headings, lists, and data tables. Removes decorative filler. |
Note: --exclude flags are redundant with audiobook since it already strips those elements.
Custom instructions
The -i / --instruction flag appends custom rules to the OCR prompt:
How it works
- Text-based PDFs use a
pdftotextfast path — no vision API call, much cheaper and faster. - Scanned / image-heavy PDFs render each page as a PNG and send it to the vision model.
- This is automatic based on extracted-text coverage. No flag needed.
- Images are sent directly to the vision model. TIFF/BMP are transcoded to PNG for upload; a folder of images is processed as one multi-page document (sorted lexicographically) with full cross-page stitching.
Configuration
# API key is stored by `lmocr auth` at:
# macOS: ~/Library/Application Support/ai.lmocr.lmocr/config.toml
# Linux: ~/.config/lmocr/config.toml
# Or set per-session:
Cache lives at .lmocr/cache/ relative to the current directory. To clear it:
# or skip cache for a single run:
The cache key includes: source file, page number, DPI, model, preset, excludes, prompt version, custom instruction, and previous-page context. Changing any of these invalidates the cached result for that page.
Reliability Notes
--max-retriesmeans retries after the first attempt (total attempts = retries + 1).- Retry loop uses linear backoff.
- Image OCR retries now include automatic lower-DPI fallbacks (
300 -> 240 -> 200 -> 180 -> 150) before failing a page. - Continuity logic merges split sentences and de-hyphenates page-break fragments.
- Boundary artifact stripping removes common leaked seam headers when they interrupt sentence flow, including default mode.
Evaluation
Fast local checks
Regression evals (maintainer workflow)
The eval_*.sh scripts below run against a corpus of real-world PDFs that is
not distributed with this repo (size + copyright). It lives in a private
Hugging Face dataset (finnatsea/lmocr-test-pdfs); with access, fetch it into
project/test_pdfs/ with:
Without dataset access you can't run these scripts — cargo test is the
check to run for contributions. The eval descriptions are kept here so the
regression surface is documented.
Smoke eval against known failure modes
project/eval_smoke.sh runs targeted OCR slices from project/test_pdfs/ and asserts:
- first-page stability run across all 10 evaluation PDFs (crash/no-crash gate)
- no leaked
Introductionboundary header in Heidegger p14-15 - poetry block in Heidegger p28-30 is wrapped in
<pre> - missing Cannae paragraph (
A condition of success lies...) is present - numbered running headers (for example
FREDERICK ... 9) are removed
Cross-PDF boundary/generalization eval
project/eval_generalization.sh runs short boundary slices across all evaluation PDFs in both markdown and audiobook presets and asserts:
- no known Cannae running-header artifacts (
FREDERICK ... 9,LO CANNAE) - no isolated page-number-like leak lines (Arabic/Roman standalone tokens)
License
AI training
The author reserves rights to this work under applicable text-and-data-mining provisions and asks that it not be used as AI training data.