# ljutsch – 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 und einem englischen Scan von 1893 (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`. ## 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 # 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 `_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` | Teile bzw. Kapitel als Einzeldateien in einem Ordner `/` 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) | | `--lang` | Sprache im EPUB (Standard: aus `--ocr-language`, `deu`→`de`, `eng`→`en`) | | `--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), **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. - **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 `` geschrieben und sind damit in Markdown-Viewern und im EPUB anklickbar. Beim Zeilenumbruch zerbrochene URLs (`deutsche-|sehen`, `…/ youtube-…`) werden mit den Linkzielen der PDF abgeglichen und repariert; Tracking-Parameter (`utm_…`) entfallen. Ohne Linkziel wird die URL aus dem gedruckten Text zusammengesetzt. - **Seitenzahlen, Kolumnentitel, Text in Fotos** werden entfernt. - **YAML-Kopf:** Jede Markdown-Datei beginnt mit `title`, `author`, `lang`, `cover` (Einzeldateien von `--split` mit `title`, `part`, `chapter`, `order`, `book`, `author`). `--from-md` liest daraus die EPUB-Angaben. ## Tests ```bash python3 -m pytest # ca. 45 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. - **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 anderem Text als der URL (z. B. „hier“) werden nicht übernommen. - Zeilengetreue Erkennung: Verszeilen mit Satzzeichen am Ende werden zu eigenen Absätzen statt zu Zeilen; einzelne Konfigurations- oder Ausgabezeilen neben Code (`[dependencies]`) stehen als Zeilen statt im Codeblock. - Reine Zahlen können als Überschrift erscheinen (Fußnotenzeichen, römische Seitenzahlen). 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