epub2md wandelt eine EPUB-Buch kapitelweise in MD-Dateien um
  • Python 95%
  • Lua 3.5%
  • Shell 0.9%
  • CSS 0.6%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
dschlueter 02449734a2 feat: epub2md – EPUB nach kapitelweisem Markdown
Kommandozeilenwerkzeug, das DRM-freie EPUB-2/3-Dateien offline in
kapitelweise Markdown-Dateien umwandelt. Erzeugt werden außerdem Medien,
metadata.yaml, manifest.json, conversion-report.json und Diagnose-Dateien.

- Spine-basierte Kapitelbildung mit --chapter-level, epub:type-Abschnitten,
  Fortsetzungsdateien und Erkennung von Layout-Überschriften
- Code mit exakter Einrückung, sicheren Fences und konservativer
  Spracherkennung
- MathML->TeX (konservativ), TeX aus alt-Texten, SVG-/Bildformeln
  bleiben Bilder
- Fußnoten-Modi end-of-chapter, end-of-book, inline und keep-links
- Interne Links/Anker werden umgeschrieben, Linkcheck über die Ausgabe
- Sicher: kein Entpacken, Schutz vor Path Traversal und XXE,
  DRM-Erkennung, atomares Schreiben, --force nur für frühere Ausgaben
- 82 pytest-Tests mit synthetischen EPUBs und Invariantentests für Beispiele/

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 19:33:42 +02:00
epub2md feat: epub2md – EPUB nach kapitelweisem Markdown 2026-10-05 19:33:42 +02:00
tests feat: epub2md – EPUB nach kapitelweisem Markdown 2026-10-05 19:33:42 +02:00
.gitignore feat: epub2md – EPUB nach kapitelweisem Markdown 2026-10-05 19:33:42 +02:00
convert_epub2md.sh feat: epub2md – EPUB nach kapitelweisem Markdown 2026-10-05 19:33:42 +02:00
epub2md.py feat: epub2md – EPUB nach kapitelweisem Markdown 2026-10-05 19:33:42 +02:00
Prompt.md feat: epub2md – EPUB nach kapitelweisem Markdown 2026-10-05 19:33:42 +02:00
Prompt_Kontrolle.md feat: epub2md – EPUB nach kapitelweisem Markdown 2026-10-05 19:33:42 +02:00
pytest.ini feat: epub2md – EPUB nach kapitelweisem Markdown 2026-10-05 19:33:42 +02:00
README.md feat: epub2md – EPUB nach kapitelweisem Markdown 2026-10-05 19:33:42 +02:00
requirements-dev.txt feat: epub2md – EPUB nach kapitelweisem Markdown 2026-10-05 19:33:42 +02:00
requirements.txt feat: epub2md – EPUB nach kapitelweisem Markdown 2026-10-05 19:33:42 +02:00
split-md-chapters.py feat: epub2md – EPUB nach kapitelweisem Markdown 2026-10-05 19:33:42 +02:00

epub2md

Kommandozeilenwerkzeug, das DRM-freie EPUB-Dateien (EPUB 2 und EPUB 3) lokal und offline in eine strukturierte Sammlung kapitelweiser Markdown-Dateien umwandelt – mit extrahierten Medien, YAML-Metadaten, Inhaltsverzeichnis, maschinenlesbarem Manifest und einem Konvertierungsbericht.

Gedacht für die Weiterverarbeitung mit Markdown-Renderern, Pandoc, Obsidian/Zettelkasten, Git, RAG-/Embedding-Pipelines, Volltextsuche und lokale LLMs.

Grenzen

  • Kein DRM: Verschlüsselte EPUBs (META-INF/encryption.xml mit verschlüsselten Inhalten) werden mit Exit-Code 2 abgelehnt. Es wird kein Kopierschutz umgangen. Reine Font-Obfuscation ist erlaubt.
  • Keine OCR: Gescannte Bücher (Seiten als Bilder) liefern nur Bildreferenzen, keinen Text.
  • Formeln als Bild/SVG werden nie in TeX „geraten“. Sie bleiben als Bild/SVG erhalten und werden im Bericht gemeldet. TeX entsteht nur aus echtem MathML, TeX-Annotationen oder TeX im alt-Text.
  • Layout-Heuristiken (z. B. Überschriften, die als Tabellen gesetzt sind) sind konservativ und erfassen nicht jedes Verlagslayout.

Installation (Ubuntu 24.04)

sudo apt install python3 python3-venv
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt          # Laufzeit: nur lxml
pip install -r requirements-dev.txt      # Tests: pytest, PyYAML

Laufzeitabhängigkeit ist ausschließlich lxml (sicheres XML/HTML-Parsing). Alles andere ist Standardbibliothek. Pandoc, Calibre, Java oder Node.js werden nicht benötigt. Pandoc ist nur für die optionale Weiterverarbeitung der Ausgabe nützlich.

Aufruf

python3 epub2md.py INPUT.epub OUTPUT_VERZEICHNIS [Optionen]
# alternativ
python3 -m epub2md INPUT.epub OUTPUT_VERZEICHNIS [Optionen]

Beispiele:

# Programmierbuch mit Standardoptionen
python3 epub2md.py \
  Beispiele/programmierbuch.epub \
  output/programmierbuch \
  --format gfm \
  --math auto \
  --footnotes end-of-chapter \
  --extract-media \
  --frontmatter \
  --toc \
  --verbose

# Roman mit Teilen (h1) und Kapiteln (h2): ein File pro Kapitel
python3 epub2md.py roman.epub output/roman --chapter-level 2

# Nur analysieren, nichts schreiben
python3 epub2md.py buch.epub output/buch --dry-run

# Erneut konvertieren und frühere Ausgabe ersetzen
python3 epub2md.py buch.epub output/buch --force --report berichte/buch.json

Optionen

Option Standard Wirkung
--format {gfm,commonmark} gfm gfm: Pipe-Tabellen, ~~durchgestrichen~~, [^fn]-Fußnoten. commonmark: Tabellen als HTML, <del>, Fußnoten als Anker-Links
--extract-media / --no-extract-media an Medien nach media/ extrahieren. Ohne Extraktion stehen Bildreferenzen als Text [Bild: alt – Pfad] da (keine kaputten Links)
--copy-original / --no-copy-original aus Original-EPUB nach source/ kopieren
--chapter-level N 1 Überschriftenebene (1–6), ab der eine neue Kapiteldatei beginnt
--toc / --no-toc an README.md mit Inhaltsverzeichnis, Statistik und Reproduktionsbefehl
--frontmatter / --no-frontmatter an YAML-Frontmatter in jeder Kapiteldatei
--footnotes {keep-links,end-of-chapter,end-of-book,inline} end-of-chapter siehe Fußnoten
--math {auto,tex,mathml,image} auto siehe Mathematik
--images {relative,flat,original-path} relative relative: media/images/0001-name.ext; flat: media/name.ext; original-path: media/<Pfad im EPUB>
--slug-language {de,en,none} de Dateinamen: de ä→ae, ß→ss; en ä→a, ß→ss; none Unicode bleibt
--keep-html aus Unbekannte/nicht sicher konvertierbare Elemente als bereinigtes HTML erhalten (sonst: Text übernommen + Eintrag in diagnostics/unsupported-elements.md)
--strict aus Abbruch bei kritischen Fehlern (fehlende/unlesbare Spine-Dokumente, unsichere ZIP-Pfade, ungültige Spine-Verweise)
--force aus Bestehendes Ausgabeverzeichnis ersetzen – siehe Sicherheit
--verbose, -v aus Verarbeitungsschritte und Warnungen auf stderr
--dry-run aus EPUB analysieren, geplante Kapiteldateien anzeigen, nichts schreiben (auch kein --report)
--report DATEI.json – Bericht zusätzlich an diesen Ort schreiben. OUTPUT/conversion-report.json entsteht immer
--version – Programmversion

Rückgabecodes

Code Bedeutung
0 Erfolgreich, ggf. mit nicht-kritischen Warnungen
1 Ungültige Parameter, Eingabedatei fehlt/nicht lesbar, Ausgabeverzeichnis belegt
2 ZIP-/Container-Fehler, DRM, unsichere ZIP-Pfade (mit --strict)
3 OPF-/Spine-/XHTML-Fehler (z. B. leere Spine; mit --strict auch einzelne defekte Dokumente)
4 Unerwarteter interner Fehler (Traceback mit --verbose)

Ausgabestruktur

output/mein-buch/
├── README.md                     # Titel, Metadaten, Inhaltsverzeichnis, Statistik, Reproduktion
├── metadata.yaml                 # Buchmetadaten (nur vorhandene Felder, nichts erfunden)
├── manifest.json                 # Kapitel mit Anker-Listen, Wortzahl, Code/Bild/Formel-Zählern – für RAG
├── conversion-report.json        # vollständiger Bericht (Optionen, SHA-256, Warnungen, Formeln, Links …)
├── source/mein-buch.epub         # nur mit --copy-original
├── media/
│   ├── images/0001-cover.jpg     # stabile, kollisionsfreie Namen; jede Quelle genau einmal
│   ├── audio/  video/  other/    # Audio, Video, Schriften, CSS …
├── chapters/
│   ├── 001-cover.md
│   ├── 002-vorwort.md
│   ├── 003-1-einleitung.md
│   ├── …
│   └── 999-anmerkungen.md        # nur bei --footnotes end-of-book
└── diagnostics/
    ├── warnings.md               # Warnungen/Hinweise gruppiert, für Menschen
    └── unsupported-elements.md
  • Dateinamen: fortlaufend dreistellig nummeriert (001-…) plus Slug aus dem Kapiteltitel. Gleiche Titel bekommen eindeutige Namen, weil die Nummer immer Teil des Namens ist.
  • Kapitel-Frontmatter (YAML, ----Begrenzer): book_title, book_author, language, chapter_number, chapter_title, source_epub, source_href (+ source_hrefs, wenn ein Kapitel aus mehreren XHTML-Dateien besteht), epub_type, conversion_status (converted oder converted-with-warnings).
  • Spezialabschnitte (Glossar, Literatur, Sach-/Personenregister, Anhang, Endnoten) erhalten eigene Dateien und werden in README.md unter „Besondere Abschnitte“ verlinkt.
  • Nicht-lineare Spine-Dokumente (linear="no", z. B. Bild-Zoomseiten) bleiben in Spine-Reihenfolge erhalten und sind im Inhaltsverzeichnis als Zusatzseite markiert.

Wie Kapitel gebildet werden

  1. Die OPF-Spine bestimmt die Lesereihenfolge (nicht Dateinamen, nicht das Inhaltsverzeichnis).
  2. Überschriften h1…hN (N = --chapter-level) und semantische Abschnitte (epub:type/role: chapter, part, appendix, glossary, bibliography, index, preface, …) beginnen neue Kapiteldateien, auch mitten in einer XHTML-Datei.
  3. Eine XHTML-Datei, die nur mit einer tieferen Überschrift beginnt (z. B. eine Datei pro Abschnitt 1.2), wird an das laufende Kapitel angehängt.
  4. Dateien ohne Überschrift (Cover, Titelei, Widmung) werden eigene Kapitel. Ihr Titel kommt aus NAV/NCX, <title>, epub:type oder dem Dateinamen.
  5. Ein HTML-Inhaltsverzeichnis wird übersprungen, wenn NAV/NCX vorhanden ist (keine doppelte Ausgabe). Links darauf zeigen auf README.md.
  6. Überschriftenebenen werden je Datei verschoben: Der Kapiteltitel ist #, alle weiteren beginnen bei ##.

Verlagsspezifische Heuristiken: Layout-Tabellen mit Klassen wie Head1/Title_document werden zu Überschriften. <pre> mit <br/>-Zeilen und div.*programlisting* > span.pre werden zu Codeblöcken. Literaturlisten mit eigenen Labels ([BryL06]) werden nicht doppelt nummeriert.

Tipp: Hat ein Buch Teile als h1 und Kapitel als h2 (typisch bei Romanen), erzeugt --chapter-level 2 eine Datei pro Kapitel. Mit dem Standard 1 entsteht eine Datei pro Teil.

Math-Modi

Modus MathML TeX in alt/Annotation SVG-/Bildformel ohne TeX
auto konservativ → TeX $…$ / $$…$$; bei Unklarheit MathML behalten TeX $…$ / $$…$$ Bild + Warnung
tex wie auto, aber Begrenzer \(…\) / \[…\] (nie $) TeX \(…\) / \[…\] Bild + Warnung
mathml MathML als eingebettetes XML (Namespace normalisiert) Bild (TeX bleibt im Alt-Text) Bild
image altimg-Bild falls vorhanden, sonst MathML + Warnung Bild Bild
  • Die MathML→TeX-Umsetzung unterstützt mi, mn, mo, mtext, mspace, mrow, mstyle, mfrac, msqrt, mroot, msup, msub, msubsup, mover, munder, munderover, mfenced, mtable/mtr/mtd, semantics, maction. Bei allem anderen (z. B. menclose, mmultiscripts, mlabeledtr) bleibt das MathML erhalten. Lieber MathML als falsches TeX.
  • TeX-Annotationen (<annotation encoding="application/x-tex">) haben Vorrang.
  • Im alt-Text doppelt escapte Zeichen (&amp;amp; → &) werden repariert.
  • Gleichungsnummern wie (3.7) bleiben als Text sichtbar, Gleichungsanker als <a id> erhalten.
  • Formelbilder, die durch TeX ersetzt wurden, werden nicht extrahiert. Die Anzahl steht im Bericht (formula_images_replaced_by_tex).
  • Qualität hängt vom EPUB ab: Echtes MathML/TeX liefert semantische Formeln. Reine SVG-/Bitmap-Formeln bleiben Bilder.

Fußnoten-Modi

Erkannt werden epub:type="noteref|footnote|endnote|rearnote", role="doc-noteref|doc-footnote|doc-endnote", Klassen wie footnote/endnote und Links auf solche Ziele. Rücklinks innerhalb der Note werden entfernt. Die Note erscheint nicht zusätzlich im Fließtext.

Modus Ergebnis
end-of-chapter Text[^fn-001] und [^fn-001]: … am Kapitelende. Mehrere Absätze, Code, Formeln und Links bleiben erhalten. Mehrfachverweise nutzen dieselbe Definition
end-of-book chapters/999-anmerkungen.md mit Ankern. Verweise <sup>[1](999-anmerkungen.md#fn-001)</sup>, Rücklinks ↩ zu jeder Verweisstelle
inline Text (Anmerkung: …). Noten mit Blockinhalt (Code, Listen, Tabellen) fallen auf end-of-chapter zurück (Hinweis im Bericht)
keep-links Originalstruktur: Note bleibt an ihrer Stelle, Verweise sind normale Links auf Anker

Mit --format commonmark werden Fußnoten als Anker-Links mit Abschnitt „Anmerkungen“ ausgegeben, weil CommonMark keine [^…]-Syntax kennt.

Qualitätshinweise

  • Formelbasierte EPUBs: Am besten sind EPUBs mit MathML oder TeX im alt-Text (z. B. Springer).
  • SVG-Formeln bleiben SVG (nie gerastert) und werden im Bericht als Formel markiert.
  • Layouttabellen / komplexe Tabellen (colspan, rowspan, Blockinhalte in Zellen, verschachtelte Tabellen) bleiben als bereinigtes HTML im Markdown erhalten, mit Warnung. Einfache Tabellen werden GFM-Pipe-Tabellen. Einzellige Layouttabellen werden als normaler Text ausgegeben.
  • Syntax-Highlighting: Farb-Spans, <strong>/<em> in Listings und Zeilennummern werden entfernt. Einrückung, Tabs, <, >, &, $, |, Backslashes und Backticks bleiben exakt erhalten. Die Sprache kommt aus Klassen/Attributen (language-python, data-lang, …) oder starken Inhaltssignalen (#include, def …:, Shebang, …). Ist sie unsicher, bleibt das Label leer.
  • Gescannte Texte: nur Bilder, kein Text (keine OCR).
  • DRM: wird erkannt und abgelehnt.
  • Register: Buchstabengruppen werden zu ##-Überschriften, Untereinträge erhalten – . Interne Registerlinks werden auf Kapiteldatei + Anker umgeschrieben. Seitenzahlen bleiben als Text sichtbar.

Weiterverarbeitung mit Pandoc

pandoc output/programmierbuch/chapters/*.md \
  --metadata-file=output/programmierbuch/metadata.yaml \
  -o programmierbuch.html

pandoc output/mathematikbuch/chapters/*.md \
  --metadata-file=output/mathematikbuch/metadata.yaml \
  --pdf-engine=xelatex \
  -o mathematikbuch.pdf
  • Für HTML mit Formeln --mathml, --mathjax oder --katex verwenden. Pandocs Standard-HTML kann Matrizen nicht in Unicode umsetzen, meldet „Could not convert TeX math“ und belässt das TeX.
  • Ausgaben mit --math tex (\(…\)) brauchen -f markdown+tex_math_single_backslash.
  • Das YAML-Frontmatter jeder Kapiteldatei wird von Pandoc als Metadaten gelesen. Für ein Gesamtdokument ggf. --no-frontmatter verwenden.

Bericht und Diagnose

conversion-report.json enthält u. a. Programmversion, Zeitstempel, Eingabedatei mit SHA-256, EPUB-Version, Optionen, Metadaten, Kapitel mit Statistik, alle Medien, Codeblöcke (Sprache + Erkennungsart), Formeln (Herkunft + Darstellung), Fußnoten (Modus, erkannt/referenziert/ausgegeben), Spezialabschnitte, Warnungen, nicht-fatale Fehler, nicht aufgelöste interne Links, nicht unterstützte Elemente, übersprungene Dokumente und das Ergebnis eines Linkchecks über die erzeugte Ausgabe. Dabei wird jeder relative Link und jedes Bild auf Zieldatei und Anker geprüft.

status ist success, success-with-warnings oder success-with-errors. Bei Warnungen wird nie Verlustfreiheit behauptet (lossless: false).

Sicherheit und Fehlerbehandlung

  • Das EPUB wird nie pauschal entpackt. Einträge werden einzeln gelesen. Absolute Pfade und .. werden ignoriert und gemeldet (mit --strict: Abbruch). Alle Ausgabedateien werden vor dem Schreiben gegen das Zielverzeichnis geprüft.
  • Schutz gegen ZIP-Bomben: Größenlimit je Eintrag und Prüfung des Kompressionsverhältnisses.
  • XML wird mit lxml ohne DTD-Laden, ohne Entity-Auflösung und ohne Netzwerk geparst (kein XXE). HTML-Entities (&nbsp; …) werden vorab in Zeichenreferenzen umgesetzt.
  • Ungültiges UTF-8 wird mit Ersatzzeichen gelesen und als Warnung gemeldet.
  • Keine Netzwerkzugriffe, keine Shell-Aufrufe, keine Telemetrie.
  • Die Ausgabe entsteht in einem temporären Verzeichnis neben dem Ziel und wird erst am Ende an ihren Platz verschoben. Bei Fehlern wird das temporäre Verzeichnis gelöscht, und es bleibt keine halbe Ausgabe zurück.
  • Bestehendes Ausgabeverzeichnis: Ist es nicht leer, bricht das Programm ab (Exit 1). --force ersetzt es nur, wenn es eine frühere epub2md-Ausgabe ist (conversion-report.json/manifest.json vorhanden), nicht das Arbeits- oder Home-Verzeichnis ist und die Eingabedatei nicht darin liegt.

Architektur

epub2md.py                 Einstiegspunkt
epub2md/
├── cli.py                 argparse, Exit-Codes
├── pipeline.py            Ablaufsteuerung, Schreiben (atomar), Bericht/Manifest
├── epub_reader.py         ZIP-Sicherheit, DRM-Check, container.xml, OPF, NAV, NCX, Guide
├── normalize.py           Laden der Spine-Dokumente, Layout-Heuristiken, Seitenumbrüche, HTML-TOC
├── footnotes.py           Erkennung/Auslagerung von Fuß- und Endnoten
├── chapters.py            Kapitelbildung (Spine + Überschriften + epub:type), Klassifikation
├── references.py          Ziel-IDs → Kapiteldatei + Anker, Link-Auflösung
├── html_converter.py      rekursiver XHTML→Markdown-Renderer (Blöcke, Inline, Tabellen, Listen, Bilder)
├── codeblocks.py          Codeerkennung, exakte Textextraktion, Spracherkennung, Fences
├── mathml.py              konservative MathML→TeX-Konvertierung, TeX-Erkennung
├── media.py               Medienregistry mit stabilen Namen
├── linkcheck.py           Validierung aller relativen Links/Anker der Ausgabe
├── writer.py              README, metadata.yaml, Frontmatter, Diagnose-Markdown
├── diagnostics.py         zentrale Sammlung von Warnungen/Fehlern
├── models.py              Dataclasses (Kapitel, Medien, Noten, Metadaten …)
└── utils.py               Slugs, YAML-Emitter, Pfadhelfer

Statt BeautifulSoup wird direkt lxml verwendet: Es ist für die XHTML-Bäume ausreichend, schneller und ermöglicht die sichere Parser-Konfiguration (XXE). Die Konvertierung ist deterministisch. Gleiche Eingabe und Optionen ergeben identische Kapiteldateien.

Tests

python3 -m pytest -q

Die Tests erzeugen synthetische Minimal-EPUBs (EPUB 2/3, NAV/NCX, Code, Tabellen, MathML, SVG, Fußnoten, Glossar/Literatur/Register, unsichere ZIP-Pfade, DRM, XXE, defekte Container, Kodierung) und prüfen die tatsächliche CLI-Ausgabe. Liegen EPUBs unter Beispiele/, werden sie zusätzlich auf Struktur-Invarianten geprüft: keine kaputten Links, balancierte Code-Fences, gültiges YAML, alle Medien vorhanden. Dabei werden keine Buchtexte verglichen. Beispiele/ ist per .gitignore vom Repository ausgeschlossen.