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>
This commit is contained in:
Dieter Schlüter 2026-10-05 19:33:42 +02:00
commit 02449734a2

45
split-md-chapters.py Executable file
View file

@ -0,0 +1,45 @@
#!/usr/bin/env python3
import re
import sys
from pathlib import Path
if len(sys.argv) != 3:
raise SystemExit(f"Aufruf: {sys.argv[0]} EINGABE.md AUSGABE_ORDNER")
src = Path(sys.argv[1])
outdir = Path(sys.argv[2])
outdir.mkdir(parents=True, exist_ok=True)
text = src.read_text(encoding="utf-8")
parts = re.split(r"(?m)^(?=#\s+)", text)
def slugify(title: str) -> str:
title = title.lower()
title = re.sub(r"[^\w\s-]", "", title, flags=re.UNICODE)
title = re.sub(r"[\s_-]+", "-", title).strip("-")
return title[:80] or "kapitel"
index = []
chapter_no = 0
for part in parts:
if not part.strip():
continue
m = re.match(r"^#\s+(.+?)\s*$", part, re.M)
if m:
chapter_no += 1
title = m.group(1).strip()
filename = f"{chapter_no:03d}-{slugify(title)}.md"
else:
filename = "000-vorspann.md"
title = "Vorspann"
(outdir / filename).write_text(part.rstrip() + "\n", encoding="utf-8")
index.append(f"- [{title}]({filename})")
(outdir / "README.md").write_text(
"# Inhaltsverzeichnis\n\n" + "\n".join(index) + "\n",
encoding="utf-8",
)