Skip to content

Latest commit

 

History

8 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

unbind

Turn a PDF book into a clean, reflowable EPUB 3 — and fix EPUBs that refuse to open.

Python 3.12+ License: MIT

PDFs are frozen paper pages; e-readers want flowing text. Naive conversion drags the paper layout along — running headers and page numbers in the middle of paragraphs, words hyphen-ated across line breaks, chapters the reader can't navigate. unbind extracts the text with PyMuPDF4LLM, cleans all of that up, and builds the EPUB with Pandoc:

$ unbind book.pdf
PDF type: text_based (0 of 312 pages need OCR)
Extracting Markdown from: book.pdf
Removed 618 running header, footer, or page-number lines.
Creating EPUB: book.epub

Created successfully: book.epub

For the longer story — what goes wrong in a naive conversion and how unbind handles it — see the project write-up.

What it does

  • Chapters from PDF bookmarks — bookmarks become the authoritative heading hierarchy: detected headings are re-leveled, headings are inserted for image-only chapter pages, and false positives (like the book's printed table of contents) are demoted. --ignore-pdf-toc opts out.
  • Page-artifact cleanup — lines that repeat at the top or bottom of many pages (running titles, bare page numbers) are removed; real headings and images are never touched. --no-cleanup opts out.
  • Hyphenation repair — words split across line and page breaks are rejoined.
  • Metadata and cover for free — title/author come from the PDF metadata (fallback: filename), and the first page is rendered as the cover. Override with --title/--author/--cover, or --no-cover.
  • Scanned books, recognised automatically — unbind checks every page, detects the script the pages are printed in, and OCRs the ones that need it, including pages whose existing OCR layer was recognised in the wrong language (the usual cause of mojibake output). Pages are recognised in parallel. See below.
  • Batch conversion — point it at a directory; --jobs N converts books in parallel, failures are reported and skipped, and the exit code tells the truth.
  • EPUB check and repair — diagnose and fix EPUBs that readers refuse to open. See below.
  • Inspectable intermediate--keep-markdown retains the extracted Markdown and images for hand-editing.

Every converted book is verified as a well-formed EPUB before unbind reports success.

Install

Requires Python 3.12+ and Pandoc. Scanned books additionally need Tesseract with the language data for the book — unbind names the exact package if it is missing.

git clone https://github.com/Han8931/unbind
cd unbind
uv sync
uv run unbind --help

Usage

uv run unbind book.pdf
uv run unbind book.pdf -o output/ --title "Book title" --author "Author"
uv run unbind scanned.pdf
uv run unbind book.pdf --keep-markdown

The output defaults to the input filename with an .epub extension; passing a directory to -o places the EPUB inside it. --force overwrites existing output, --toc-depth N controls table-of-contents depth (default 2), and --language sets the book's language code (default: the script detected during OCR, otherwise en). --link-footnotes (experimental) turns superscript markers into linked EPUB footnotes when the footnote text is found on the same page.

Scanned books

Nothing extra to pass — unbind scanned.pdf is the whole command:

$ unbind zorba.pdf
OCR: 496 pages in 'kor' (the existing text is only 0% Hangul though the pages are)
Recognising pages takes a few seconds each; this is the slow part.
OCR: 496/496 pages
Extracting Markdown from: zorba.pdf
Creating EPUB: zorba.epub

Created successfully: zorba.epub

unbind classifies every page as blank, scan, existing-OCR-layer, or born-digital text. Scans with no text are always recognised. Pages that already carry an OCR layer are re-recognised only when that layer disagrees with the script Tesseract sees in the page images — the failure mode that produces 3] x 7}O|A} 20A]|7] where the page plainly reads 그리스인 조르바. Born-digital books are never rendered or recognised, so they cost nothing.

The detected script also sets the EPUB's language, so the reader hyphenates and line-breaks correctly without --language.

Flag Effect
(none) Recognise the pages that need it; keep good text as-is
--ocr Recognise every page, discarding any text the PDF carries
--no-ocr Never recognise; extract the existing text layer as-is
--ocr-language kor Skip script detection and use this Tesseract language (kor+eng also works)
--ocr-jobs N Pages to recognise in parallel; defaults to every core but one

Missing language data is reported before any work starts, with the package to install for your distribution.

Batch mode

Passing a directory converts every PDF in it (non-recursively):

uv run unbind pdfs/
uv run unbind pdfs/ -o epubs/ --jobs 4
uv run unbind pdfs/ --keep-markdown

-o names the output directory (default: the input directory). Each book's title and author come from its own metadata or filename, so --title, --cover, and --markdown-output are rejected in batch mode; everything else applies to every file. --ocr-jobs is divided among the books running in parallel, so the machine is not oversubscribed.

Checking and repairing EPUBs

Some EPUBs simply won't open — and the reader never tells you why. Pass an .epub to unbind and it will:

$ unbind book.epub
book.epub has problems that can keep readers from opening it:
  - content file fails strict XML parsing: OEBPS/Text/page-13.xhtml
    (undefined entity  : line 13, column 5)

Notes:
  - EPUB version 2.0

Re-run with --repair to attempt a fix.

$ unbind book.epub --repair
...
Repaired EPUB written to: book.repaired.epub

The check covers the container (mimetype entry placement and compression, container.xml, package manifest, missing resources) and every content document, parsed the same strict way reader apps parse XML — which catches the two classic killers: named HTML entities like &nbsp;, legal under the XHTML DTD but undefined to a parser that never loads it, and bare void tags like <br>, valid HTML but not valid XML.

Either one makes a reader fall back to lenient HTML recovery for every file in the spine, so a book that still opens can take far longer to index than its size suggests.

--repair writes a fixed copy next to the original (never modifying it): the archive is repackaged correctly, named entities become numeric character references (&nbsp;&#160;, identical rendering), bare void tags are closed (<br><br/>), stray HTML pasted into a stylesheet is stripped, duplicate entries are dropped, and a missing container.xml is regenerated when possible. Text is never altered.

The report ends with brief notes on what repair cannot change: DRM encryption (a DRM-locked book only opens in a reader authorized for it — unbind does not remove DRM), font obfuscation (harmless), and the EPUB version. For a full specification audit beyond "will it open," use epubcheck.

Scope

unbind is built for books — prose with chapters. Multi-column academic papers and heavily designed magazines are out of scope, and heading detection is only as good as the PDF's bookmarks and typography.

Development

uv sync
uv run pytest
uv run ruff check src tests
uv run ruff format src tests

CI runs the linter and tests on Python 3.12 and 3.14.

License

MIT — see LICENSE.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages