# lmocr
`lmocr` is a Rust CLI that converts PDFs and images to Markdown using an OpenRouter multimodal model.
Most OCR treats pages as isolated blobs. `lmocr` treats a document as one stream: pages run serially, each LLM call sees the previous page tail, and the results are stitched back together.
- **Clean seams:** joins sentences and de-hyphenates words split across page breaks.
- **Less cruft:** skips blank pages and can filter running headers, footers, and page numbers instead of mixing them into the text.
- **Tunable output:** presets for Markdown, audiobooks, and search; `--exclude` and custom `-i` instructions for everything else.
- **Layout-aware:** the vision path can preserve tables as HTML and translate charts, graphics, checkboxes, and structured layouts into useful Markdown instead of dropping them.
- **LLM cleanup on every non-blank page:** text PDFs take a cheaper `pdftotext` path; scans and images use vision. Both still get document-aware cleanup.
- **Practical for long documents:** retries and resumable caching are built in.
Examples: make a PDF listenable without hearing page numbers and running headers every minute; convert a report without losing the table or figure that carries the point.
## Install
```bash
# needed for PDF inputs only (macOS); image inputs have no external dependencies
brew install poppler
# from crates.io
cargo install lmocr
# or from a checkout
cargo install --path . --locked
```
## Setup
```bash
# configure once
lmocr auth
# or set per-run
export OPENROUTER_API_KEY=sk-or-v1-...
```
## Usage
```bash
lmocr /path/to/file.pdf -o out.md
lmocr /path/to/file.pdf -p 10-12 --exclude headers,footers,page-numbers
lmocr /path/to/file.pdf --preset audiobook
lmocr screenshot.png -o out.md
lmocr ./scanned_pages/ -o out.md # folder of images = multi-page document
```
Run `lmocr -h` for the full option list.
## Presets
| `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:
```bash
lmocr doc.pdf -i "Translate all text to French"
lmocr doc.pdf -i "Number headings with outline format (1, 1.1, 1.1.1)"
lmocr doc.pdf -i "Omit all image descriptions"
```
## How it works
- **Text-based PDFs** use a `pdftotext` fast 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
```bash
# 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:
export OPENROUTER_API_KEY=sk-or-v1-...
```
**Cache** lives at `.lmocr/cache/` relative to the current directory. To clear it:
```bash
rm -rf .lmocr/cache
# or skip cache for a single run:
lmocr doc.pdf --no-cache
```
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-retries` means 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.
## Development
Run `cargo test` before contributing. Maintainer regression evals are documented in [project/EVAL.md](project/EVAL.md).
## License
[MIT](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.