#!/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 ("-orig") is used: plain "" 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 download(url, track, is_auto, target_base): """Download one subtitle track; yt-dlp writes ..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: 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 '–'}" ) 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()