--keep-lines für Gedichtbände, OCR-Neuerkennung repariert, OCR-Rauschen entfernen
- --ocr always / schiefe OCR-Ebenen: alte unsichtbare Textebene vor der
OCR entfernen (nur in der Kopie im Speicher); vorher blieb der alte
Text erhalten bzw. stand doppelt da ("bliht" statt "blüht")
- --keep-lines: jede PDF-Zeile bleibt eine Zeile, Strophen als Absätze,
Einrückungen bleiben, nichts wird zusammengefügt (auch keine Trennung)
- OCR-Text selbst erkennen (unsichtbar, wie pymupdf4llm 1.28 intern);
Buchkante "|" entfernen, Rauschzeilen und Fetzen neben dem Textbereich
verwerfen, Zeilen mit verschiedenen Grundlinien trennen, Überschriften
ohne Buchstaben/Ziffern verwerfen
- Fraktur: frk+deu dokumentiert, Grenzen in TODO
- Tests: Scan mit falscher OCR-Ebene, Gedicht in schmaler Spalte,
OCR-Rauschen; README, Bedienungsanleitung, TODO
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
1ba5b5005f
commit
3e5195efc2
1 changed files with 265 additions and 13 deletions
|
|
@ -204,3 +204,84 @@ def test_single_code_like_register_entry_stays_text(tmp_path):
|
|||
])
|
||||
assert "```" not in md
|
||||
assert "@classmethod, 190\\" in md
|
||||
|
||||
|
||||
# --- --keep-lines (Gedichtbände) ----------------------------------------------------
|
||||
|
||||
POEM = [
|
||||
"O wie blüht mein Leib aus jeder Ader",
|
||||
"duftender, seitdem ich dich erkenn;",
|
||||
"sieh, ich gehe schlanker und gerader,",
|
||||
"und du wartest nur: wer bist du denn?",
|
||||
None, # Strophenabstand
|
||||
"Sieh: ich fühle, wie ich mich entferne,",
|
||||
"wie ich Altes, Blatt um Blatt, verlier. Lei-",
|
||||
"be und Seele sind wie lauter Sterne.",
|
||||
]
|
||||
|
||||
|
||||
def make_poem_pdf(path):
|
||||
"""Schmale Seite, Verse füllen die Zeile fast (wie im Rilke-Band)."""
|
||||
doc = pymupdf.open()
|
||||
page = doc.new_page(width=272, height=400)
|
||||
y = 60
|
||||
for line in POEM:
|
||||
if line is None:
|
||||
y += 14
|
||||
continue
|
||||
page.insert_text((44, y), line, fontname="tiro", fontsize=10)
|
||||
y += 13
|
||||
doc.save(path)
|
||||
return path
|
||||
|
||||
|
||||
def test_poem_without_keep_lines_becomes_prose(tmp_path):
|
||||
out = tmp_path / "gedicht.md"
|
||||
run(make_poem_pdf(tmp_path / "gedicht.pdf"), out)
|
||||
assert "aus jeder Ader duftender" in out.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_keep_lines_keeps_every_line_and_stanza(tmp_path):
|
||||
out = tmp_path / "gedicht.md"
|
||||
run(make_poem_pdf(tmp_path / "gedicht.pdf"), out, "--keep-lines")
|
||||
md = out.read_text(encoding="utf-8")
|
||||
assert (
|
||||
"O wie blüht mein Leib aus jeder Ader\\\n"
|
||||
"duftender, seitdem ich dich erkenn;\\\n"
|
||||
"sieh, ich gehe schlanker und gerader,\\\n"
|
||||
"und du wartest nur: wer bist du denn?\n\n"
|
||||
"Sieh: ich fühle, wie ich mich entferne,\\\n"
|
||||
"wie ich Altes, Blatt um Blatt, verlier. Lei-\\\n"
|
||||
"be und Seele sind wie lauter Sterne."
|
||||
) in md
|
||||
assert "```" not in md # Zeilen auf ";" sind kein Code
|
||||
|
||||
|
||||
def test_ocr_noise_outside_text_area_is_dropped():
|
||||
line = {"bbox": (0, 10, 12, 20), "spans": [{"text": "aoe", "size": 10}]}
|
||||
p._page_text[0] = (44, 212)
|
||||
try:
|
||||
assert p._outside_text_area([line], 10)
|
||||
assert not p._outside_text_area([{**line, "bbox": (44, 10, 200, 20)}], 10)
|
||||
finally:
|
||||
p._page_text[0] = None
|
||||
|
||||
|
||||
def _ocr_span(text, x, y, size=10):
|
||||
return {"text": text, "size": size, "flags": 0, "char_flags": 0, "font": "GlyphLessFont",
|
||||
"origin": (x, y), "bbox": (x, y - size, x + 6 * len(text), y)}
|
||||
|
||||
|
||||
def test_clean_ocr_lines_removes_edge_noise_and_splits_baselines():
|
||||
lines = [
|
||||
{"bbox": (0, 0, 400, 20), "spans": [_ocr_span("||", 10, 20), _ocr_span("Und", 60, 20),
|
||||
_ocr_span("Don", 62, 36), _ocr_span("tauchte", 90, 20),
|
||||
_ocr_span("|", 390, 20)]},
|
||||
{"bbox": (0, 40, 30, 50), "spans": [_ocr_span("„+|", 5, 50)]}, # nur Rauschen
|
||||
]
|
||||
cleaned = p._clean_ocr_lines(lines)
|
||||
assert ["".join(s["text"] for s in line["spans"]) for line in cleaned] == ["Undtauchte", "Don"]
|
||||
|
||||
|
||||
def test_heading_without_letters_is_dropped():
|
||||
assert p.normalize_markdown("# ||\n\nText.\n") == "Text.\n"
|
||||
|
|
|
|||
Loading…
Reference in a new issue