Skip to main content

this_file: README.md

vexy-pdf3md

Documentation: https://vexy.dev/vexy-pdf3md/

Convert one PDF or an entire folder tree to Markdown. Choose a converter, let the package select one, or run several strategies side by side. Failed conversions try another eligible tool; results record every attempt and the actual winner.

Install

uv sync
uv run vexy-pdf3md --help
# Install this checkout as a CLI available outside the repository:
uv tool install .

Python 3.12+ is required. PyMuPDF provides a working text-layer backend immediately. Optional tools are discovered on PATH:

  • markit: install from upstream instructions. Preferred for fast text-layer conversion and multi-column layouts.
  • pdf22md: build/install pdf22md on macOS for PDFKit extraction and Apple Vision OCR.
  • Tesseract: brew install tesseract on macOS; install the corresponding package on other systems. Language models named by --lang that are missing from the system tessdata are downloaded from tessdata_fast into ~/.cache/vexy-pdf3md/tessdata on first use (brew install tesseract-lang works offline instead).
  • Docling: uv sync --extra docling, then use uv run --extra docling .... The adapter uses Tesseract for OCR and may download layout/table models on first use. For an installed CLI, use uv tool install '.[docling]' from this checkout.

Convert

# book.pdf -> book.md, beside the PDF
uv run vexy-pdf3md --input book.pdf

# Explicit output file, with images in ./results/renamed/
uv run vexy-pdf3md --input book.pdf --output results/renamed.md --media

# Recursively map PDFs to Markdown, preserving relative subfolders
uv run vexy-pdf3md --input ./pdfs --output ./markdown --workers=4

# Omit --output to write Markdown alongside PDFs throughout the input tree
uv run vexy-pdf3md --input ./pdfs

# Run independent strategies concurrently
uv run vexy-pdf3md --input book.pdf --strategies=markit,pymupdf --media

# OCR, selected pages, language hints, and full-page images (including vectors)
uv run vexy-pdf3md --input scan.pdf --strategies=tesseract \
  --pages=1-3,5 --lang=pl,en --media=pages --dpi=200

# Every strategy side by side
uv run vexy-pdf3md --input scan.pdf --strategies=all --lang=pl,en

# Use Docling for layout/table reconstruction
uv run --extra docling vexy-pdf3md --input book.pdf --strategies=docling

pdf3md and python -m vexy_pdf3md expose the same Fire CLI. Single-file input accepts an explicit .md filename or an existing output directory. Directory input always treats --output as a directory, even if its name ends in .md.

Output contract

pdfs/part/book.PDF
  --output markdown
    -> markdown/part/book.md
  --output markdown --strategies=markit,pymupdf --media
    -> markdown/part/book.markit.md
       markdown/part/book.markit/page-0001-001.png
    -> markdown/part/book.pymupdf.md
       markdown/part/book.pymupdf/page-0001-001.png

Suffixes are added only when multiple strategies are requested. They identify the requested strategy, even if a fallback produces that output; JSON reports identify the actual backend. Use --fallback=False for strict backend comparisons.

--media / --media=images exports displayed raster-image regions as PNGs, appended under a Media heading in page order. --media=pages renders each complete selected page, including vector artwork. Media links are relative and URL-escaped. Original embedded image formats, editable vectors, and exact inline placement are not preserved. The media directory uses the complete Markdown stem and is created even if no images are found; .vexy-pdf3md marks it as generated.

Existing Markdown is skipped by default. --overwrite=True replaces it only after conversion passes quality checks. Existing media directories are replaceable only if they carry this package's ownership marker. Keep curated files outside generated media directories. Case-insensitive destination collisions are rejected before processing. Directory symlinks are not followed and PDF symlinks within trees are skipped; an explicit single-file symlink is resolved to its target.

Selection and fallback

  • Text-layer PDFs: markit → pymupdf → pdf22md → tesseract → docling.
  • Suspected scanned/mixed PDFs: pdf22md → tesseract → docling. A page counts as scanned when it is mostly image, or when its text layer is unreadable control codes (subset fonts without a ToUnicode map).
  • Only installed candidates participate in automatic selection. An explicitly named missing backend produces a failed attempt before fallback.
  • Native crashes, timeout, nonzero exit, empty output, replacement-character corruption, and substantial text loss trigger fallback. The default retention threshold is 50% of the PDF's alphanumeric text layer, before media is appended.
  • All selected pages are inspected. A document is routed to OCR only when at least 20% of the selected pages look scanned; a lone screenshot page in a long text document produces a warning instead. Detection and length checks are heuristics; they cannot prove complete or correct OCR. Use --ocr=always when automatic routing misses scanned content. --ocr=never deliberately permits text-layer-only extraction.
  • With several strategies requested, a failed strategy never falls back to one of its siblings: those already get their own output, so the file would be a duplicate under a misleading suffix. A warning names the actual backend whenever it differs.

PyMuPDF's basic fallback preserves text blocks and infers larger-font headings. It does not reconstruct sophisticated tables. For difficult layouts, compare outputs or request Docling. Image-only documents still require usable OCR text: exporting page images alone does not count as successful Markdown conversion.

Engine comparison

Measured on 123 Microsoft localization style guides (text-layer PDFs, 30–100 pages, 59 in non-Latin scripts), each converted with every strategy. Only English OCR models were installed, so OCR ratings cover text-layer input, not scans.

Strategy Rating Observed
markit ★★★★☆ default Tables, reflowed paragraphs, running headers/footers dropped. Loses 4–22% of Greek and Indic letters (conjuncts, pre-base vowels), joins hyphenated words, shows 312x90pt image placeholders, may emit U+FFFD for fonts without Unicode maps.
pymupdf ★★★☆☆ most complete text Keeps every mapped glyph, font-size headings, page footers, and hard line breaks. No tables. Missed inline Cyrillic runs in one Serbian guide that markit recovered.
pdf22md ★★☆☆☆ Text equals PyMuPDF's layer, but one line per text run, no headings or tables, footers kept. Vision OCR turned Cyrillic into Latin lookalikes when routing forced OCR.
tesseract ★☆☆☆☆ on text PDFs About 5% of words lost and 8% garbage on Latin scripts; non-Latin scripts unreadable without language packs. Only for real scans.
docling not rated Not installed during the run: all 123 .docling.md files were byte-identical markit fallbacks, which prompted the sibling-fallback rule above.

Best choice per guide: markit for 110 documents, PyMuPDF for the Greek and ten Indic guides where markit dropped script letters, and for two Bengali/Konkani guides whose fonts lack Unicode maps. One Kyrgyz guide was misrouted to OCR by a single screenshot page; the page-share threshold above fixes that.

Options

Option Default Meaning
--strategies auto One tool, all, or comma-separated independent variants
--workers 2 Maximum simultaneous isolated jobs, 1–64
--timeout 600 Seconds per inspection, backend attempt, or media export
--fallback True Try alternative eligible converters
--media False True/images, pages, or False/none
--overwrite False Replace Markdown and owned generated media
--pages all One-based ranges, e.g. 1-3,5, sorted and deduplicated
--password empty Password for encrypted PDFs; omitted from reports
--ocr auto auto, always, or never
--lang eng OCR language hints as ISO 639-1 or Tesseract codes, e.g. pl,en or pol+eng; shared by Tesseract, Docling, and pdf22md Vision
--dpi 150 OCR and media resolution, 72–600
--min-text-ratio 0.5 Minimum retained text-layer fraction, 0–1
--verbose False Backend diagnostics on stderr

Timeouts apply per operation, so fallback can extend total time. POSIX timeout and Ctrl-C terminate the worker process group, including converter subprocesses. On Windows only the direct worker is terminated. Separate invocations should not overwrite the same destinations concurrently.

Python API and exit codes

from vexy_pdf3md import convert

batch = convert("pdfs", "markdown", strategies="auto", media=True, workers=4)
for item in batch.results:
    print(item.input, item.output, item.status, item.strategy)
    for attempt in item.attempts:
        print(attempt.strategy, attempt.status, attempt.error)
assert batch.ok

The CLI prints a JSON batch report to stdout. Exit status 0 means every output succeeded or was skipped, 1 means at least one conversion failed, and 2 means invalid configuration/input arguments. A bad PDF does not stop other jobs. API argument and path errors raise; individual document failures are returned in BatchResult. No cloud OCR or AI service is invoked by this package.

Develop

./test.sh                                    # lint, types, 80% coverage gate, smoke
uv run --extra docling pytest -m optional    # real Docling + model integration
uvx hatch test                              # isolated package test environment
uv build                                    # Git-derived version, wheel and sdist
./build.sh docs                             # documentation site into docs/

Publish

./build.sh package    # clean dist/, uv build, twine check
./publish.sh          # gitnextver commit + tag + push, then build and uv publish

hatch-vcs derives versions from Git tags and generates the gitignored src/vexy_pdf3md/__version__.py during builds/installs. Rebuild or reinstall after changing tags to refresh editable-install versions; never commit the generated file.

publish.sh runs uvx gitnextver, which stages and commits all nonignored changes, creates the next vX.Y.Z tag, and pushes. It stops if HEAD carries no such tag (gitnextver only tags when there are changes to commit). It then runs ./build.sh package and uploads dist/* with uv publish. Configure uv publishing credentials, e.g. UV_PUBLISH_TOKEN, through your environment. Run ./test.sh first.

See ANALYSIS.md for both source-project investigations and routing tradeoffs, DEPENDENCIES.md for dependency roles/licenses, and WORK.md for verification evidence. The package's own code is Apache-2.0; dependencies have their own licenses, including PyMuPDF's AGPL/commercial terms.

Metadata

Release files for vexy-pdf3md 1.0.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for vexy-pdf3md 1.0.5
File Size Uploaded Provenance Actions
vexy_pdf3md-1.0.5.tar.gz 203.3 kB Without provenance Details for vexy_pdf3md-1.0.5.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for vexy-pdf3md 1.0.5
File Interpreter ABI Platform Actions
vexy_pdf3md-1.0.5-py3-none-any.whl Python 3 none any Details for vexy_pdf3md-1.0.5-py3-none-any.whl

Total release size: 232.3 kB

Release files / vexy_pdf3md-1.0.5.tar.gz

Download URL vexy_pdf3md-1.0.5.tar.gz
Size 203.3 kB
Tags Source
SHA-256 checksum
How to use checksums
738cef0d86149947b1f3c969802ab4c7ab2c3d3384cd7c40dc9dbebd7f3fbdf8
BLAKE2b-256 checksum
How to use checksums
167f4d2eb0c275d5c718e8707b075dcb676e5c6def9097aab1ecde3aa2396a4b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.18 {"installer":{"name":"uv","version":"0.12.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / vexy_pdf3md-1.0.5-py3-none-any.whl

Download URL vexy_pdf3md-1.0.5-py3-none-any.whl
Size 28.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f1520a5cea076b7f861def4fce3a86e3e3db1371865c7046f55ed157865881e6
BLAKE2b-256 checksum
How to use checksums
55aa249d0f87a183c9ae2d3260cb58563a9d361d7d51b2ea94f5cce83f78e33b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.18 {"installer":{"name":"uv","version":"0.12.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for vexy-pdf3md 1.0.5

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page