pdf2md.py wandelt eine PDF-Datei in eine oder kapitelweise mehrere MD-Dateien um (zur Weiterverarbeitung, z. B. HTML oder für RAG) und kann daraus komplette EPUB-Dateien für den Pocketbook-Reader (und andere Reader) erzeugen.
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Dieter Schlüter 0fc87da74f Doku: --formulas für Mathebücher, MathML im EPUB
README und BEDIENUNGSANLEITUNG: Option --formulas (pix2text, jede Formel auf der Seite,
Liste für formula_img2latex), pix2text als optionale Abhängigkeit, Formeln im EPUB als
MathML, Grenze und häufiges Problem "Formeln als Bilder/Zeichensalat" aktualisiert.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 03:35:06 +02:00
tests --split seiten, --epub-per-file, Nachspann, --from-md --split, --split-chars 2026-10-03 01:04:50 +02:00
.gitignore .gitignore aufgeräumt: veraltete Einträge entfernt, History/, .ruff_cache/, *.png, *.sdr/ ergänzt 2026-10-02 18:30:02 +02:00
BEDIENUNGSANLEITUNG.md Doku: --formulas für Mathebücher, MathML im EPUB 2026-10-10 03:35:06 +02:00
CLAUDE.md --split seiten, --epub-per-file, Nachspann, --from-md --split, --split-chars 2026-10-03 01:04:50 +02:00
pdf2md.py --formulas: Formeln seitenweise erkennen, auch im Fließtext und in Kästen 2026-10-10 03:31:58 +02:00
pytest.ini pdf2md v5: gescannte und digitale PDFs, EPUB-Export, Tests 2026-10-02 00:33:21 +02:00
README.md Doku: --formulas für Mathebücher, MathML im EPUB 2026-10-10 03:35:06 +02:00
requirements.txt Initial commit: pdf2md v1–v4 und Recherche 2026-10-01 23:46:54 +02:00
TODO.md --split seiten, --epub-per-file, Nachspann, --from-md --split, --split-chars 2026-10-03 01:04:50 +02:00

pdf2md.py – PDF → Markdown → EPUB

Werkzeug, um PDF-Bücher in sauberes Markdown und anschließend in ein EPUB für den PocketBook-Reader umzuwandeln – mit Überschriften, Bildern und echten Absätzen. Funktioniert für digitale PDFs (Textebene vorhanden) ebenso wie für gescannte PDFs (nur Bilder, per OCR) und für Scans, die bereits eine OCR-Ebene haben (z. B. von ocrmypdf).

Entwickelt an Operation Ljutsch (Band 1 und 2), außerdem erprobt an einem deutschen Sachbuch, einem englischen Handbuch, einem Mathematikbuch, einem Bericht mit Fußnoten, einem englischen Scan von 1893 und zwei gescannten Gedichtbänden (Rilke in Antiqua, Löwenberg 1895 in Fraktur; Ergebnisse siehe Grenzen).

Installation (Ubuntu 24.04)

sudo apt install tesseract-ocr tesseract-ocr-deu pandoc
pip install -r requirements.txt     # pymupdf4llm, pymupdf-layout, pytest

tesseract-ocr wird nur für Scans gebraucht, pandoc nur für --epub, pix2text nur für --formulas (pip install "pix2text[multilingual]", siehe formula_img2latex). Für Fraktur-Scans das Modell deu_latf aus tessdata_best nach /usr/share/tesseract-ocr/5/tessdata/ laden und --ocr-language deu_latf angeben (besser als das Ubuntu-Paket tesseract-ocr-frk).

Verwendung

Ausführliche Schritt-für-Schritt-Anleitung, auch für den Weg „Markdown von Hand bearbeiten, dann EPUB“: BEDIENUNGSANLEITUNG.md.

# digitales oder gescanntes PDF -> Markdown (OCR wird automatisch entschieden)
python3 pdf2md.py buch.pdf buch.md

# zusätzlich Bilder und EPUB (Titel/Autor aus den PDF-Metadaten bzw. dem Dateinamen
# "Autor_-_Titel.pdf", Seite 1 als Cover)
python3 pdf2md.py --epub buch.pdf buch.md

# Scan in Deutsch und Englisch, 8 parallele OCR-Prozesse
python3 pdf2md.py --ocr-language deu+eng --jobs 8 scan.pdf scan.md

# Kapitel als Einzeldateien (Ordner buch/ mit index.md), dazu EPUB
python3 pdf2md.py --epub --split kapitel buch.pdf buch.md

# Buch ohne Kapitel: je etwa 25 PDF-Seiten eine Datei und ein EPUB je Datei
python3 pdf2md.py --split seiten --pages-per-file 25 --epub-per-file buch.pdf buch.md

# bearbeitetes Gesamt-Markdown nachträglich in Kapitel teilen
python3 pdf2md.py --from-md buch.md --split kapitel

# EPUB aus bearbeitetem Markdown bzw. aus einem --split-Ordner (Reihenfolge: index.md)
python3 pdf2md.py --from-md buch.md
python3 pdf2md.py --from-md buch/

# englisches Buch; alter Scan mit schlechter OCR-Ebene neu erkennen
python3 pdf2md.py --epub --ocr-language eng --ocr always scan.pdf scan.md   # EPUB-Sprache: en
Option Bedeutung
--ocr auto (Standard) Scanseiten per OCR lesen; vorhandene, gerade OCR-Ebenen übernehmen, schiefe neu erkennen; reine Bildseiten in digitalen PDFs (Fotos, Cover) nur nach erfolgreicher Probe-OCR
--ocr always / --ocr never (--no-ocr) alle Seiten neu erkennen / keine OCR
--ocr-language, --ocr-dpi Tesseract-Sprache(n) (deu), Auflösung (300)
--jobs N parallele OCR-Prozesse (Standard: halbe Kernzahl, höchstens 8); reine Textdokumente laufen immer sequenziell
--images Grafiken in <name>_images/ speichern und verlinken
--image-format jpg|png Bildformat (Standard jpg; png verlustfrei, aber ca. 3× größer)
--jpeg-quality 1-100 JPEG-Qualität (Standard 85; 95 fast verlustfrei, Bilder ca. 1,7× größer)
--epub, --epub-file PFAD EPUB 3 mit pandoc erzeugen (schaltet --images ein); $…$/$$…$$ werden MathML
--formulas Mathebücher: jede Formel auf der Seite finden (pix2text, auch im Fließtext und in Merkkästen), zeichengenau aus dem Text schneiden und als Bild <pdf>-<seite>-f<nr>.png speichern, Liste in <name>_images/formulas-*.json; für formula_img2latex, das sie in LaTeX übersetzt (schaltet --images ein)
--split teile|kapitel|seiten Teile, Kapitel oder PDF-Seitenbereiche als Einzeldateien in einem Ordner <name>/ mit index.md (Reihenfolge) und images/; Anhang/Autor/Impressum nach dem letzten Kapitel als „Nachspann“
--pages-per-file N mit --split seiten: etwa N PDF-Seiten je Datei (Standard 20), Schnitt an Absatzgrenzen, bevorzugt an einer Überschrift bis zu einer Seite neben der Grenze
--page-ranges 1-12,13-40,41- mit --split seiten: feste Seitenbereiche (streng, ohne Ausrichten); Seiten außerhalb werden eigene Dateien
--split-chars N --split teile|kapitel ohne Gliederung: Abschnitte von etwa N Zeichen (Standard 30 000)
--epub-per-file mit --split: zusätzlich ein EPUB je Einzeldatei in <name>/epub/ (Gesamt-EPUB nur mit --epub)
--from-md PFAD nur EPUB erzeugen – aus einer (bearbeiteten) .md oder einem --split-Ordner; mit --split teile|kapitel eine .md stattdessen in einen Ordner teilen
--title, --author Titel/Autor im EPUB vorgeben (statt PDF-Metadaten bzw. YAML-Kopf)
--lang Sprache im EPUB (Standard: aus --ocr-language, deu→de, eng→en)
--keep-lines jede Zeile der PDF bleibt eine Zeile (Gedichtbände, Liedtexte): Strophen als Absätze, Einrückungen bleiben; nichts wird zusammengefügt, auch keine Silbentrennung. Scans werden direkt aus Tesseract gelesen, Titel aus der PDF-Gliederung übernommen
--keep-headers, --keep-footers Kolumnentitel bzw. Seitenzahlen nicht entfernen

Was die Nachbearbeitung macht

  • Fließtext oder zeilengetreu: Ein Zeilenumbruch gilt als weich (vom Satzprogramm), wenn das erste Wort der nächsten Zeile nicht mehr in die Zeile gepasst hätte – dann werden die Zeilen zu Fließtext verbunden. Sonst hat ihn der Autor gesetzt (hart), und die Zeile bleibt samt Einrückung erhalten. So bleiben Code (auch in Proportionalschrift, als ```-Block mit Einrückung; ebenso Konfigurationszeilen wie [dependencies], Befehle wie cargo build und Programmausgaben), Register, Glossare, Verse, Adressen, Lebensläufe zeilengetreu (Markdown: \ am Zeilenende, Einrückung als geschütztes Leerzeichen). Codeblöcke über eine Seitengrenze werden zusammengeführt; lange Codezeilen bricht der Reader um.
  • Verse: Eine kurze Zeile mit Satzende gilt nur dann als Absatzende, wenn die nächste Zeile eingerückt ist (Roman); sonst ist sie eine Verszeile und bleibt eine Zeile. Füllen Verse die Zeile fast (schmale Gedichtbände), ist das geometrisch nicht von Fließtext zu unterscheiden – dafür gibt es --keep-lines.
  • Absätze: eine Absatzgrenze gilt nach Satzende (. ! ? …, auch mit « “ » usw. dahinter) oder an einem harten Umbruch; auf Scans werden Absätze am Einzug erkannt.
  • Silbentrennung: Geheim-|dienst → Geheimdienst, Militär-|Geheimdienst bleibt mit Bindestrich, Arbeits- und bleibt. Weiche Trennstriche (U+00AD) und ¬ am Zeilenende werden ebenfalls aufgelöst (wach­|senden → wachsenden).
  • Überschriften: Ebenen aus den PDF-Lesezeichen (Teile ##, Kapitel ###); Satzreste, die fälschlich als Überschrift erkannt wurden, kommen zurück in den Text.
  • Inhaltsverzeichnis: zusammengeklebte Einträge werden getrennt; jeder Eintrag wird ein eigener Absatz (auch bei Verzeichnissen ohne Überschrift „Inhalt“, z. B. „Vorschau Band Zwei“), nie eine Zeile mit \. Liegt das Verzeichnis als Tabelle vor, bleibt die Tabelle erhalten.
  • OCR-Ebenen in Courier werden nicht als Programmcode (`…`) gesetzt.
  • Links: URLs werden als <https://…> geschrieben und sind damit in Markdown-Viewern und im EPUB anklickbar. Beim Umbruch zerbrochene URLs (deutsche-|sehen, …/ youtube-…, auch über eine Blockgrenze oder als Listenpunkt fortgesetzt) werden mit den Linkzielen der PDF abgeglichen und repariert; Tracking-Parameter (utm_…) entfallen. Ohne Linkziel wird die URL aus dem gedruckten Text zusammengesetzt; Wortpaare wie „und/oder“ danach gehören nicht dazu. Links mit eigenem Text („hier“, „Bericht“) werden über den Linktext und die Wörter davor im Markdown wiedergefunden – auch bei verlorener Ligatur („Verifcation“) – und als [hier](https://…) gesetzt.
  • Seitenzahlen, Kolumnentitel, Text in Fotos werden entfernt – auch Überschriften, die nur aus einer Zahl bestehen (## 90, ## xxiv: Fußnotenzeichen, römische Seitenzahlen), sofern nicht direkt eine Titelzeile folgt (## 1 + ## Kapiteltitel bleibt) und sie nicht in der PDF-Gliederung stehen; Kapitelnummern mit Punkt (## IV.) und Register-Buchstaben bleiben.
  • Markdown-Sonderzeichen im Text (\ ` * _ [ ] < > $ ~ ^) werden mit \ maskiert, damit z. B. __add__ nicht fett und <p> nicht zu HTML wird; _ mitten im Wort (snake_case) und Code bleiben unverändert.
  • YAML-Kopf: Jede Markdown-Datei beginnt mit title, author, lang, cover (Einzeldateien von --split mit title, part, chapter, order, source_pages, book, author; source_pages = Seiten der PDF-Datei, nicht die gedruckten). --from-md liest daraus die EPUB-Angaben. Ohne verwertbare Gliederung teilt --split nach Länge (ca. 30 000 Zeichen, an Absatzgrenzen) in „Abschnitt 1“, „Abschnitt 2“ …
  • --split seiten: Das Gesamt-Markdown wird nur zerschnitten, nie gekürzt – alle Dateien aneinandergehängt ergeben wieder den ganzen Text. Geschnitten wird vor dem ersten Absatz, der auf der Grenzseite beginnt; ein Absatz über die Seitengrenze bleibt ganz in der Datei davor, Überschriften direkt vor dem Schnitt wandern in die neue Datei. Die Seite jedes Absatzes wird im Rohtext der Seiten gesucht (bei Scans im OCR-Text); source_pages darf sich deshalb um eine Seite überlappen. Titel: die Überschrift am Dateianfang, sonst „Seiten 21-40“.

Tests

python3 -m pytest            # ca. 80 s; OCR-/EPUB-Tests werden ohne Tesseract/pandoc übersprungen

Die Test-PDFs (digital, gescannt, mit OCR-Ebene, gemischt, Fotoseite) werden zur Laufzeit synthetisch erzeugt.

Grenzen

  • Die Ergebnisqualität hängt von pymupdf4llm/pymupdf-layout ab. Einige Schwächen der Bibliothek gleicht pdf2md.py per Patch aus (Bindestriche und weiche Trennstriche am Zeilenende, Absätze in Scan-Blöcken, OCR-Ebenen in Courier). Eine weitere ist bekannt: Bei Schriften mit Ligaturen (ff, fi) mit übergroßer Glyphenbox kann pymupdf4llm einzelne Zeilen verlieren. Bei den Ljutsch-Bänden tritt das nicht auf (geprüft per Wortabgleich PDF ↔ Markdown).
  • OCR-Fehler (z. B. fehlende Leerzeichen) werden nicht korrigiert. Bei alten Scans mit mitgelieferter OCR-Ebene liefert --ocr always meist deutlich besseren Text (die alte Ebene wird dabei verworfen, z. B. „bliht“ → „blüht“). OCR-Rauschen vom Scanrand (Buchkante als „|“, Buchstabenfetzen neben dem Textbereich) wird entfernt.
  • Fraktur: Das lange s bleibt als „ſ“ erhalten; auch deu_latf liest alte Scans nicht fehlerfrei.
  • Gedicht-Scans ohne --keep-lines: Die Layout-Analyse von pymupdf4llm kann eng stehende Verse verschränken („UndDon tauchteweißen …“) – mit --keep-lines werden Scans direkt aus Tesseract gelesen.
  • Mathematische Formeln werden ohne --formulas als Bilder übernommen (abgesetzte Formeln) bzw. als Glyphensalat im Fließtext (x4n statt x⁴ⁿ). Mit --formulas wird jede Formel ein eigenes Bild, das formula_img2latex in LaTeX übersetzt (Corral, Elementary Calculus: 9239 Formeln, Stichprobe 79 % automatisch). Formeln in Tabellen erfasst die Erkennung noch nicht; „Formel“-Boxen mit viel Text werden zu Text, solche mit Kurven bleiben Grafik.
  • Was am PDF liegt, bleibt: fehlende Ligaturen ohne Textzuordnung („Offzielle“), gesperrt gesetzte Überschriften („M e i n k l e i nes …“), flache PDF-Gliederungen (alle Kapitel auf einer Ebene).
  • Links mit eigenem Text: Weicht der Linktext stark vom gedruckten Text ab, bleibt er ohne Link (Zensur-Report: alle 76 gesetzt). Ohne Linkziel in der PDF wird eine URL nur über typische Umbruchstellen (-, /, _) mit ihrer Fortsetzung verbunden.
  • Zeilengetreue Erkennung: Ist ein Gedicht genauso eingerückt wie Absatzanfänge, werden Verszeilen mit Satzzeichen am Ende zu eigenen Absätzen (Abhilfe: --keep-lines). Listings mit eingedruckten Zeilennummern werden nicht immer als ein Codeblock erkannt. Weitere bekannte Schwächen: TODO.md.
  • Bilder werden als JPEG (Qualität 85) gespeichert; EPUB Band 1 ca. 3 MB.

Dateien

  • pdf2md.py – aktuelle Version
  • tests/ – pytest-Suite