pdf2md/README.md
Dieter Schlüter 5cd5e5d69b --split seiten, --epub-per-file, Nachspann, --from-md --split, --split-chars
- --split seiten mit --pages-per-file N (Standard 20, Ausrichtung an
  Überschriften bis eine Seite neben der Grenze) oder --page-ranges
  (streng; Seiten außerhalb werden eigene Dateien)
- Schnitt nur im fertigen Gesamt-Markdown (lückenlos): vor dem ersten
  Absatz, der auf der Grenzseite beginnt; Überschriften vor dem Schnitt
  wandern in die neue Datei; source_pages aus Anfang/Ende jedes Absatzes
- Seitenzuordnung über die Rohtexte je Seite (pymupdf4llm page_chunks=True,
  aneinandergehängt byte-gleich; Band 1/2 unverändert), auch für OCR-Seiten
- --epub-per-file: ein EPUB je Einzeldatei in <name>/epub/
- Nachspann: Anhang/Autor/Impressum nach dem letzten Kapitel als eigene Datei
- --from-md DATEI --split teile|kapitel: bearbeitetes Markdown nachträglich
  teilen, Bild- und Cover-Pfade angepasst
- --split-chars N; Längen-Abschnitte tragen die Überschrift am Anfang als Titel
- Tests (tests/test_split_pages.py), README, Bedienungsanleitung, CLAUDE.md, TODO

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 01:04:50 +02:00

175 lines
12 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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](#grenzen)).
## 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`. Für Fraktur-Scans das
Modell `deu_latf` aus [tessdata_best](https://github.com/tesseract-ocr/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](BEDIENUNGSANLEITUNG.md)**.
```bash
# 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) |
| `--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
```bash
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 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).
- 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](TODO.md).
- Bilder werden als JPEG (Qualität 85) gespeichert; EPUB Band 1 ca. 3 MB.
## Dateien
- `pdf2md.py` – aktuelle Version
- `tests/` – pytest-Suite