From 19b6bbf5a2bef6b63a03ac00fe33907f53c11dd3 Mon Sep 17 00:00:00 2001 From: dschlueter Date: Sat, 10 Oct 2026 19:53:42 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20extract=5Ftext.py=20--fliesstext=20f?= =?UTF-8?q?=C3=BCr=20lesbaren=20Flie=C3=9Ftext?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Umbruch auf 80 Zeichen ohne Worttrennung, auch nicht am Bindestrich. Co-Authored-By: Claude Opus 5.5 --- README.md | 2 ++ extract_text.py | 24 +++++++++++++++++++++--- tests/test_extract_text.py | 16 ++++++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 57a0a26..84c9d78 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ Zwischenschritt im temporären Verzeichnis. Die Spurwahl ist dieselbe wie bei ```bash ./extract_text.py -u 'https://www.youtube.com/watch?v=rm6lADgf6GQ' # → output.txt ./extract_text.py -u URL -l de -o testdaten/rede.txt # Sprache und Ziel +./extract_text.py -u URL -f -o rede.txt # lesbarer Fließtext ./rede_korrigieren.py -i output.txt # → output.korrigiert.txt ``` @@ -63,6 +64,7 @@ Zwischenschritt im temporären Verzeichnis. Die Spurwahl ist dieselbe wie bei | `-u`, `--url` | YouTube-URL, Pflicht | | `-l`, `--lang` | Sprache; Standard: Sprache des Original-Audios (`-orig`) | | `-o`, `--output` | Ausgabedatei (Verzeichnisse werden angelegt, vorhandene Datei überschrieben); Standard: `output.txt` | +| `-f`, `--fliesstext` | Fließtext, auf 80 Zeichen umbrochen (Wörter werden nie getrennt), statt der kurzen Untertitelzeilen; beides versteht `rede_korrigieren.py` | ### Untertitel herunterladen: `yt_untertitel.py` diff --git a/extract_text.py b/extract_text.py index 20b9eb0..4b27e59 100755 --- a/extract_text.py +++ b/extract_text.py @@ -5,12 +5,15 @@ from pathlib import Path import argparse import shutil import tempfile +import textwrap from vtt2txt import vtt_to_lines from yt_untertitel import ( YT_DLP, choose_track, download, fetch_info, no_track_message, original_language, ) +FLOW_WIDTH = 80 # Zeilenbreite für --fliesstext + def parse_args(): parser = argparse.ArgumentParser( @@ -25,6 +28,10 @@ def parse_args(): "-o", "--output", type=Path, default=Path("output.txt"), help="Ausgabedatei (Standard: output.txt)", ) + parser.add_argument( + "-f", "--fliesstext", action="store_true", + help=f"Fließtext, auf {FLOW_WIDTH} Zeichen umbrochen, statt Untertitelzeilen", + ) args = parser.parse_args() if args.output.is_dir(): @@ -60,10 +67,21 @@ def main(): if not lines: raise SystemExit("Die Untertitel enthalten keinen Text.") + if args.fliesstext: + # Never split words, not even at hyphens ("EU-Gipfel") + text = textwrap.fill( + " ".join(lines), width=FLOW_WIDTH, + break_long_words=False, break_on_hyphens=False, + ) + else: + text = "\n".join(lines) + args.output.parent.mkdir(parents=True, exist_ok=True) - args.output.write_text("\n".join(lines) + "\n", encoding="utf-8") - words = sum(len(line.split()) for line in lines) - print(f"Gespeichert: {args.output} ({len(lines)} Zeilen, {words} Wörter)") + args.output.write_text(text + "\n", encoding="utf-8") + print( + f"Gespeichert: {args.output} " + f"({len(text.splitlines())} Zeilen, {len(text.split())} Wörter)" + ) if __name__ == "__main__": diff --git a/tests/test_extract_text.py b/tests/test_extract_text.py index 8e70313..35d4d61 100644 --- a/tests/test_extract_text.py +++ b/tests/test_extract_text.py @@ -46,6 +46,22 @@ def test_default_language_is_original_audio(fake_yt_dlp, tmp_path, monkeypatch, assert [p.name for p in tmp_path.iterdir()] == ["output.txt"] +def test_fliesstext(fake_yt_dlp, tmp_path, monkeypatch, capsys): + long_vtt = "WEBVTT\n\n" + "".join( + f"00:00:{i:02d}.000 --> 00:00:{i + 1:02d}.000\nSatz Nummer {i} zum EU-Gipfel.\n\n" + for i in range(20) + ) + monkeypatch.setattr(yt.subprocess, "run", fake_yt_dlp(GERMAN_VIDEO, vtt=long_vtt)) + target = tmp_path / "rede.txt" + run_main(monkeypatch, "-u", URL, "-f", "-o", target) + + lines = target.read_text(encoding="utf-8").splitlines() + assert 1 < len(lines) < 20 + assert all(len(line) <= extract_text.FLOW_WIDTH for line in lines) + assert " ".join(lines) == " ".join(f"Satz Nummer {i} zum EU-Gipfel." for i in range(20)) + assert "100 Wörter" in capsys.readouterr().out + + def test_explicit_language_prefers_manual(fake_yt_dlp, tmp_path, monkeypatch): run = fake_yt_dlp(GERMAN_VIDEO, vtt=VTT) monkeypatch.setattr(yt.subprocess, "run", run)