youtube_abschrift_korrigieren/yt_untertitel.py
dschlueter 0413455ea5 refactor: Spur-Fehlermeldung und yt-dlp-Fake gemeinsam nutzen
- yt_untertitel.no_track_message() für yt_untertitel.py und extract_text.py
- tests/conftest.py: Fixtures fake_yt_dlp und yt_dlp_installed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 19:52:20 +02:00

135 lines
4.6 KiB
Python
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Lädt die Untertitel eines YouTube-Videos als VTT-Datei herunter (über yt-dlp),
als Eingabe für vtt2txt.py bzw. rede_korrigieren.py."""
from pathlib import Path
import argparse
import json
import shutil
import subprocess
YT_DLP = "yt-dlp"
def fetch_info(url):
"""Return the video metadata from yt-dlp, without downloading anything."""
result = subprocess.run(
[YT_DLP, "--dump-single-json", "--skip-download", "--no-playlist", url],
capture_output=True, text=True,
)
if result.returncode != 0:
raise SystemExit(f"yt-dlp konnte das Video nicht abrufen:\n{result.stderr.strip()}")
return json.loads(result.stdout)
def choose_track(info, lang):
"""Pick the subtitle track for `lang`: (track name, is_auto) or None.
Manual subtitles come first. Of the automatic captions only the original
language ("<lang>-orig") is used: plain "<lang>" there is a machine
translation whenever the video is in another language.
"""
if lang in (info.get("subtitles") or {}):
return lang, False
if f"{lang}-orig" in (info.get("automatic_captions") or {}):
return f"{lang}-orig", True
return None
def original_language(info):
"""Language of the video's audio, or None if yt-dlp does not know it.
YouTube marks the automatic captions in the spoken language with "-orig".
"""
for name in info.get("automatic_captions") or {}:
if name.endswith("-orig"):
return name.removesuffix("-orig")
return info.get("language")
def available_tracks(info):
manual = sorted((info.get("subtitles") or {}).keys())
original = sorted(
name.removesuffix("-orig")
for name in (info.get("automatic_captions") or {})
if name.endswith("-orig")
)
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 <target_base>.<track>.vtt."""
command = [
YT_DLP, "--skip-download", "--no-playlist", "--force-overwrites",
"--write-auto-subs" if is_auto else "--write-subs",
"--sub-langs", track, "--sub-format", "vtt",
"-o", f"{target_base}.%(ext)s",
url,
]
result = subprocess.run(command, capture_output=True, text=True)
if result.returncode != 0:
raise SystemExit(f"Download fehlgeschlagen:\n{result.stderr.strip()}")
def parse_args():
parser = argparse.ArgumentParser(
description="Lädt die Untertitel eines YouTube-Videos als VTT-Datei herunter."
)
parser.add_argument("-u", "--url", required=True, help="YouTube-URL des Videos")
parser.add_argument(
"-n", "--name",
help="Dateiname ohne Endung, z. B. Lahnstein (Standard: Video-ID)",
)
parser.add_argument(
"-l", "--lang", default="de", help="Sprache der Untertitel (Standard: de)"
)
parser.add_argument(
"-d", "--dir", type=Path, default=Path("."),
help="Zielverzeichnis (Standard: aktuelles Verzeichnis)",
)
args = parser.parse_args()
if args.name is not None and (not args.name or Path(args.name).name != args.name):
parser.error("-n/--name ist ein Dateiname ohne Verzeichnis; dafür gibt es -d/--dir.")
if shutil.which(YT_DLP) is None:
parser.error(f"{YT_DLP} nicht gefunden (Installation: pip install yt-dlp).")
return args
def main():
args = parse_args()
info = fetch_info(args.url)
print(f"Video: {info.get('title', '?')}")
chosen = choose_track(info, args.lang)
if chosen is None:
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)
args.dir.mkdir(parents=True, exist_ok=True)
target_base = args.dir / (args.name or info["id"])
download(args.url, track, is_auto, target_base)
target = Path(f"{target_base}.{track}.vtt")
if not target.is_file():
raise SystemExit(f"yt-dlp hat keine Datei {target} geschrieben.")
print(f"Gespeichert: {target}")
print(f"Weiter mit: ./vtt2txt.py -i {target}")
if __name__ == "__main__":
main()