feat: extract_text.py --fliesstext für lesbaren Fließtext
Umbruch auf 80 Zeichen ohne Worttrennung, auch nicht am Bindestrich. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
0413455ea5
commit
19b6bbf5a2
3 changed files with 39 additions and 3 deletions
|
|
@ -55,6 +55,7 @@ Zwischenschritt im temporären Verzeichnis. Die Spurwahl ist dieselbe wie bei
|
||||||
```bash
|
```bash
|
||||||
./extract_text.py -u 'https://www.youtube.com/watch?v=rm6lADgf6GQ' # → output.txt
|
./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 -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
|
./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 |
|
| `-u`, `--url` | YouTube-URL, Pflicht |
|
||||||
| `-l`, `--lang` | Sprache; Standard: Sprache des Original-Audios (`<sprache>-orig`) |
|
| `-l`, `--lang` | Sprache; Standard: Sprache des Original-Audios (`<sprache>-orig`) |
|
||||||
| `-o`, `--output` | Ausgabedatei (Verzeichnisse werden angelegt, vorhandene Datei überschrieben); Standard: `output.txt` |
|
| `-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`
|
### Untertitel herunterladen: `yt_untertitel.py`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,15 @@ from pathlib import Path
|
||||||
import argparse
|
import argparse
|
||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import textwrap
|
||||||
|
|
||||||
from vtt2txt import vtt_to_lines
|
from vtt2txt import vtt_to_lines
|
||||||
from yt_untertitel import (
|
from yt_untertitel import (
|
||||||
YT_DLP, choose_track, download, fetch_info, no_track_message, original_language,
|
YT_DLP, choose_track, download, fetch_info, no_track_message, original_language,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
FLOW_WIDTH = 80 # Zeilenbreite für --fliesstext
|
||||||
|
|
||||||
|
|
||||||
def parse_args():
|
def parse_args():
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
|
|
@ -25,6 +28,10 @@ def parse_args():
|
||||||
"-o", "--output", type=Path, default=Path("output.txt"),
|
"-o", "--output", type=Path, default=Path("output.txt"),
|
||||||
help="Ausgabedatei (Standard: 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()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.output.is_dir():
|
if args.output.is_dir():
|
||||||
|
|
@ -60,10 +67,21 @@ def main():
|
||||||
if not lines:
|
if not lines:
|
||||||
raise SystemExit("Die Untertitel enthalten keinen Text.")
|
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.parent.mkdir(parents=True, exist_ok=True)
|
||||||
args.output.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
args.output.write_text(text + "\n", encoding="utf-8")
|
||||||
words = sum(len(line.split()) for line in lines)
|
print(
|
||||||
print(f"Gespeichert: {args.output} ({len(lines)} Zeilen, {words} Wörter)")
|
f"Gespeichert: {args.output} "
|
||||||
|
f"({len(text.splitlines())} Zeilen, {len(text.split())} Wörter)"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
|
|
@ -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"]
|
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):
|
def test_explicit_language_prefers_manual(fake_yt_dlp, tmp_path, monkeypatch):
|
||||||
run = fake_yt_dlp(GERMAN_VIDEO, vtt=VTT)
|
run = fake_yt_dlp(GERMAN_VIDEO, vtt=VTT)
|
||||||
monkeypatch.setattr(yt.subprocess, "run", run)
|
monkeypatch.setattr(yt.subprocess, "run", run)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue