feat: extract_text.py – YouTube-URL → Textdatei in einem Schritt
- Kombiniert yt_untertitel.py und vtt2txt.py; die VTT bleibt temporär - Standardsprache ist die des Original-Audios (original_language()) - -o Ausgabedatei, Standard output.txt - Tests ohne Netz, README Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
b0b79515ca
commit
ba61434798
1 changed files with 246 additions and 5 deletions
116
tests/test_extract_text.py
Normal file
116
tests/test_extract_text.py
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
"""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
|
||||
|
||||
import extract_text
|
||||
import yt_untertitel as yt
|
||||
|
||||
URL = "https://www.youtube.com/watch?v=abc123"
|
||||
|
||||
VTT = (
|
||||
"WEBVTT\n\n"
|
||||
"00:00:01.000 --> 00:00:02.000\n \nGuten<00:00:01.500><c> Abend.</c>\n\n"
|
||||
"00:00:02.000 --> 00:00:03.000\nGuten Abend.\nSchön, dass Sie da sind.\n"
|
||||
)
|
||||
|
||||
GERMAN_VIDEO = {
|
||||
"id": "abc123",
|
||||
"title": "Eine Rede",
|
||||
"subtitles": {"en": []},
|
||||
"automatic_captions": {"de-orig": [], "de": [], "en": []},
|
||||
}
|
||||
|
||||
|
||||
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}")
|
||||
|
||||
|
||||
def run_main(monkeypatch, *args):
|
||||
monkeypatch.setattr(sys, "argv", ["extract_text.py", *map(str, args)])
|
||||
extract_text.main()
|
||||
|
||||
|
||||
def test_default_language_is_original_audio(tmp_path, monkeypatch, capsys):
|
||||
run = fake_yt_dlp(GERMAN_VIDEO)
|
||||
monkeypatch.setattr(yt.subprocess, "run", run)
|
||||
monkeypatch.chdir(tmp_path)
|
||||
run_main(monkeypatch, "-u", URL)
|
||||
|
||||
text = (tmp_path / "output.txt").read_text(encoding="utf-8")
|
||||
assert text == "Guten Abend.\nSchön, dass Sie da sind.\n"
|
||||
assert "de-orig" in run.calls[1]
|
||||
assert "--write-auto-subs" in run.calls[1]
|
||||
out = capsys.readouterr().out
|
||||
assert "output.txt (2 Zeilen, 7 Wörter)" in out
|
||||
# Only the text file remains, no VTT
|
||||
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)
|
||||
monkeypatch.setattr(yt.subprocess, "run", run)
|
||||
target = tmp_path / "neu" / "rede.en.txt"
|
||||
run_main(monkeypatch, "-u", URL, "-l", "en", "-o", target)
|
||||
|
||||
assert target.is_file()
|
||||
assert "--write-subs" in run.calls[1]
|
||||
|
||||
|
||||
def test_original_language_from_metadata_without_auto_captions(tmp_path, monkeypatch):
|
||||
info = {"id": "x", "language": "fr", "subtitles": {"fr": []}, "automatic_captions": {}}
|
||||
run = fake_yt_dlp(info)
|
||||
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"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("info, args, message", [
|
||||
# Translation only: no "en-orig", no manual "en"
|
||||
(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))
|
||||
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):
|
||||
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")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("args, message", [
|
||||
([], "required"),
|
||||
(["-u", URL, "-o", "{dir}"], "ist ein Verzeichnis"),
|
||||
])
|
||||
def test_cli_errors(tmp_path, monkeypatch, capsys, args, message):
|
||||
args = [a.format(dir=tmp_path) 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
|
||||
Loading…
Reference in a new issue