"""Tests for vtt2txt.py.""" import shutil import sys from pathlib import Path import pytest import vtt2txt REPO = Path(__file__).resolve().parent.parent SAMPLE_VTT = REPO / "testdaten" / "ramstein.de.vtt" SAMPLE_TXT = REPO / "testdaten" / "ramstein.de.txt" @pytest.fixture def sample_copy(tmp_path): target = tmp_path / SAMPLE_VTT.name shutil.copy(SAMPLE_VTT, target) return target def cue(start, *lines): return f"00:00:{start:02d}.000 --> 00:00:{start + 1:02d}.000\n" + "\n".join(lines) + "\n\n" def run_main(monkeypatch, *args): monkeypatch.setattr(sys, "argv", ["vtt2txt.py", *map(str, args)]) vtt2txt.main() def test_vtt_to_lines_strips_tags_and_rolling_duplicates(): vtt = ( "WEBVTT\nKind: captions\n\n" "00:00:01.000 --> 00:00:02.000 align:start\n" "Erste<00:00:01.500> Zeile\n\n" "00:00:02.000 --> 00:00:03.000\nErste Zeile\n \n\n" "cue-id\n00:00:03.000 --> 00:00:04.000\nErste Zeile\nA & B\n" ) assert vtt2txt.vtt_to_lines(vtt) == ["Erste Zeile", "A & B"] def test_vtt_to_lines_keeps_text_after_space_only_line(): # YouTube cues start with a line holding a single space; the text after it # must survive even if no later cue repeats it (here: end of file) vtt = "WEBVTT\n\n00:00:01.000 --> 00:00:02.000\n \n[applaus]\n" assert vtt2txt.vtt_to_lines(vtt) == ["[applaus]"] def test_vtt_to_lines_skips_note_and_normalizes_whitespace(): vtt = ( "WEBVTT\n\nNOTE Kommentar\nnicht übernehmen\n\n" "00:00:01.000 --> 00:00:02.000\nA  B\n\n" "00:00:02.000 --> 00:00:03.000\nA B\nC\n" ) assert vtt2txt.vtt_to_lines(vtt) == ["A B", "C"] def test_vtt_to_lines_keeps_real_repetition(): # Rolling window: each cue repeats the previous line, then adds new text. # The speaker really says "Nein." twice; both must survive. vtt = "WEBVTT\n\n" + "".join([ cue(1, " ", "Er sagt:"), cue(2, "Er sagt:", " "), cue(3, "Er sagt:", "Nein."), cue(4, "Nein.", " "), cue(5, "Nein.", "Nein."), cue(6, " ", " "), cue(7, "Nein.", "Ende."), ]) assert vtt2txt.vtt_to_lines(vtt) == ["Er sagt:", "Nein.", "Nein.", "Ende."] def test_vtt_to_lines_without_rolling_window(): vtt = "WEBVTT\n\n1\n" + cue(1, "A", "B") + "2\n" + cue(2, "C") assert vtt2txt.vtt_to_lines(vtt) == ["A", "B", "C"] def test_main_default_output_matches_sample(sample_copy, monkeypatch, capsys): run_main(monkeypatch, "-i", sample_copy) target = sample_copy.with_name("ramstein.de.txt") assert target.read_text(encoding="utf-8") == SAMPLE_TXT.read_text(encoding="utf-8") assert f"Erstellt: {target}" in capsys.readouterr().out @pytest.mark.parametrize("name", ["ramstein.de", "magdeburg.de-orig", "Lahnstein.de-orig"]) def test_sample_vtt_gives_sample_txt(name): vtt = (REPO / "testdaten" / f"{name}.vtt").read_text(encoding="utf-8-sig") txt = (REPO / "testdaten" / f"{name}.txt").read_text(encoding="utf-8") assert vtt2txt.vtt_to_lines(vtt) == txt.splitlines() def test_main_multiple_inputs(sample_copy, monkeypatch): second = sample_copy.with_name("rede.en.vtt") shutil.copy(sample_copy, second) run_main(monkeypatch, "-i", sample_copy, second) assert sample_copy.with_suffix(".txt").is_file() assert second.with_suffix(".txt").is_file() def test_main_expands_glob_without_duplicates(sample_copy, monkeypatch, capsys): shutil.copy(sample_copy, sample_copy.with_name("ramstein.en.vtt")) shutil.copy(sample_copy, sample_copy.with_name("anders.de.vtt")) run_main(monkeypatch, "-i", sample_copy.parent / "ramstein.*.vtt", sample_copy) out = capsys.readouterr().out assert out.count("Erstellt:") == 2 assert not sample_copy.with_name("anders.de.txt").exists() @pytest.mark.parametrize("make_dir, suffix", [(True, ""), (False, "/")]) def test_main_output_directory(sample_copy, monkeypatch, make_dir, suffix): second = sample_copy.with_name("rede.en.vtt") shutil.copy(sample_copy, second) out_dir = sample_copy.parent / "out" if make_dir: out_dir.mkdir() run_main(monkeypatch, "-i", sample_copy, second, "-o", f"{out_dir}{suffix}") assert (out_dir / "ramstein.de.txt").is_file() assert (out_dir / "rede.en.txt").is_file() def test_main_output_option_creates_directories(sample_copy, monkeypatch): target = sample_copy.parent / "neu" / "unter" / "rede.txt" run_main(monkeypatch, "-i", sample_copy, "-o", target) assert target.is_file() @pytest.mark.parametrize("content, message", [ ("WEBVTT\n".encode("utf-8"), "Keine Untertitel"), ("WEBVTT\n\n00:00:01.000 --> 00:00:02.000\nGrüße\n".encode("latin-1"), "Keine UTF-8-Datei"), ]) def test_main_bad_file_continues_batch(sample_copy, monkeypatch, capsys, content, message): bad = sample_copy.with_name("aaa.vtt") bad.write_bytes(content) with pytest.raises(SystemExit) as exc: run_main(monkeypatch, "-i", bad, sample_copy) assert exc.value.code == 1 assert message in capsys.readouterr().err assert not bad.with_suffix(".txt").exists() assert sample_copy.with_suffix(".txt").is_file() @pytest.mark.parametrize("args, message", [ ([], "required"), (["-i", "gibtsnicht.vtt"], "Eingabedatei nicht gefunden"), (["-i", "{txt}"], "Keine VTT-Datei"), (["-i", "{vtt}", "{vtt2}", "-o", "{dir}/x.txt"], "muss -o/--output ein Verzeichnis sein"), (["-i", "{dir}/*.gibtsnicht"], "Keine Datei passt zu"), (["-i", "{vtt}", "{dir}/sub/ramstein.de.vtt", "-o", "{dir}/"], "dieselbe Ausgabedatei"), (["-i", "{vtt}", "-o", "{vtt}"], "nicht die Eingabedatei"), ]) def test_cli_errors(sample_copy, monkeypatch, capsys, args, message): txt = sample_copy.with_suffix(".txt") txt.write_text("x\n", encoding="utf-8") vtt2 = sample_copy.with_name("rede.en.vtt") shutil.copy(sample_copy, vtt2) (sample_copy.parent / "sub").mkdir() shutil.copy(sample_copy, sample_copy.parent / "sub" / sample_copy.name) args = [a.format(vtt=sample_copy, vtt2=vtt2, txt=txt, dir=sample_copy.parent) for a in args] with pytest.raises(SystemExit) as exc: run_main(monkeypatch, *args) assert exc.value.code == 2 assert message in capsys.readouterr().err