README
ยถ
๐ qocr

A lightweight, self-contained CLI that extracts structured text from images, multi-page PDFs, and EPUB books using either the Baidu Unlimited-OCR model (default), the GLM-OCR model, or the built-in native text-layer extractor (no inference engine needed, no GPU, no network) โ all selectable via flags.
[!IMPORTANT] The
-nativemode (PDF) and EPUB mode require no inference engine. They read text directly from the document's embedded text layer. The AI-powered modes still require an OpenAI-compatible inference engine (such as vLLM). See the Native Text Extraction and EPUB Conversion sections for details.
๐ Prerequisites
Inference Engine
The CLI sends rendered page images to a chat-completions endpoint. By default it expects the server at http://localhost:8080.
Quick start Baidu Unlimited-OCR with vLLM (recommended):
docker run --gpus all \
--privileged --ipc=host -p 8000:8000 \
-v ~/.cache/huggingface:/root/.cache/huggingface \
vllm/vllm-openai:unlimited-ocr baidu/Unlimited-OCR \
--trust-remote-code \
--logits_processors vllm.model_executor.models.unlimited_ocr:NGramPerReqLogitsProcessor \
--no-enable-prefix-caching \
--mm-processor-cache-gb 0 \
--tensor-parallel-size 1
Quick start GLM-OCR with vLLM:
vllm serve zai-org/GLM-OCR \
--allowed-local-media-path / \
--port 8000 \
--gpu-memory-utilization 0.75 \
--speculative-config '{"method": "mtp", "num_speculative_tokens": 1}'
Quick start with Ollama:
If you are using Ollama (which runs on port 11434 by default), you can run GLM-OCR locally.
[!TIP] By default, Ollama configures model instances with a small context window (
num_ctx 2048) and output generation limit (num_predict 128). High-resolution images (like the default 200 DPI PDF renders) translate to a high number of visual tokens, filling up the default context window and causing Ollama to truncate its responses early.To run with Ollama, you have two options:
- Option A (Zero-Setup Sweetspot): Just run the CLI with a lower resolution of
-dpi 75(requires no changes to Ollama):ocr -endpoint http://localhost:11434 -model glm-ocr:latest -dpi 75 document.pdf- Option B (Use full 200 DPI): Create a custom model in Ollama with expanded limits:
- Create a text file named
Modelfilecontaining:FROM glm-ocr:latest PARAMETER num_ctx 8192 PARAMETER num_predict 4096- Register the customized model version in Ollama:
ollama create glm-ocr-large -f Modelfile- Run the CLI targeting the new model and Ollama endpoint:
ocr -endpoint http://localhost:11434 -model glm-ocr-large document.pdf
Remote server:
If the engine runs on another host, simply specify the endpoint. Images are automatically embedded and sent as base64 data-URIs:
qocr -endpoint http://10.0.0.5:8000 document.pdf
โจ Key Features
- ๐ Zero Dependencies: Built with pure Go + WebAssembly. No need for
poppler,mupdf, or any system-level PDF tools. - ๐ฆ Self-Contained: PDF rendering is embedded inside the binary. Single file, works everywhere.
- ๐ Multi-Engine Support: Powered by Baidu Unlimited-OCR by default (
-engine baidu), with support for GLM-OCR (-engine glm) and the built-in native text-layer extractor (-native) โ same CLI flags, same output formats. - ๐ Robust Multi-Page PDF Support: Renders pages locally, then dispatches to the engine โ page-by-page by default (use
-batch-sizeto group multiple pages per request for Baidu to leverage native multi-page reasoning), or sequentially for GLM-OCR. - ๐ค Native Text Extraction: For digitally-born PDFs, extract text, headings, and tables directly from the PDF's internal text layer with zero inference โ offline, instant, GPU-free.
- ๐ EPUB Conversion: Convert EPUB books to Markdown, HTML, JSON, LaTeX, or plain text โ no AI, no OCR, no network. Auto-detected by file extension.
- ๐ฏ Multiple Outputs: Get results in Markdown, HTML, Plain Text, JSON, or LaTeX.
- ๐ Cross-Platform: Compiled for Linux, macOS, and Windows (AMD64 & ARM64).
๐ ๏ธ Build & Install
Ensure you have Go 1.25+ installed.
# Build for your current platform (default target)
make
# Cross-compile for all supported platforms (linux, darwin, windows for amd64 & arm64)
make build-all
# Install to your Go bin directory
make install
The resulting binaries will be placed in the dist/ folder. You can install the qocr binary to your system PATH using:
# After building
sudo cp dist/qocr /usr/local/bin/qocr # Linux/macOS
# or
copy dist\qocr.exe "C:\Program Files\qocr\qocr.exe" # Windows
The resulting binaries will be placed in the dist/ folder.
๐ Usage
The CLI supports flags in any position (before or after the input file). You can use either a single dash - or a double dash --.
ocr [options] <file>
Options
| Flag | Description | Default |
|---|---|---|
-endpoint |
API base URL | http://localhost:8080 |
-port |
Override port in endpoint URL | 0 (uses port from endpoint) |
-model |
Model name | baidu/Unlimited-OCR (or zai-org/GLM-OCR in glm mode) |
-engine |
OCR engine to use: baidu, glm, native, hybrid, or epub |
baidu |
-baidu |
Use Baidu engine (alias for -engine baidu) |
false |
-glm |
Use GLM engine (alias for -engine glm) |
false |
-native |
Extract text from PDF text layer โ no OCR, no AI, no network | false |
-hybrid |
Use native PDF text with Baidu table OCR | false |
-epub |
Force EPUB extraction mode (auto-detected by .epub extension) |
false |
-prompt |
Instruction sent with the file | Automatic prompt recipe (<image>document parsing. / <image>Multi page parsing.) |
-output |
Write output to file instead of stdout | stdout |
-dpi |
PDF rendering resolution | 200 |
-resume |
Resume previous execution if interrupted | true |
-markdown |
Output as Markdown | true |
-html |
Output as HTML document (1:1 detection indexing with inline metadata) | false |
-text |
Output as plain text (flattens tables) | false |
-json |
Output as structured JSON (includes dimensions & rotation metadata) | false |
-latex |
Output as LaTeX document fragment (tables are auto-scaled) | false |
-bbox |
Embed normalized bounding boxes as HTML comments in markdown | false |
-batch-size |
Number of pages per request (Baidu mode only, 0 = one per request) | 0 (one page per request) |
-max-tokens |
Max tokens to generate (0 means use default: 8192 for baidu, unset for glm) | 0 |
-raw |
Dump raw model response (debug) | false |
-help |
Show usage information | false |
-version |
Print version and exit | false |
๐ก Examples
Basic OCR
Prints formatted Markdown to your terminal:
qocr scan.png
Multi-page PDF to File
Renders all pages and combines them into a single Markdown document. Flags can follow the filename:
qocr document.pdf -output result.md -dpi 150
Remote Server
Specify the custom endpoint when the vLLM server is on a different machine:
qocr -endpoint http://10.0.0.5 invoice.pdf
Structured Data
Extract raw JSON data for programmatic use:
qocr -json -output result.json document.pdf
Using the Baidu Alias
Use the handy -baidu flag as an alias:
qocr -baidu document.pdf -output result.txt
Using the Baidu Unlimited-OCR Engine
The Baidu engine is the default, so you can omit -engine baidu and just set a custom endpoint or model:
qocr -endpoint http://192.168.0.12:4000 -model baidu/Unlimited-OCR document.pdf -latex -output result.tex
Native PDF Extraction (No OCR)
For digitally-born PDFs โ reports, papers, word-processor exports โ extract text and tables instantly without any inference engine:
qocr -native document.pdf -output result.md
EPUB Conversion (No OCR)
Convert any EPUB book to your desired output format instantly. The .epub extension is auto-detected โ no flags needed:
qocr book.epub -output book.md
qocr book.epub -json -output book.json
qocr book.epub -latex -output book.tex
Both the GLM-OCR and Baidu Unlimited-OCR models require images as input. Since neither model can process raw PDF blobs directly, this CLI performs the following steps (engine-dependent behaviors are noted inline):
- PDF Rendering: Uses
go-pdfiumrunning on thewazeroWebAssembly engine to render PDF pages into images. The default is 200 DPI, which is optimal for balance between speed and OCR quality. - Sequential vs. Batched Processing: With the
glmengine, pages are sent one at a time to avoid overwhelming the GPU or hitting context limits. With thebaiduengine, pages are sent one at a time by default (use-batch-sizeto group multiple pages into a single request for native multi-page reasoning). The CLI prints a beautiful, color-coded real-time dashboard of current progress and timing. - Automatic Resuming: If
-resumeis enabled, the CLI computes a unique SHA-256 hash representing the input file (path, size, modification time) and API parameters (including the chosen engine). Every successfully processed page is saved locally to your system cache directory (~/.cache/ocr-cli/or equivalent). If interrupted, re-running the same command will restore all cached pages and skip API calls, resuming right where it left off. Cache files are cleaned up upon successful completion. - Structured Parsing: The results are combined and parsed into the chosen format. Engine-specific output formats (GLM-OCR's JSON array of blocks, Baidu's markdown laced with
<|det|>grounding tokens and<PAGE>page markers) are normalized into the requested output format. If the model returns mixed content, the CLI extracts the JSON part automatically.
๐ฆ Output Formats
๐ Markdown (Default)
Maps block labels (title, text, table, figure) to appropriate Markdown elements. Multi-page documents are separated by --- lines and include page comments.
๐ HTML (-html)
Converts detections directly into structured, editable HTML elements (<h1>, <h2>, <p>, ocr-table, ocr-image, ocr-page-number) with data-detection-index attributes for 1:1 indexing and DOM manipulation/translation.
๐ Plain Text (-text)
Strips all Markdown decoration and flattens tables for easy copy-pasting or grep-ing.
๐ข JSON (-json)
Returns a full structured object containing the source path, model used, a list of page metadata (width, height, DPI, rotation), and a list of all detected blocks with their coordinates (bbox_2d).
๐งฎ LaTeX (-latex)
Returns a LaTeX document fragment containing the OCRed text paragraphs and tables. Tables are dynamically measured: if a table's natural width exceeds the page's text line width, it is auto-scaled down using a native LaTeX savebox conditional wrapper to fit within the margins; narrow tables are left at their natural size to prevent ugly layout stretching.
๐ค Baidu Unlimited-OCR Engine
The CLI supports the Baidu Unlimited-OCR model via -engine baidu. Key features of this integration:
- Recipes: Automatic instruction tuning based on page count (
<image>document parsing.for single page,<image>Multi page parsing.for multi-page). - Logit Processor Configuration: Passes the official
"custom_logit_processor": "DeepseekOCRNoRepeatNGramLogitProcessor"and"custom_params"(ngram_sizeandwindow_size) configuration parameters, preventing infinite loops and text repetition on the server. - Batching: Processes page-by-page sequentially by default (batch size of 1) for maximum memory stability, avoiding out-of-memory errors on large documents. You can customize the batch size using the
-batch-sizeflag. - Special Tokens: Preserves grounding coordinates and page tokens returned by the server to construct layout-accurate 2D mappings.
- Cache: Unique caching strategy that serializes the full raw document output to skip inference.
Example usage:
# Using Baidu engine with custom model and endpoint
qocr -engine baidu -model <your-vllm-model-id> -endpoint http://192.168.0.12:4000 document.pdf -latex -output result.tex
๐ค Native Text Extraction (No-OCR)
For digitally-born PDFs โ exported from Word, LaTeX, InDesign, or any PDF writer โ qocr can extract text, headings, and tables directly from the PDF's internal text layer with zero inference engine, zero GPU, and zero network calls.
qocr -native document.pdf -output result.md
How it works
The native mode uses a two-tier detection strategy:
Tier 1 โ Tagged PDFs (Word exports, InDesign, PDF/UA, accessibility-compliant docs)
Many professionally-produced PDFs embed a full logical structure tree with semantic <Table>, <TR>, <TD>, <TH>, <H1>โ<H6>, <P> elements. When detected, qocr reads this tree directly via PDFium's FPDF_StructTree API โ the document author's own structural intent, stored in the file. Table cells are extracted exactly as authored, with zero spatial guessing.
Tier 2 โ Untagged PDFs (LaTeX output, older tools)
For PDFs without a structure tree, qocr uses GxPDF's 4-Pass Hybrid table detection โ a pure-Go, MIT-licensed library with no CGO:
- Pass 1: Gap detection (adaptive threshold)
- Pass 2: Overlap detection (Tabula-inspired)
- Pass 3: Alignment detection (geometric column clustering)
- Pass 4: Multi-line cell merging
Heading detection (both tiers)
Headings are detected using actual font size metadata from the PDF's internal font tables, read via GetPageTextStructured(CollectFontInformation=true). The modal (most-frequent) font size on the page becomes the body-text baseline:
| Ratio vs. body font size | Markdown output |
|---|---|
| โฅ 1.6ร | ## Heading (title) |
| โฅ 1.25ร | ### Heading (header) |
| Bold at body size, short line | ### Heading (header) |
| Otherwise | Paragraph |
Limitations
- Scanned PDFs: Produces empty output. Use the default OCR mode for scanned documents.
- Complex layouts: Multi-column or magazine-style layouts may have reading-order issues. Use OCR mode for maximum fidelity.
๐ EPUB Conversion (No-OCR)
For EPUB books โ novels, textbooks, technical documentation, Project Gutenberg titles โ qocr can extract text, headings, and tables directly from the embedded XHTML with zero inference engine, zero GPU, and zero network calls.
The .epub extension is auto-detected; no flag is required:
qocr book.epub -output book.md
You can also force EPUB mode explicitly with -epub or -engine epub.
[!IMPORTANT] If you pass an EPUB with an AI engine flag (e.g.
-baidu book.epub), qocr will warn you and automatically switch to native EPUB extraction. EPUB files contain machine-readable XHTML text โ no OCR is needed or beneficial.
How it works
EPUB files are standard ZIP archives containing:
META-INF/container.xmlโ points to the OPF package file- An OPF file (
content.opf) โ lists all content items and the reading-order spine - XHTML chapter files referenced by the spine
qocr reads the spine in order and converts each XHTML chapter to structured blocks:
| XHTML element | OCRBlock label | Markdown output |
|---|---|---|
<h1>, <h2> |
title |
# Heading / ## Heading |
<h3>โ<h6> |
header |
## Heading |
<p> |
text |
Paragraph |
<table> |
table |
Markdown table |
<figcaption> |
caption |
Italic caption |
<img> |
image |
![alt text]() placeholder |
<nav>, <script>, <style> |
โ | Skipped |
Tables are extracted as raw HTML and passed through the same rendering pipeline as PDF tables, so they are correctly converted to Markdown, LaTeX tabular environments, JSON blocks, etc.
Limitations
- DRM-protected EPUBs: Cannot be read (the ZIP is encrypted). Remove DRM with a compatible tool first.
- Fixed-layout EPUBs: Comics, picture books, and heavily graphical EPUBs may produce minimal text output since their content is image-based.
- Embedded fonts/styles: Visual formatting (bold, italic within paragraphs) is stripped; only the text content is extracted.
โ๏ธ License
This project is licensed under the MIT License (see LICENSE).
Documentation
ยถ
There is no documentation for this package.