lmocr 0.1.0

Convert PDFs and images to Markdown using OpenRouter multimodal LLMs
# 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

```bash
# needed for PDF inputs only (macOS); image inputs have no external dependencies
brew install poppler

# build + install to ~/.cargo/bin
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

| 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:

```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.

## Evaluation

### Fast local checks

```bash
cargo test
cargo check
```

### 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:

```bash
project/fetch_eval_data.sh
```

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

```bash
project/eval_smoke.sh
```

`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 `Introduction` boundary 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

```bash
project/eval_generalization.sh
```

`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

[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.