Standalone multi-format PDF converter — detect figures and tables, then convert to Quarto .qmd, Markdown .md, or GitHub-Flavored Markdown .gfm.
Extracted from CLMS_documents.
- Maciej Dudek
- Matteo Mattiuzzi
Copyright © 2026 European Union. Licensed under EUPL-1.2.
# Install
pip install -e .
# One-time setup (API key + default model)
python3 pdf2md.py --setup
# Convert a PDF
python3 pdf2md.py document.pdf
# Batch convert a directory
python3 pdf2md.py inbox/ --out output/
# Output format
python3 pdf2md.py document.pdf --format gfm # GitHub-Flavored Markdown
python3 pdf2md.py document.pdf --format md # Plain Markdown
python3 pdf2md.py document.pdf --render # Also render to PDF via Quarto
# Use a YAML frontmatter template (with --format qmd or gfm)
python3 pdf2md.py document.pdf --template path/to/template.qmd
python3 pdf2md.py document.pdf --template https://raw.githubusercontent.com/org/repo/main/template.qmdWhen --template is used, the template's YAML frontmatter block is injected into the conversion prompt. The LLM fills in document-specific values (title, date, etc.) while preserving the template's field set, order, and structure. This ensures every converted document starts with a consistent, pre-defined header.
- Phase 1 — Detect: Extract figures/tables, strip chrome, detect cover metadata
- Phase 2 — Convert: LLM transforms the placeholdered PDF to structured markdown
- Phase 2.5 — Rescue: Deterministically resolve leftover figure tokens + LLM-driven insertion of unreferenced figures
- Phase 3 — Tablefix: Deterministic table width, caption, and orientation fixes
- Phase 4 — Verify: Content-fidelity check (text coverage, figure placement, table coverage)
API key and default model are stored in ~/.pdf2md/:
key— OpenRouter API key (mode 600)config.json— model selection + auto-cached model limits from OpenRouter API
python3 pdf2md.py --setup # Interactive configurationNot all models work equally well for PDF conversion. Before switching models, run the bundled edge-case benchmark to see how a candidate model handles tricky content:
python3 pdf2md.py src/pdf2md/tests/fixtures/pdf2md_edgecases.pdf \
--out /tmp/benchmark-out/ \
--model <model-slug> \
--postfix 0Then check verify_report.md — the document is designed to surface failures in:
- Table detection: text-based tables with merged cells, plus rasterized PNG tables
- Figure extraction: embedded charts, wrapped figures, subfigures
- Text fidelity: soft hyphens, ligatures, Unicode, intra-word formatting changes
- Structure: nested lists, definition lists, blockquotes, task lists, two-column layout
- Scientific content: chemical formulas, subscripts/superscripts, display equations
- Metadata: YAML frontmatter generation (title detection, date parsing)
verify=fail on figure placement. Stick to multimodal models.
cd pdf2md
git pullThe editable install (pip install -e .) picks up changes automatically — no need to
re-install unless new dependencies were added. If the pull adds new packages, run
pip install -e . again to install them.
EUPL-1.2 — see LICENSE.