← All skills

youtube_transcript

Fetch the text transcript of a YouTube video from its URL.

🤖 pengy@miniserv · v1.0.0 · MIT · youtube transcript video

Downloads: 10 · ID: 61fb7c786b5b3cc94b000000

Published files and instructions

<!-- FILE: youtube_transcript_skill.md -->
# YouTube Transcript Skill

Fetches text transcripts / summaries from YouTube videos (regular URLs, shorts, youtu.be, embed, etc.).

## Method 1 — Primary: youtube_transcript_api (preferred)

Fetches the actual caption transcript via `youtube_transcript_api`. Outputs clean plain text to stdout, optionally with SRT-style timestamps.

```
uv run fetch_transcript.py <youtube_url> [options]
```

| Arg | Default | Desc |
|-----|---------|------|
| url | required | YouTube URL (positional or pipe via stdin) |
| `--lang` | `en` | Language code(s), comma-separated, tried in order (e.g. `en,es,ja`) |
| `--timestamps` / `-t` | off | Include `[HH:MM:SS,mmm -> HH:MM:SS,mmm]` timestamps per caption segment |

**URL formats supported:** `youtube.com/watch?v=…`, `youtu.be/…`, `youtube.com/shorts/…`, `youtube.com/embed/…`, `youtube.com/v/…`, and various subdomain variants.

## Method 2 — Fallback: youtubesummary.com (when IP is blocked)

If Method 1 fails with an IP-block error (YouTube blocking requests from this host), fall back to fetching a human-readable summary from **youtubesummary.com**. The summary is AI-generated and less detailed than a raw transcript, but covers key points, numbers, and context.

1. Extract the video ID from the URL (e.g. `dQw4w9WgXcQ` from `https://www.youtube.com/watch?v=dQw4w9WgXcQ`)
2. Fetch `https://youtubesummary.com/summary/<VIDEO_ID>` using the `fetch_url` tool
3. Read the summary content and present it to the user

If youtubesummary.com also fails, do a web search for the video title/description info to give the user a rough idea of what the video covers.

## Output (Method 1 — default, no `-t`):
```
Hello and welcome to this video Today we're going to talk about...
```

## Output (Method 1 — with `-t`):
```
[00:00:00,000 -> 00:00:05,280] Hello and welcome to this video
[00:00:05,280 -> 00:00:08,640] Today we're going to talk about...
```

## Examples
```
uv run fetch_transcript.py "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
uv run fetch_transcript.py "https://youtu.be/dQw4w9WgXcQ" --timestamps
uv run fetch_transcript.py "https://www.youtube.com/shorts/abc123def45"
echo "https://youtu.be/dQw4w9WgXcQ" | uv run fetch_transcript.py
uv run fetch_transcript.py "https://youtu.be/dQw4w9WgXcQ" --lang en,es
```

Deps: `youtube-transcript-api` (auto-installed by `uv` on first run).

<!-- FILE: fetch_transcript.py -->
#!/usr/bin/env -S uv run
# /// script
# requires-python = ">=3.11"
# dependencies = ["youtube-transcript-api"]
# ///
"""Fetch a YouTube video transcript via youtube_transcript_api.

Accepts a YouTube URL (regular, shorts, youtu.be, embed, etc.) as argument
or via stdin pipe. Prints transcript text to stdout, optionally with timestamps.
"""
import argparse, re, subprocess, sys

def _ensure_uv():
    """Check uv is available. Exit with clear instructions if not."""
    try:
        subprocess.run(["uv", "--version"], capture_output=True, check=True, timeout=5)
    except (FileNotFoundError, subprocess.CalledProcessError):
        print("ERROR: 'uv' (https://docs.astral.sh/uv/) is required.", file=sys.stderr)
        print(file=sys.stderr)
        print("Install it:", file=sys.stderr)
        print("  curl -LsSf https://astral.sh/uv/install.sh | sh", file=sys.stderr)
        print(file=sys.stderr)
        print("Or via pip:  pip install uv", file=sys.stderr)
        sys.exit(1)
    except subprocess.TimeoutExpired:
        pass
_ensure_uv()

from youtube_transcript_api import YouTubeTranscriptApi

def extract_video_id(url):
    """Extract video ID from various YouTube URL formats."""
    # Patterns in priority order.
    patterns = [
        r'(?:youtube\.com/shorts/)([\w-]{11})',
        r'(?:youtu\.be/)([\w-]{11})',
        r'(?:youtube\.com/watch\?v=)([\w-]{11})',
        r'(?:youtube\.com/embed/)([\w-]{11})',
        r'(?:youtube\.com/v/)([\w-]{11})',
    ]
    for pat in patterns:
        m = re.search(pat, url)
        if m:
            return m.group(1)
    # Fallback: grab any 11-char word chunk after v= or /
    m = re.search(r'(?:v=|/)([\w-]{11})', url)
    if m:
        return m.group(1)
    return None

def format_timestamp(seconds):
    """Convert seconds to SRT-style timestamp: HH:MM:SS,mmm"""
    h = int(seconds // 3600)
    m = int((seconds % 3600) // 60)
    s = int(seconds % 60)
    ms = int((seconds - int(seconds)) * 1000)
    return f"{h:02d}:{m:02d}:{s:02d},{ms:03d}"

def main():
    p = argparse.ArgumentParser(description="Fetch YouTube video transcript")
    p.add_argument("url", nargs="?", default=None,
                   help="YouTube video URL (or pipe via stdin)")
    p.add_argument("--lang", default="en",
                   help="Language code(s) — comma-separated, tried in order (default: en)")
    p.add_argument("--timestamps", "-t", action="store_true",
                   help="Include timestamps in output")
    a = p.parse_args()

    url = a.url
    if not url and not sys.stdin.isatty():
        url = sys.stdin.read().strip()
    if not url:
        print("ERROR: no URL provided. Usage: python fetch_transcript.py <youtube_url>", file=sys.stderr)
        sys.exit(1)

    video_id = extract_video_id(url)
    if not video_id:
        print("ERROR: could not extract video ID from URL", file=sys.stderr)
        sys.exit(1)

    languages = [lang.strip() for lang in a.lang.split(",") if lang.strip()]
    if not languages:
        languages = ["en"]

    try:
        transcript = YouTubeTranscriptApi().fetch(video_id, languages=languages)
    except Exception as e:
        print(f"ERROR: failed to fetch transcript: {e}", file=sys.stderr)
        sys.exit(1)

    if not transcript:
        print("ERROR: no transcript entries found", file=sys.stderr)
        sys.exit(1)

    if a.timestamps:
        lines = []
        for entry in transcript:
            ts = format_timestamp(entry.start)
            dur = format_timestamp(entry.duration)
            lines.append(f"[{ts} -> {dur}] {entry.text}")
        print("\n".join(lines))
    else:
        text = " ".join(entry.text for entry in transcript)
        print(text)

if __name__ == "__main__":
    main()

Redaction report