No description
  • Dockerfile 60.1%
  • Shell 30.2%
  • Python 9.7%
Find a file
Sergey Popov 6d076b3567
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
fix(pyproject): create minimal marker_ocr package for hatchling
hatchling raises 'Unable to determine which files to ship inside the wheel'
when packages = [] is set. The wheel builder needs at least one package
directory. Create an empty marker_ocr/__init__.py with a docstring explaining
the wrapper nature of the project, and point packages = ["marker_ocr"].
2026-07-27 00:27:51 +03:00
marker_ocr fix(pyproject): create minimal marker_ocr package for hatchling 2026-07-27 00:27:51 +03:00
.env.example feat!: rewrite as marker-pdf v2.0.0 CLI wrapper (uv + multi-arch Docker) 2026-07-27 00:21:32 +03:00
.gitignore feat!: rewrite as marker-pdf v2.0.0 CLI wrapper (uv + multi-arch Docker) 2026-07-27 00:21:32 +03:00
.woodpecker.yml feat!: rewrite as marker-pdf v2.0.0 CLI wrapper (uv + multi-arch Docker) 2026-07-27 00:21:32 +03:00
docker-compose.yml feat!: rewrite as marker-pdf v2.0.0 CLI wrapper (uv + multi-arch Docker) 2026-07-27 00:21:32 +03:00
Dockerfile fix(dockerfile): COPY README.md for hatchling metadata generation 2026-07-27 00:24:43 +03:00
marker-ocr-llm feat!: rewrite as marker-pdf v2.0.0 CLI wrapper (uv + multi-arch Docker) 2026-07-27 00:21:32 +03:00
pyproject.toml fix(pyproject): create minimal marker_ocr package for hatchling 2026-07-27 00:27:51 +03:00
README.md feat!: rewrite as marker-pdf v2.0.0 CLI wrapper (uv + multi-arch Docker) 2026-07-27 00:21:32 +03:00
uv.lock feat!: rewrite as marker-pdf v2.0.0 CLI wrapper (uv + multi-arch Docker) 2026-07-27 00:21:32 +03:00

Marker OCR

Thin wrapper around marker-pdf v2.0.0+ that makes it easy to run via uv (native, with Apple Silicon MPS support) or Docker / apple-container (CPU, portable).

The package itself contributes almost nothing: a single script entry point that re-exports marker.scripts.convert_single:convert_single_cli as marker-ocr, plus a small marker-ocr-llm wrapper that injects OpenAI-compatible LLM-boost flags from environment variables.

Why

  • marker-pdf CLI does not read OPENAI_* env vars — only CLI flags. The marker-ocr-llm wrapper bridges that for .env / shell workflows.
  • uvx makes one-off native runs trivial (no venv management, no Docker).
  • The published image is multi-arch (linux/amd64 + linux/arm64).
  • Models are NOT bundled in the image; the recommended pattern is a persistent HF cache volume (see docker-compose.yml).

Quickstart

1. uvx one-off (native, MPS on Apple Silicon)

# macOS only — system deps for marker-pdf[full] (WeasyPrint).
brew install cairo pango gdk-pixbuf libffi

# Run from a git ref (latest commit on default branch):
uvx --from git+https://git.ppvn.ru/wailorman/marker-ocr marker-ocr \
    /path/to/book.pdf --output_dir ./out

# Or pinned to a tag/SHA:
uvx --from git+https://git.ppvn.ru/wailorman/marker-ocr@<commit-sha> marker-ocr ...

MPS is auto-detected on Apple Silicon. Verify:

uvx --from marker-pdf python -c "import torch; print('mps' if torch.backends.mps.is_available() else 'cpu')"

2. uv tool install (persistent binary on PATH)

uv tool install git+https://git.ppvn.ru/wailorman/marker-ocr
marker-ocr /path/to/book.pdf --output_dir ./out

Upgrade with uv tool upgrade marker-ocr.

3. docker run one-off (CPU, portable)

docker run --rm \
    -v "$PWD:/work" \
    -v marker-ocr-hf-cache:/root/.cache/huggingface \
    cr.ppvn.ru/marker-ocr/marker-ocr:latest \
    /work/book.pdf --output_dir /work/out

4. docker compose run (env_file + volume management)

git clone https://git.ppvn.ru/wailorman/marker-ocr
cd marker-ocr
cp .env.example .env  # fill in OPENAI_*
mkdir -p data && cp ~/Downloads/book.pdf data/

docker compose run --rm marker-ocr \
    /work/data/book.pdf --output_dir /work/data/out

For pure-OCR (no LLM-boost), override the entrypoint:

docker compose run --rm --entrypoint marker-ocr marker-ocr \
    /work/data/book.pdf --output_dir /work/data/out

LLM-boost (OpenAI-compatible endpoint)

LLM-boost sends page images to a vision-capable model to clean up OCR output. The OpenAIService client speaks the OpenAI Chat Completions protocol and works with any compatible endpoint: OpenAI, Azure OpenAI, OpenRouter, vLLM, Ollama, routerai.ru, LocalAI, etc.

Required CLI flags (when calling marker-ocr directly)

marker-ocr book.pdf --output_dir ./out \
    --use_llm \
    --llm_service marker.services.openai.OpenAIService \
    --openai_base_url "https://api.openai.com/v1" \
    --openai_api_key "sk-..." \
    --openai_model "gpt-4o-mini"

Or use marker-ocr-llm wrapper (reads env vars)

export OPENAI_BASE_URL=https://api.openai.com/v1   # NO trailing slash
export OPENAI_API_KEY=sk-...
export OPENAI_MODEL=gpt-4o-mini
marker-ocr-llm book.pdf --output_dir ./out

The same wrapper is the default entrypoint in docker-compose.yml.

Caveats

  • Model must be vision-capable (gpt-4o, gpt-4o-mini, claude-sonnet, gemini-flash, qwen2.5-vl). marker sends page images to the LLM.
  • NO trailing slash in --openai_base_url (known bug in the openai Python client — request URLs become //chat/completions).
  • OPENAI_API_KEY env var is ignored by marker-pdf CLI. It only reads the --openai_api_key flag. The marker-ocr-llm wrapper exists for this reason.

Modes (marker-pdf v2.0.0)

Mode CLI Use case
balanced --mode balanced VLM layout + full-page OCR. Highest quality. Default on GPU.
fast --mode fast Lightweight rf-detr layout + pdftext. Default on CPU/MPS.
no OCR --disable_ocr Text-layer only, no VLM. Fastest.

Defaults are device-aware: balanced on CUDA GPU, fast on CPU/MPS.

# Fast on the laptop
marker-ocr book.pdf --output_dir ./out --mode fast

# Best quality, no OCR (text-based PDF)
marker-ocr book.pdf --output_dir ./out --disable_ocr

Apple Silicon MPS vs Docker CPU

Path Device Speed Portability
uvx / uv tool install native MPS (Metal) ~5-8× faster than CPU macOS only
docker run / docker compose CPU baseline any Linux/macOS
apple-container CPU baseline macOS only

Metal/MPS is macOS-only — Virtualization.framework (which backs both Docker Desktop and apple-container) does not expose Metal to Linux guests. Use native uvx on Apple Silicon when speed matters.

Model cache

marker-pdf v2.0.0 spins up a separate surya inference server lazily on first OCR call. Model weights (~2-5 GB) are downloaded to the HuggingFace cache:

  • Native (uvx): ~/.cache/huggingface/
  • Docker / apple-container: /root/.cache/huggingface/ inside the container (mount as a volume — see docker-compose.yml).

The Docker image intentionally does NOT pre-cache weights: doing so would bloat the image to 5+ GB and complicate multi-arch builds. The persistent volume pattern in docker-compose.yml achieves the same effect across runs.

Output layout

<output_dir>/
└── <input-basename>/
    ├── <input-basename>.md       # (or .json / .html per --output_format)
    └── images/
        ├── image_0.jpg
        └── ...

Versions

  • marker-pdf: >=2.0.0 (pinned via pyproject.toml).
  • Python: >=3.10, <4.
  • Image tags: cr.ppvn.ru/marker-ocr/marker-ocr:latest and cr.ppvn.ru/marker-ocr/marker-ocr:<commit-sha>.

Development

git clone https://git.ppvn.ru/wailorman/marker-ocr
cd marker-ocr
uv sync
uv run marker-ocr --help

The CI pipeline (.woodpecker.yml) builds and publishes multi-arch images on push to main/master.