Bessere Standardwerte: EPUB-Sprache, Titel/Autor, Prozesszahl
- --lang ohne Angabe aus der ersten OCR-Sprache ableiten (eng -> en) - Fehlen Titel/Autor in den PDF-Metadaten: aus dem Dateinamen nach dem Muster "Autor_-_Titel" übernehmen, sonst Dateiname mit Leerzeichen - --jobs: halbe Kernzahl, höchstens 8 (bisher 4) - Tests (tests/test_defaults.py), README und Bedienungsanleitung Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
619421ef10
commit
fa90050535
4 changed files with 108 additions and 15 deletions
|
|
@ -65,10 +65,15 @@ Danach liegen in `Output/`:
|
|||
| :-- | :-- |
|
||||
| `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) |
|
||||
| `band1.epub` | das fertige E-Book (Titel und Autor aus der PDF, siehe unten) |
|
||||
|
||||
Ein Band mit ca. 700 Seiten dauert etwa anderthalb Minuten.
|
||||
|
||||
**Titel und Autor im EPUB** stammen aus den Metadaten der PDF. Fehlen sie, wertet das Programm
|
||||
den Dateinamen nach dem Muster `Autor_-_Titel.pdf` aus: `Katja_Adler_-_Rolle_rückwärts.pdf` ergibt
|
||||
Titel „Rolle rückwärts“ und Autor „Katja Adler“. Ohne ` - ` im Namen wird der ganze Dateiname
|
||||
(mit Leerzeichen statt `_`) zum Titel. Es lohnt sich also, PDFs vorher passend umzubenennen.
|
||||
|
||||
---
|
||||
|
||||
## 3. Weg B – Markdown von Hand bearbeiten, dann EPUB
|
||||
|
|
@ -166,9 +171,9 @@ python3 pdf2md.py --epub scan.pdf Output/scan.md
|
|||
| :-- | :-- |
|
||||
| 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) |
|
||||
| mehr oder weniger parallele OCR-Prozesse | `--jobs N` (Standard: halbe Kernzahl, höchstens 8) |
|
||||
| OCR erzwingen, auch für vorhandene OCR-Ebenen | `--ocr always` |
|
||||
| englisches Buch | `--ocr-language eng --lang en` (Sprachpaket `tesseract-ocr-eng` nötig) |
|
||||
| englisches Buch | `--ocr-language eng` (Sprachpaket `tesseract-ocr-eng` nötig; EPUB-Sprache wird automatisch `en`) |
|
||||
| keine OCR | `--ocr never` oder `--no-ocr` |
|
||||
|
||||
So entscheidet das Programm von selbst (`--ocr auto`):
|
||||
|
|
@ -199,12 +204,12 @@ Kolumnentitel und Seitenzahlen gedruckter Bücher werden automatisch entfernt, g
|
|||
| `--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) |
|
||||
| `--jobs N` | parallele OCR-Prozesse (Standard: halbe Kernzahl, höchstens 8) |
|
||||
| `--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`) |
|
||||
| `--lang` | Sprache im EPUB (Standard: aus `--ocr-language` abgeleitet, `deu`→`de`, `eng`→`en`; sonst `de`) |
|
||||
| `--keep-headers` | Kolumnentitel/Seitenköpfe nicht entfernen |
|
||||
| `--keep-footers` | Seitenzahlen/Seitenfüße nicht entfernen |
|
||||
|
||||
|
|
|
|||
|
|
@ -27,14 +27,15 @@ dann EPUB“: **[BEDIENUNGSANLEITUNG.md](BEDIENUNGSANLEITUNG.md)**.
|
|||
# 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, Seite 1 als Cover)
|
||||
# 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
|
||||
|
||||
# englisches Buch; alter Scan mit schlechter OCR-Ebene neu erkennen
|
||||
python3 pdf2md.py --epub --lang en --ocr-language eng --ocr always scan.pdf scan.md
|
||||
python3 pdf2md.py --epub --ocr-language eng --ocr always scan.pdf scan.md # EPUB-Sprache: en
|
||||
```
|
||||
|
||||
| Option | Bedeutung |
|
||||
|
|
@ -42,11 +43,11 @@ python3 pdf2md.py --epub --lang en --ocr-language eng --ocr always scan.pdf scan
|
|||
| `--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 4); reine Textdokumente laufen immer sequenziell |
|
||||
| `--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) |
|
||||
| `--epub`, `--epub-file PFAD` | EPUB 3 mit pandoc erzeugen (schaltet `--images` ein) |
|
||||
| `--lang` | Sprache im EPUB (`de`) |
|
||||
| `--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
|
||||
|
|
|
|||
48
pdf2md.py
48
pdf2md.py
|
|
@ -1057,6 +1057,39 @@ def heading_depth(markdown: str) -> int:
|
|||
return min(max(levels, default=1), 3)
|
||||
|
||||
|
||||
# Tesseract-Sprachcode -> Sprachcode im EPUB (ISO 639-1)
|
||||
EPUB_LANGUAGES = {
|
||||
"deu": "de", "deu_latf": "de", "frk": "de", "eng": "en", "fra": "fr",
|
||||
"spa": "es", "ita": "it", "nld": "nl", "por": "pt", "pol": "pl",
|
||||
"ces": "cs", "dan": "da", "swe": "sv", "nor": "no", "fin": "fi",
|
||||
"rus": "ru", "ukr": "uk", "lat": "la", "ell": "el", "tur": "tr",
|
||||
}
|
||||
|
||||
|
||||
def epub_language(ocr_language: str) -> str:
|
||||
"""EPUB-Sprache aus der (ersten) OCR-Sprache: "eng+deu" -> "en"."""
|
||||
first = ocr_language.split("+")[0].strip().lower()
|
||||
return EPUB_LANGUAGES.get(first, "de")
|
||||
|
||||
|
||||
def default_jobs() -> int:
|
||||
"""Halbe Kernzahl, höchstens 8: Jeder Prozess lädt das Layout-Modell."""
|
||||
return max(1, min(8, (os.cpu_count() or 2) // 2))
|
||||
|
||||
|
||||
def title_author_from_filename(stem: str) -> tuple[str, str]:
|
||||
"""Titel und Autor aus einem Dateinamen nach dem Muster "Autor_-_Titel".
|
||||
|
||||
"Katja_Adler_-_Rolle_rückwärts" -> ("Rolle rückwärts", "Katja Adler");
|
||||
ohne " - " ist der ganze Name der Titel.
|
||||
"""
|
||||
name = re.sub(r"\s+", " ", stem.replace("_", " ")).strip()
|
||||
author, sep, title = name.partition(" - ")
|
||||
if sep and author.strip() and title.strip():
|
||||
return title.strip(), author.strip()
|
||||
return name, ""
|
||||
|
||||
|
||||
def write_epub(
|
||||
markdown_path: Path,
|
||||
epub_path: Path,
|
||||
|
|
@ -1142,8 +1175,9 @@ def main() -> int:
|
|||
parser.add_argument(
|
||||
"--jobs",
|
||||
type=int,
|
||||
default=min(4, os.cpu_count() or 1),
|
||||
help="Anzahl paralleler Prozesse (Standard: %(default)s)",
|
||||
default=default_jobs(),
|
||||
help="Anzahl paralleler OCR-Prozesse (Standard: halbe Kernzahl, "
|
||||
"höchstens 8; hier %(default)s)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--images",
|
||||
|
|
@ -1169,8 +1203,8 @@ def main() -> int:
|
|||
)
|
||||
parser.add_argument(
|
||||
"--lang",
|
||||
default="de",
|
||||
help="Sprache im EPUB (Standard: de)",
|
||||
help="Sprache im EPUB, z. B. en (Standard: aus --ocr-language, "
|
||||
"deu -> de, eng -> en)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--keep-headers",
|
||||
|
|
@ -1183,6 +1217,7 @@ def main() -> int:
|
|||
help="Seitenfüße/Seitenzahlen übernehmen",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
args.lang = args.lang or epub_language(args.ocr_language)
|
||||
|
||||
source = args.pdf.expanduser()
|
||||
target = (args.markdown or source.with_suffix(".md")).expanduser()
|
||||
|
|
@ -1206,8 +1241,9 @@ def main() -> int:
|
|||
outline = load_outline(doc)
|
||||
uris = load_link_uris(doc)
|
||||
page_count = doc.page_count
|
||||
title = (doc.metadata or {}).get("title") or source.stem
|
||||
author = (doc.metadata or {}).get("author") or ""
|
||||
file_title, file_author = title_author_from_filename(source.stem)
|
||||
title = ((doc.metadata or {}).get("title") or "").strip() or file_title
|
||||
author = ((doc.metadata or {}).get("author") or "").strip() or file_author
|
||||
|
||||
counts = Counter(kinds)
|
||||
print(
|
||||
|
|
|
|||
51
tests/test_defaults.py
Normal file
51
tests/test_defaults.py
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
"""Standardwerte: EPUB-Sprache, Prozesszahl, Titel/Autor aus dem Dateinamen."""
|
||||
|
||||
import zipfile
|
||||
|
||||
import pymupdf
|
||||
import pytest
|
||||
|
||||
import pdf2md as p
|
||||
from conftest import needs_pandoc
|
||||
from test_e2e import run
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"ocr_language, expected",
|
||||
[("deu", "de"), ("eng", "en"), ("eng+deu", "en"), ("fra", "fr"), ("xyz", "de")],
|
||||
)
|
||||
def test_epub_language_follows_ocr_language(ocr_language, expected):
|
||||
assert p.epub_language(ocr_language) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("cores, expected", [(None, 1), (1, 1), (4, 2), (24, 8)])
|
||||
def test_default_jobs(monkeypatch, cores, expected):
|
||||
monkeypatch.setattr(p.os, "cpu_count", lambda: cores)
|
||||
assert p.default_jobs() == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"stem, expected",
|
||||
[
|
||||
("Katja_Adler_-_Rolle_rückwärts_DDR", ("Rolle rückwärts DDR", "Katja Adler")),
|
||||
("Zensur_Netzwerk_Deutschland_de-report-de", ("Zensur Netzwerk Deutschland de-report-de", "")),
|
||||
("A_-_B_-_C", ("B - C", "A")),
|
||||
],
|
||||
)
|
||||
def test_title_author_from_filename(stem, expected):
|
||||
assert p.title_author_from_filename(stem) == expected
|
||||
|
||||
|
||||
@needs_pandoc
|
||||
def test_epub_without_metadata_uses_filename_and_ocr_language(pdfs, tmp_path):
|
||||
doc = pymupdf.open(pdfs["digital"])
|
||||
doc.set_metadata({})
|
||||
source = tmp_path / "Max_Muster_-_Ein_Buch.pdf"
|
||||
doc.save(source)
|
||||
|
||||
run(source, tmp_path / "buch.md", "--epub", "--ocr-language", "eng")
|
||||
with zipfile.ZipFile(tmp_path / "buch.epub") as z:
|
||||
opf = z.read("EPUB/content.opf").decode()
|
||||
assert ">Ein Buch<" in opf
|
||||
assert ">Max Muster<" in opf
|
||||
assert "<dc:language>en</dc:language>" in opf
|
||||
Loading…
Reference in a new issue