youtube_abschrift_korrigieren/yt_untertitel.py
dschlueter 903aa92b09 feat: YouTube-Untertitel herunterladen (yt_untertitel.py)
- yt_untertitel.py lädt über yt-dlp die Untertitel eines Videos als VTT:
  manuelle Untertitel in der Sprache, sonst automatische in Originalsprache
  (<lang>-orig); Maschinenübersetzungen werden nie genommen
- Tests ohne Netz (yt-dlp durch Fakes ersetzt)
- Testdaten: Lahnstein-Rede (VTT und daraus erzeugter Rohtext)
- README: Ablauf URL → VTT → TXT → Korrektur

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

118 lines
4 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 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 <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:
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()