Konvertiert gescannte PDF-Dateien zu PDF/A-2b (ISO 19005-2) mit deutscher OCR und MRC-Kompression.
| Script | Zweck |
|---|---|
| pdf2pdfa.py | MRC-Kompression + PDF/A-2b Konvertierung (Hauptscript) |
| analyze_pdfs.py | PDF-Analyse: Seiten, DPI, Farbmodus, OCR-Status, PDF/A-Version |
| cleanup_pdfs.py | Paperless-ngx inspirierte Bereinigung: Deskew, Hintergrund-Entfernung, Re-OCR |
- PDF/A-2b – Langzeitarchivierungsformat nach ISO 19005-2
- Deutsche OCR – Texterkennung mit Tesseract (Sprache: Deutsch)
- MRC-Kompression (Mixed Raster Content)
- Text-Maske: 300 DPI, JBIG2 verlustfrei (5–10× besser als JPEG)
- Hintergrund: 150 DPI, JPEG Q40 (kompakte Farben/Logos)
- 4x Upscaling vor Sauvola-Binarisierung für glatte Textkanten
- QR-Code-sicher – JBIG2 verlustfrei erhält QR-Codes exakt
- Batch-Verarbeitung – Einzeldateien, Glob-Muster oder ganze Ordner
- Fortschrittsanzeige –
[1/5] datei.pdf - Fehlerprotokoll – optionale Logdatei
- Statistik – Dateigröße vorher/nachher, Einsparung in %
brew install ocrmypdf poppler jbig2enc imagemagick
pip install pikepdf pillow numpyDas installiert:
ocrmypdf(v17+) – OCR + PDF/A Metadatenjbig2enc– JBIG2-Encoder für verlustfreie S/W-Kompressionpoppler– pdftoppm für Seiten-Renderingimagemagick– convert für Bildverarbeitungtesseract– OCR-Engine (via ocrmypdf)
Deutsche Sprachdaten prüfen:
tesseract --list-langs | grep deuFalls deu fehlt:
brew install tesseract-langOptional (für --thinning):
pip install scikit-imagesudo apt install ocrmypdf tesseract-ocr-deu jbig2enc poppler-utils imagemagick
pip install pikepdf pillow numpyPython 3.10+ wird benötigt.
git clone https://github.com/arnepluhar/pdf2pdfa.git
cd pdf2pdfa
pip install pikepdf pillow numpyOptional: Script global verfügbar machen:
chmod +x pdf2pdfa.py
ln -s "$(pwd)/pdf2pdfa.py" /usr/local/bin/pdf2pdfapython pdf2pdfa.py scan.pdf
# Ausgabe: scan.pdf → pdfa/scan.pdfpython pdf2pdfa.py *.pdf
python pdf2pdfa.py rechnung.pdf vertrag.pdf brief.pdfpython pdf2pdfa.py /Dokumente/Scans/
# Konvertiert alle *.pdf im Ordner → /Dokumente/Scans/pdfa/# Relativer Unterordner (wird neben der Quelldatei angelegt)
python pdf2pdfa.py *.pdf --output-dir archiv
# Absoluter Pfad
python pdf2pdfa.py /Scans/ --output-dir /Archiv/pdfa# Dünnere Textstriche (bei zu fettem Text)
python pdf2pdfa.py scan.pdf --sauvola-k 0.15
# Dickere Textstriche (bei zu dünnem Text)
python pdf2pdfa.py scan.pdf --sauvola-k 0.25# CLAHE für blasse Scans oder vergilbtes Papier
python pdf2pdfa.py scan.pdf --clahepython pdf2pdfa.py /Scans/ --log konvertierung.logpython pdf2pdfa.py scan.pdf --verbose| Option | Standard | Beschreibung |
|---|---|---|
PDF ... |
– | PDF-Datei(en) oder Verzeichnis |
--output-dir ORDNER |
pdfa |
Ausgabe-Unterordner (relativ oder absolut) |
--bg-quality 1-100 |
40 |
JPEG-Qualität für Farb-Hintergrund |
--bg-dpi 50-300 |
150 |
DPI für Farb-Hintergrund |
--window-size N |
25 |
Fenstergröße für Sauvola-Binarisierung |
--sauvola-k K |
0.18 |
Sauvola k-Faktor (0.15-0.25, niedriger = dünnere Striche) |
--erosion N |
0 |
Erosions-Iterationen (0-2) |
--clahe |
aus | CLAHE-Kontrastverbesserung |
--thinning N |
0 |
Zhang-Suen Thinning (0-3, benötigt scikit-image) |
--log DATEI |
– | Fehlerprotokoll in Datei schreiben |
--verbose, -v |
– | Ausführliche Debug-Ausgabe |
2026-07-15 10:23:01 [INFO] pdf2pdfa - PDF → PDF/A-2b Konverter (MRC-Modus)
2026-07-15 10:23:01 [INFO] Ausgabe-Ordner: pdfa | Hintergrund: 150 DPI, Q40 | Sauvola: window=25, k=0.18
2026-07-15 10:23:01 [INFO] Gefunden: 3 PDF-Datei(en)
2026-07-15 10:23:01 [INFO] [1/3] brief.pdf
2026-07-15 10:23:01 [INFO] Konvertiere: brief.pdf → pdfa/brief.pdf
2026-07-15 10:23:08 [INFO] OK 7.2s | 4.2 MB → 0.5 MB (88.1% kleiner)
...
2026-07-15 10:23:45 [INFO] ============================================================
2026-07-15 10:23:45 [INFO] STATISTIK
2026-07-15 10:23:45 [INFO] ============================================================
2026-07-15 10:23:45 [INFO] Gesamt: 3 Datei(en)
2026-07-15 10:23:45 [INFO] Erfolgreich: 3
2026-07-15 10:23:45 [INFO] Größe vorher: 12.6 MB
2026-07-15 10:23:45 [INFO] Größe nachher: 1.5 MB
2026-07-15 10:23:45 [INFO] Einsparung: 88.1%
2026-07-15 10:23:45 [INFO] Gesamtdauer: 44.3s
2026-07-15 10:23:45 [INFO] ============================================================
Mixed Raster Content (MRC) trennt jede Seite in zwei Ebenen:
| Ebene | Auflösung | Kompression | Inhalt |
|---|---|---|---|
| Text-Maske | 300 DPI (4x upscaled) | JBIG2 verlustfrei | S/W-Text, QR-Codes |
| Hintergrund | 150 DPI | JPEG Q40 | Farben, Logos, Fotos |
Warum MRC?
- ~85-90% Dateigröße-Reduktion (vs. ~50% ohne MRC)
- Gestochen scharfer Text bei 300 DPI
- Kompakter Hintergrund ohne sichtbare Artefakte
Die Text-Maske wird mit adaptiver Sauvola-Schwellwertbildung erstellt:
- Graustufenbild 4x upscalen (300 → 1200 DPI äquivalent)
- Sauvola-Algorithmus mit lokalem Fenster (25×4 = 100 px)
- Binärmaske zurück auf 300 DPI skalieren (Majority Voting)
- JBIG2 verlustfrei komprimieren
Der --sauvola-k Parameter steuert die Empfindlichkeit:
- Niedriger (0.10-0.15): Dünnere Striche, mehr Details
- Standard (0.18): Ausgewogen
- Höher (0.20-0.25): Dickere Striche, weniger Rauschen
Falls ein PDF bereits OCR-Text enthält, wird automatisch auf skip_text=True zurückgegriffen: Das Dokument wird trotzdem zu PDF/A-2b konvertiert und komprimiert, aber nicht erneut per OCR verarbeitet.
- Relativer Pfad (z. B.
pdfa): Wird als Unterordner neben jeder Quelldatei angelegt - Absoluter Pfad (z. B.
/Archiv/pdfa): Alle Ausgabedateien landen in diesem Ordner
Siehe docs/adr-001-tool-choice.md für die vollständige Begründung der Tool-Wahl.
MIT – siehe LICENSE
Analysiert PDFs und zeigt eine Übersichtstabelle mit:
- Seitenzahl, Dateigröße, DPI
- Farbmodus (Farbe/Graustufen)
- OCR-Status
- PDF/A-Version (1a, 1b, 2a, 2b, 3a, 3b)
python analyze_pdfs.py /Pfad/zu/PDFs/
# JSON-Export
python analyze_pdfs.py /Pfad/zu/PDFs/ --json analyse.json============================================================================
Filename Pages Size DPI Color OCR PDF/A Images
============================================================================
2024-01-15 - Rechnung.pdf 1 54 KB 300 color ✓ PDF/A-2b 2
2024-02-20 - Vertrag.pdf 4 320 KB 300 grayscale ✓ PDF/A-2b 11
============================================================================
Summary: 2 PDFs, 5 pages, 374 KB total
OCR: 2/2, PDF/A: 2/2, Grayscale: 1/2
Paperless-ngx inspirierte Bereinigung für bereits vorhandene PDFs. Ideal für:
- Verbesserung der OCR-Qualität (Deutsch)
- Konvertierung zu PDF/A-2b
- Deskew (schiefe Scans begradigen)
- Hintergrund-Entfernung (durchscheinende Rückseiten)
| Modus | Beschreibung | Dateigröße |
|---|---|---|
| --redo-ocr (Standard) | Ersetzt nur OCR-Layer, behält Bilder | ≈ gleich |
| --force-ocr | Rendert PDF komplett neu (ermöglicht --deskew) | größer |
| --skip-ocr | Nur PDF/A-2b Konvertierung | variabel |
# Standard: Nur OCR verbessern + PDF/A-2b
python cleanup_pdfs.py /Pfad/zu/PDFs/ -o ausgabe/
# Mit Deskew (benötigt --force-ocr)
python cleanup_pdfs.py /Pfad/zu/PDFs/ -o ausgabe/ --force-ocr
# Volle Pipeline: Unpaper + Hintergrund-Entfernung
python cleanup_pdfs.py /Pfad/zu/PDFs/ -o ausgabe/ --full-pipeline --unpaper --remove-background
# Einzeldatei
python cleanup_pdfs.py dokument.pdf -o ausgabe/| Option | Beschreibung |
|---|---|
-o, --output |
Ausgabe-Ordner (Standard: ./cleaned) |
--force-ocr |
PDF komplett neu rendern (langsamer, größere Dateien) |
--skip-ocr |
Nur PDF/A-2b, kein OCR |
--unpaper |
Unpaper für Deskew/Cleanup (langsam, gut für alte Scans) |
--remove-background |
Hintergrund/Durchscheinen entfernen |
--grayscale |
In Graustufen konvertieren |
-l, --language |
OCR-Sprache (Standard: deu+eng) |
-j, --workers |
Parallele Verarbeitung |
--overwrite |
Vorhandene Dateien überschreiben |
--redo-ocrist nicht kompatibel mit--deskew,--clean-final,--remove-background- Für Deskew muss
--force-ocrverwendet werden (rendert das PDF neu) --full-pipelinewird automatisch aktiviert bei--unpaperoder--remove-background