baoyu-youtube-transcript
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.
1How It Works & Mechanism
The subtitle source is YouTube's internal InnerTube player endpoint rather than the official Data API: the script extracts INNERTUBE_API_KEY directly from the watch page HTML via regex on the fly, then sends a POST request with it to the player endpoint.
const apiKey = html.match(/"INNERTUBE_API_KEY":\s*"([a-zA-Z0-9_-]+)"/)?.[1];
When flagged as a bot by YouTube, it avoids brute-force retries and instead rotates client identities: sequentially retrying across ANDROID, WEB, and IOS client declarations and User-Agents, falling back to yt-dlp only if all fail.
com.google.android.youtube/20.10.38 (Linux; U; Android 14; en_US; Pixel 8 Pro; Build/AP1A.240405.002)
The yt-dlp fallback operates by "probing first, selecting commands based on capabilities": testing yt-dlp, uvx --from yt-dlp yt-dlp, and python3 -m yt_dlp sequentially, requiring both --js-runtimes and --remote-components to appear in --help to qualify.
helpText.includes("--js-runtimes") &&
The final execution uses spawnSync(command, argvArray) without shell string concatenation; both probing and execution enforce a maxBuffer limit.
const result = spawnSync(command.command, args, {
--translate does not invoke any external translation API; it simply appends &tlang=<code> to the subtitle track URL to leverage YouTube's native machine-translated tracks.
url += `&tlang=${translateTo}`;
--speakers uses a "script generates source + sub-agent processes" approach: the script outputs raw markdown with SRT timestamps, and the agent delegates speaker labeling and chapter segmentation to a cost-effective model sub-agent.
spawn a sub-agent (use a cheaper model like Sonnet for cost efficiency)
The cache serves as the data surface: the initial run outputs meta.json + transcript-raw.json + transcript-sentences.json + imgs/cover.jpg, while `.index.json` maintains the videoId-to-directory mapping, enabling zero network requests for subsequent runs on the same video.
return existsSync(join(videoDir, "meta.json")) && existsSync(join(videoDir, "transcript-raw.json"));
Text output consists of "sentences" rather than raw snippets: split by punctuation, with timestamps allocated proportionally based on character length, and CJK text merged.
split by sentence-ending punctuation (`.?!…。?!` etc.), timestamps proportionally allocated by character length, CJK-aware text merging
2Core Capabilities
3External Dependencies
| Kind | Dependency / Endpoint |
|---|---|
| api | YouTube InnerTube player API(非公开接口) |
| network | YouTube watch 页(抓 INNERTUBE_API_KEY / visitorData) |
| network | 字幕轨 baseUrl(InnerTube/yt-dlp 返回,fmt=json3/xml) |
| network | YouTube 缩略图 CDN(封面) |
| network | consent.youtube.com 表单(仅字符串匹配,重试用 CONSENT cookie 再请求 watch 页) |
| cli | yt-dlp(直接二进制) |
| cli | uvx --from yt-dlp yt-dlp(免安装形态) |
| cli | python3 -m yt_dlp(已安装模块形态) |
| cli | bun / npx -y bun(脚本运行时) |
| network | yt-dlp --remote-components ejs:github 拉取的 GitHub 远端 EJS 组件 |
4Recommended Governance & Evidence
- SKILL.md / 脚本: Invokes unofficial or reverse-engineered private APIs / spoofs client identifiers
- SKILL.md / 会话存储: Reads or reuses browser user login states and session cookies
- 本地凭据 / 浏览器配置: Reads user login cookies/sessions/keyrings: process.env.YOUTUBE_TRANSCRIPT_COOKIES_FROM_BROWSER (the only env read in the entire skill) → used as yt-dlp parameter
- scripts/: Executes local scripts or commands: ${BUN_X} {baseDir}/scripts/main.ts <url|id> (via bun, or npx -y bun; skill directory contains only TS)
- 网络端点: Declares external network access: https://www.youtube.com/watch?v=<id>&hl=en&persist_hl=1&has_verified=1&bpct
- 本地工作目录: File I/O and artifact output: youtube-transcript/ (default, configurable via --output-dir)
5Second-Pass Verification
- [ok] InnerTube 端点与 key 现场抓取(无硬编码 key) — youtube.ts 含 INNERTUBE_URL/WATCH_URL 常量与 html.match(/"INNERTUBE_API_KEY":...) 抓取逻辑;全仓无硬编码 API key,只有会随版本过期的客户端版本号与 UA 常量。
- [ok] 三套客户端身份轮换(ANDROID/WEB/IOS)+ 对应头 — clientName "ANDROID"/"WEB"/"IOS"、X-YouTube-Client-Name / X-YouTube-Client-Version / X-Goog-Visitor-Id 头与 Pixel 8 Pro UA 均在源码中;与 SKILL.md 的『bot detected』说明一致。
- [ok] yt-dlp 回退的探测与参数 — 三个候选命令(yt-dlp / uvx / python3 -m yt_dlp),--version 探测后还要 --help 命中 --js-runtimes 与 --remote-components;执行参数为 -J --skip-download --js-runtimes bun --remote-components ejs:github(可选 --cookies-from-browser)。
- [ok] --translate 无任何外部翻译服务 — 只有 url += `&tlang=${translateTo}` 与 isTranslatable / translationLanguages 前置检查;无 Google Translate / LLM / HTTP 翻译调用,依赖面为零 npm 包。
- [ok] 零 npm 依赖、skill 内无 package.json — skill 目录仅 .ts 与 prompt 模板;全部 import 为 node 内置(fs/path/child_process/node:test/node:assert)与相对 .ts;根 package.json 的 workspaces 只覆盖 packages/*。
- [ok] 全 skill 唯一 env 读取 = YOUTUBE_TRANSCRIPT_COOKIES_FROM_BROWSER — 跨目录 grep process.env / env[ / YOUTUBE_ 仅命中该变量(读取一处、拼参数一处);未设置时回退路径不带 cookie,故凭证面为『闸门 + 委派』而非默认开启。
- [ok] 写盘清单与 SKILL.md 输出树一致 — 代码写入 .index.json / meta.json / transcript-raw.json / transcript-sentences.json / imgs/cover.jpg / transcript.md|.srt,与 SKILL.md 目录树逐项对齐;不落盘任何 token。
- [ok] 文档与代码的次要出入 — SKILL.md 工作流小节出现两个编号为 3 的步骤(文档笔误,不影响结论);--speakers 隐含 --chapters 的说明与代码分支一致。
6Verdict & Scenarios
dbf11afd644a4963…