#!/usr/bin/env python3 """PDF nach Markdown konvertieren und weiche Zeilenumbrüche entfernen.""" import argparse import re import sys from pathlib import Path import pymupdf4llm HEADING = re.compile(r"^\s{0,3}#{1,6}\s+") LIST_ITEM = re.compile(r"^\s{0,3}(?:[-+*]|\d+[.)])\s+") BLOCKQUOTE = re.compile(r"^\s{0,3}>\s?") FENCE = re.compile(r"^\s{0,3}(`{3,}|~{3,})") RULE = re.compile(r"^\s{0,3}(?:-\s*){3,}$|^\s{0,3}(?:\*\s*){3,}$") IMAGE = re.compile(r"^\s*!\[.*?\]\(.*\)\s*$") HTML_BLOCK = re.compile(r"^\s* bool: """Zeilen, die nicht mit dem vorherigen Fließtext verbunden werden.""" return bool( HEADING.match(line) or LIST_ITEM.match(line) or BLOCKQUOTE.match(line) or RULE.match(line) or IMAGE.match(line) or HTML_BLOCK.match(line) or line.lstrip().startswith("|") or line.startswith((" ", "\t")) ) def normalize_markdown(markdown: str) -> str: result = [] paragraph = [] in_fence = False fence_char = "" fence_length = 0 def flush_paragraph(): if paragraph: result.append(" ".join(paragraph)) paragraph.clear() for original in markdown.replace("\r\n", "\n").replace("\r", "\n").split("\n"): line = original.rstrip() marker = FENCE.match(line) if in_fence: result.append(line) if marker: token = marker.group(1) if token[0] == fence_char and len(token) >= fence_length: in_fence = False continue if marker: flush_paragraph() result.append(line) in_fence = True fence_char = marker.group(1)[0] fence_length = len(marker.group(1)) continue if not line.strip(): flush_paragraph() if result and result[-1] != "": result.append("") continue if is_structural(line): flush_paragraph() result.append(line) continue paragraph.append(line.strip()) flush_paragraph() return "\n".join(result).strip() + "\n" def main() -> int: parser = argparse.ArgumentParser( description="PDF in Markdown umwandeln; Zeilen innerhalb von Absätzen verbinden." ) parser.add_argument("pdf", type=Path, help="Eingabe-PDF") parser.add_argument( "markdown", type=Path, nargs="?", help="Ausgabe-Datei; Standard: gleicher Name mit .md", ) parser.add_argument( "--ocr-language", default="deu", help="Tesseract-Sprachcode bei erforderlicher OCR (Standard: deu)", ) parser.add_argument( "--no-ocr", action="store_true", help="OCR abschalten, wenn die PDF sicher durchsuchbaren Text enthält", ) parser.add_argument( "--keep-headers", action="store_true", help="Wiederkehrende Seitenköpfe in die Ausgabe übernehmen", ) parser.add_argument( "--keep-footers", action="store_true", help="Wiederkehrende Seitenfüße in die Ausgabe übernehmen", ) args = parser.parse_args() source = args.pdf.expanduser() target = (args.markdown or source.with_suffix(".md")).expanduser() if not source.is_file(): parser.error(f"Eingabedatei nicht gefunden: {source}") if source.resolve() == target.resolve(): parser.error("Eingabe- und Ausgabedatei müssen verschieden sein.") try: markdown = pymupdf4llm.to_markdown( str(source), header=args.keep_headers, footer=args.keep_footers, use_ocr=not args.no_ocr, ocr_language=args.ocr_language, ) target.parent.mkdir(parents=True, exist_ok=True) target.write_text(normalize_markdown(markdown), encoding="utf-8") except Exception as exc: print(f"Fehler bei der Konvertierung: {exc}", file=sys.stderr) return 1 print(f"Markdown geschrieben: {target}") return 0 if __name__ == "__main__": raise SystemExit(main())