AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Yt Playlist Subs

skill-jjohnnystwsss-yt-playlist-subs-yt-playlist-subs · by jjohnnystwsss

>-

No reviews yet
0 installs
8 views
0.0% view→install

Install

$ agentstack add skill-jjohnnystwsss-yt-playlist-subs-yt-playlist-subs

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No 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.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-jjohnnystwsss-yt-playlist-subs-yt-playlist-subs)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
1mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

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 →
Are you the author of Yt Playlist Subs? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

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:

  1. Creator's manual subtitles — most accurate, preferred.
  2. Original-language auto (ASR) subtitles — next best.
  3. Machine-translated subtitles (codes shaped like en-zh-TW, i.e. "translate lang A

into lang B") — usually noise, avoid.

  1. No captions at all → download audio, transcribe with Whisper.
  2. 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.txt everywhere; 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 / rotated is often a soft warning — member content

still downloads. Don't trust the warning; probe a real member video instead.

  • yt-dlp rewrites cookies.txt as 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-langs comes from

config's sub_langs.

  • --impersonate chrome + cookie makes yt-dlp use the web_creator client, 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 channelmembers-only (note the id for step 5).
  • An srt was written to subs/has captions.
  • There are no subtitlesno 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 the corrections dict;
  • 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-playlisttitles.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.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.