Skip to content

About

No description, website, or topics provided.

Resources

Stars

9 stars

Watchers

1 watching

Forks

Repository files navigation

Doc-Worker

A Docker-based OCR pipeline worker that:

  1. Polls an inbox directory for new PDF files.
  2. Runs OCR using OCRmyPDF with the PaddleOCR plugin via Python API.
  3. Generates sidecar documents via the Docling API (Markdown + JSON).
  4. Pushes the processed PDFs into a Paperless-ngx consume directory.
  5. Serves a FastAPI endpoint for on-demand document understanding (Open-WebUI compatible).

Architecture

INBOX/*.pdf
  │
  ├─ Stability check (waits for upload to finish)
  │
  ├─ PROCESSING/
  │   ├─ Docling API  →  sidecar JSON
  │   ├─ ocrmypdf.ocr() + PaddleOCR  →  searchable PDF
  │   └─ Push → Paperless consume/
  │
  ├─ DONE/       (successfully processed)
  └─ ERROR/      (failed processing, for inspection)

API Server (port 8000)
  ├─ GET  /health           — Health check
  ├─ POST /layout-parsing   — Open-WebUI PaddleOCR-VL endpoint
  └─ POST /extract          — Direct text extraction (non-Open-WebUI)

Quick Start

1. Build or pull the image

Published image tags:

Tag Backend
:latest CPU default
:cpu CPU explicit
:cuda NVIDIA CUDA

Build locally:

# CPU (default)
docker build -t doc-worker:cpu -t doc-worker:latest .

# CUDA GPU (NVIDIA)
docker build --build-arg PADDLE_GPU=cuda -t doc-worker:cuda .

2. Run with Docker Compose

See docker-compose.yml for a complete example:

services:
  doc-worker:
    image: doc-worker
    ports:
      - "8000:8000"    # API server
    volumes:
      - ./inbox:/work/inbox
      - ./processing:/work/processing
      - ./done:/work/done
      - ./error:/work/error
      - ./docling:/work/docling
      - ./paperless-consume:/paperless-consume
    environment:
      - DOCLING_BASE_URL=http://docling:12000
      - DOCLING_MODE=best_effort
      - OCR_LANG=deu
      - OCR_USE_GPU=false
      - POLL_INTERVAL=5

For GPU acceleration (NVIDIA):

    environment:
      - OCR_USE_GPU=true
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]

3. Drop PDFs into the inbox

cp new-document.pdf ./inbox/

The worker will pick it up within POLL_INTERVAL seconds.

4. Configure Open-WebUI

In Open-WebUI, go to Admin Settings > Documents:

  1. Set Content Extraction Engine to PaddleOCR-VL
  2. Set API Base URL to http://doc-worker:8000
  3. (Optional) Set API Token if you configured PADDLEOCR_VL_TOKEN

Open-WebUI will call POST /layout-parsing with base64-encoded files.

5. Direct API access

# Health check
curl http://localhost:8000/health

# Extract text (direct API, non-Open-WebUI)
curl -X POST http://localhost:8000/extract \
  -F "file=@document.pdf" | python -m json.tool

Configuration

Worker (folder polling)

Variable Default Description
INBOX /work/inbox Directory to poll for new PDFs
PROCESSING /work/processing Staging area during processing
DONE /work/done Successfully processed files
ERROR /work/error Failed files (for inspection)
DOCLING_DIR /work/docling Docling sidecar output
PAPERLESS_CONSUME /paperless-consume Paperless-ngx consume directory
OCR_LANG deu OCR language (see Language Codes)
OCR_USE_GPU false Enable GPU acceleration (true/false)
POLL_INTERVAL 5 Seconds between inbox polls
DOCLING_BASE_URL http://docling:12000 Docling API endpoint
DOCLING_MODE best_effort Sidecar mode: off, best_effort, required, or native
DOCLING_TIMEOUT 900 Docling API timeout (seconds)
MAX_RETRIES 3 Max OCR retry attempts
RETRY_DELAY 10 Seconds between OCR retries
PADDLEOCR_VL_TOKEN (none) Bearer token for /layout-parsing and /extract auth (Open-WebUI)
PADDLEOCR_MODELS /app/models Path to pre-downloaded PaddleOCR model dirs
MAX_REQUEST_SIZE 104857600 Max request body size in bytes (100 MB default)
MAX_OCR_PAGES 500 Max pages per OCR request; over-cap documents 413 before staging (unauthenticated-DoS guard). Counted by walking the page tree with pypdf — the /Count entry is spec-optional and attacker-writable, and both parsers' count APIs return it uncross-checked (a lying /Count would otherwise pass the cap: every /Count-gated consumer — poppler's render loop included, which is clamped to the raw /Count — sees only the first /Count pages; the concrete DoS residual is the page-size dimension, closed by MAX_RENDERED_PIXELS)
MAX_RENDERED_PIXELS 100000000 Max width_px * height_px of a rendered PDF page at RENDER_DPI — measured from the LARGER of the page's effective MediaBox and CropBox (poppler-faithful: inherited from the page tree when the page sets none, inverted corners normalized — measured via pypdf, since pypdfium2's page-level box APIs see neither, PDFium crbug 1786; pdftoppm renders the MediaBox even when a CropBox is set); over-cap pages 413 before staging. ALSO the cap on an IMAGE upload's header-declared canvas (PIL Image.open().size, metadata-only — a few-KB highly-compressible PNG can declare a canvas whose decode is tens of GB); the same envelope, no separate knob
MAX_CONCURRENT_OCR_JOBS 2 Max in-flight OCR jobs; saturated requests get 503 instead of queueing
MAX_CONCURRENT_PDF_PARSSES 2 Max concurrent pre-slot PDF cap-check parses (page-tree count + per-page box measurement, offloaded to a worker thread; image uploads' header-size read is bounded by this same gate — a pathological image header is O(file size) to parse). The parse runs BEFORE the job slot — a rejected job must not take a slot — so its fan-out is bounded by this knob, not MAX_CONCURRENT_OCR_JOBS; saturated requests get 503 instead of queueing

Temp-disk sizing note: staging scales with RENDER_DPI × page count × per-page pixels, so tune RENDER_DPI, MAX_OCR_PAGES and MAX_RENDERED_PIXELS together to fit the temp volume (the default cap envelope is 500 pages × 100 Mpx ≈ 150 GB theoretical worst case).

Build Arguments

Argument Default Description
PADDLE_GPU cpu PaddlePaddle variant: cpu or cuda

API Endpoints

GET /health

Health check. Returns:

{"status": "ok", "ocr_lang": "deu", "paddleocr_lang": "german", "gpu": false}

POST /layout-parsing (Open-WebUI)

The endpoint Open-WebUI calls when PaddleOCR-VL is selected as Content Extraction Engine.

Request:

POST /layout-parsing
Authorization: Bearer <PADDLEOCR_VL_TOKEN>
Content-Type: application/json

{
  "file": "<base64-encoded file bytes>",
  "fileType": 0,              // 0=PDF, 1=image
  "useDocOrientationClassify": false,
  "useDocUnwarping": false,
  "useChartRecognition": false
}

Response:

{
  "result": {
    "layoutParsingResults": [
      { "markdown": { "text": "Page 1 extracted text..." } },
      { "markdown": { "text": "Page 2 extracted text..." } }
    ]
  }
}

POST /extract (Direct API)

Upload a PDF or image, extract text via PaddleOCR.

Request: multipart/form-data with field file Response:

{
  "filename": "document.pdf",
  "pages": [
    {
      "page": 1,
      "text": "Full page text...",
      "blocks": [
        {"text": "Line text", "bbox": [x0, y0, x1, y1], "confidence": 0.98}
      ]
    }
  ],
  "full_text": "All pages concatenated"
}

Important Notes

OCR Language

The bundled local ocrmypdf_paddleocr plugin accepts common Tesseract-style OCR language codes:

Language Code
German deu
English eng
French fra
Spanish spa
Italian ita
Portuguese por
Dutch nld
Polish pol
Turkish tur
Chinese (Simplified) ch_sim
Chinese (Traditional) ch_tra
Japanese jpn
Korean kor

Pre-downloaded Models

The Docker image ships with the PP-OCRv6 medium detection/recognition models and text-line orientation model pre-downloaded. PP-OCRv6 medium is a unified multilingual model, so normal OCR does not need to download per-language models at runtime.

Sidecar Modes

Mode Behavior
best_effort (default) Attempt Docling API. If it fails, continue with OCR.
off Skip sidecar generation entirely.
required If sidecar generation fails, move to ERROR.
native Use local PaddleOCR instead of the Docling API. Same output format, no external dependency.

Volume Mounts

Path Purpose Required
/work/inbox Input directory for new PDFs Yes
/work/processing Temporary staging area Yes
/work/done Archive of processed files Yes
/work/error Failed files for inspection Yes
/work/docling Docling sidecar output Optional
/paperless-consume Paperless-ngx consume directory Yes

Error Handling

  • Unstable files: Skipped if size changes during stability check.
  • Docling failures: Non-fatal by default (mode=best_effort).
  • OCR failures: Retried up to MAX_RETRIES times. On final failure → ERROR.
  • Paperless push failures: File moved to ERROR.

License

[Add your license here]

About

No description, website, or topics provided.

Resources

Stars

9 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages