2026-10-02 18:35:04 +02:00
|
|
|
|
# pdf2md.py – PDF → Markdown → EPUB
|
2026-10-01 23:46:54 +02:00
|
|
|
|
|
2026-10-02 14:24:58 +02:00
|
|
|
|
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 und
|
|
|
|
|
|
einem englischen Scan von 1893 (Ergebnisse siehe [Grenzen](#grenzen)).
|
2026-10-01 23:46:54 +02:00
|
|
|
|
|
pdf2md v5: gescannte und digitale PDFs, EPUB-Export, Tests
- Seitenklassifizierung (Text / Scan / OCR-Ebene / leer) mit OCR-Plan pro Seite:
Scans per Tesseract, gerade OCR-Ebenen übernehmen, schiefe neu erkennen,
Foto-/Coverseiten nur nach Probe-OCR; --ocr auto|always|never, --jobs
- Nachbearbeitung: Satzende-Erkennung mit « “ ‹, Silbentrennung, Überschriften-
ebenen aus PDF-Lesezeichen, Rückstufung von Satzresten, Inhaltsverzeichnis
trennen, Seitenzahlen/Kolumnentitel/Bildtext entfernen
- Patches für pymupdf4llm: Bindestrich-Komposita am Zeilenende, Absätze per
Einzug in Scan-Blöcken
- --epub über pandoc (Metadaten, Cover, Kapitelaufteilung)
- pytest-Suite mit synthetischen Test-PDFs (79 Tests)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 00:33:21 +02:00
|
|
|
|
## Installation (Ubuntu 24.04)
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
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`.
|
|
|
|
|
|
|
|
|
|
|
|
## Verwendung
|
|
|
|
|
|
|
2026-10-02 01:23:36 +02:00
|
|
|
|
Ausführliche Schritt-für-Schritt-Anleitung, auch für den Weg „Markdown von Hand bearbeiten,
|
|
|
|
|
|
dann EPUB“: **[BEDIENUNGSANLEITUNG.md](BEDIENUNGSANLEITUNG.md)**.
|
|
|
|
|
|
|
pdf2md v5: gescannte und digitale PDFs, EPUB-Export, Tests
- Seitenklassifizierung (Text / Scan / OCR-Ebene / leer) mit OCR-Plan pro Seite:
Scans per Tesseract, gerade OCR-Ebenen übernehmen, schiefe neu erkennen,
Foto-/Coverseiten nur nach Probe-OCR; --ocr auto|always|never, --jobs
- Nachbearbeitung: Satzende-Erkennung mit « “ ‹, Silbentrennung, Überschriften-
ebenen aus PDF-Lesezeichen, Rückstufung von Satzresten, Inhaltsverzeichnis
trennen, Seitenzahlen/Kolumnentitel/Bildtext entfernen
- Patches für pymupdf4llm: Bindestrich-Komposita am Zeilenende, Absätze per
Einzug in Scan-Blöcken
- --epub über pandoc (Metadaten, Cover, Kapitelaufteilung)
- pytest-Suite mit synthetischen Test-PDFs (79 Tests)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 00:33:21 +02:00
|
|
|
|
```bash
|
|
|
|
|
|
# digitales oder gescanntes PDF -> Markdown (OCR wird automatisch entschieden)
|
2026-10-02 13:12:36 +02:00
|
|
|
|
python3 pdf2md.py buch.pdf buch.md
|
pdf2md v5: gescannte und digitale PDFs, EPUB-Export, Tests
- Seitenklassifizierung (Text / Scan / OCR-Ebene / leer) mit OCR-Plan pro Seite:
Scans per Tesseract, gerade OCR-Ebenen übernehmen, schiefe neu erkennen,
Foto-/Coverseiten nur nach Probe-OCR; --ocr auto|always|never, --jobs
- Nachbearbeitung: Satzende-Erkennung mit « “ ‹, Silbentrennung, Überschriften-
ebenen aus PDF-Lesezeichen, Rückstufung von Satzresten, Inhaltsverzeichnis
trennen, Seitenzahlen/Kolumnentitel/Bildtext entfernen
- Patches für pymupdf4llm: Bindestrich-Komposita am Zeilenende, Absätze per
Einzug in Scan-Blöcken
- --epub über pandoc (Metadaten, Cover, Kapitelaufteilung)
- pytest-Suite mit synthetischen Test-PDFs (79 Tests)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 00:33:21 +02:00
|
|
|
|
|
2026-10-02 15:20:08 +02:00
|
|
|
|
# zusätzlich Bilder und EPUB (Titel/Autor aus den PDF-Metadaten bzw. dem Dateinamen
|
|
|
|
|
|
# "Autor_-_Titel.pdf", Seite 1 als Cover)
|
2026-10-02 13:12:36 +02:00
|
|
|
|
python3 pdf2md.py --epub buch.pdf buch.md
|
pdf2md v5: gescannte und digitale PDFs, EPUB-Export, Tests
- Seitenklassifizierung (Text / Scan / OCR-Ebene / leer) mit OCR-Plan pro Seite:
Scans per Tesseract, gerade OCR-Ebenen übernehmen, schiefe neu erkennen,
Foto-/Coverseiten nur nach Probe-OCR; --ocr auto|always|never, --jobs
- Nachbearbeitung: Satzende-Erkennung mit « “ ‹, Silbentrennung, Überschriften-
ebenen aus PDF-Lesezeichen, Rückstufung von Satzresten, Inhaltsverzeichnis
trennen, Seitenzahlen/Kolumnentitel/Bildtext entfernen
- Patches für pymupdf4llm: Bindestrich-Komposita am Zeilenende, Absätze per
Einzug in Scan-Blöcken
- --epub über pandoc (Metadaten, Cover, Kapitelaufteilung)
- pytest-Suite mit synthetischen Test-PDFs (79 Tests)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 00:33:21 +02:00
|
|
|
|
|
|
|
|
|
|
# Scan in Deutsch und Englisch, 8 parallele OCR-Prozesse
|
2026-10-02 13:12:36 +02:00
|
|
|
|
python3 pdf2md.py --ocr-language deu+eng --jobs 8 scan.pdf scan.md
|
2026-10-02 14:24:58 +02:00
|
|
|
|
|
--split, --from-md, --jpeg-quality, YAML-Kopf, --title/--author
- --split teile|kapitel: Einzeldateien in einem Ordner mit index.md
(Reihenfolge) und images/; Titelei und Inhaltsverzeichnis als Vorspann;
YAML-Kopf je Datei (title, part, chapter, order, book, author)
- --from-md DATEI|ORDNER: EPUB aus bearbeitetem Markdown bzw. Split-Ordner,
Angaben aus dem YAML-Kopf; ersetzt den pandoc-Befehl aus Weg B
- Markdown beginnt mit YAML-Kopf (title, author, lang, cover); Cover wird
mit --images immer abgelegt
- --jpeg-quality (Standard 85 statt fest 95): EPUB Band 1 3 MB statt 5 MB
- --title/--author überschreiben PDF-Metadaten
- Tests (tests/test_split.py), README, Bedienungsanleitung (Weg C),
CLAUDE.md, TODO.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:04:57 +02:00
|
|
|
|
# Kapitel als Einzeldateien (Ordner buch/ mit index.md), dazu EPUB
|
|
|
|
|
|
python3 pdf2md.py --epub --split kapitel buch.pdf buch.md
|
|
|
|
|
|
|
|
|
|
|
|
# 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/
|
|
|
|
|
|
|
2026-10-02 14:24:58 +02:00
|
|
|
|
# englisches Buch; alter Scan mit schlechter OCR-Ebene neu erkennen
|
2026-10-02 15:20:08 +02:00
|
|
|
|
python3 pdf2md.py --epub --ocr-language eng --ocr always scan.pdf scan.md # EPUB-Sprache: en
|
pdf2md v5: gescannte und digitale PDFs, EPUB-Export, Tests
- Seitenklassifizierung (Text / Scan / OCR-Ebene / leer) mit OCR-Plan pro Seite:
Scans per Tesseract, gerade OCR-Ebenen übernehmen, schiefe neu erkennen,
Foto-/Coverseiten nur nach Probe-OCR; --ocr auto|always|never, --jobs
- Nachbearbeitung: Satzende-Erkennung mit « “ ‹, Silbentrennung, Überschriften-
ebenen aus PDF-Lesezeichen, Rückstufung von Satzresten, Inhaltsverzeichnis
trennen, Seitenzahlen/Kolumnentitel/Bildtext entfernen
- Patches für pymupdf4llm: Bindestrich-Komposita am Zeilenende, Absätze per
Einzug in Scan-Blöcken
- --epub über pandoc (Metadaten, Cover, Kapitelaufteilung)
- pytest-Suite mit synthetischen Test-PDFs (79 Tests)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 00:33:21 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
| 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) |
|
2026-10-02 15:20:08 +02:00
|
|
|
|
| `--jobs N` | parallele OCR-Prozesse (Standard: halbe Kernzahl, höchstens 8); reine Textdokumente laufen immer sequenziell |
|
2026-10-02 00:47:42 +02:00
|
|
|
|
| `--images` | Grafiken in `<name>_images/` speichern und verlinken |
|
|
|
|
|
|
| `--image-format jpg\|png` | Bildformat (Standard jpg; png verlustfrei, aber ca. 3× größer) |
|
--split, --from-md, --jpeg-quality, YAML-Kopf, --title/--author
- --split teile|kapitel: Einzeldateien in einem Ordner mit index.md
(Reihenfolge) und images/; Titelei und Inhaltsverzeichnis als Vorspann;
YAML-Kopf je Datei (title, part, chapter, order, book, author)
- --from-md DATEI|ORDNER: EPUB aus bearbeitetem Markdown bzw. Split-Ordner,
Angaben aus dem YAML-Kopf; ersetzt den pandoc-Befehl aus Weg B
- Markdown beginnt mit YAML-Kopf (title, author, lang, cover); Cover wird
mit --images immer abgelegt
- --jpeg-quality (Standard 85 statt fest 95): EPUB Band 1 3 MB statt 5 MB
- --title/--author überschreiben PDF-Metadaten
- Tests (tests/test_split.py), README, Bedienungsanleitung (Weg C),
CLAUDE.md, TODO.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:04:57 +02:00
|
|
|
|
| `--jpeg-quality 1-100` | JPEG-Qualität (Standard 85; 95 fast verlustfrei, Bilder ca. 1,7× größer) |
|
pdf2md v5: gescannte und digitale PDFs, EPUB-Export, Tests
- Seitenklassifizierung (Text / Scan / OCR-Ebene / leer) mit OCR-Plan pro Seite:
Scans per Tesseract, gerade OCR-Ebenen übernehmen, schiefe neu erkennen,
Foto-/Coverseiten nur nach Probe-OCR; --ocr auto|always|never, --jobs
- Nachbearbeitung: Satzende-Erkennung mit « “ ‹, Silbentrennung, Überschriften-
ebenen aus PDF-Lesezeichen, Rückstufung von Satzresten, Inhaltsverzeichnis
trennen, Seitenzahlen/Kolumnentitel/Bildtext entfernen
- Patches für pymupdf4llm: Bindestrich-Komposita am Zeilenende, Absätze per
Einzug in Scan-Blöcken
- --epub über pandoc (Metadaten, Cover, Kapitelaufteilung)
- pytest-Suite mit synthetischen Test-PDFs (79 Tests)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 00:33:21 +02:00
|
|
|
|
| `--epub`, `--epub-file PFAD` | EPUB 3 mit pandoc erzeugen (schaltet `--images` ein) |
|
--split, --from-md, --jpeg-quality, YAML-Kopf, --title/--author
- --split teile|kapitel: Einzeldateien in einem Ordner mit index.md
(Reihenfolge) und images/; Titelei und Inhaltsverzeichnis als Vorspann;
YAML-Kopf je Datei (title, part, chapter, order, book, author)
- --from-md DATEI|ORDNER: EPUB aus bearbeitetem Markdown bzw. Split-Ordner,
Angaben aus dem YAML-Kopf; ersetzt den pandoc-Befehl aus Weg B
- Markdown beginnt mit YAML-Kopf (title, author, lang, cover); Cover wird
mit --images immer abgelegt
- --jpeg-quality (Standard 85 statt fest 95): EPUB Band 1 3 MB statt 5 MB
- --title/--author überschreiben PDF-Metadaten
- Tests (tests/test_split.py), README, Bedienungsanleitung (Weg C),
CLAUDE.md, TODO.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:04:57 +02:00
|
|
|
|
| `--split teile\|kapitel` | Teile bzw. Kapitel als Einzeldateien in einem Ordner `<name>/` mit `index.md` (Reihenfolge) und `images/` |
|
|
|
|
|
|
| `--from-md PFAD` | nur EPUB erzeugen – aus einer (bearbeiteten) `.md` oder einem `--split`-Ordner |
|
|
|
|
|
|
| `--title`, `--author` | Titel/Autor im EPUB vorgeben (statt PDF-Metadaten bzw. YAML-Kopf) |
|
2026-10-02 15:20:08 +02:00
|
|
|
|
| `--lang` | Sprache im EPUB (Standard: aus `--ocr-language`, `deu`→`de`, `eng`→`en`) |
|
pdf2md v5: gescannte und digitale PDFs, EPUB-Export, Tests
- Seitenklassifizierung (Text / Scan / OCR-Ebene / leer) mit OCR-Plan pro Seite:
Scans per Tesseract, gerade OCR-Ebenen übernehmen, schiefe neu erkennen,
Foto-/Coverseiten nur nach Probe-OCR; --ocr auto|always|never, --jobs
- Nachbearbeitung: Satzende-Erkennung mit « “ ‹, Silbentrennung, Überschriften-
ebenen aus PDF-Lesezeichen, Rückstufung von Satzresten, Inhaltsverzeichnis
trennen, Seitenzahlen/Kolumnentitel/Bildtext entfernen
- Patches für pymupdf4llm: Bindestrich-Komposita am Zeilenende, Absätze per
Einzug in Scan-Blöcken
- --epub über pandoc (Metadaten, Cover, Kapitelaufteilung)
- pytest-Suite mit synthetischen Test-PDFs (79 Tests)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 00:33:21 +02:00
|
|
|
|
| `--keep-headers`, `--keep-footers` | Kolumnentitel bzw. Seitenzahlen nicht entfernen |
|
|
|
|
|
|
|
|
|
|
|
|
## Was die Nachbearbeitung macht
|
|
|
|
|
|
|
Zeilengetreue Übernahme von Code, Registern, Versen und Adressen
Ein Zeilenumbruch gilt als weich (vom Satzprogramm), wenn das erste Wort
der nächsten Zeile nicht mehr gepasst hätte (Toleranz 5 % der Spalte);
sonst als hart, und die Zeile bleibt samt Einrückung erhalten.
- lines_to_md (Patch von text_to_md): harte/weiche Umbrüche aus der
Geometrie; Blöcke mit überwiegend harten Umbrüchen zeilengetreu als
Codeblock (auch Proportionalschrift, Einrückung rekonstruiert) oder als
Zeilen mit "\" und geschützten Leerzeichen
- Blockgrenzen: freie Zeichen der letzten Zeile werden mitgegeben,
normalize_markdown prüft damit das erste Wort des nächsten Blocks
- normalize_markdown: harte Umbrüche, Absatzenden ohne Satzzeichen,
Codeblöcke über Seitengrenzen zusammenführen, wörtliches ``` maskieren
- EPUB-CSS: lange Codezeilen umbrechen (white-space: pre-wrap)
- Tests (tests/test_verbatim.py), README, Bedienungsanleitung, CLAUDE.md,
TODO.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:39:06 +02:00
|
|
|
|
- **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,
|
Zahl-Überschriften entfernen, Code-Nachbarzeilen in Codeblöcke
- "## 90", "## xxiv" (Fußnotenzeichen, römische Seitenzahlen ii–xcix)
wie Seitenzahlen entfernen, außer direkt vor einer Titel-Überschrift
oder in der PDF-Gliederung; "## IV." und Register-Buchstaben bleiben
- _mixed_block: zeilengetreue Blöcke zeilenweise prüfen; Konfiguration
([dependencies], name = "wert"), Befehle (cargo build, $ …) und
Programmausgaben (… ok, running n tests) als Codeblock, Anleitungen
als Text; kurze umbrochene Codezeilen erkennen
- Registereinträge ("match(), 247") nie als Code
- _raw_text: Leerzeichen bei Wortabstand ohne Leerzeichen-Zeichen
- Tests, README, Bedienungsanleitung, TODO
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:13:42 +02:00
|
|
|
|
als ```-Block mit Einrückung; ebenso Konfigurationszeilen wie `[dependencies]`, Befehle wie
|
|
|
|
|
|
`cargo build` und Programmausgaben), **Register, Glossare, Verse, Adressen, Lebensläufe** zeilengetreu
|
Zeilengetreue Übernahme von Code, Registern, Versen und Adressen
Ein Zeilenumbruch gilt als weich (vom Satzprogramm), wenn das erste Wort
der nächsten Zeile nicht mehr gepasst hätte (Toleranz 5 % der Spalte);
sonst als hart, und die Zeile bleibt samt Einrückung erhalten.
- lines_to_md (Patch von text_to_md): harte/weiche Umbrüche aus der
Geometrie; Blöcke mit überwiegend harten Umbrüchen zeilengetreu als
Codeblock (auch Proportionalschrift, Einrückung rekonstruiert) oder als
Zeilen mit "\" und geschützten Leerzeichen
- Blockgrenzen: freie Zeichen der letzten Zeile werden mitgegeben,
normalize_markdown prüft damit das erste Wort des nächsten Blocks
- normalize_markdown: harte Umbrüche, Absatzenden ohne Satzzeichen,
Codeblöcke über Seitengrenzen zusammenführen, wörtliches ``` maskieren
- EPUB-CSS: lange Codezeilen umbrechen (white-space: pre-wrap)
- Tests (tests/test_verbatim.py), README, Bedienungsanleitung, CLAUDE.md,
TODO.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:39:06 +02:00
|
|
|
|
(Markdown: `\` am Zeilenende, Einrückung als geschütztes Leerzeichen). Codeblöcke über
|
|
|
|
|
|
eine Seitengrenze werden zusammengeführt; lange Codezeilen bricht der Reader um.
|
Markdown-Sonderzeichen maskieren, Verszeilen mit Satzende erhalten
- md_escape im Span-Patch: \ ` * [ ] < > $ ~ ^ und "_" an Wortgrenzen
werden maskiert (__add__ nicht mehr fett, <p> nicht mehr HTML); Code
bleibt wörtlich
- Folgeschritte maskierungsfest: norm_title, clean_inline, link_urls
(Positionstabelle inkl. Backslash), link_anchors, --split-Titel
- Kurze Zeile mit Satzende ist nur bei eingerückter Folgezeile ein
Absatzende, sonst eine Verszeile; Einrückung gegen den Spaltenrand der
Seite gemessen (trennt auch verklebte einzeilige Dialogabsätze)
- Tests, README, Bedienungsanleitung, TODO
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 19:28:56 +02:00
|
|
|
|
- **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.
|
Zeilengetreue Übernahme von Code, Registern, Versen und Adressen
Ein Zeilenumbruch gilt als weich (vom Satzprogramm), wenn das erste Wort
der nächsten Zeile nicht mehr gepasst hätte (Toleranz 5 % der Spalte);
sonst als hart, und die Zeile bleibt samt Einrückung erhalten.
- lines_to_md (Patch von text_to_md): harte/weiche Umbrüche aus der
Geometrie; Blöcke mit überwiegend harten Umbrüchen zeilengetreu als
Codeblock (auch Proportionalschrift, Einrückung rekonstruiert) oder als
Zeilen mit "\" und geschützten Leerzeichen
- Blockgrenzen: freie Zeichen der letzten Zeile werden mitgegeben,
normalize_markdown prüft damit das erste Wort des nächsten Blocks
- normalize_markdown: harte Umbrüche, Absatzenden ohne Satzzeichen,
Codeblöcke über Seitengrenzen zusammenführen, wörtliches ``` maskieren
- EPUB-CSS: lange Codezeilen umbrechen (white-space: pre-wrap)
- Tests (tests/test_verbatim.py), README, Bedienungsanleitung, CLAUDE.md,
TODO.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:39:06 +02:00
|
|
|
|
- **Absätze:** eine Absatzgrenze gilt nach Satzende (`.` `!` `?` `…`, auch mit `«` `“` `»`
|
|
|
|
|
|
usw. dahinter) oder an einem harten Umbruch; auf Scans werden Absätze am Einzug erkannt.
|
pdf2md v5: gescannte und digitale PDFs, EPUB-Export, Tests
- Seitenklassifizierung (Text / Scan / OCR-Ebene / leer) mit OCR-Plan pro Seite:
Scans per Tesseract, gerade OCR-Ebenen übernehmen, schiefe neu erkennen,
Foto-/Coverseiten nur nach Probe-OCR; --ocr auto|always|never, --jobs
- Nachbearbeitung: Satzende-Erkennung mit « “ ‹, Silbentrennung, Überschriften-
ebenen aus PDF-Lesezeichen, Rückstufung von Satzresten, Inhaltsverzeichnis
trennen, Seitenzahlen/Kolumnentitel/Bildtext entfernen
- Patches für pymupdf4llm: Bindestrich-Komposita am Zeilenende, Absätze per
Einzug in Scan-Blöcken
- --epub über pandoc (Metadaten, Cover, Kapitelaufteilung)
- pytest-Suite mit synthetischen Test-PDFs (79 Tests)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 00:33:21 +02:00
|
|
|
|
- **Silbentrennung:** `Geheim-|dienst` → `Geheimdienst`, `Militär-|Geheimdienst` bleibt mit
|
2026-10-02 14:24:58 +02:00
|
|
|
|
Bindestrich, `Arbeits- und` bleibt. Weiche Trennstriche (U+00AD) und `¬` am Zeilenende
|
|
|
|
|
|
werden ebenfalls aufgelöst (`wach|senden` → `wachsenden`).
|
pdf2md v5: gescannte und digitale PDFs, EPUB-Export, Tests
- Seitenklassifizierung (Text / Scan / OCR-Ebene / leer) mit OCR-Plan pro Seite:
Scans per Tesseract, gerade OCR-Ebenen übernehmen, schiefe neu erkennen,
Foto-/Coverseiten nur nach Probe-OCR; --ocr auto|always|never, --jobs
- Nachbearbeitung: Satzende-Erkennung mit « “ ‹, Silbentrennung, Überschriften-
ebenen aus PDF-Lesezeichen, Rückstufung von Satzresten, Inhaltsverzeichnis
trennen, Seitenzahlen/Kolumnentitel/Bildtext entfernen
- Patches für pymupdf4llm: Bindestrich-Komposita am Zeilenende, Absätze per
Einzug in Scan-Blöcken
- --epub über pandoc (Metadaten, Cover, Kapitelaufteilung)
- pytest-Suite mit synthetischen Test-PDFs (79 Tests)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 00:33:21 +02:00
|
|
|
|
- **Überschriften:** Ebenen aus den PDF-Lesezeichen (Teile `##`, Kapitel `###`);
|
|
|
|
|
|
Satzreste, die fälschlich als Überschrift erkannt wurden, kommen zurück in den Text.
|
2026-10-02 17:32:20 +02:00
|
|
|
|
- **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.
|
2026-10-02 14:24:58 +02:00
|
|
|
|
- **OCR-Ebenen** in Courier werden nicht als Programmcode (`` `…` ``) gesetzt.
|
2026-10-02 15:07:47 +02:00
|
|
|
|
- **Links:** URLs werden als `<https://…>` geschrieben und sind damit in Markdown-Viewern
|
2026-10-02 20:35:01 +02:00
|
|
|
|
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.
|
Zahl-Überschriften entfernen, Code-Nachbarzeilen in Codeblöcke
- "## 90", "## xxiv" (Fußnotenzeichen, römische Seitenzahlen ii–xcix)
wie Seitenzahlen entfernen, außer direkt vor einer Titel-Überschrift
oder in der PDF-Gliederung; "## IV." und Register-Buchstaben bleiben
- _mixed_block: zeilengetreue Blöcke zeilenweise prüfen; Konfiguration
([dependencies], name = "wert"), Befehle (cargo build, $ …) und
Programmausgaben (… ok, running n tests) als Codeblock, Anleitungen
als Text; kurze umbrochene Codezeilen erkennen
- Registereinträge ("match(), 247") nie als Code
- _raw_text: Leerzeichen bei Wortabstand ohne Leerzeichen-Zeichen
- Tests, README, Bedienungsanleitung, TODO
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:13:42 +02:00
|
|
|
|
- **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 maskieren, Verszeilen mit Satzende erhalten
- md_escape im Span-Patch: \ ` * [ ] < > $ ~ ^ und "_" an Wortgrenzen
werden maskiert (__add__ nicht mehr fett, <p> nicht mehr HTML); Code
bleibt wörtlich
- Folgeschritte maskierungsfest: norm_title, clean_inline, link_urls
(Positionstabelle inkl. Backslash), link_anchors, --split-Titel
- Kurze Zeile mit Satzende ist nur bei eingerückter Folgezeile ein
Absatzende, sonst eine Verszeile; Einrückung gegen den Spaltenrand der
Seite gemessen (trennt auch verklebte einzeilige Dialogabsätze)
- Tests, README, Bedienungsanleitung, TODO
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 19:28:56 +02:00
|
|
|
|
- **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.
|
--split, --from-md, --jpeg-quality, YAML-Kopf, --title/--author
- --split teile|kapitel: Einzeldateien in einem Ordner mit index.md
(Reihenfolge) und images/; Titelei und Inhaltsverzeichnis als Vorspann;
YAML-Kopf je Datei (title, part, chapter, order, book, author)
- --from-md DATEI|ORDNER: EPUB aus bearbeitetem Markdown bzw. Split-Ordner,
Angaben aus dem YAML-Kopf; ersetzt den pandoc-Befehl aus Weg B
- Markdown beginnt mit YAML-Kopf (title, author, lang, cover); Cover wird
mit --images immer abgelegt
- --jpeg-quality (Standard 85 statt fest 95): EPUB Band 1 3 MB statt 5 MB
- --title/--author überschreiben PDF-Metadaten
- Tests (tests/test_split.py), README, Bedienungsanleitung (Weg C),
CLAUDE.md, TODO.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:04:57 +02:00
|
|
|
|
- **YAML-Kopf:** Jede Markdown-Datei beginnt mit `title`, `author`, `lang`, `cover`
|
2026-10-02 19:03:51 +02:00
|
|
|
|
(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“ …
|
pdf2md v5: gescannte und digitale PDFs, EPUB-Export, Tests
- Seitenklassifizierung (Text / Scan / OCR-Ebene / leer) mit OCR-Plan pro Seite:
Scans per Tesseract, gerade OCR-Ebenen übernehmen, schiefe neu erkennen,
Foto-/Coverseiten nur nach Probe-OCR; --ocr auto|always|never, --jobs
- Nachbearbeitung: Satzende-Erkennung mit « “ ‹, Silbentrennung, Überschriften-
ebenen aus PDF-Lesezeichen, Rückstufung von Satzresten, Inhaltsverzeichnis
trennen, Seitenzahlen/Kolumnentitel/Bildtext entfernen
- Patches für pymupdf4llm: Bindestrich-Komposita am Zeilenende, Absätze per
Einzug in Scan-Blöcken
- --epub über pandoc (Metadaten, Cover, Kapitelaufteilung)
- pytest-Suite mit synthetischen Test-PDFs (79 Tests)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 00:33:21 +02:00
|
|
|
|
|
|
|
|
|
|
## Tests
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
--split, --from-md, --jpeg-quality, YAML-Kopf, --title/--author
- --split teile|kapitel: Einzeldateien in einem Ordner mit index.md
(Reihenfolge) und images/; Titelei und Inhaltsverzeichnis als Vorspann;
YAML-Kopf je Datei (title, part, chapter, order, book, author)
- --from-md DATEI|ORDNER: EPUB aus bearbeitetem Markdown bzw. Split-Ordner,
Angaben aus dem YAML-Kopf; ersetzt den pandoc-Befehl aus Weg B
- Markdown beginnt mit YAML-Kopf (title, author, lang, cover); Cover wird
mit --images immer abgelegt
- --jpeg-quality (Standard 85 statt fest 95): EPUB Band 1 3 MB statt 5 MB
- --title/--author überschreiben PDF-Metadaten
- Tests (tests/test_split.py), README, Bedienungsanleitung (Weg C),
CLAUDE.md, TODO.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:04:57 +02:00
|
|
|
|
python3 -m pytest # ca. 45 s; OCR-/EPUB-Tests werden ohne Tesseract/pandoc übersprungen
|
pdf2md v5: gescannte und digitale PDFs, EPUB-Export, Tests
- Seitenklassifizierung (Text / Scan / OCR-Ebene / leer) mit OCR-Plan pro Seite:
Scans per Tesseract, gerade OCR-Ebenen übernehmen, schiefe neu erkennen,
Foto-/Coverseiten nur nach Probe-OCR; --ocr auto|always|never, --jobs
- Nachbearbeitung: Satzende-Erkennung mit « “ ‹, Silbentrennung, Überschriften-
ebenen aus PDF-Lesezeichen, Rückstufung von Satzresten, Inhaltsverzeichnis
trennen, Seitenzahlen/Kolumnentitel/Bildtext entfernen
- Patches für pymupdf4llm: Bindestrich-Komposita am Zeilenende, Absätze per
Einzug in Scan-Blöcken
- --epub über pandoc (Metadaten, Cover, Kapitelaufteilung)
- pytest-Suite mit synthetischen Test-PDFs (79 Tests)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 00:33:21 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Die Test-PDFs (digital, gescannt, mit OCR-Ebene, gemischt, Fotoseite) werden zur Laufzeit
|
|
|
|
|
|
synthetisch erzeugt.
|
|
|
|
|
|
|
|
|
|
|
|
## Grenzen
|
|
|
|
|
|
|
2026-10-02 14:24:58 +02:00
|
|
|
|
- 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.
|
|
|
|
|
|
- **Mathematische Formeln** werden als Bilder übernommen; für Mathebücher ist ein anderes
|
|
|
|
|
|
Werkzeug besser geeignet (z. B. Marker, Nougat).
|
|
|
|
|
|
- **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).
|
2026-10-02 20:35:01 +02:00
|
|
|
|
- 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.
|
Markdown-Sonderzeichen maskieren, Verszeilen mit Satzende erhalten
- md_escape im Span-Patch: \ ` * [ ] < > $ ~ ^ und "_" an Wortgrenzen
werden maskiert (__add__ nicht mehr fett, <p> nicht mehr HTML); Code
bleibt wörtlich
- Folgeschritte maskierungsfest: norm_title, clean_inline, link_urls
(Positionstabelle inkl. Backslash), link_anchors, --split-Titel
- Kurze Zeile mit Satzende ist nur bei eingerückter Folgezeile ein
Absatzende, sonst eine Verszeile; Einrückung gegen den Spaltenrand der
Seite gemessen (trennt auch verklebte einzeilige Dialogabsätze)
- Tests, README, Bedienungsanleitung, TODO
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 19:28:56 +02:00
|
|
|
|
- Zeilengetreue Erkennung: Ist ein Gedicht genauso eingerückt wie Absatzanfänge, werden
|
Zahl-Überschriften entfernen, Code-Nachbarzeilen in Codeblöcke
- "## 90", "## xxiv" (Fußnotenzeichen, römische Seitenzahlen ii–xcix)
wie Seitenzahlen entfernen, außer direkt vor einer Titel-Überschrift
oder in der PDF-Gliederung; "## IV." und Register-Buchstaben bleiben
- _mixed_block: zeilengetreue Blöcke zeilenweise prüfen; Konfiguration
([dependencies], name = "wert"), Befehle (cargo build, $ …) und
Programmausgaben (… ok, running n tests) als Codeblock, Anleitungen
als Text; kurze umbrochene Codezeilen erkennen
- Registereinträge ("match(), 247") nie als Code
- _raw_text: Leerzeichen bei Wortabstand ohne Leerzeichen-Zeichen
- Tests, README, Bedienungsanleitung, TODO
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:13:42 +02:00
|
|
|
|
Verszeilen mit Satzzeichen am Ende zu eigenen Absätzen. Listings mit eingedruckten
|
|
|
|
|
|
Zeilennummern werden nicht immer als ein Codeblock erkannt.
|
2026-10-02 14:24:58 +02:00
|
|
|
|
Weitere bekannte Schwächen: [TODO.md](TODO.md).
|
--split, --from-md, --jpeg-quality, YAML-Kopf, --title/--author
- --split teile|kapitel: Einzeldateien in einem Ordner mit index.md
(Reihenfolge) und images/; Titelei und Inhaltsverzeichnis als Vorspann;
YAML-Kopf je Datei (title, part, chapter, order, book, author)
- --from-md DATEI|ORDNER: EPUB aus bearbeitetem Markdown bzw. Split-Ordner,
Angaben aus dem YAML-Kopf; ersetzt den pandoc-Befehl aus Weg B
- Markdown beginnt mit YAML-Kopf (title, author, lang, cover); Cover wird
mit --images immer abgelegt
- --jpeg-quality (Standard 85 statt fest 95): EPUB Band 1 3 MB statt 5 MB
- --title/--author überschreiben PDF-Metadaten
- Tests (tests/test_split.py), README, Bedienungsanleitung (Weg C),
CLAUDE.md, TODO.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:04:57 +02:00
|
|
|
|
- Bilder werden als JPEG (Qualität 85) gespeichert; EPUB Band 1 ca. 3 MB.
|
pdf2md v5: gescannte und digitale PDFs, EPUB-Export, Tests
- Seitenklassifizierung (Text / Scan / OCR-Ebene / leer) mit OCR-Plan pro Seite:
Scans per Tesseract, gerade OCR-Ebenen übernehmen, schiefe neu erkennen,
Foto-/Coverseiten nur nach Probe-OCR; --ocr auto|always|never, --jobs
- Nachbearbeitung: Satzende-Erkennung mit « “ ‹, Silbentrennung, Überschriften-
ebenen aus PDF-Lesezeichen, Rückstufung von Satzresten, Inhaltsverzeichnis
trennen, Seitenzahlen/Kolumnentitel/Bildtext entfernen
- Patches für pymupdf4llm: Bindestrich-Komposita am Zeilenende, Absätze per
Einzug in Scan-Blöcken
- --epub über pandoc (Metadaten, Cover, Kapitelaufteilung)
- pytest-Suite mit synthetischen Test-PDFs (79 Tests)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 00:33:21 +02:00
|
|
|
|
|
|
|
|
|
|
## Dateien
|
|
|
|
|
|
|
2026-10-02 13:12:36 +02:00
|
|
|
|
- `pdf2md.py` – aktuelle Version
|
pdf2md v5: gescannte und digitale PDFs, EPUB-Export, Tests
- Seitenklassifizierung (Text / Scan / OCR-Ebene / leer) mit OCR-Plan pro Seite:
Scans per Tesseract, gerade OCR-Ebenen übernehmen, schiefe neu erkennen,
Foto-/Coverseiten nur nach Probe-OCR; --ocr auto|always|never, --jobs
- Nachbearbeitung: Satzende-Erkennung mit « “ ‹, Silbentrennung, Überschriften-
ebenen aus PDF-Lesezeichen, Rückstufung von Satzresten, Inhaltsverzeichnis
trennen, Seitenzahlen/Kolumnentitel/Bildtext entfernen
- Patches für pymupdf4llm: Bindestrich-Komposita am Zeilenende, Absätze per
Einzug in Scan-Blöcken
- --epub über pandoc (Metadaten, Cover, Kapitelaufteilung)
- pytest-Suite mit synthetischen Test-PDFs (79 Tests)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 00:33:21 +02:00
|
|
|
|
- `tests/` – pytest-Suite
|