1仕組みと動作原理
字幕の取得元は公式Data APIではなく、YouTube内蔵のInnerTubeプレーヤーエンドポイントです。スクリプトがwatchページのHTMLから正規表現でINNERTUBE_API_KEYをその場で抽出し、それを付与してプレーヤーエンドポイントにPOSTリクエストを送信します。
const apiKey = html.match(/"INNERTUBE_API_KEY":\s*"([a-zA-Z0-9_-]+)"/)?.[1];
YouTubeからbot判定された場合、単純なリトライは行わずクライアント識別子を変更します。ANDROID / WEB / IOSの3種類のクライアント宣言とUser-Agentを順に切り替えてリトライし、すべて失敗した場合にのみyt-dlpへフォールバックします。
com.google.android.youtube/20.10.38 (Linux; U; Android 14; en_US; Pixel 8 Pro; Build/AP1A.240405.002)
yt-dlpへのフォールバックは「事前プローブ後に機能に応じてコマンドを選択」する方式です。yt-dlp、uvx --from yt-dlp yt-dlp、python3 -m yt_dlpを順次試し、--helpに--js-runtimesと--remote-componentsの両方が含まれている場合のみ適合と判定します。
helpText.includes("--js-runtimes") &&
最終的な実行はspawnSync(コマンド, argv配列)で行われ、シェル文字列の連結は存在しません。プローブと実行の両方にmaxBuffer上限が設定されています。
const result = spawnSync(command.command, args, {
--translateは外部の翻訳APIを呼び出さず、字幕トラックURLに&tlang=<code>を追加してYouTube独自の機械翻訳トラックを利用します。
url += `&tlang=${translateTo}`;
--speakersは「スクリプトによる素材出力 + サブエージェントによる加工」を採用しています。スクリプトはSRTタイムスタンプ付きの元markdownを1件出力するにとどめ、エージェントが安価なモデルのサブエージェントに話者ラベリングとチャプター分割を委託します。
spawn a sub-agent (use a cheaper model like Sonnet for cost efficiency)
キャッシュがデータ面として機能します。初回実行時にmeta.json + transcript-raw.json + transcript-sentences.json + imgs/cover.jpgを出力し、`.index.json`がvideoId→ディレクトリのマッピングを管理するため、同一動画の次回実行時はネットワーク通信ゼロで動作します。
return existsSync(join(videoDir, "meta.json")) && existsSync(join(videoDir, "transcript-raw.json"));
テキスト出力は未加工のsnippetsではなく「文(sentences)」単位です。句読点で分割され、タイムスタンプは文字長の比率に応じて配分され、CJKテキストは適切に結合されます。
split by sentence-ending punctuation (`.?!…。?!` etc.), timestamps proportionally allocated by character length, CJK-aware text merging
2コア機能
3外部依存関係
| 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 组件 |
4推奨統制措置と判定根拠
- SKILL.md / 脚本: 未公開またはリバースエンジニアリングされたプライベートAPIの呼び出し/クライアント識別子の偽装
- SKILL.md / 会话存储: ブラウザのユーザーログイン状態およびセッションCookieの読み取りまたは再利用
- 本地凭据 / 浏览器配置: ユーザーログインCookie/セッション/キーリングの読み取り: process.env.YOUTUBE_TRANSCRIPT_COOKIES_FROM_BROWSER(スキル全体で唯一の環境変数読み取り箇所)→ yt-dlpパラメータとして使用
- scripts/: ローカルスクリプトまたはコマンドの実行: ${BUN_X} {baseDir}/scripts/main.ts <url|id>(bunまたはnpx -y bun経由。スキルディレクトリ内はTSファイルのみ)
- 网络端点: 外部ネットワークアクセスの宣言: https://www.youtube.com/watch?v=<id>&hl=en&persist_hl=1&has_verified=1&bpct
- 本地工作目录: ファイル読み書きおよび成果物出力: youtube-transcript/(デフォルト、--output-dirで変更可能)
5二重目の独立検証
- [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 的说明与代码分支一致。
6結論
dbf11afd644a4963…