Bedienungsanleitung ergänzt, Bildpfade relativ zur Markdown-Datei
- BEDIENUNGSANLEITUNG.md: Installation, Weg A (PDF -> EPUB), Weg B (Markdown
bearbeiten, dann pandoc), Scans, Optionen, Prüfen, häufige Probleme;
Verweis in README.md
- Fix: pymupdf4llm schreibt Bildpfade relativ zum Arbeitsverzeichnis
("Output/x_images/…"), wenn der Bilderordner darunter liegt. Die Links
zeigen jetzt immer relativ zur Markdown-Datei; Regressionstests ergänzt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
9fc620ad55
commit
85a01ac5c7
5 changed files with 285 additions and 8 deletions
236
BEDIENUNGSANLEITUNG.md
Normal file
236
BEDIENUNGSANLEITUNG.md
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
# Bedienungsanleitung: pdf2md_v5.py – PDF → Markdown → EPUB
|
||||
|
||||
`pdf2md_v5.py` wandelt PDF-Bücher in sauberes Markdown um: echte Absätze, Überschriften mit
|
||||
richtiger Ebene, Bilder und ohne Seitenzahlen. Daraus entsteht auf Wunsch direkt ein EPUB für
|
||||
den PocketBook-Reader. Das funktioniert mit digitalen PDFs (Text lässt sich markieren) ebenso wie
|
||||
mit gescannten PDFs (nur Seitenbilder, der Text wird per OCR erkannt).
|
||||
|
||||
Es gibt zwei Arbeitswege:
|
||||
|
||||
- **Weg A:** PDF → EPUB in einem Schritt, wenn das Ergebnis ohne Nacharbeit passt.
|
||||
- **Weg B:** PDF → Markdown → von Hand bearbeiten → EPUB, wenn du Überschriften, Text oder
|
||||
Bilder noch korrigieren willst.
|
||||
|
||||
---
|
||||
|
||||
## 1. Installation und Prüfung
|
||||
|
||||
Einmalig unter Ubuntu 24.04:
|
||||
|
||||
```bash
|
||||
sudo apt install tesseract-ocr tesseract-ocr-deu pandoc
|
||||
cd ~/Downloads/ljutsch
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
- `tesseract-ocr` + `tesseract-ocr-deu` braucht man nur für gescannte PDFs.
|
||||
- `pandoc` braucht man nur für das EPUB.
|
||||
|
||||
Prüfen, ob alles da ist:
|
||||
|
||||
```bash
|
||||
tesseract --list-langs # muss "deu" enthalten
|
||||
pandoc --version # Version 3.x
|
||||
python3 pdf2md_v5.py --help # zeigt alle Optionen
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Weg A – PDF → EPUB in einem Schritt
|
||||
|
||||
```bash
|
||||
cd ~/Downloads/ljutsch
|
||||
python3 pdf2md_v5.py --epub Operation_Ljutsch_Band_1_-_Reinhard_Otto_Kranz.pdf Output/band1.md
|
||||
```
|
||||
|
||||
Das Programm meldet zuerst, was es in der PDF gefunden hat:
|
||||
|
||||
```
|
||||
706 Seiten: 705 Text, 1 Scan/Bild, 0 mit OCR-Ebene, 0 leer → 0 Seiten werden per OCR gelesen
|
||||
Hinweis: 1 Bildseite(n) ohne Text bleiben ohne OCR (Fotos/Cover oder --ocr never).
|
||||
[1/15] Seiten 1–50 (Text) fertig
|
||||
...
|
||||
```
|
||||
|
||||
| Begriff | Bedeutung |
|
||||
| :-- | :-- |
|
||||
| Text | Seiten mit echter Textebene, keine OCR nötig |
|
||||
| Scan/Bild | Seiten, die nur aus einem Bild bestehen (Scan, Foto, Cover) |
|
||||
| mit OCR-Ebene | Scans, die schon unsichtbaren Text enthalten (z. B. von `ocrmypdf`) |
|
||||
| „Hinweis: … Bildseite(n)“ | Bildseiten, die bewusst *nicht* per OCR gelesen werden, meist das Cover |
|
||||
|
||||
Danach liegen in `Output/`:
|
||||
|
||||
| Datei | Inhalt |
|
||||
| :-- | :-- |
|
||||
| `band1.md` | das Buch als Markdown |
|
||||
| `band1_images/` | Bilder als JPEG, dazu `cover.jpg` (Seite 1 der PDF) |
|
||||
| `band1.epub` | das fertige E-Book (Titel und Autor aus der PDF) |
|
||||
|
||||
Ein Band mit ca. 700 Seiten dauert etwa anderthalb Minuten.
|
||||
|
||||
---
|
||||
|
||||
## 3. Weg B – Markdown von Hand bearbeiten, dann EPUB
|
||||
|
||||
### Schritt 1: Markdown erzeugen
|
||||
|
||||
Wie bei Weg A, also **mit** `--epub`. So entstehen gleich das Cover (`cover.jpg`) und ein erstes
|
||||
EPUB zum Vergleichen:
|
||||
|
||||
```bash
|
||||
cd ~/Downloads/ljutsch
|
||||
python3 pdf2md_v5.py --epub Operation_Ljutsch_Band_1_-_Reinhard_Otto_Kranz.pdf Output/band1.md
|
||||
```
|
||||
|
||||
Wenn du kein Cover und kein Probe-EPUB brauchst, reicht `--images` statt `--epub`.
|
||||
|
||||
### Schritt 2: `band1.md` bearbeiten
|
||||
|
||||
Öffne die Datei in einem beliebigen Texteditor und halte dich an diese Regeln:
|
||||
|
||||
| Element | Schreibweise | Wirkung im EPUB |
|
||||
| :-- | :-- | :-- |
|
||||
| Teil | `## Teil I: Das Vermächtnis` | eigener Abschnitt im Inhaltsverzeichnis |
|
||||
| Kapitel | `### 1 Der Notar` | eigenes Kapitel (eigene Datei im EPUB), unter dem Teil eingeordnet |
|
||||
| Absatz | durch **eine Leerzeile** vom nächsten getrennt | neuer Absatz |
|
||||
| Zeilenumbruch innerhalb eines Absatzes | einfacher Umbruch | wird zu einem Leerzeichen |
|
||||
| Bild | `` auf eigener Zeile | Bild an dieser Stelle |
|
||||
| kursiv / fett | `_Wort_` / `**Wort**` | kursiv / fett |
|
||||
|
||||
Wichtig:
|
||||
|
||||
- Der Ordner `band1_images/` muss **neben** `band1.md` liegen bleiben. Benennst du die
|
||||
Markdown-Datei um, ändern sich die Bildpfade nicht, die Bilder bleiben gültig.
|
||||
- Bildzeilen nicht verändern, nur löschen, wenn das Bild weg soll.
|
||||
- Nach `#` immer ein Leerzeichen: `## Titel`, nicht `##Titel`.
|
||||
|
||||
Beispiel: Eine falsch erkannte Überschrift korrigieren.
|
||||
|
||||
Vorher (ein Satz, der als Überschrift erkannt wurde, und ein Kapiteltitel ohne Überschrift):
|
||||
|
||||
```markdown
|
||||
## Er ging nach Hause.
|
||||
|
||||
**4 Flucht aufs Land**
|
||||
|
||||
Am nächsten Morgen ...
|
||||
```
|
||||
|
||||
Nachher:
|
||||
|
||||
```markdown
|
||||
Er ging nach Hause.
|
||||
|
||||
### 4 Flucht aufs Land
|
||||
|
||||
Am nächsten Morgen ...
|
||||
```
|
||||
|
||||
### Schritt 3: EPUB aus dem bearbeiteten Markdown erzeugen
|
||||
|
||||
Im Ordner der Markdown-Datei starten (wichtig für die Bilder):
|
||||
|
||||
```bash
|
||||
cd ~/Downloads/ljutsch/Output
|
||||
pandoc band1.md \
|
||||
--from markdown-yaml_metadata_block --to epub3 \
|
||||
--output band1.epub \
|
||||
--resource-path . \
|
||||
--split-level=3 --toc-depth=3 \
|
||||
--metadata title="Operation Ljutsch" \
|
||||
--metadata author="Reinhard Otto Kranz" \
|
||||
--metadata lang=de \
|
||||
--epub-cover-image band1_images/cover.jpg
|
||||
```
|
||||
|
||||
Das sind dieselben Einstellungen, die `pdf2md_v5.py --epub` verwendet. Titel und Autor trägst du
|
||||
selbst ein. Ohne Cover lässt du die letzte Zeile weg und entfernst den `\` am Ende der Zeile davor.
|
||||
|
||||
Schritt 2 und 3 kannst du beliebig oft wiederholen. **Achtung:** Schritt 1 erneut auszuführen
|
||||
überschreibt deine Änderungen in `band1.md`. Leg dir vorher eine Kopie an oder nimm einen anderen
|
||||
Dateinamen.
|
||||
|
||||
---
|
||||
|
||||
## 4. Gescannte PDFs
|
||||
|
||||
Bei Scans liest das Programm den Text per OCR (Tesseract). Der Aufruf ist derselbe:
|
||||
|
||||
```bash
|
||||
python3 pdf2md_v5.py --epub scan.pdf Output/scan.md
|
||||
```
|
||||
|
||||
| Situation | Option |
|
||||
| :-- | :-- |
|
||||
| Buch in Deutsch (Standard) | – (`--ocr-language deu`) |
|
||||
| Deutsch und Englisch gemischt | `--ocr-language deu+eng` (Sprachpaket `tesseract-ocr-eng` nötig) |
|
||||
| schneller auf vielen Kernen | `--jobs 8` (Standard 4) |
|
||||
| OCR erzwingen, auch für vorhandene OCR-Ebenen | `--ocr always` |
|
||||
| keine OCR | `--ocr never` oder `--no-ocr` |
|
||||
|
||||
So entscheidet das Programm von selbst (`--ocr auto`):
|
||||
|
||||
- **Scanseiten** werden per OCR gelesen. Richtwert: ca. 1 Sekunde pro Seite mit 4 Prozessen,
|
||||
also ca. 5 Minuten für 300 Seiten.
|
||||
- **Scans mit vorhandener OCR-Ebene** (z. B. von `ocrmypdf`): Eine gerade Ebene wird
|
||||
übernommen, das geht sehr schnell. Eine schiefe Ebene (Scan nicht entzerrt) wird neu erkannt,
|
||||
weil ihre Lesereihenfolge oft falsch ist.
|
||||
- **Reine Bildseiten in einem sonst digitalen PDF** (Fotos, Cover) werden nur übernommen, wenn eine
|
||||
Probe-OCR echten Fließtext liefert. Sonst bleiben sie Bild, und das Programm meldet einen
|
||||
„Hinweis“.
|
||||
|
||||
Kolumnentitel und Seitenzahlen gedruckter Bücher werden automatisch entfernt, getrennte Wörter
|
||||
(„Geheim-|dienst“) zusammengefügt.
|
||||
|
||||
---
|
||||
|
||||
## 5. Alle Optionen
|
||||
|
||||
| Option | Bedeutung |
|
||||
| :-- | :-- |
|
||||
| `pdf` | Eingabe-PDF |
|
||||
| `markdown` | Ausgabe-Datei; ohne Angabe: gleicher Name wie die PDF mit `.md` |
|
||||
| `--ocr auto\|always\|never` | OCR-Entscheidung (Standard `auto`, siehe Abschnitt 4) |
|
||||
| `--no-ocr` | Kurzform für `--ocr never` |
|
||||
| `--ocr-language` | Tesseract-Sprache(n), z. B. `deu+eng` (Standard `deu`) |
|
||||
| `--ocr-dpi` | Auflösung für die OCR (Standard 300) |
|
||||
| `--jobs N` | parallele OCR-Prozesse (Standard 4) |
|
||||
| `--images` | Bilder in `<name>_images/` speichern und im Markdown verlinken |
|
||||
| `--image-format jpg\|png` | Bildformat (Standard `jpg`; `png` verlustfrei, aber ca. 3× größer) |
|
||||
| `--epub` | zusätzlich EPUB erzeugen (schaltet `--images` ein) |
|
||||
| `--epub-file PFAD` | anderer Pfad für das EPUB |
|
||||
| `--lang` | Sprache im EPUB (Standard `de`) |
|
||||
| `--keep-headers` | Kolumnentitel/Seitenköpfe nicht entfernen |
|
||||
| `--keep-footers` | Seitenzahlen/Seitenfüße nicht entfernen |
|
||||
|
||||
---
|
||||
|
||||
## 6. Ergebnis prüfen
|
||||
|
||||
1. **Überschriften auflisten.** So sieht man schnell, ob Teile und Kapitel stimmen:
|
||||
```bash
|
||||
grep -n "^#" Output/band1.md
|
||||
```
|
||||
Erwartet: `## Teil …` und darunter `### 1 …`, `### 2 …` usw. Ganze Sätze als Überschrift
|
||||
(endend mit `.` oder `«`) sind Erkennungsfehler, die du in Weg B korrigierst.
|
||||
2. **Stichproben** am Anfang, in der Mitte und am Ende des Markdowns mit der PDF vergleichen:
|
||||
Fehlen Absätze, sind Absätze zusammengeklebt, stehen Bilder an der richtigen Stelle?
|
||||
3. **EPUB auf dem PocketBook testen:** Inhaltsverzeichnis, Kapitelsprünge, Bilder, Cover.
|
||||
4. Für Feinarbeit direkt am EPUB eignen sich **Sigil** oder der **Calibre-Editor** („Buch bearbeiten“).
|
||||
|
||||
---
|
||||
|
||||
## 7. Häufige Probleme
|
||||
|
||||
| Meldung / Problem | Lösung |
|
||||
| :-- | :-- |
|
||||
| `Tesseract-Sprachdaten fehlen: deu – installieren mit: …` | den angezeigten Befehl ausführen, z. B. `sudo apt install tesseract-ocr-deu` |
|
||||
| `Tesseract nicht gefunden` | `sudo apt install tesseract-ocr tesseract-ocr-deu` |
|
||||
| `pandoc nicht gefunden` | `sudo apt install pandoc` |
|
||||
| Bilder fehlen im EPUB (Weg B) | pandoc im Ordner der `.md` starten (`cd Output`) bzw. `--resource-path` auf diesen Ordner setzen; der `_images`-Ordner muss daneben liegen |
|
||||
| Gescannter Text fehlt, Meldung „Hinweis: … Bildseite(n)“ | die Seite wurde für ein Foto gehalten → `--ocr always` |
|
||||
| Satz als Überschrift oder Kapitel ohne Überschrift | in Weg B von Hand korrigieren (Abschnitt 3) |
|
||||
| Auf Scans fehlen Leerzeichen („deutetin“) | OCR-Fehler; höhere Auflösung versuchen (`--ocr-dpi 400`) oder von Hand korrigieren |
|
||||
| EPUB zu groß | `--image-format jpg` (Standard) verwenden; PNG ist ca. 3× größer |
|
||||
| Meine Änderungen in der `.md` sind weg | Schritt 1 wurde erneut ausgeführt → vorher Kopie anlegen |
|
||||
|
|
@ -17,6 +17,9 @@ pip install -r requirements.txt # pymupdf4llm, pymupdf-layout, pytest
|
|||
|
||||
## 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_v5.py buch.pdf buch.md
|
||||
|
|
|
|||
26
pdf2md_v5.py
26
pdf2md_v5.py
|
|
@ -867,15 +867,25 @@ def extract_markdown(source: Path, plan: list[str], options: dict, jobs: int) ->
|
|||
return "\n\n".join(parts)
|
||||
|
||||
|
||||
def make_image_links_relative(markdown: str, image_dir: Path) -> str:
|
||||
"""Absolute Bildpfade durch Pfade relativ zur Markdown-Datei ersetzen."""
|
||||
absolute_dir = image_dir.resolve().as_posix()
|
||||
relative_dir = image_dir.name
|
||||
IMAGE_LINK = re.compile(r"(!\[[^\]]*\]\()([^)\s]+)\)")
|
||||
|
||||
return markdown.replace(
|
||||
absolute_dir + "/",
|
||||
relative_dir + "/",
|
||||
)
|
||||
|
||||
def make_image_links_relative(markdown: str, image_dir: Path) -> str:
|
||||
"""Bildpfade relativ zur Markdown-Datei machen ("<name>_images/datei.jpg").
|
||||
|
||||
pymupdf4llm schreibt den Pfad relativ zum Arbeitsverzeichnis, wenn der
|
||||
Bilderordner darunter liegt (z. B. "Output/x_images/…"), sonst absolut.
|
||||
"""
|
||||
image_dir = image_dir.resolve()
|
||||
|
||||
def fix(match: re.Match) -> str:
|
||||
path = Path(match.group(2))
|
||||
full = path if path.is_absolute() else Path.cwd() / path
|
||||
if full.resolve().parent != image_dir:
|
||||
return match.group(0)
|
||||
return f"{match.group(1)}{image_dir.name}/{path.name})"
|
||||
|
||||
return IMAGE_LINK.sub(fix, markdown)
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
|
|
|
|||
|
|
@ -13,12 +13,13 @@ from conftest import ROOT, TITLE, needs_pandoc, needs_tesseract
|
|||
SCRIPT = ROOT / "pdf2md_v5.py"
|
||||
|
||||
|
||||
def run(*args) -> subprocess.CompletedProcess:
|
||||
def run(*args, cwd=None) -> subprocess.CompletedProcess:
|
||||
done = subprocess.run(
|
||||
[sys.executable, str(SCRIPT), *map(str, args)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=600,
|
||||
cwd=cwd,
|
||||
)
|
||||
assert done.returncode == 0, done.stderr
|
||||
return done
|
||||
|
|
@ -128,3 +129,14 @@ def test_png_on_request(pdfs, tmp_path):
|
|||
run(pdfs["digital"], out, "--images", "--image-format", "png")
|
||||
md = out.read_text(encoding="utf-8")
|
||||
assert re.search(r"!\[\]\(png_images/[^)]+\.png\)", md)
|
||||
|
||||
|
||||
def test_image_links_relative_to_markdown_with_relative_output(pdfs, tmp_path):
|
||||
# Aufruf wie "python3 pdf2md_v5.py --images buch.pdf Output/buch.md" aus dem Projektordner
|
||||
run(pdfs["digital"], "Output/buch.md", "--images", cwd=tmp_path)
|
||||
md = (tmp_path / "Output" / "buch.md").read_text(encoding="utf-8")
|
||||
links = re.findall(r"!\[\]\(([^)]+)\)", md)
|
||||
assert links
|
||||
for link in links:
|
||||
assert link.startswith("buch_images/"), link
|
||||
assert (tmp_path / "Output" / link).is_file()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
"""Unit-Tests für die Markdown-Nachbearbeitung (ohne PDF, schnell)."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pymupdf
|
||||
import pytest
|
||||
|
||||
|
|
@ -263,3 +265,17 @@ def test_split_by_indent_needs_sentence_end():
|
|||
def test_demoted_heading_after_closed_paragraph_starts_new_paragraph():
|
||||
md = "Erster Absatz.\n## Sie verabschiedeten sich.\n"
|
||||
assert norm(md) == "Erster Absatz.\n\nSie verabschiedeten sich.\n"
|
||||
|
||||
|
||||
def test_make_image_links_relative(tmp_path, monkeypatch):
|
||||
image_dir = tmp_path / "Output" / "buch_images"
|
||||
image_dir.mkdir(parents=True)
|
||||
monkeypatch.chdir(tmp_path)
|
||||
md = (
|
||||
f"\n\n\n\n"
|
||||
"\n"
|
||||
)
|
||||
assert p.make_image_links_relative(md, Path("Output/buch_images")) == (
|
||||
"\n\n\n\n"
|
||||
"\n"
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue