This skill is used for OCR, scan recognition, image text recognition, document recognition, and converting PDFs, images, Office documents, and URLs into Markdown that can continue to be edited, analyzed, and archived. It is first a general OCR entry point; when the result is identified as legal material, conservative legal post-processing is then automatically enabled. By default, it uses configuration-prioritized automatic routing:
- For local PDFs, the native text layer is probed first by default: for court electronic service judgments, electronic contracts, government official documents, and other PDFs with a reliable text layer, direct reading is more accurate, faster, and more quota-efficient than OCR. If quality meets the standard, text is extracted directly and converted to Markdown; only if it does not meet the standard does it fall back to OCR. See "PDF Text Layer Dual Path" for details.
- Only PaddleOCR configured: PDFs/images supported by PaddleOCR are prioritized through PaddleOCR; when beyond its capability boundary, prompt the user or switch to a MinerU-supported path.
- Only MinerU Token configured: all inputs supported by MinerU go uniformly through MinerU, including PDFs, images, Office files, remote document URLs, and web URLs.
- Both API sets configured: local PDFs/images prioritize PaddleOCR, while Office files/web URLs prioritize MinerU; when the preferred backend encounters quota, rate, authentication, network, or service failures, the candidate backend is automatically tried.
- When RapidOCR is already installed on the local machine, for local PDFs/images, local RapidOCR serves as the last-level candidate after cloud failure.
- Neither API set configured: when RapidOCR is already installed on the local machine, local recognition is prioritized (materials do not leave the local machine), and the MinerU lightweight interface becomes the fallback; when RapidOCR is not installed, use the MinerU lightweight interface to process small files, and prompt the user to add a Token when the limit is exceeded.
- When sensitive materials are not allowed to be transmitted externally, explicitly use
--backend rapid(local onnx inference, never leaves the local machine throughout the process).
The old paddle-ocr and mineru-ocr remain available; this skill is the new unified entry point, aiming to cover the common OCR/conversion scenarios of both.
When to Use
Use this skill in the following scenarios:
- The user explicitly needs OCR, scan recognition, image text recognition, or document recognition.
- The input may be a PDF, image, Office document, remote document URL, or web URL, and the user wants it converted to Markdown.
- The user wants to preserve an archive for reviewing the original file, backend results, Markdown, and image resources.
- The user needs automatic selection of the OCR backend rather than manually determining whether to use PaddleOCR or MinerU.
- The user wants non-legal materials to retain general OCR output, while legal materials receive terminology and document structure optimization.
Scenarios where this skill is not prioritized:
- Only a short piece of clear text needs to be read quickly, and no Markdown file or archive is needed.
- The user needs context-based fact rewriting, supplementation of missing information, in-depth seal annotation, or chart semantic analysis; these capabilities are still in the follow-up plan.
Dependencies
System Dependencies
| Dependency | Installation Method |
|---|---|
python3 |
Usually built in on macOS |
uv |
macOS: brew install uv |
Python Packages
Scripts use uv run for execution, and dependencies are written at the top of the scripts; it is recommended to use uv run scripts/convert.pydirectly, without the need to separately maintain requirements.txt。
| Package Name | Purpose | Installation Commands |
|---|---|---|
httpx |
Call PaddleOCR and MinerU API | pip install httpx |
pypdfium2 |
Read PDF page count and split page ranges | pip install pypdfium2 |
rapidocr + onnxruntime |
Optional: local RapidOCR backend (--backend rapid) |
uv run --with rapidocr --with onnxruntime scripts/convert.py ... or pip install rapidocr |
If you run directly with python scripts/convert.py and dependencies are missing, the script will provide installation hints.
Capability boundaries of the local RapidOCR backend
- Text detection + recognition only, no layout analysis: paragraphs are reconstructed by a unified post-processing chain; single-column documents (judgments, scanned contracts, etc.) are reliable, multi-column layouts may be out of order.
- Does not extract image assets: seals, signatures, and charts will not appear in the Markdown.
- Office documents and URLs are not supported (still use MinerU).
- Default rendering DPI is 220, adjustable with
LEGAL_OCR_RAPID_DPI; confidence thresholdLEGAL_OCR_RAPID_MIN_SCORE(default 0.5).
Initial Setup
Copy the configuration template:
cd legal-ocr/config
cp .env.example .env
nano .env
Optional configuration:
- PaddleOCR: fill in
PADDLEOCR_DOC_PARSING_API_URLandPADDLEOCR_ACCESS_TOKEN。 - MinerU Token: fill in
MINERU_API_TOKEN; if left empty, small files default to the MinerU lightweight interface. - Auto-routing: keep
LEGAL_OCR_BACKEND=auto。 - Legal terminology optimization: keep
LEGAL_OCR_LEGAL_TERMS=auto, enabled only when legal materials are detected; to force enable, set totrue, to disable, set tofalse。 - General hard line break optimization: keep
LEGAL_OCR_LINE_MERGE=true; to load custom legal terminology, setLEGAL_OCR_CUSTOM_TERMS_PATH。
This skill will also try to read the MinerU Token from environment variables and ~/.mineru/config.yaml .
PaddleOCR also supports the pdf-processor used by PADDLE_OCR_API_ENDPOINT / PADDLE_OCR_API_KEY, and supports the /api/v2/ocr/jobs asynchronous task interface.
Common Commands
Run in the skill root directory:
uv run scripts/convert.py "/path/to/file.pdf"
uv run scripts/convert.py "/path/to/file.pdf" --pages "1-20"
uv run scripts/convert.py "/path/to/file.pdf" --backend paddle
uv run scripts/convert.py "/path/to/file.pdf" --backend paddle --paddle-model PaddleOCR-VL-1.5
uv run scripts/convert.py "https://example.com/document.pdf" --backend auto
uv run scripts/convert.py "https://example.com/article" --backend mineru
uv run scripts/convert.py "/path/to/judgment.pdf" --legal-terms always
uv run scripts/convert.py checktoken
Local recognition of sensitive materials (does not leave this machine):
uv run --with rapidocr --with onnxruntime scripts/convert.py "/path/to/scan.pdf" --backend rapid
JXA-compatible entry point:
/usr/bin/osascript -l JavaScript scripts/convert.js "/path/to/file.pdf"
Optional parameters:
| Parameter | Description |
|---|---|
| `--backend auto | paddle |
| `--text-layer auto | never |
--output <path> |
Output Markdown path or directory |
--pages <spec> |
Page range, e.g. 1-20、1-5,8,10-12 |
--archive-name <name> |
Custom archive directory name |
--no-archive |
Do not write to archive |
--no-post-process |
Skip all post-processing |
--no-legal-terms |
Skip legal terminology optimization |
| `--legal-terms auto | always |
--no-line-merge |
Skip OCR hard line break cleanup |
| `--model pipeline | vlm` |
| `--paddle-model PP-OCRv5 | PaddleOCR-VL-1.5` |
| `--paddle-api-protocol auto | sync |
--paddle-api-extra-json <path> |
Merge additional PaddleOCR optionalPayload |
The PaddleOCR synchronous interface verifies the actual number of pages returned by the backend. If the number of returned pages is less than the number of pages in the local PDF batch, the conversion will fail and prompt you to lower PADDLEOCR_BATCH_PAGES or use --pages to rerun, preventing missing-page results from being mistaken for success.
PDF Text Layer Dual Path
Before a local PDF enters the OCR backend, it first detects whether it has a usable native text layer (v1.5.0+). This is a high-frequency optimization in legal scenarios: PDFs such as court electronically served judgments, electronic contracts, and government documents usually already have a reliable text layer, and direct reading is more accurate, faster, and does not consume API quota than OCR.
Workflow
- Triggered only for local
.pdf; images, Office, and URLs are not involved. - Use
pypdfium2to extract text page by page, and calculate 4 metrics: text page coverage, average CJK per page, garbled text ratio (PUA + replacement characters + uncommon characters), and total character count. - All thresholds met → convert directly to Markdown, reusing the existing post-process (legal terminology → hard line break cleanup → basic cleanup).
- If any criterion is not met → fall back to the original OCR candidates (PaddleOCR → MinerU), fully compatible with the old workflow.
Mode (CLI --text-layer or env LEGAL_OCR_TEXT_LAYER)
| Mode | Behavior |
|---|---|
auto(default) |
After detection, use the text layer if criteria are met; otherwise fall back to OCR |
never |
Completely disable the text layer, reverting to the old pure OCR behavior |
always |
Force use of the text layer; if unavailable, fail directly with exit=2, for easier troubleshooting |
and --backend priority
--backend auto+--text-layer auto: optimal path, text layer first, then OCR if criteria are not met.--backend paddle|mineru: treat as the user explicitly wanting OCR, skip the text layer branch (unless--text-layer alwaysis also set to force an override).
Thresholds (conservative defaults, ideal values pending calibration on real case files)
| env | Default | Meaning |
|---|---|---|
LEGAL_OCR_TEXT_LAYER_MIN_COVERAGE |
0.8 |
Lower bound on the proportion of text pages among probed pages |
LEGAL_OCR_TEXT_LAYER_MIN_CHARS_PER_PAGE |
50 |
Lower bound on the average CJK character count of text pages |
LEGAL_OCR_TEXT_LAYER_MAX_GARBLE_RATIO |
0.05 |
Upper bound on the proportion of PUA + replacement characters + uncommon characters |
LEGAL_OCR_TEXT_LAYER_MIN_TOTAL_CHARS |
100 |
Lower bound on the total number of non-whitespace characters |
For the rationale behind the default threshold values, see references/text-layer-detection.md and DECISIONS.md. If you get a batch of real case files and find misjudgments (for example, a PDF that should go through OCR goes through the text layer, or vice versa), feed the metrics and samples back to the maintainers, then adjust the thresholds or add new rules.
archive record
Regardless of whether the text layer is used, PDF input will leave a metadata.json field in text_layer :
- Text layer used:
enabled=true+probefull metrics (page count, coverage, garbled_ratio, threshold snapshot). - Text layer not used:
enabled=false+probe.reason(such asno_text_layer/high_garbled_ratio), for reviewing why it fell back to OCR.
Automatic routing
autofirst looks at which APIs the user has actually configured; when only one set is configured, try to route everything through that set to reduce the user's decision burden.- When both sets of APIs are configured, choose the preferred backend by material type, and use the other available backend as a candidate.
- If the backend returns 429, insufficient quota, insufficient balance, rate limiting, authentication failure, network timeout, or service failure, the failure category will be recorded in
result.jsonandmetadata.jsonofroute.attempts; if a candidate backend exists, conversion will continue automatically. - Currently there is no separate quota pre-check interface integrated; quota determination comes from API response codes and error messages. If the service provider offers a stable quota endpoint, add a pre-conversion check.
Automatic retry for transient errors
- Scope: all HTTP calls (synchronous submission, asynchronous submission, asynchronous polling, MinerU upload/polling/download, Token self-check) are wrapped with transient error classification and retry.
- Transient definition: currently
httpx.RequestError(DNS resolution failure, connection failure, connection/read timeout, remote closed connection, protocol error). HTTP 4xx is still raised immediately (authentication, quota, parameter errors); HTTP 5xx and 429 are covered by the same retry wrapper on the polling path. - Default parameters: 3 attempts (including the first), 1.0 second before the first retry, maximum wait of 30.0 seconds per retry (exponential backoff 1 → 2 → 4 → 8 → ...).
- Configuration items: uniformly use
LEGAL_OCR_RETRY_ATTEMPTS/LEGAL_OCR_RETRY_BASE_DELAY/LEGAL_OCR_RETRY_MAX_DELAY; you can usePADDLEOCR_RETRY_*andMINERU_RETRY_*to override a single backend. Setting it to 1 is equivalent to disabling retries. - Before retrying, a line is output to stderr
PaddleOCR/MinerU 瞬态错误 …logs, making it easier to troubleshoot real network issues.
OCR and Legal Enhancement
- Non-legal materials only undergo general Markdown cleanup, blank line tidying, and hard line break tidying by default.
- Legal enhancement is in
automode by default: OCR raw text and file names are scanned first, and legal terminology optimization runs only when sufficient signals such as courts, case numbers, party labels, and judgment/ruling structures are matched. - Detection results are written to the
result.jsonandmetadata.jsonfields ofpostprocess.legal_context/legal_context. - If the user confirms that the input is definitely legal material,
--legal-terms alwaysorLEGAL_OCR_LEGAL_TERMS=truecan be used to force it on. - If processing non-legal materials and you want to completely disable legal replacement, use
--legal-terms never、--no-legal-termsorLEGAL_OCR_LEGAL_TERMS=false。
Legal Terminology Optimization
- By default, conservative legal terminology post-processing is enabled only when legal material is detected; it only handles high-confidence OCR broken characters and common misrecognitions, and does not perform fact completion or semantic rewriting.
- By default, it covers common terms such as document names, subject labels, litigation procedures, evidence materials, and court document structure words.
- By default, it tidies obvious OCR hard line breaks, merging only physical line breaks within the same Chinese paragraph; titles, party labels, numbering, tables, quotations, and Markdown structures are preserved.
- Each replacement is written to
postprocess_log.json, andresult_raw.mdis retained for review. - For the custom terminology format, see
references/legal_terms.md。
Output
- Markdown is saved by default in the same directory as the source file; remote URLs are saved by default in the current directory.
- Image resources are saved by default in the same directory as the Markdown, in
<文件名>_images/。 - archive is saved by default in
legal-ocr/archive/时间戳_文件名/。
Input/Output
Input
- Required: local file path, remote URL, or
checktoken。 - Optional:
--backend、--output、--pages、--archive-name、--modeland PaddleOCR-related parameters.
Output
- Markdown main file: the converted editable text.
- Image directory: image resources returned by the backend or referenced by the Markdown.
- Archive: by default saves the original result, final result, backend response, routing record, and post-processing log; the input file's
path/sha256/size_bytes(local) or original URL (remote) is recorded through themetadata.jsonfield ofsource, and the input copy is no longer duplicated separately.
The archive contains:
output/result.mdoutput/result_raw.mdoutput/result.jsonbackend_result/metadata.json(the input file'spath/sha256/size_bytesor remote URL is recorded through thesourcefield; the input copy is not saved separately)- Includes image resources and
postprocess_log.json
For the detailed structure, see references/output_schema.md。
Troubleshooting
| Issue | Solution |
|---|---|
| PaddleOCR is not configured | Add PADDLEOCR_DOC_PARSING_API_URL and PADDLEOCR_ACCESS_TOKEN, or explicitly use --backend mineru |
| MinerU lightweight API limit exceeded | Configure MINERU_API_TOKEN and retry, or install RapidOCR to use local recognition |
| Sensitive materials are not allowed to be transmitted externally | uv run --with rapidocr --with onnxruntime scripts/convert.py <文件> --backend rapid |
--backend rapid shows as not installed |
Use uv run --with rapidocr --with onnxruntime ..., or pip install rapidocr and then use python3 to run directly |
| One API quota is exhausted | Configure the other API set at the same time and keep --backend auto; during conversion, candidate backends will be tried automatically |
| Web page URL failed | Web page URLs require a MinerU Token; lightweight mode is not supported |
| DOCX/PPTX via PaddleOCR failed | Office documents can only go through MinerU; use --backend auto or --backend mineru |
| PaddleOCR returned insufficient pages | Lower PADDLEOCR_BATCH_PAGES or use --pages to rerun with a smaller range; the current cloud API has been measured to reliably return a maximum of about 100 pages per call |
| Conversion quality needs review | Check the result_raw.md、result.json and backend_result/ |
Maintenance
After modifying this skill, synchronously update the TASKS.md、DECISIONS.md and CHANGELOG.md。