byjimliu· 68 skills
Downloads YouTube video transcripts/subtitles and cover images by URL or video ID. Supports multiple languages, translation, chapters, and speaker identification. Caches raw data for fast re-formatting. Use when user asks to "get YouTube transcript", "download subtitles", "get captions", "YouTube字幕", "YouTube封面", "视频封面", "video thumbnail", "video cover image", or provides a YouTube URL and wants the transcript/subtitle text or cover image extracted.
$npx -y skills add jimliu/baoyu-skills --skill baoyu-youtube-transcriptInstalls into the current project.
Run `npx skills use "https://github.com/jimliu/baoyu-skills" --skill "jimliu/baoyu-skills/baoyu-youtube-transcript"` and follow the generated skill instructions now. Read its complete output, redirecting it to a temporary file first if necessary. Resolve relative paths from the supporting-files directory it provides.
Use the skills in "https://github.com/jimliu/baoyu-skills" that are relevant to the current task. Run `npx skills add "https://github.com/jimliu/baoyu-skills"` and select the relevant skills, then follow their instructions.
| 1 | # YouTube Transcript |
| 2 | |
| 3 | Downloads transcripts (subtitles/captions) from YouTube videos. Works with both manually created and auto-generated transcripts. No API key or browser required — uses YouTube's InnerTube API directly and automatically falls back to `yt-dlp` when YouTube blocks the direct API path. |
| 4 | |
| 5 | Fetches video metadata and cover image on first run, caches raw data for fast re-formatting. |
| 6 | |
| 7 | ## Script Directory |
| 8 | |
| 9 | Scripts in `scripts/` subdirectory. `{baseDir}` = this SKILL.md's directory path. Resolve `${BUN_X}` runtime: if `bun` installed → `bun`; if `npx` available → `npx -y bun`; else suggest installing bun. Replace `{baseDir}` and `${BUN_X}` with actual values. |
| 10 | |
| 11 | | Script | Purpose | |
| 12 | |--------|---------| |
| 13 | | `scripts/main.ts` | Transcript download CLI | |
| 14 | |
| 15 | ## Usage |
| 16 | |
| 17 | ```bash |
| 18 | # Default: markdown with timestamps (English) |
| 19 | ${BUN_X} {baseDir}/scripts/main.ts <youtube-url-or-id> |
| 20 | |
| 21 | # Specify languages (priority order) |
| 22 | ${BUN_X} {baseDir}/scripts/main.ts <url> --languages zh,en,ja |
| 23 | |
| 24 | # Without timestamps |
| 25 | ${BUN_X} {baseDir}/scripts/main.ts <url> --no-timestamps |
| 26 | |
| 27 | # With chapter segmentation |
| 28 | ${BUN_X} {baseDir}/scripts/main.ts <url> --chapters |
| 29 | |
| 30 | # With speaker identification (requires AI post-processing) |
| 31 | ${BUN_X} {baseDir}/scripts/main.ts <url> --speakers |
| 32 | |
| 33 | # SRT subtitle file |
| 34 | ${BUN_X} {baseDir}/scripts/main.ts <url> --format srt |
| 35 | |
| 36 | # Translate transcript |
| 37 | ${BUN_X} {baseDir}/scripts/main.ts <url> --translate zh-Hans |
| 38 | |
| 39 | # List available transcripts |
| 40 | ${BUN_X} {baseDir}/scripts/main.ts <url> --list |
| 41 | |
| 42 | # Force re-fetch (ignore cache) |
| 43 | ${BUN_X} {baseDir}/scripts/main.ts <url> --refresh |
| 44 | ``` |
| 45 | |
| 46 | ## Options |
| 47 | |
| 48 | | Option | Description | Default | |
| 49 | |--------|-------------|---------| |
| 50 | | `<url-or-id>` | YouTube URL or video ID (multiple allowed) | Required | |
| 51 | | `--languages <codes>` | Language codes, comma-separated, in priority order | `en` | |
| 52 | | `--format <fmt>` | Output format: `text`, `srt` | `text` | |
| 53 | | `--translate <code>` | Translate to specified language code | | |
| 54 | | `--list` | List available transcripts instead of fetching | | |
| 55 | | `--timestamps` | Include `[HH:MM:SS → HH:MM:SS]` timestamps per paragraph | on | |
| 56 | | `--no-timestamps` | Disable timestamps | | |
| 57 | | `--chapters` | Chapter segmentation from video description | | |
| 58 | | `--speakers` | Raw transcript with metadata for speaker identification | | |
| 59 | | `--exclude-generated` | Skip auto-generated transcripts | | |
| 60 | | `--exclude-manually-created` | Skip manually created transcripts | | |
| 61 | | `--refresh` | Force re-fetch, ignore cached data | | |
| 62 | | `-o, --output <path>` | Save to specific file path | auto-generated | |
| 63 | | `--output-dir <dir>` | Base output directory | `youtube-transcript` | |
| 64 | |
| 65 | ## Optional Environment Variables |
| 66 | |
| 67 | | Variable | Description | |
| 68 | |----------|-------------| |
| 69 | | `YOUTUBE_TRANSCRIPT_COOKIES_FROM_BROWSER` | Passed to `yt-dlp --cookies-from-browser` during fallback, e.g. `chrome`, `safari`, `firefox`, or `chrome:Profile 1` | |
| 70 | |
| 71 | ## Input Formats |
| 72 | |
| 73 | Accepts any of these as video input: |
| 74 | - Full URL: `https://www.youtube.com/watch?v=dQw4w9WgXcQ` |
| 75 | - Short URL: `https://youtu.be/dQw4w9WgXcQ` |
| 76 | - Embed URL: `https://www.youtube.com/embed/dQw4w9WgXcQ` |
| 77 | - Shorts URL: `https://www.youtube.com/shorts/dQw4w9WgXcQ` |
| 78 | - Video ID: `dQw4w9WgXcQ` |
| 79 | |
| 80 | ## Output Formats |
| 81 | |
| 82 | | Format | Extension | Description | |
| 83 | |--------|-----------|-------------| |
| 84 | | `text` | `.md` | Markdown with frontmatter (incl. `description`), title heading, summary, optional TOC/cover/timestamps/chapters/speakers | |
| 85 | | `srt` | `.srt` | SubRip subtitle format for video players | |
| 86 | |
| 87 | ## Output Directory |
| 88 | |
| 89 | ``` |
| 90 | youtube-transcript/ |
| 91 | ├── .index.json # Video ID → directory path mapping (for cache lookup) |
| 92 | └── {channel-slug}/{title-full-slug}/ |
| 93 | ├── meta.json # Video metadata (title, channel, description, duration, chapters, etc.) |
| 94 | ├── transcript-raw.json # Raw transcript snippets from YouTube API (cached) |
| 95 | ├── transcript-sentences.json # Sentence-segmented transcript (split by punctuation, merged across snippets) |
| 96 | ├── imgs/ |
| 97 | │ └── cover.jpg # Video thumbnail |
| 98 | ├── transcript.md # Markdown transcript (generated from sentences) |
| 99 | └── transcript.srt # SRT subtitle (generated from raw snippets, if --format srt) |
| 100 | ``` |
| 101 | |
| 102 | - `{channel-slug}`: Channel name in kebab-case |
| 103 | - `{title-full-slug}`: Full video title in kebab-case |
| 104 | |
| 105 | The `--list` mode outputs to stdout only (no file saved). |
| 106 | |
| 107 | ## Caching |
| 108 | |
| 109 | On first fetch, the script saves: |
| 110 | - `meta.json` — video metadata, chapters, cover image path, language info |
| 111 | - `transcript-raw.json` — raw transcript snippets from YouTube API (`{ text, start, duration }[]`) |
| 112 | - `transcript-sentences.json` — sentence-segmented transcript (`{ text, start: "HH:mm:ss", end: "HH:mm:ss" }[]`), split by sentence-ending punctuation (`.?!…。?!` etc.), timestamps proportionally allocated by character length, CJK-aware text merging |
| 113 | - `imgs/cover.jpg` — video thumbnail |
| 114 | |
| 115 | Subsequent runs for the same video use cached data (no network calls). Use `--refresh` to force re-fetch. If a different language is requested, the cache is automatically refreshed. |
| 116 | |
| 117 | When YouTube returns anti-bot / blocked responses on the direct InnerTube path, the script retries with alternate client identities and then falls back to `yt-dlp` if available. If fallback is needed but `yt-dlp` is unavailable, the agent should decide how to make `yt-dlp` available and continue rather than pushing the installation decision to the user. |
| 118 | |
| 119 | SRT output (`--format srt`) is generated from `transcript-raw.json`. Text/markdown output uses `transcript-sentences.json` for natural sentence boundaries. |
| 120 | |
| 121 | ## Workflow |
| 122 | |
| 123 | When user provides a YouTube URL and wants the transcript: |
| 124 | |
| 125 | 1. Run with `--list` first if the user hasn't specified a language, to show available options |
| 126 | 2. **Always single-quote the URL** when running the script — zsh treats `?` as a glob wildcard, so an unquoted YouTube URL causes "no matches found": use `'https://www.youtube.com/watch?v=ID'` |
| 127 | 3. Default: run with `--chapters --speakers` for the richest output (chapters + speaker identification) |
| 128 | 3. The script auto-saves cached data + output file and prints the file path |
| 129 | 4. For `--speakers` mode: after the script saves the raw file, follow the speaker identification workflow below to post-process with speaker labels |
| 130 | |
| 131 | When user only wants a cover image or metadata, running the script with any option will also cache `meta.json` and `imgs/cover.jpg`. |
| 132 | |
| 133 | When re-formatting the same video (e.g., first text then SRT), the cached data is reused — no re-fetch needed. |
| 134 | |
| 135 | ## Chapter & Speaker Workflow |
| 136 | |
| 137 | ### Chapters (`--chapters`) |
| 138 | |
| 139 | The script parses chapter timestamps from the video description (e.g., `0:00 Introduction`), segments the transcript by chapter boundaries, groups snippets into readable paragraphs, and saves as `.md` with a Table of Contents. No further processing needed. |
| 140 | |
| 141 | If no chapter timestamps exist in the description, the transcript is output as grouped paragraphs without chapter headings. |
| 142 | |
| 143 | ### Speaker Identification (`--speakers`) |
| 144 | |
| 145 | Speaker identification requires AI processing. The script outputs a raw `.md` file containing: |
| 146 | - YAML frontmatter with video metadata (title, channel, date, cover, description, language) |
| 147 | - Video description (for speaker name extraction) |
| 148 | - Chapter list from description (if available) |
| 149 | - Raw transcript in SRT format (pre-computed start/end timestamps, token-efficient) |
| 150 | |
| 151 | After the script saves the raw file, spawn a sub-agent (use a cheaper model like Sonnet for cost efficiency) to process speaker identification: |
| 152 | |
| 153 | 1. Read the saved `.md` file |
| 154 | 2. Read the prompt template at `{baseDir}/prompts/speaker-transcript.md` |
| 155 | 3. Process the raw transcript following the prompt: |
| 156 | - Identify speakers using video metadata (title → guest, channel → host, description → names) |
| 157 | - Detect speaker turns from conversation flow, question-answer patterns, and contextual cues |
| 158 | - Segment into chapters (use description chapters if available, else create from topic shifts) |
| 159 | - Format with `**Speaker Name:**` labels, paragraph grouping (2-4 sentences), and `[HH:MM:SS → HH:MM:SS]` timestamps |
| 160 | 4. Overwrite the `.md` file with the processed transcript (keep the YAML frontmatter) |
| 161 | |
| 162 | When `--speakers` is used, `--chapters` is implied — the processed output always includes chapter segmentation. |
| 163 | |
| 164 | ## Error Cases |
| 165 | |
| 166 | | Error | Meaning | |
| 167 | |-------|---------| |
| 168 | | Transcripts disabled | Video has no captions at all | |
| 169 | | No transcript found | Requested language not available | |
| 170 | | Video unavailable | Video deleted, private, or region-locked | |
| 171 | | IP blocked | Too many requests, try again later | |
| 172 | | Age restricted | Video requires login for age verification | |
| 173 | | bot detected | The script retries alternate clients and then `yt-dlp`; if fallback tooling is missing, the agent should resolve that itself, otherwise if it still fails try `YOUTUBE_TRANSCRIPT_COOKIES_FROM_BROWSER=safari` (or your browser) | |