Install
$ agentstack add skill-jjohnnystwsss-yt-playlist-subs-yt-playlist-subs ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
YouTube playlist → subtitles / transcripts
Given a YouTube playlist URL (or a list of video URLs), get the cleanest available transcript for each video and merge them into one episode-ordered, source-badged markdown. This workflow was validated end-to-end on macOS (a 10-video playlist covering all four situations → 10/10 transcribed).
繁體中文版流程見文末「中文流程」。
Core principle (read this first)
Subtitle sources have a priority order, and one video can carry several tracks — pick the best one:
- Creator's manual subtitles — most accurate, preferred.
- Original-language auto (ASR) subtitles — next best.
- Machine-translated subtitles (codes shaped like
en-zh-TW, i.e. "translate lang A
into lang B") — usually noise, avoid.
- No captions at all → download audio, transcribe with Whisper.
- Members-only videos → need a member cookie, then the same logic.
Language codes vary per video (manual is often zh-Hant; auto may be zh-TW or zh-Hant-zh-Hant), so fetch with a loose matcher to grab everything in one pass (e.g. zh-Hant.*,zh-TW or en.*), then pick one track per episode at assembly time. assemble.py does this and, with no priority configured, still avoids machine-translated tracks via a language-neutral heuristic.
Don't hard-code a language. All domain settings (subtitle languages, Whisper prompt, corrections dictionary, output paths) live in config.json — see references/config.md.
Two points that need the user (stop and ask)
The cookie step is where this gets stuck. When you hit it, prompt clearly and pause — don't try to brute-force it, and don't try to automate the export (it's a manual browser action by design; automating it walks back into the rotation problem below):
- Keychain prompt (macOS, for decrypting Chrome cookies of public videos): the first run
pops a Keychain dialog — ask the user to click Allow.
- Member cookie export (only if fetching members-only videos): ask the user to install
the Chrome extension "Get cookies.txt LOCALLY" and export it themselves. ⚠️ This file is as sensitive as their login — remind them to delete it when done.
See step 1.
One-time setup
./setup.sh # creates ytenv (yt-dlp+curl_cffi) and whisperenv (faster-whisper, py3.11)
Or manually:
# yt-dlp with browser impersonation (Homebrew's build lacks curl_cffi)
python3 -m venv ytenv && ./ytenv/bin/pip install -r requirements.txt
./ytenv/bin/yt-dlp --list-impersonate-targets # must list Chrome
# faster-whisper — its native deps often lack wheels on the newest Python, so use 3.11
python3.11 -m venv whisperenv && ./whisperenv/bin/pip install "faster-whisper>=1.0.0"
Work in a clean folder; the relative paths below (subs/, audio/, whisper_out/, titles.tsv) are relative to it. Copy references/config.example.json (or config.zh-example.json) to config.json and fill it in.
> Shortcut: ./run.sh "" [cookies.txt] runs steps 0,2,3,4,6,7 for you. The > steps below are for doing it by hand or understanding each stage.
Workflow (seven steps)
(0) Playlist video list → titles.tsv
./ytenv/bin/yt-dlp --flat-playlist --cookies cookies.txt \
--print "%(playlist_index)03d\t%(id)s\t%(title)s" "" > titles.tsv
> ⚠️ The \t in --print is emitted as a literal backslash-t, so parse with > split /\\t|\t/ — the scripts already handle this.
(1) Get a cookie — most common snag, follow exactly
Public videos:
# On macOS, fetch the key once (pops a Keychain prompt — ask the user to Allow)
security find-generic-password -ws "Chrome Safe Storage"
# Then export the cookie to a file immediately; reuse the file so the prompt doesn't recur
./ytenv/bin/yt-dlp --cookies-from-browser chrome --cookies cookies.txt \
--skip-download --playlist-items 0 ""
- macOS often reports
cannot decrypt v10 cookies: no key found; the line above is the fix. - After exporting, use
--cookies cookies.txteverywhere; don't keep using
--cookies-from-browser.
Members-only videos: --cookies-from-browser reports no longer valid / rotated (YouTube rotates __Secure-3PSIDTS; closing Chrome doesn't help). Reliable fix: the user installs "Get cookies.txt LOCALLY" and exports cookies.txt from a youtube.com tab. To avoid the export being rotated instantly, have them: open an incognito window → log in → open a members-only video and confirm it plays → export without closing the tab → save the file → then close the incognito window. ⚠️ Sensitive file — remind them to delete it.
Cookie troubleshooting (counter-intuitive — know these):
account cookies no longer valid / rotatedis often a soft warning — member content
still downloads. Don't trust the warning; probe a real member video instead.
- yt-dlp rewrites
cookies.txtas it runs, so a newer mtime does not mean the user
exported a fresh cookie. Verify by content or a real probe, not the timestamp.
- Preflight before a big batch (classify login + membership up front):
``bash scripts/cookie_check.sh cookies.txt ./ytenv/bin/yt-dlp ` Downloading subtitles / no subtitles → cookie works (grab subs, else Whisper). Join this channel` → cookie invalid or the account isn't a member — ask the user to re-export (or confirm membership) before continuing.
(2) Batch-fetch subtitles (situations 1, 2)
./ytenv/bin/yt-dlp --skip-download --write-subs --write-auto-subs --ignore-errors \
--sub-langs "" --sub-format "srt/vtt" --convert-subs srt \
--cookies cookies.txt --impersonate chrome \
--sleep-requests 1 --retries 8 --retry-sleep 12 \
-o "subs/%(playlist_index)03d-%(id)s.%(ext)s" ""
--write-subs(manual) +--write-auto-subs(auto) in one pass.--sub-langscomes from
config's sub_langs.
--impersonate chrome+ cookie makes yt-dlp use theweb_creatorclient, so the subtitle
endpoint isn't 429'd. Don't pin --player-client — let it choose.
(3) Classify each video (scan stderr / inspect subs/)
- Log shows
Join this channel→ members-only (note the id for step 5). - An srt was written to
subs/→ has captions. There are no subtitles→ no captions (Whisper in steps 4/6).
Collect caption-less ids into nocap_urls.txt (one https://youtu.be/ per line).
(4) No captions → download audio (situation 3)
./ytenv/bin/yt-dlp -f "bestaudio[ext=m4a]/bestaudio" --ignore-errors \
--cookies cookies.txt --impersonate chrome --sleep-requests 1 --retries 8 \
-o "audio/%(id)s.%(ext)s" -a nocap_urls.txt
> With an explicit URL list, playlist_index becomes NA, so name by %(id)s and map back > to episode numbers via titles.tsv.
(5) Members-only videos (situation 4)
Swap in the member cookie from step 1 and rerun step 2 for the member ids (output to member_subs/%(id)s.%(ext)s); for any still caption-less, do steps 4 + 6.
(6) Whisper-transcribe the caption-less videos
./whisperenv/bin/python scripts/whisper_transcribe.py --config config.json
Transcribes every file in audio/ into whisper_out/, and:
- uses
initial_prompt(domain terms) to cut errors, then applies thecorrectionsdict; - VAD for speed, but a very short clip can be filtered to empty → it **retries once with
VAD off** if it gets 0 segments;
- hallucination guard: on silent / music-only audio Whisper invents text (looping a line,
or spam like "please subscribe"); such outputs are flagged "no narration" and not used;
- resumable: already-done srt files are skipped.
(7) Assemble → one markdown
python3 scripts/assemble.py --config config.json
Per episode picks by source priority (public official > member official > Whisper) → parses the srt to clean text (strips indices / timestamps / tags / overlaps) → applies corrections → merges into one episode-ordered markdown with source badges; anything missing is listed separately. Output goes to config's dest.
Gotchas (learned the hard way)
- 429 Too Many Requests (timedtext throttle): dense requests get the subtitle endpoint
temporarily blocked. Fix = cookies + --impersonate chrome + --sleep-requests + --retries — a background batch then runs with zero errors.
- PO Token / JS challenge: some player clients need a PO token; with a cookie, yt-dlp
uses web_creator + deno to solve the JS challenge. Don't pin the player client; ensure deno is on PATH (Homebrew's yt-dlp bundles it).
--convert-subs srt: vtt→srt uses yt-dlp's built-in converter; no ffmpeg needed.- Silent / no-narration clips: nothing can extract speech that isn't there — flag them
honestly as "no narration".
Bundled resources
run.sh— one-shot orchestrator for steps 0,2,3,4,6,7.setup.sh— creates both venvs, checks impersonation.scripts/whisper_transcribe.py— Whisper (config-driven + VAD fallback + hallucination guard).scripts/assemble.py— track selection + srt cleaning + markdown (config-driven).scripts/cookie_check.sh— cookie/membership preflight.references/config.md— every config field;references/config.example.json/
config.zh-example.json — templates.
中文流程(摘要)
完整規格同上英文版。七步:(0) --flat-playlist 取 titles.tsv(注意 \t 是字面反斜線 t)→ (1) 取 cookie(公開:鑰匙圈授權後匯出檔;會員:用「Get cookies.txt LOCALLY」擴充、無痕視窗匯出、 匯完再關)→ (2) --write-subs --write-auto-subs 帶 --impersonate chrome 批次抓、寬鬆比對語言碼 → (3) 分類(Join this channel=會員、有 srt=有字幕、no subtitles=無字幕)→ (4) 無字幕下載 bestaudio → (5) 會員影片換會員 cookie 重跑 → (6) whisper_transcribe.py 轉錄(VAD 後備 + 幻覺偵測 + 校正字典)→ (7) assemble.py 依來源優先序挑軌、清理、合併成一份 markdown。
cookie 疑難排解:rotated 常是軟警告(用 scripts/cookie_check.sh 拿一支會員影片實測才準); yt-dlp 會回寫 cookie 檔,mtime 變新不代表換新。互動點(鑰匙圈、會員 cookie 匯出)要停下等使用者, 用完提醒刪掉敏感 cookie 檔。所有語言/領域設定放 config.json,見 references/config.md。
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: jjohnnystwsss
- Source: jjohnnystwsss/yt-playlist-subs
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.