118 lines
4 KiB
Python
118 lines
4 KiB
Python
|
|
#!/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()
|