Skip to main content

Local CLI to convert EPUB files into machine-readable PDFs.

Project description

epub2pdf

PyPI License: MIT CI Docker

Local CLI for turning EPUB files into searchable PDFs, plus optional AI-readable extraction from existing PDFs.

The project is intentionally CLI-first. It does not run a long-lived server for the default workflows, and Codex/OpenCode integrations are thin wrappers around the same installed epub2pdf command.

What It Does

  • Converts .epub files into selectable/searchable PDFs.
  • Writes optional EPUB sidecars as structured JSON, normalized HTML, Markdown, and AI-friendly JSONL (one chapter per line).
  • Inspects EPUB metadata, manifest, spine, TOC, and chapter order without rendering.
  • Extracts Markdown, JSON, text, or HTML from existing PDFs through native backends (no Java required by default).
  • Installs global Codex and OpenCode skills that call this CLI instead of duplicating conversion logic.

Requirements

  • Python 3.10+
  • WeasyPrint system libraries for the default renderer (Pango, Cairo, GDK-PixBuf)
  • Playwright Chromium only when using --engine playwright
  • Java 11+ only when using --engine opendataloader for pdf-extract
  • macOS/Linux shell environment

Check runtime dependencies:

python3 --version

Install

PyPI

From PyPI (recommended):

python3 -m pip install epub2pdf-cli

From source:

python3 -m pip install -e .

Install the optional Playwright backend for Chromium-based rendering:

python3 -m pip install epub2pdf-cli[playwright]
playwright install chromium

Install enhanced PDF extraction backends:

# Best structured extraction (tables, reading order, OCR)
python3 -m pip install epub2pdf-cli[docling]

# Table specialist
python3 -m pip install epub2pdf-cli[pdfplumber]

# Legacy Java-based extractor
python3 -m pip install epub2pdf-cli[legacy-pdf]

Install the MCP server for Claude Desktop:

python3 -m pip install epub2pdf-cli[mcp]

Or use the Docker image (no local Python dependencies):

docker run --rm -v "$PWD:/workspace" ghcr.io/min9lin9/epub2pdf:0.3.0 \
  convert book.epub --no-validate

Use ghcr.io/min9lin9/epub2pdf:main for the latest development build.

Install Codex/OpenCode skill wrappers globally:

python3 scripts/install_agent_skills.py

The installer copies templates into:

~/.codex/skills/epub2pdf/
~/.config/opencode/skills/epub2pdf/

Quick Start

Convert an EPUB to PDF:

epub2pdf convert book.epub

Convert with stable output paths and sidecars:

epub2pdf convert book.epub \
  --output book.pdf \
  --sidecar-json book.json \
  --sidecar-html book.html \
  --sidecar-markdown book.md \
  --sidecar-jsonl book.jsonl

Skip PDF validation to speed up batch pipelines:

epub2pdf convert book.epub --no-validate

Convert multiple EPUBs in parallel:

epub2pdf batch *.epub \
  --output-dir out/ \
  --workers 4 \
  --sidecar-json \
  --sidecar-jsonl \
  --force

Inspect an EPUB before rendering:

epub2pdf inspect book.epub --json book.inspect.json

Extract Markdown and JSON from an existing PDF:

epub2pdf pdf-extract book.pdf \
  --output-dir book_extracted \
  --format markdown,json

Use a specific extraction backend:

epub2pdf pdf-extract book.pdf \
  --engine docling \
  --output-dir book_extracted \
  --format markdown,json

CLI Reference

convert

epub2pdf convert INPUT.epub [options]

Common options:

  • -o, --output PATH: output PDF path. Defaults to the input basename with .pdf.
  • --engine playwright|weasyprint: rendering backend. Default: weasyprint.
  • --sidecar-json PATH: write structured conversion metadata.
  • --sidecar-html PATH: write the normalized merged HTML used for rendering.
  • --sidecar-markdown PATH: write a Markdown version of the EPUB.
  • --sidecar-jsonl PATH: write an AI-friendly JSONL sidecar (one chapter per line).
  • --page-size A4|Letter: output page size. Default: A4.
  • --margin-mm N: page margin in millimeters. Default: 12.
  • --cover first|none: include or skip the detected cover image. Default: first.
  • --no-validate: skip PDF validation after rendering.
  • --force: overwrite an existing output PDF.

On success, stdout prints only the PDF path.

batch

epub2pdf batch INPUT1.epub INPUT2.epub ... [options]

Common options:

  • -o, --output-dir DIR: required output directory for PDFs and sidecars.
  • -j, --workers N: number of parallel worker processes. Default: 1.
  • --engine playwright|weasyprint: rendering backend. Default: weasyprint.
  • --sidecar-json: write a JSON report next to each PDF.
  • --sidecar-html: write normalized merged HTML next to each PDF.
  • --sidecar-markdown: write Markdown next to each PDF.
  • --sidecar-jsonl: write an AI-friendly JSONL sidecar next to each PDF.
  • --no-validate: skip PDF validation after rendering.
  • --force: overwrite existing outputs.

On success, stdout prints one output PDF path per line.

inspect

epub2pdf inspect INPUT.epub [--json PATH]

Use this when an agent or script needs EPUB structure before rendering. Without --json, the report is written to stdout.

pdf-extract

epub2pdf pdf-extract INPUT.pdf [options]

Common options:

  • -o, --output-dir DIR: output directory. Defaults to <pdf-stem>_extracted.
  • --engine pypdfium2|docling|pdfplumber|opendataloader: extraction backend. Default: pypdfium2.
  • --format LIST: comma-separated formats. Default: markdown,json.
  • --pages SPEC: page selection, for example 1,3,5-7.
  • --password: password for encrypted PDF files.
  • --use-struct-tree: use tagged PDF structure when available.
  • --sanitize: redact common sensitive data patterns.
  • --keep-line-breaks: preserve original line breaks.
  • --include-header-footer: include page headers and footers.
  • --detect-strikethrough: detect strikethrough text in Markdown/HTML.
  • --table-method default|cluster: table detection mode.
  • --reading-order off|xycut: reading order algorithm. Default: xycut.
  • --image-output off|embedded|external: extracted image handling. Default: external.
  • --image-dir: directory for extracted images.
  • --threads: worker thread count for native extraction.
  • --sidecar-json PATH: write structured extraction report JSON to this path.
  • --force: overwrite existing extraction outputs.

On success, stdout prints one created output path per line.

Extraction Backends

Engine Speed Quality Best for Dependencies
pypdfium2 Fastest High text fidelity Digital text PDFs Bundled with base install
docling Moderate Best structure/tables Complex layouts, tables, OCR pip install -e '.[docling]'
pdfplumber Slower Excellent tables Table-heavy financial docs pip install -e '.[pdfplumber]'
opendataloader Moderate Highest overall accuracy Legacy high-quality extraction Java 11+ + pip install -e '.[legacy-pdf]'

AI-Readable Outputs

convert --sidecar-json writes a stable report with:

  • source: input path, hash, conversion timestamp
  • metadata: title, language, creators, identifiers, publisher, dates
  • manifest: EPUB manifest items and media types
  • spine: ordered reading sequence
  • toc: table of contents entries
  • chapters: chapter ids, hrefs, titles, text statistics, anchors
  • assets: rewritten images, CSS assets, and embedded resources
  • warnings: unsupported media, missing assets, image-heavy chapters
  • output: PDF path, backend, page size, validation summary

convert --sidecar-html writes the normalized merged HTML that was rendered into the PDF.

convert --sidecar-markdown writes a Markdown version of the EPUB suitable for RAG ingestion.

convert --sidecar-jsonl writes one JSON object per chapter, optimized for LLM/RAG pipelines:

{"record_type": "chapter", "book_title": "Book Title", "chapter_index": 1, "title": "Chapter 1", "text": "...", "word_count": 1200}
{"record_type": "chapter", "book_title": "Book Title", "chapter_index": 2, "title": "Chapter 2", "text": "...", "word_count": 950}

pdf-extract writes Markdown/JSON/text/HTML files from an existing PDF. This is the path to use when the source is already a PDF rather than an EPUB.

Programmatic API

Use epub2pdf_cli.api.Epub2Pdf to convert from Python. When the Playwright engine is selected, use the client as a context manager to keep one browser process alive across multiple conversions.

from epub2pdf_cli.api import Epub2Pdf

# WeasyPrint (no context manager required)
client = Epub2Pdf(engine="weasyprint")
client.convert("book.epub", "book.pdf")

# Playwright with browser pooling
with Epub2Pdf(engine="playwright") as client:
    client.convert("a.epub", "a.pdf")
    client.convert("b.epub", "b.pdf")

# Parallel batch conversion
with Epub2Pdf(engine="playwright") as client:
    reports = client.batch_convert(
        [("a.epub", "a.pdf"), ("b.epub", "b.pdf")],
        max_workers=4,
    )

MCP (Claude Desktop / Claude Code)

Install the optional MCP dependency:

python3 -m pip install -e '.[mcp]'

Add the server to your Claude Desktop config (~/Library/Application Support/Claude/claude_desktop_config.json on macOS or %APPDATA%/Claude/claude_desktop_config.json on Windows):

{
  "mcpServers": {
    "epub2pdf": {
      "command": "epub2pdf-mcp"
    }
  }
}

If epub2pdf-mcp is not on your PATH, use the absolute path to your Python interpreter:

{
  "mcpServers": {
    "epub2pdf": {
      "command": "/path/to/python3",
      "args": ["-m", "epub2pdf_cli.mcp_server"]
    }
  }
}

The MCP server uses low-resource defaults (WeasyPrint, no PDF validation, no long-lived browser) and exposes these tools:

  • convert_epub
  • batch_convert
  • inspect_epub
  • extract_pdf

Codex and OpenCode Usage

After running:

python3 scripts/install_agent_skills.py

Codex and OpenCode can use the global epub2pdf skill.

Example prompts:

  • Use $epub2pdf to inspect this EPUB and convert it to a searchable PDF with JSON and HTML sidecars.
  • Use $epub2pdf to extract Markdown and JSON from this PDF for RAG ingestion.
  • Use $epub2pdf to inspect the EPUB TOC before rendering the PDF.

The skill should call epub2pdf inspect, epub2pdf convert, or epub2pdf pdf-extract directly.

Guarantees

  • Normal XHTML-based EPUB text is rendered as a real PDF text layer, not as raster-only pages.
  • Rendered PDFs are validated for page count and extractable text when textual source chapters exist.
  • EPUB sidecars are produced from the source EPUB before PDF rendering, so AI workflows do not need to reverse-engineer the PDF.
  • PDF extraction is local and deterministic by default.

Limitations

  • Fixed-layout comics and image-only EPUBs are rendered, but not OCR-processed.
  • Scanned PDFs that require OCR need a separate workflow; the local pdf-extract command does not start the hybrid OCR backend unless Docling is used.
  • Complex EPUB CSS may be simplified during normalization.
  • WeasyPrint is the default backend and may require native system libraries (Pango, Cairo, GDK-PixBuf).
  • pdf-extract no longer requires Java by default; Java is only needed for the legacy opendataloader engine.

Development

For detailed setup instructions, see docs/development/setup.md.

Quick start:

python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install -e '.[all,dev]'
PYTHONPATH=src python3 -m pytest -q

Before committing, run all checks:

python3 -m ruff check src tests
python3 -m mypy src
PYTHONPATH=src python3 -m pytest -q --cov=epub2pdf_cli --cov-fail-under=60

The source is organized into layered packages:

  • epub2pdf_cli/cli.py — argument parsing and command dispatch only.
  • epub2pdf_cli/config.py — validated configuration objects.
  • epub2pdf_cli/epub/ — EPUB container, OPF, TOC, and chapter parsing.
  • epub2pdf_cli/html/ — HTML normalization, asset rewriting, CSS rewriting, and template generation.
  • epub2pdf_cli/render/ — PDF rendering engines behind a Renderer protocol.
  • epub2pdf_cli/pdf/ — PDF validation and extraction adapters.
  • epub2pdf_cli/pipeline/ — high-level inspect, convert, and extract workflows.

Examples are under examples/:

  • batch_convert.sh — convert all EPUBs in a directory.
  • custom_renderer_plugin.py — skeleton Renderer implementation.
  • custom_extractor_plugin.py — skeleton Extractor implementation.

Refresh global skill wrappers after editing integration templates:

python3 scripts/install_agent_skills.py

Community

Contributing

We welcome bug reports, feature requests, documentation fixes, and pull requests.

Repository

Public GitHub repository:

https://github.com/min9lin9/epub2pdf

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

epub2pdf_cli-0.3.4.tar.gz (42.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

epub2pdf_cli-0.3.4-py3-none-any.whl (53.6 kB view details)

Uploaded Python 3

File details

Details for the file epub2pdf_cli-0.3.4.tar.gz.

File metadata

  • Download URL: epub2pdf_cli-0.3.4.tar.gz
  • Upload date:
  • Size: 42.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for epub2pdf_cli-0.3.4.tar.gz
Algorithm Hash digest
SHA256 335b62fc754cba3282a4978ae8ac04e7b41630a200dda4d569e19cb4fabeba39
MD5 1dc0a08ed8fd82645c0837ae6a086b68
BLAKE2b-256 1a6746374a8afd2393b5bfcc89a34361f9af81afe42a39edddd2666cde04abf5

See more details on using hashes here.

Provenance

The following attestation bundles were made for epub2pdf_cli-0.3.4.tar.gz:

Publisher: release.yml on min9lin9/epub2pdf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file epub2pdf_cli-0.3.4-py3-none-any.whl.

File metadata

  • Download URL: epub2pdf_cli-0.3.4-py3-none-any.whl
  • Upload date:
  • Size: 53.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for epub2pdf_cli-0.3.4-py3-none-any.whl
Algorithm Hash digest
SHA256 1b437e4e01bb273b3fd37d59ec1fdc92e07ba3144915ad779f58c05974137432
MD5 5b1188ba9f0fa23304b77e671bbf0267
BLAKE2b-256 3a918a29e10039f2a558c86b47ebdc6403e5a944bb47ef79e7c42d0ba72dfd02

See more details on using hashes here.

Provenance

The following attestation bundles were made for epub2pdf_cli-0.3.4-py3-none-any.whl:

Publisher: release.yml on min9lin9/epub2pdf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page