Files
playground/youtube_transcript_v2.py
T
abdus c9228ae4a7 feat(youtube_transcript_v2): Add --language/--auto options and unwrap output text
Auto mode prioritizes de/en/tr and falls back to the first available
transcript language; a shorter error is shown when the default fetch fails.
2026-07-26 11:08:02 +02:00

155 lines
4.9 KiB
Python
Executable File

#!/usr/bin/env -S uv run --script
# /// script
# dependencies = [
# "youtube-transcript-api",
# ]
# ///
import argparse
import logging
import sys
from pathlib import Path
from urllib.parse import urlparse, parse_qs
from youtube_transcript_api import YouTubeTranscriptApi
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s: %(message)s")
def extract_video_id(url: str) -> str:
parsed = urlparse(url)
if parsed.hostname in ("youtu.be", "www.youtu.be"):
return parsed.path.lstrip("/")
if parsed.hostname in ("youtube.com", "www.youtube.com", "m.youtube.com"):
if parsed.path == "/watch":
return parse_qs(parsed.query)["v"][0]
elif parsed.path.startswith("/embed/"):
return parsed.path.split("/")[2]
elif parsed.path.startswith("/v/"):
return parsed.path.split("/")[2]
raise ValueError(f"Could not extract video ID from URL: {url}")
def format_timestamp(seconds: float) -> str:
hours = int(seconds // 3600)
minutes = int((seconds % 3600) // 60)
secs = int(seconds % 60)
millis = int((seconds % 1) * 1000)
return f"{hours:02d}:{minutes:02d}:{secs:02d},{millis:03d}"
def transcript_to_srt(transcript: list[dict]) -> str:
srt_lines = []
for i, entry in enumerate(transcript, start=1):
start_time = format_timestamp(entry["start"])
end_time = format_timestamp(entry["start"] + entry["duration"])
text = entry["text"]
srt_lines.append(f"{i}")
srt_lines.append(f"{start_time} --> {end_time}")
srt_lines.append(text)
srt_lines.append("")
return "\n".join(srt_lines)
PRIORITY_LANGUAGES = ("de", "en", "tr")
def pick_auto_language(transcript_list) -> str:
available = {t.language_code: t for t in transcript_list}
if not available:
raise ValueError("No transcripts available for this video")
for lang in PRIORITY_LANGUAGES:
if lang in available:
return lang
return next(iter(available))
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Fetch YouTube video transcripts",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument("url", help="YouTube video URL")
parser.add_argument(
"--save-srt",
type=Path,
metavar="FILE_PATH",
help="Save transcript as SRT file to the specified path",
)
lang_group = parser.add_mutually_exclusive_group()
lang_group.add_argument(
"--language",
metavar="LANG",
help="Fetch transcript in the given language code (exits with error if unavailable)",
)
lang_group.add_argument(
"--auto",
action="store_true",
help=f"Auto-pick a language, preferring {', '.join(PRIORITY_LANGUAGES)} in that order, "
"then falling back to the first available generated/translated language",
)
return parser.parse_args()
def main() -> None:
args = parse_args()
try:
video_id = extract_video_id(args.url)
logging.info(f"Fetching transcript for video ID: {video_id}")
ytt_api = YouTubeTranscriptApi()
if args.language:
transcript_list = ytt_api.list(video_id)
try:
chosen = transcript_list.find_transcript([args.language])
except Exception:
available = ", ".join(sorted(t.language_code for t in transcript_list))
logging.error(
f"Language '{args.language}' not available. Available languages: {available}"
)
sys.exit(1)
logging.info(f"Using language: {chosen.language_code}")
transcript = chosen.fetch()
elif args.auto:
transcript_list = ytt_api.list(video_id)
lang = pick_auto_language(transcript_list)
logging.info(f"Auto-selected language: {lang}")
transcript = transcript_list.find_transcript([lang]).fetch()
else:
try:
transcript = ytt_api.fetch(video_id)
except Exception:
transcript_list = ytt_api.list(video_id)
available = ", ".join(sorted(t.language_code for t in transcript_list))
logging.error(
f"No transcript available in the default language. "
f"Available languages: {available}. Use --language or --auto."
)
sys.exit(1)
if args.save_srt:
srt_content = transcript_to_srt(transcript.to_raw_data())
args.save_srt.write_text(srt_content)
logging.info(f"SRT file saved to: {args.save_srt}")
else:
text = " ".join(snippet.text for snippet in transcript)
print(text)
except Exception as e:
logging.error(f"Failed to fetch transcript: {e}")
sys.exit(1)
if __name__ == "__main__":
main()