scripts/fetch_transcript.py
scripts/fetch_transcript.pyBrowse 3 files
995 tokens
4,054 bytes
Token encoding: o200k_base
Snapshot 24fd22b
← Back to SKILL.md
1#!/usr/bin/env python32"""3Fetch a YouTube video transcript and output it as structured JSON.4 5Usage:6 uv run python3 fetch_transcript.py <url_or_video_id> [--language en,tr] [--timestamps]7 8Output (JSON):9 {10 "video_id": "...",11 "language": "en",12 "segments": [{"text": "...", "start": 0.0, "duration": 2.5}, ...],13 "full_text": "complete transcript as plain text",14 "timestamped_text": "00:00 first line\n00:05 second line\n..."15 }16 17Install dependency: uv pip install youtube-transcript-api18"""19 20import argparse21import json22import re23import sys24 25 26def extract_video_id(url_or_id: str) -> str:27 """Extract the 11-character video ID from various YouTube URL formats."""28 url_or_id = url_or_id.strip()29 patterns = [30 r'(?:v=|youtu\.be/|shorts/|embed/|live/)([a-zA-Z0-9_-]{11})',31 r'^([a-zA-Z0-9_-]{11})$',32 ]33 for pattern in patterns:34 match = re.search(pattern, url_or_id)35 if match:36 return match.group(1)37 return url_or_id38 39 40def format_timestamp(seconds: float) -> str:41 """Convert seconds to HH:MM:SS or MM:SS format."""42 total = int(seconds)43 h, remainder = divmod(total, 3600)44 m, s = divmod(remainder, 60)45 if h > 0:46 return f"{h}:{m:02d}:{s:02d}"47 return f"{m}:{s:02d}"48 49 50def fetch_transcript(video_id: str, languages: list = None):51 """Fetch transcript segments from YouTube.52 53 Returns a list of dicts with 'text', 'start', and 'duration' keys.54 Compatible with youtube-transcript-api v1.x.55 """56 try:57 from youtube_transcript_api import YouTubeTranscriptApi58 except ImportError:59 print("Error: youtube-transcript-api not installed. Run: uv pip install youtube-transcript-api",60 file=sys.stderr)61 sys.exit(1)62 63 api = YouTubeTranscriptApi()64 if languages:65 result = api.fetch(video_id, languages=languages)66 else:67 result = api.fetch(video_id)68 69 # v1.x returns FetchedTranscriptSnippet objects; normalize to dicts70 return [71 {"text": seg.text, "start": seg.start, "duration": seg.duration}72 for seg in result73 ]74 75 76def main():77 parser = argparse.ArgumentParser(description="Fetch YouTube transcript as JSON")78 parser.add_argument("url", help="YouTube URL or video ID")79 parser.add_argument("--language", "-l", default=None,80 help="Comma-separated language codes (e.g. en,tr). Default: auto")81 parser.add_argument("--timestamps", "-t", action="store_true",82 help="Include timestamped text in output")83 parser.add_argument("--text-only", action="store_true",84 help="Output plain text instead of JSON")85 args = parser.parse_args()86 87 video_id = extract_video_id(args.url)88 languages = [l.strip() for l in args.language.split(",")] if args.language else None89 90 try:91 segments = fetch_transcript(video_id, languages)92 except Exception as e:93 error_msg = str(e)94 if "disabled" in error_msg.lower():95 print(json.dumps({"error": "Transcripts are disabled for this video."}))96 elif "no transcript" in error_msg.lower():97 print(json.dumps({"error": "No transcript found. Try specifying a language with --language."}))98 else:99 print(json.dumps({"error": error_msg}))100 sys.exit(1)101 102 full_text = " ".join(seg["text"] for seg in segments)103 timestamped = "\n".join(104 f"{format_timestamp(seg['start'])} {seg['text']}" for seg in segments105 )106 107 if args.text_only:108 print(timestamped if args.timestamps else full_text)109 return110 111 result = {112 "video_id": video_id,113 "segment_count": len(segments),114 "duration": format_timestamp(segments[-1]["start"] + segments[-1]["duration"]) if segments else "0:00",115 "full_text": full_text,116 }117 if args.timestamps:118 result["timestamped_text"] = timestamped119 120 print(json.dumps(result, ensure_ascii=False, indent=2))121 122 123if __name__ == "__main__":124 main()125