From d9512bcedc088d5bb43ea88250624bf5492f93a1 Mon Sep 17 00:00:00 2001 From: dschlueter Date: Sun, 11 Oct 2026 21:58:12 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Hinweis,=20wenn=20ein=20Zeitbereich=20?= =?UTF-8?q?=C3=BCber=20das=20Ende=20der=20Untertitel=20reicht?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit „-z 0-05:00“ bedeutet fünf Stunden und bearbeitete bisher stillschweigend das ganze Video. vtt2txt.py (und damit extract_text.py und abschrift_korrigieren.py) nennt jetzt das Ende der Untertitel, bei Bereichen ab einer Stunde mit dem Tipp „Minute 5 ist 00:05“. Co-Authored-By: Claude Opus 5.5 --- BEDIENUNGSANLEITUNG.md | 5 ++++- README.md | 4 +++- tests/test_vtt2txt.py | 25 ++++++++++++++++++++++++- vtt2txt.py | 28 ++++++++++++++++++++++++++-- 4 files changed, 57 insertions(+), 5 deletions(-) diff --git a/BEDIENUNGSANLEITUNG.md b/BEDIENUNGSANLEITUNG.md index d7dc224..0f044ca 100644 --- a/BEDIENUNGSANLEITUNG.md +++ b/BEDIENUNGSANLEITUNG.md @@ -559,6 +559,8 @@ Option kennen `extract_text.py` und `vtt2txt.py`. > **Vorsicht:** `05:00` bedeutet 5 Stunden, nicht 5 Minuten. Für „5 Minuten“ > `00:05` oder `5` schreiben, für 5 Minuten 30 Sekunden `00:05:30`. +> Reicht ein Bereich über das Ende des Videos hinaus, weist das Programm darauf hin +> und nennt die Länge der Untertitel. ### 10.2 Bereiche @@ -712,7 +714,8 @@ Weitere Einstellungen (Abschnittsgröße, Absatzlängen, Schwellen) sind in der | `Abschnitt 3 wurde abgeschnitten. CHUNK_SIZE verkleinern und erneut starten.` | Die Antwort des Modells war zu lang. `CHUNK_SIZE` oben in `abschrift_korrigieren.py` verkleinern (z. B. 900). | | `Absatzgliederung: unbrauchbare Antwort: …` / `Absätze verbinden: unbrauchbare Antwort: …` | Das Modell hat kein gültiges JSON geliefert; selten. Erneut starten, die Korrektur selbst ist zwischengespeichert. Tritt es wiederholt auf, ein anderes Modell versuchen. | | `Hinweis: keine Wortliste für „de“ (Paket wngerman); …` | `sudo apt install wngerman` (für Englisch `wamerican`). Ohne Wortliste fehlt im Bericht nur der Grund „gültiges Wort ersetzt“. | -| `Hinweis: keine Untertitel im Bereich …` | Einer von mehreren Bereichen ist leer und wird übersprungen. | +| `Hinweis: keine Untertitel im Bereich …` | Einer von mehreren Bereichen ist leer und wird übersprungen. In Klammern steht, wo die Untertitel enden. | +| `Hinweis: Bereich Anfang – 05:00:00 reicht über das Ende der Untertitel hinaus (…)` | Der Bereich endet nach dem Video. Meist war Minute statt Stunde gemeint: `05:00` ist Stunde 5, Minute 5 ist `00:05`. Bearbeitet wird trotzdem, bis zum Ende des Videos. | | `Keine Berechtigung` beim Start mit `./…` | `chmod +x *.py` oder `python3 programm.py …` aufrufen. | | Lauf ist extrem langsam | Das Modell passt nicht in den Grafikspeicher. `OLLAMA_NUM_PARALLEL=1` setzen (siehe [Abschnitt 2.2](#22-grafikkarte)), andere Programme mit Grafikspeicherbedarf schließen oder ein kleineres Modell wählen. Prüfen mit `ollama ps` (Spalte PROCESSOR sollte „100% GPU“ zeigen). | diff --git a/README.md b/README.md index 7a1e834..6deb17b 100644 --- a/README.md +++ b/README.md @@ -182,7 +182,9 @@ Gilt gleich für `vtt2txt.py`, `extract_text.py` und `abschrift_korrigieren.py`. | `25` | Minute 25 | Achtung: Zwei Zahlen sind Stunden:Minuten, `05:00` ist also Stunde 5, nicht -Minute 5 (dafür `00:05` oder `5`). +Minute 5 (dafür `00:05` oder `5`). Reicht ein Bereich über die letzte +Untertitelzeile hinaus, nennt ein Hinweis das Ende der Untertitel, bei Bereichen ab +einer Stunde mit diesem Tipp. | Aufruf | Text | |---|---| diff --git a/tests/test_vtt2txt.py b/tests/test_vtt2txt.py index 69ad399..ff3c4f6 100644 --- a/tests/test_vtt2txt.py +++ b/tests/test_vtt2txt.py @@ -366,10 +366,33 @@ def test_vtt_to_text_skips_empty_range(capsys): assert text == "[00:01:40 - 00:01:41]\nErster Satz ist hier." out = capsys.readouterr().out assert "keine Untertitel im Bereich 00:00:00 – 00:00:50" in out - assert "keine Untertitel im Bereich 00:03:20 – Ende" in out + assert "keine Untertitel im Bereich 00:03:20 – Ende (die Untertitel enden bei" in out assert vtt2txt.vtt_to_text(SPEECH, [(200, None)]) == "" +def test_vtt_to_text_notes_range_past_last_caption(capsys): + # "-z 0-05:00" means five hours, not five minutes + text = vtt2txt.vtt_to_text(SPEECH, [(0, 5 * 3600)]) + assert text.startswith("[00:01:40 - ") + out = capsys.readouterr().out + assert "Bereich 00:00:00 – 05:00:00 reicht über das Ende der Untertitel hinaus" in out + assert "Stunden:Minuten, Minute 5 ist 00:05" in out + + +@pytest.mark.parametrize("start, end, expected", [ + (100, 110, ""), # inside + (None, None, ""), + (100, 200, " (die Untertitel enden bei 00:02:00)"), + (150, None, " (die Untertitel enden bei 00:02:00)"), + (0, 3600, " (die Untertitel enden bei 00:02:00; zwei Zahlen wie 05:00 sind " + "Stunden:Minuten, Minute 5 ist 00:05)"), + (0, 200, ""), # no captions at all: no end known +]) +def test_end_hint(start, end, expected): + captions_end = None if (start, end) == (0, 200) else 120 + assert vtt2txt.end_hint(start, end, captions_end) == expected + + def test_vtt_to_text_without_ranges_has_no_header(): assert vtt2txt.vtt_to_text(SPEECH).splitlines()[0] == "Erster Satz ist hier." diff --git a/vtt2txt.py b/vtt2txt.py index 4e7f2af..f6c2764 100755 --- a/vtt2txt.py +++ b/vtt2txt.py @@ -307,18 +307,42 @@ def range_label(start, end): return f"{left} – {right}" +def end_hint(start, end, captions_end): + """Note for a range reaching past the last caption, else ""; two numbers + are hours:minutes, so "05:00" for minute 5 is a likely slip.""" + if captions_end is None: + return "" + past_end = (end is not None and end > captions_end) or ( + start is not None and start >= captions_end + ) + if not past_end: + return "" + hint = f" (die Untertitel enden bei {format_time(captions_end)}" + if max(start or 0, end or 0) >= 3600 > captions_end: + hint += "; zwei Zahlen wie 05:00 sind Stunden:Minuten, Minute 5 ist 00:05" + return hint + ")" + + def excerpts(timed, ranges, exact=False): """Return (start, end in seconds, lines) of the text block of each time range. Blocks that overlap after growing to whole sentences are merged; ranges - without captions are skipped with a note. + without captions are skipped with a note, ranges reaching past the last + caption get one. """ + captions_end = max((end for _, end, _ in timed if end is not None), default=None) bounds = [] for start, end in ranges: found = excerpt_bounds(timed, start, end, exact) + hint = end_hint(start, end, captions_end) if found is None: - print(f"Hinweis: keine Untertitel im Bereich {range_label(start, end)}") + print(f"Hinweis: keine Untertitel im Bereich {range_label(start, end)}{hint}") continue + if hint: + print( + f"Hinweis: Bereich {range_label(start, end)} reicht über das Ende " + f"der Untertitel hinaus{hint}" + ) if bounds and found[0] < bounds[-1][1]: bounds[-1] = (bounds[-1][0], max(bounds[-1][1], found[1])) else: