diff --git a/extract_text.py b/extract_text.py index c1ce5a2..20b9eb0 100755 --- a/extract_text.py +++ b/extract_text.py @@ -8,7 +8,7 @@ import tempfile from vtt2txt import vtt_to_lines from yt_untertitel import ( - YT_DLP, available_tracks, choose_track, download, fetch_info, original_language, + YT_DLP, choose_track, download, fetch_info, no_track_message, original_language, ) @@ -43,14 +43,7 @@ def main(): lang = args.lang or original_language(info) chosen = choose_track(info, lang) if lang else None if chosen is None: - manual, original = available_tracks(info) - wanted = f"„{lang}“" if lang else "der Originalsprache" - raise SystemExit( - f"Keine Untertitel in {wanted} (weder manuell noch automatisch in " - f"Originalsprache).\n" - f"Manuell: {', '.join(manual) or '–'}\n" - f"Automatisch (Originalsprache): {', '.join(original) or '–'}" - ) + raise SystemExit(no_track_message(info, lang)) track, is_auto = chosen kind = "automatische" if is_auto else "manuelle" print(f"Lade {kind} Untertitel „{track}“ …", flush=True) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..522ed2b --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,42 @@ +"""Shared fixtures: yt-dlp is always replaced by fakes (no network).""" +import json +import shutil +import subprocess +from pathlib import Path + +import pytest + + +@pytest.fixture +def fake_yt_dlp(): + """Factory for a subprocess.run stand-in. + + It answers the info request with `info` and writes `vtt` where yt-dlp would + write the subtitle file. `run.calls` records every command. + """ + def make(info, vtt="WEBVTT\n", write=True, fail_download=False): + calls = [] + + def run(command, capture_output, text): + calls.append(command) + if "--dump-single-json" in command: + return subprocess.CompletedProcess(command, 0, json.dumps(info), "") + if fail_download: + return subprocess.CompletedProcess(command, 1, "", "HTTP Error 429") + track = command[command.index("--sub-langs") + 1] + template = command[command.index("-o") + 1] + if write: + Path(template.replace("%(ext)s", f"{track}.vtt")).write_text( + vtt, encoding="utf-8" + ) + return subprocess.CompletedProcess(command, 0, "", "") + + run.calls = calls + return run + + return make + + +@pytest.fixture +def yt_dlp_installed(monkeypatch): + monkeypatch.setattr(shutil, "which", lambda name: f"/usr/bin/{name}") diff --git a/tests/test_extract_text.py b/tests/test_extract_text.py index 363d6a2..8e70313 100644 --- a/tests/test_extract_text.py +++ b/tests/test_extract_text.py @@ -1,8 +1,5 @@ """Tests for extract_text.py; yt-dlp is always replaced by fakes (no network).""" -import json -import subprocess import sys -from pathlib import Path import pytest @@ -25,26 +22,7 @@ GERMAN_VIDEO = { } -def fake_yt_dlp(info, vtt=VTT): - """Stand-in for subprocess.run: answers the info request, writes the VTT file.""" - calls = [] - - def run(command, capture_output, text): - calls.append(command) - if "--dump-single-json" in command: - return subprocess.CompletedProcess(command, 0, json.dumps(info), "") - track = command[command.index("--sub-langs") + 1] - template = command[command.index("-o") + 1] - Path(template.replace("%(ext)s", f"{track}.vtt")).write_text(vtt, encoding="utf-8") - return subprocess.CompletedProcess(command, 0, "", "") - - run.calls = calls - return run - - -@pytest.fixture(autouse=True) -def yt_dlp_installed(monkeypatch): - monkeypatch.setattr(extract_text.shutil, "which", lambda name: f"/usr/bin/{name}") +pytestmark = pytest.mark.usefixtures("yt_dlp_installed") def run_main(monkeypatch, *args): @@ -52,8 +30,8 @@ def run_main(monkeypatch, *args): extract_text.main() -def test_default_language_is_original_audio(tmp_path, monkeypatch, capsys): - run = fake_yt_dlp(GERMAN_VIDEO) +def test_default_language_is_original_audio(fake_yt_dlp, tmp_path, monkeypatch, capsys): + run = fake_yt_dlp(GERMAN_VIDEO, vtt=VTT) monkeypatch.setattr(yt.subprocess, "run", run) monkeypatch.chdir(tmp_path) run_main(monkeypatch, "-u", URL) @@ -68,8 +46,8 @@ def test_default_language_is_original_audio(tmp_path, monkeypatch, capsys): assert [p.name for p in tmp_path.iterdir()] == ["output.txt"] -def test_explicit_language_prefers_manual(tmp_path, monkeypatch): - run = fake_yt_dlp(GERMAN_VIDEO) +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) target = tmp_path / "neu" / "rede.en.txt" run_main(monkeypatch, "-u", URL, "-l", "en", "-o", target) @@ -78,9 +56,11 @@ def test_explicit_language_prefers_manual(tmp_path, monkeypatch): assert "--write-subs" in run.calls[1] -def test_original_language_from_metadata_without_auto_captions(tmp_path, monkeypatch): +def test_original_language_from_metadata_without_auto_captions( + fake_yt_dlp, tmp_path, monkeypatch +): info = {"id": "x", "language": "fr", "subtitles": {"fr": []}, "automatic_captions": {}} - run = fake_yt_dlp(info) + run = fake_yt_dlp(info, vtt=VTT) monkeypatch.setattr(yt.subprocess, "run", run) run_main(monkeypatch, "-u", URL, "-o", tmp_path / "out.txt") assert run.calls[1][run.calls[1].index("--sub-langs") + 1] == "fr" @@ -91,14 +71,14 @@ def test_original_language_from_metadata_without_auto_captions(tmp_path, monkeyp (GERMAN_VIDEO, ["-l", "fr"], "Keine Untertitel in „fr“"), ({"id": "x", "subtitles": {}, "automatic_captions": {}}, [], "Originalsprache"), ]) -def test_no_matching_track(tmp_path, monkeypatch, info, args, message): - monkeypatch.setattr(yt.subprocess, "run", fake_yt_dlp(info)) +def test_no_matching_track(fake_yt_dlp, tmp_path, monkeypatch, info, args, message): + monkeypatch.setattr(yt.subprocess, "run", fake_yt_dlp(info, vtt=VTT)) with pytest.raises(SystemExit, match=message): run_main(monkeypatch, "-u", URL, "-o", tmp_path / "out.txt", *args) assert not (tmp_path / "out.txt").exists() -def test_empty_subtitles(tmp_path, monkeypatch): +def test_empty_subtitles(fake_yt_dlp, tmp_path, monkeypatch): monkeypatch.setattr(yt.subprocess, "run", fake_yt_dlp(GERMAN_VIDEO, vtt="WEBVTT\n")) with pytest.raises(SystemExit, match="keinen Text"): run_main(monkeypatch, "-u", URL, "-o", tmp_path / "out.txt") diff --git a/tests/test_yt_untertitel.py b/tests/test_yt_untertitel.py index 9afc96a..1088043 100644 --- a/tests/test_yt_untertitel.py +++ b/tests/test_yt_untertitel.py @@ -1,8 +1,6 @@ """Tests for yt_untertitel.py; yt-dlp is always replaced by fakes (no network).""" -import json import subprocess import sys -from pathlib import Path import pytest @@ -18,31 +16,7 @@ AUTO_ONLY = { } -def fake_yt_dlp(info, write=True, fail_download=False): - """Stand-in for subprocess.run: answers the info request, writes the VTT file.""" - calls = [] - - def run(command, capture_output, text): - calls.append(command) - if "--dump-single-json" in command: - return subprocess.CompletedProcess(command, 0, json.dumps(info), "") - if fail_download: - return subprocess.CompletedProcess(command, 1, "", "HTTP Error 429") - track = command[command.index("--sub-langs") + 1] - template = command[command.index("-o") + 1] - if write: - Path(template.replace("%(ext)s", f"{track}.vtt")).write_text( - "WEBVTT\n", encoding="utf-8" - ) - return subprocess.CompletedProcess(command, 0, "", "") - - run.calls = calls - return run - - -@pytest.fixture(autouse=True) -def yt_dlp_installed(monkeypatch): - monkeypatch.setattr(yt.shutil, "which", lambda name: f"/usr/bin/{name}") +pytestmark = pytest.mark.usefixtures("yt_dlp_installed") def run_main(monkeypatch, *args): @@ -79,7 +53,7 @@ def test_available_tracks(): # === Command line === -def test_main_downloads_auto_original(tmp_path, monkeypatch, capsys): +def test_main_downloads_auto_original(fake_yt_dlp, tmp_path, monkeypatch, capsys): run = fake_yt_dlp(AUTO_ONLY) monkeypatch.setattr(yt.subprocess, "run", run) run_main(monkeypatch, "-u", URL, "-n", "Rede", "-d", tmp_path / "neu") @@ -93,7 +67,7 @@ def test_main_downloads_auto_original(tmp_path, monkeypatch, capsys): assert "vtt2txt.py -i" in out -def test_main_prefers_manual_and_defaults_to_video_id(tmp_path, monkeypatch): +def test_main_prefers_manual_and_defaults_to_video_id(fake_yt_dlp, tmp_path, monkeypatch): info = dict(AUTO_ONLY, subtitles={"de": []}) run = fake_yt_dlp(info) monkeypatch.setattr(yt.subprocess, "run", run) @@ -103,7 +77,7 @@ def test_main_prefers_manual_and_defaults_to_video_id(tmp_path, monkeypatch): assert "--write-subs" in run.calls[1] -def test_main_no_matching_track(tmp_path, monkeypatch): +def test_main_no_matching_track(fake_yt_dlp, tmp_path, monkeypatch): monkeypatch.setattr(yt.subprocess, "run", fake_yt_dlp(AUTO_ONLY)) with pytest.raises(SystemExit, match="Automatisch \\(Originalsprache\\): de"): run_main(monkeypatch, "-u", URL, "-l", "en", "-d", tmp_path) @@ -122,7 +96,7 @@ def test_main_info_error(tmp_path, monkeypatch): ({"fail_download": True}, "HTTP Error 429"), ({"write": False}, "keine Datei"), ]) -def test_main_download_errors(tmp_path, monkeypatch, kwargs, message): +def test_main_download_errors(fake_yt_dlp, tmp_path, monkeypatch, kwargs, message): monkeypatch.setattr(yt.subprocess, "run", fake_yt_dlp(AUTO_ONLY, **kwargs)) with pytest.raises(SystemExit, match=message): run_main(monkeypatch, "-u", URL, "-d", tmp_path) diff --git a/yt_untertitel.py b/yt_untertitel.py index 11f0eb1..d695ac9 100755 --- a/yt_untertitel.py +++ b/yt_untertitel.py @@ -56,6 +56,18 @@ def available_tracks(info): return manual, original +def no_track_message(info, lang): + """Explain that no usable track exists for `lang` (None: original language).""" + manual, original = available_tracks(info) + wanted = f"„{lang}“" if lang else "der Originalsprache" + return ( + f"Keine Untertitel in {wanted} (weder manuell noch automatisch in " + f"Originalsprache).\n" + f"Manuell: {', '.join(manual) or '–'}\n" + f"Automatisch (Originalsprache): {', '.join(original) or '–'}" + ) + + def download(url, track, is_auto, target_base): """Download one subtitle track; yt-dlp writes ..vtt.""" command = [ @@ -103,13 +115,7 @@ def main(): chosen = choose_track(info, args.lang) if chosen is None: - manual, original = available_tracks(info) - raise SystemExit( - f"Keine Untertitel in „{args.lang}“ (weder manuell noch automatisch in " - f"Originalsprache).\n" - f"Manuell: {', '.join(manual) or '–'}\n" - f"Automatisch (Originalsprache): {', '.join(original) or '–'}" - ) + raise SystemExit(no_track_message(info, args.lang)) track, is_auto = chosen kind = "automatische" if is_auto else "manuelle" print(f"Lade {kind} Untertitel „{track}“ …", flush=True)