diff --git a/README.md b/README.md index 01b2113..074f166 100644 --- a/README.md +++ b/README.md @@ -250,8 +250,9 @@ Blöcken korrigiert. 2. **Feste Ersetzungen** aus dem Glossar (`falsch => richtig`) werden im Rohtext angewendet, bevor das Modell ihn sieht. 3. **Korrektur:** Der Text wird in Abschnitte von ca. 1200 Wörtern geteilt (immer - am Satzende) und abschnittsweise vom Modell korrigiert. Fortschritt wird live - angezeigt. + am Satzende; Abkürzungen wie „z. B.“, „Dr.“, „bzw.“ und Ordnungszahlen wie + „2. Weltkrieg“ zählen nicht als Satzende) und abschnittsweise vom Modell + korrigiert. Fortschritt wird live angezeigt. 4. **Wortzahl-Prüfung:** Weicht die Wortzahl eines Abschnitts um mehr als 5 % ab, gibt es eine Warnung (mögliche Auslassungen oder Ergänzungen). 5. **Absätze:** Das Modell nennt nur Satznummern, an denen ein Absatz beginnt; der @@ -457,5 +458,6 @@ Oben in `abschrift_korrigieren.py`: - `vtt2txt.py` erkennt Wiederholungen an der Überlappung aufeinanderfolgender Untertitel-Blöcke. Bei Untertiteln ohne rollendes Fenster gehen deshalb zwei direkt aufeinanderfolgende, identische Blöcke zu einem zusammen. -- Abkürzungen wie „z. B.“ oder „2. Weltkrieg“ können als Satzende gelten; das - beeinflusst nur mögliche Absatzgrenzen, nicht den Text. +- Einzelbuchstaben und ein- bis zweistellige Zahlen mit Punkt gelten nie als + Satzende („Plan B.“, „Es waren 12.“); das beeinflusst nur mögliche Absatzgrenzen, + nicht den Text. diff --git a/abschrift_korrigieren.py b/abschrift_korrigieren.py index 71843fe..3e355a8 100755 --- a/abschrift_korrigieren.py +++ b/abschrift_korrigieren.py @@ -35,6 +35,11 @@ MIN_PARAGRAPH_SENTENCES = 3 # kürzere Absätze werden mit dem Nachbarn verbun SENTENCE_END = re.compile(r"[.!?…][\"'»«“”)\]]*$") SENTENCE_SPLIT = re.compile(r"(?:(?<=[.!?…])|(?<=[.!?…][\"'»«“”)\]]))\s+") +# Abbreviations and ordinals ("z. B.", "Dr.", "2. Weltkrieg", "e.g.") end no +# sentence; "usw." and "etc." usually do, four-digit years too +NO_SENTENCE_END = re.compile( + r"(?:^|[\s.])(?:\w|\d{1,2}|Dr|Prof|bzw|ca|Nr|St|vgl|sog|evtl|ggf|inkl|bspw|Mr|Mrs|Ms)\.$" +) SYSTEM = """Du korrigierst die automatische Abschrift eines deutschsprachigen gesprochenen Beitrags (z. B. Rede, Vortrag, Interview, Video). @@ -237,7 +242,7 @@ def split_chunks(text): while start < len(units): end = min(start + CHUNK_SIZE, len(units)) limit = min(end + MAX_EXTRA_WORDS, len(units)) - while end < limit and not SENTENCE_END.search(units[end - 1].rstrip()): + while end < limit and not ends_sentence(units[end - 1].rstrip()): end += 1 chunks.append("".join(units[start:end])) start = end @@ -349,9 +354,21 @@ def correct_chunk(chunk, index, system): return corrected +def ends_sentence(text): + return bool(SENTENCE_END.search(text)) and not NO_SENTENCE_END.search(text) + + def split_sentences(text): """Split flat text into sentences; joining them with spaces restores it.""" - return [s for s in SENTENCE_SPLIT.split(" ".join(text.split())) if s] + sentences = [] + for piece in SENTENCE_SPLIT.split(" ".join(text.split())): + if not piece: + continue + if sentences and NO_SENTENCE_END.search(sentences[-1]): + sentences[-1] += " " + piece + else: + sentences.append(piece) + return sentences def find_paragraph_starts(sentences, first, system): diff --git a/tests/test_abschrift_korrigieren.py b/tests/test_abschrift_korrigieren.py index 497f819..cd57b0e 100644 --- a/tests/test_abschrift_korrigieren.py +++ b/tests/test_abschrift_korrigieren.py @@ -207,6 +207,27 @@ def test_split_sentences_keeps_closing_quotes(): assert " ".join(sentences) == text +@pytest.mark.parametrize("text, expected", [ + ("Das gilt z. B. hier. Gut.", ["Das gilt z. B. hier.", "Gut."]), + ("Das heißt, d. h. nicht. Gut.", ["Das heißt, d. h. nicht.", "Gut."]), + ("Dr. Müller und Prof. Meier kamen. Gut.", ["Dr. Müller und Prof. Meier kamen.", "Gut."]), + ("Im 2. Weltkrieg am 3. Oktober. Gut.", ["Im 2. Weltkrieg am 3. Oktober.", "Gut."]), + ("Das war 1982. Dann kam 1983.", ["Das war 1982.", "Dann kam 1983."]), + ("Fruit, e.g. apples. And Mr. Smith.", ["Fruit, e.g. apples.", "And Mr. Smith."]), + ("Äpfel, Birnen usw. Dann mehr.", ["Äpfel, Birnen usw.", "Dann mehr."]), +]) +def test_split_sentences_abbreviations(text, expected): + sentences = ak.split_sentences(text) + assert sentences == expected + assert " ".join(sentences) == text + + +def test_split_chunks_does_not_end_at_abbreviation(monkeypatch): + monkeypatch.setattr(ak, "CHUNK_SIZE", 3) + chunks = ak.split_chunks("Eins zwei z. B. drei. Vier fünf sechs.") + assert chunks == ["Eins zwei z. B. drei. ", "Vier fünf sechs."] + + def test_group_paragraphs(): assert ak.group_paragraphs(7, {3, 6}) == [(1, 2), (3, 5), (6, 7)] assert ak.group_paragraphs(3, set()) == [(1, 3)]