Batch parse a folder of PDFs and images to markdown with LaTeX, tables, and layout boxes using baidu/Unlimited-OCR locally — with checkpoint/resume, incremental writes, and per-file repetition detection. Use when processing a large folder of scanned documents or research papers, when you need results written incrementally to survive interruption, or when extracting equations and tables from a document collection. TRIGGERS - batch ocr, ocr a folder, batch parse documents, parse multiple pdfs, ...
Scanned 9/2/2026
Install to Claude Code
npx -y skills add terrylica/cc-skills --skill unlimited-ocr-batch-folder --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Unlimited Ocr Batch Folder?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/terrylica-unlimited-ocr-batch-folder)More formats (shields.io, HTML) on the badges page.
---
name: unlimited-ocr-batch-folder
description: Batch parse a folder of PDFs and images to markdown with LaTeX, tables, and layout boxes using baidu/Unlimited-OCR locally — with checkpoint/resume, incremental writes, and per-file repetition detection. Use when processing a large folder of scanned documents or research papers, when you need results written incrementally to survive interruption, or when extracting equations and tables from a document collection. TRIGGERS - batch ocr, ocr a folder, batch parse documents, parse multiple pdfs, folder to markdown, extract formulas from folder, ocr multiple files, batch document extraction, document collection parsing, checkpoint resume.
allowed-tools: Bash, Read, Write, Glob
---
# Unlimited-OCR — batch parse a folder of PDFs and images
Process an **entire folder** of images and PDFs without losing work to interruption, using the same proven `baidu/Unlimited-OCR` model that powers [`unlimited-ocr-parse-document`](../unlimited-ocr-parse-document/SKILL.md). Results are written **incrementally to a manifest**, so a long run that gets interrupted can be resumed without reprocessing.
> **Self-Evolving Skill**: every number and guarantee below was measured on this hardware, not copied from upstream documentation. If a guarantee stops holding, fix this file and references immediately rather than deferring. Re-verify before changing a documented fact.
---
## The guarantees
1. **ONE IMAGE PER FORWARD PASS, ALWAYS** — per-image processing is deliberate and measured. Single-pass multi-image on MLX recovered 1/3, 4/5, and 0/10 pages; per-image recovered 100 % every time. See [`../../references/PITFALLS.md`](../../references/PITFALLS.md) § 3.
2. **CHECKPOINT AND RESUME** — every result is written to disk BEFORE the manifest is updated. If the process dies, the incomplete line is never recorded. Re-run with the same output folder to skip everything already done.
3. **CHARTS COME BACK EMPTY** — this is correct behaviour for a layout parser and a fatal surprise if you expected a vision model. See the next section.
4. **EXIT CODES**: The batch script returns a summary exit code: `0` if all files succeeded without repetition, `1` if some files had repetition suspected (check before trusting), `2` if any files failed with hard errors. Per-file status is always recorded in the manifest.
5. **MEMORY-BOUNDED** — no result accumulation. Each file runs in its own process (load, infer, write, exit), so a 1,000-file folder never threatens machine stability.
---
## What it will NOT do
**Charts come back empty.** Fed a nine-panel matplotlib figure, the model returned nine perfectly-placed `chart` bounding boxes and zero characters. That is correct for a layout parser and catastrophic if you assumed an image captioner.
If you need the _content_ of charts, use [`unlimited-ocr-segment-figure`](../unlimited-ocr-segment-figure/SKILL.md) to crop each detected region independently, then hand the crops to a vision model that does describe images.
---
## Use it
```bash
S=~/eon/cc-skills/plugins/unlimited-ocr/scripts/unlimited_ocr_batch_folder_parser.py
# Process a folder, writing results incrementally to ~/results/
uv run --no-project $S --folder ~/my_documents --output ~/results
# Resume from where it left off (skips everything in the manifest)
uv run --no-project $S --folder ~/my_documents --output ~/results
# Collapse character-spaced math (c u r v e -> curve), keep tables as HTML
uv run --no-project $S --folder ~/my_documents --output ~/results \\
--collapse-math --table-format html
# Use a specific backend (mlx on Apple Silicon, cuda on NVIDIA)
uv run --no-project $S --folder ~/my_documents --output ~/results --backend mlx
# Suppress per-file progress (quieter stderr)
uv run --no-project $S --folder ~/my_documents --output ~/results --quiet
```
---
## Output structure
```
~/results/
manifest.jsonl # One JSON object per file, newline-delimited
progress_report.txt # Summary at the end
<image_stem>.md # Markdown for each input image
<pdf_stem>.md # Markdown for each page of each PDF
<pdf_stem>.json # Layout boxes and metadata (optional)
```
Every line in `manifest.jsonl` is:
```json
{
"source_path": "/path/to/image.png",
"output_md": "image.md",
"output_json": null,
"status": "done",
"elapsed_seconds": 2.4,
"backend": "mlx",
"page_count": 1,
"error_message": null
}
```
Statuses are:
- `done` — parsed successfully, no repetition detected
- `repetition_suspected` — parsed, but a repetition detector fired; inspect before trusting
- `failed` — error (see `error_message`)
---
## Flags
| Flag | Values | Default | Meaning |
| ------------------------- | --------------------- | ---------- | ------------------------------------------------------------------------------------------- |
| `--folder` | PATH | (required) | Input folder; searched recursively for images + PDFs |
| `--output` | PATH | (required) | Output folder; created if missing |
| `--backend` | `auto`, `mlx`, `cuda` | `auto` | Which backend: MLX for Apple Silicon, CUDA for NVIDIA GPU |
| `--collapse-math` | (flag) | off | Rejoin `c u r p d f` into `curpdf` inside math (measured fix for the model's output format) |
| `--table-format` | `html`, `pipe` | `pipe` | HTML tables or pipe-markdown; pipe is suitable for concatenating into markdown documents |
| `--strip-det` | (flag) | off | Remove layout bounding box markers from output |
| `--quiet` | (flag) | off | Suppress per-file progress to stderr |
| `--allow-withheld-prompt` | (flag) | off | Force a withheld prompt mode (rarely needed; see `../../references/PITFALLS.md` § 1) |
---
## Interruption safety
The script is safe to interrupt (Ctrl+C) at any time:
1. Each file's result is written to the output folder **before** the manifest is updated.
2. The manifest is append-only; if a line is there, the output files exist.
3. Re-running with the same output folder automatically skips everything already in the manifest.
4. If a process crashes mid-write, the incomplete manifest line is never recorded.
**To re-process a file:** delete its line from `manifest.jsonl` and re-run.
---
## Choosing a backend
`--backend auto` (default) prefers MLX on Apple Silicon, CUDA on NVIDIA.
| | Apple Silicon (MLX) | NVIDIA (transformers) |
| ------------ | --------------------------------------------- | ------------------------------------ |
| Model | `mlx-community/Unlimited-OCR-mxfp8` (3.66 GB) | `baidu/Unlimited-OCR` (6.67 GB bf16) |
| Measured on | M3 Max, 36 GB | RTX 4090, 24 GB, Ada SM 8.9 |
| Single image | **2.4 s**, 5.2 GB peak | 3.7 s, 8.3 GB peak VRAM |
| Determinism | greedy, T=0 | byte-identical on re-run |
Full benchmarks and platform notes: [`../../references/EMPIRICAL.md`](../../references/EMPIRICAL.md).
---
## Understanding the repetition detector
Exit code 1 means **repetition was suspected, NOT that the run failed**. The detector is structural (short chunks repeating in the tail), not probabilistic, so a NEW repetition is caught, not just the known ones from the reference table.
Measured base rate: **~4 % on tables** (103 real images from the quantml corpus). Budget for it, but do not discard the output automatically — check it first. Most will be valid despite the warning.
All repetition detections are recorded in the manifest, so you can filter afterward:
```bash
jq 'select(.status == "repetition_suspected")' ~/results/manifest.jsonl
```
---
## Common questions
**Q: Can I run this on a CPU?**
No. The model's only published runtimes are MLX (Apple Silicon, GPU-backed) and transformers/CUDA (NVIDIA GPU). A CPU implementation does not exist. On a Mac, you have Apple Silicon MLX available.
**Q: How long does a 1,000-image folder take?**
~2 hours on a single M3 Max at ~7 s/image median. Every image runs in its own process, so if you have spare cores, parallelizing across multiple invocations (folder splits) will help. A separate tool for that is outside scope.
**Q: What if a file is corrupted or unreadable?**
It gets marked `failed` with an error message in the manifest. The batch continues without stopping.
**Q: Can I change the output folder without reprocessing?**
No. The manifest is tied to the output folder. To reprocess everything, use a new output folder.
---
## Limits and guarantees
All documented in [`../../references/PITFALLS.md`](../../references/PITFALLS.md) (every entry was hit while building this plugin) and [`../../references/EMPIRICAL.md`](../../references/EMPIRICAL.md) (every number was measured on the two machines it supports).
Key ones:
- **Single-pass multi-image is deliberately unused.** The model's headline capability does not survive contact with the MLX path — you get incomplete pages and no error. Per-image is slower and complete.
- **The image MIME type is detected from magic bytes, not the file extension.** WeChat serves PNG/WebP under `.jpg` URLs; the CLI reads the first bytes to detect the truth.
- **Bounding box coordinates are normalised to 0–1000 on BOTH axes**, independent of the image's aspect ratio.
- **Mathematics comes back character-spaced** by default (`c u r v e`). Pass `--collapse-math` to rejoin it.
- **The model writes tables as HTML `<table>`, never as pipe-markdown** — measured at 88 of 103 real
tables. This batch parser converts them to pipe-markdown by default; pass `--table-format html` to
keep the model's own serialization.
- **`--pad-pixels 0` clips the axis labels off every chart crop.** If you use `segment`, start with the default (12 px) and raise it only if captions are clipped.
---
## Post-Execution Reflection
After this skill completes, check before closing:
1. **Did repetition get detected?** — record which document types triggered it in `../../references/PITFALLS.md` § 3; the base rate is ~4 % on tables.
2. **Did the folder finish?** — check `progress_report.txt` for the final counts.
3. **Are there failures?** — `jq 'select(.status == "failed")' manifest.jsonl` to see which files errored.
4. **Do the markdown outputs look right?** — spot-check a few; tables come back as HTML unless you passed `--table-format pipe`, and charts come back empty.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!