L’interface est localisée ; les présentations et documents des Skills sont traduits automatiquement par IA dans votre langue. Le code, les commandes, chemins et termes techniques restent en version originale, et le document de l’auteur fait foi.
Explorer les Skills
Développement · SKILL.md

ytdlp-ops

ytdlp-ops

yt-dlp media acquisition layer feeding ffmpeg-ops: format selection avoiding post-download transcodes, clip-at-download, cookies/auth, channel archive sync, SponsorBlock, subtitles, failure triage (403s, nsig). Triggers on: yt-dlp, download video/playlist/channel, youtube to mp3.

Independent GitHub snapshot · Automated classification · Original documentation governs

Documentation de la Skill

GitHub

name: ytdlp-ops description: "yt-dlp media acquisition layer feeding ffmpeg-ops: format selection avoiding post-download transcodes, clip-at-download, cookies/auth, channel archive sync, SponsorBlock, subtitles, failure triage (403s, nsig). Triggers on: yt-dlp, download video/playlist/channel, youtube to mp3." license: MIT compatibility: "yt-dlp 2025.x+ (releases near-monthly; run the verifier FIRST when anything fails). ffmpeg on PATH required for merge/remux/extract-audio. A JS runtime (deno auto-enabled; node via --js-runtimes node) required for full YouTube format extraction. Scripts: bash." allowed-tools: "Read Write Edit Bash Glob Grep" metadata: author: claude-mods related-skills: ffmpeg-ops, debug-ops

yt-dlp Operations

Operational expertise for yt-dlp as the acquisition layer: get the right bytes onto disk in the right codec, politely, resumably — then hand off. Anything that re-encodes, cuts precisely, grades, or packages after download is ffmpeg-ops territory; AI-driven editing of what you acquired (transcript → EDL → final cut) is cutcraft — a separate tool, not shipped by this repo. The full chain is acquire → process → edit.

Doctrine: version first, formats second

yt-dlp vs the platforms is an arms race. Releases land near-monthly and extractors break between them — the majority of "yt-dlp is broken" reports are a stale binary. Before debugging anything, check staleness:

bash skills/ytdlp-ops/scripts/check-ytdlp-version.sh --live          # vs latest GitHub release
bash skills/ytdlp-ops/scripts/check-ytdlp-version.sh --live --json | jq '.data.days_behind'

Exit 10 = installed build is >60 days behind latest, a documented flag vanished from yt-dlp --help, or the smoke extraction hit an extractor-internal error → update before any other triage. Exit 7 is advisory: the check ran but could not verify extraction (YouTube bot-gated our IP, no JS runtime, or an unrecognised failure) — common from datacenter IPs such as CI runners:

uv tool upgrade yt-dlp        # pip/uv-managed install (preferred)
yt-dlp -U                     # standalone binary self-update only

Second rule: pick codecs at download time. The default "best" on YouTube is VP9/AV1 + Opus in WebM/MKV. If the destination needs H.264 MP4, stating that in -S costs nothing — discovering it after download costs a full transcode.

Cookbook

Format selection (-S over -f)

# Declarative sort (-S) — PREFER this. States preferences in priority order and
# always degrades gracefully to the nearest available. h264 + m4a merges
# natively into mp4: zero post-download transcode.
yt-dlp -S "res:1080,vcodec:h264,acodec:m4a" --merge-output-format mp4 URL

# Hard filter (-f) — exact control, but FAILS ("Requested format is not
# available") when nothing matches. Use only for genuine hard requirements,
# always with a / fallback chain:
yt-dlp -f "bv*[height<=1080][vcodec^=avc1]+ba[ext=m4a]/b[height<=1080]/b" URL

# Survey what the extractor actually offers before arguing with selectors:
yt-dlp -F URL

# Smallest acceptable file (bandwidth/storage constrained; + prefix = ascending):
yt-dlp -S "res:480,+size,+br" URL

# Best quality regardless of codec (archival source for later ffmpeg-ops work):
yt-dlp -S "res,fps,hdr:12,vcodec,acodec" --merge-output-format mkv URL

Sort-field reference, filter grammar, per-destination presets: references/format-selection.md + assets/format-presets.json.

Clip at download (--download-sections)

# Download ONLY 10:00-12:30 — ranged requests, not a full download + trim:
yt-dlp --download-sections "*10:00-12:30" -S "res:1080,vcodec:h264" URL

# Frame-accurate cut points (re-encodes around the cuts only):
yt-dlp --download-sections "*10:00-12:30" --force-keyframes-at-cuts URL

# Last 5 minutes / by chapter-title regex / multiple sections:
yt-dlp --download-sections "*-5:00-inf" URL
yt-dlp --download-sections "Intro" --download-sections "Outro" URL

Same physics as ffmpeg copy-cuts: without --force-keyframes-at-cuts the section boundaries snap to keyframes (can be seconds off). Need many precise cuts from one source? Download once, then use the ffmpeg-ops EDL workflow.

Audio-only extraction (STT pipelines)

# THE STT acquisition command. YouTube's best audio IS Opus — asking for opus
# means -x COPIES the stream out (no transcode, no quality loss):
yt-dlp -x --audio-format opus -o "%(id)s.%(ext)s" URL

# Zero-processing alternative — native container, no ffmpeg step at all:
yt-dlp -f "ba" -o "%(id)s.%(ext)s" URL

# Whole channel's audio for a transcription pipeline (archive = resumable):
yt-dlp -x --audio-format opus --download-archive stt-archive.txt \
  -o "%(channel)s/%(id)s.%(ext)s" CHANNEL_URL

Do NOT --audio-format mp3 for STT — that's a lossy→lossy transcode that helps nothing. Whisper-prep (16 kHz mono PCM) is the next stage: ffmpeg-ops stt-whisper.

Playlists, channels, incremental sync

# Playlist with ID-correlated filenames + archive file (resumable, dedup-safe):
yt-dlp --download-archive archive.txt \
  -o "%(playlist)s/%(playlist_index)03d - %(title).100B [%(id)s].%(ext)s" PLAYLIST_URL

# Incremental channel sync (cron-friendly): stop at the first already-archived
# video instead of re-walking the entire channel every run:
yt-dlp --download-archive archive.txt --break-on-existing --lazy-playlist \
  -S "res:1080,vcodec:h264,acodec:m4a" CHANNEL_URL

# Subset selection / list without downloading:
yt-dlp -I 1:10 PLAYLIST_URL
yt-dlp --flat-playlist --print "%(id)s %(title)s" PLAYLIST_URL

# DRY-RUN any batch before committing to it — preview every output filename
# (--print implies --simulate; nothing downloads):
yt-dlp --print filename -o "%(playlist_index)03d - %(title).100B [%(id)s].%(ext)s" PLAYLIST_URL

Archive format, sync-job patterns, when --break-on-existing misfires (non-chronological playlists): references/playlists-archives.md.

Livestreams and premieres

# Capture a livestream from its BEGINNING, not from "now" (YouTube keeps a
# rolling live buffer; without this you get the moment you pressed enter):
yt-dlp --live-from-start URL

# Scheduled premiere/stream: poll (1-10 min between retries) and start when live:
yt-dlp --wait-for-video 60-600 URL

Live capture caveats: a crashed live download is not resumable like a VOD (fragments expire) — write to fast local disk (-P temp:), not a network share. For archival quality, prefer re-downloading the VOD after the stream ends; the live manifest often caps below the post-processed VOD.

Subtitles

# Manual subs, English variants, skip live-chat pseudo-subs, as SRT:
yt-dlp --write-subs --sub-langs "en.*,-live_chat" --convert-subs srt --skip-download URL

# Auto-generated (ASR) captions — exist for most videos when manual subs don't:
yt-dlp --write-auto-subs --sub-langs en --convert-subs srt --skip-download URL

# Embed into the media file instead of a sidecar:
yt-dlp --embed-subs --sub-langs en URL

Sub formats, language matching, transcript-only workflows (subs as cheap STT): references/subtitles-metadata.md.

SponsorBlock

# Mark segments as chapters — LOSSLESS and reversible. Prefer this:
yt-dlp --sponsorblock-mark all URL

# Cut segments out of the media — modifies the file, re-encodes at boundaries:
yt-dlp --sponsorblock-remove sponsor,selfpromo URL

Category list, mark-vs-remove trade-offs, interaction with --download-sections: references/sponsorblock.md.

Cookies and auth

# Pull cookies from a browser profile (private/members/age-gated content):
yt-dlp --cookies-from-browser firefox URL

# Chrome 127+ on Windows uses app-bound cookie encryption — extraction usually
# FAILS. Use Firefox, or export a Netscape cookies.txt and pass it directly:
yt-dlp --cookies cookies.txt URL

Account-ban warning: authenticated bulk downloading is the fastest way to get an account flagged. Use a throwaway account, always pair cookies with the politeness flags below. Details + browser matrix: references/auth-cookies.md.

Rate limiting and politeness

# The polite-bulk baseline — cap bandwidth, space out requests, retry patiently:
yt-dlp --limit-rate 4M --sleep-requests 1 \
  --sleep-interval 5 --max-sleep-interval 15 \
  --retries 10 --fragment-retries 10 URL

# Speed (single video, host not throttling you): parallel fragment download:
yt-dlp --concurrent-fragments 4 URL

Politeness is self-interest: 429s and IP flags cost more time than sleeps do.

Remux vs recode

# Remux: container change only — lossless, near-instant. yt-dlp's job:
yt-dlp -S "vcodec:h264,acodec:m4a" --remux-video mp4 URL

# Recode: a FULL TRANSCODE. Almost never yt-dlp's job — you give up ffmpeg-ops'
# CRF/preset/pix_fmt control for a blind default encode. If codecs must change:
yt-dlp -S "res,vcodec,acodec" URL        # 1. acquire best-native
# 2. then transcode with the ffmpeg-ops web-compatible H.264 recipe.

Rule: --remux-video whenever the codecs already fit the target container; --recode-video only for throwaway one-offs where quality control doesn't matter.

Output templates

# ID-in-brackets convention — survives renames, correlates with archive files:
yt-dlp -o "%(uploader)s/%(upload_date)s - %(title).100B [%(id)s].%(ext)s" URL

# Cross-filesystem safety (strips spaces/unicode to ASCII-safe names):
yt-dlp --restrict-filenames -o "%(title)s [%(id)s].%(ext)s" URL

# Split destination and scratch space (-P): fragments go to temp, final to home:
yt-dlp -P "D:/media" -P "temp:C:/tmp/ytdlp" URL

%(title).100B truncates at 100 bytes (UTF-8 safe — CJK titles break char-based truncation). Full field catalog and per-type templates: references/output-templates.md.

Metadata embedding

# Self-describing files — metadata, thumbnail and chapters travel with the media:
yt-dlp --embed-metadata --embed-thumbnail --embed-chapters URL

Beyond YouTube

yt-dlp ships ~1,800 extractors (yt-dlp --list-extractors); everything in this skill except the YouTube-specific parts (nsig, player clients) applies unchanged to Twitch, Vimeo, SoundCloud, TikTok, and the rest. yt-dlp -v URL names the extractor in use. For sites with no dedicated extractor, the generic extractor sniffs direct media/HLS URLs out of the page. When a non-YouTube site returns 403 to yt-dlp but plays fine in a browser, it's usually TLS-fingerprint blocking — --impersonate fixes it (see failure-triage).

Footguns

FootgunThe trapThe rule
Default format selectionYouTube "best" = VP9/AV1+Opus in WebM/MKV; downstream tooling expecting MP4 forces a transcode you could have avoidedState codecs at download: -S "vcodec:h264,acodec:m4a" --merge-output-format mp4
-f bestSelects best single pre-merged file — caps at ~720p on YouTube; modern high-res is always video+audio mergedDrop the -f entirely or use -S; b only as the tail of a / fallback chain
-f hard filters"Requested format is not available" the moment an extractor stops offering that exact comboPrefer -S (degrades gracefully); always end -f chains with /b
--recode-video casuallyFull blind transcode — no CRF/preset/pix_fmt control, big quality/time cost--remux-video when codecs fit; real transcodes via ffmpeg-ops
--download-sections w/o --force-keyframes-at-cutsClip boundaries snap to keyframes — seconds of slopAdd the flag when cuts must be exact (re-encodes at cuts only)
Channel sync w/o --break-on-existingEvery cron run re-walks the entire channel (thousands of metadata requests)--download-archive + --break-on-existing --lazy-playlist
No %(id)s in filenameTitle changes/dupes make files impossible to correlate with the archiveAlways [%(id)s] in the template
--cookies-from-browser chrome on WindowsChrome 127+ app-bound encryption — extraction failsUse firefox, or export cookies.txt
Authenticated bulk runs, no sleepsAccount flagged/banned; IP rate-limitedThrowaway account + --sleep-requests/--sleep-interval always
Throttled to ~50-100 KB/sLooks like a network problem; it's the nsig arms raceUpdate yt-dlp FIRST (check-ytdlp-version.sh --live)
"nsig extraction failed" / "unable to extract"Debugging the command/network when the binary is staleSame — update first; these errors mean outdated, not broken usage
Raw %(title)s filenamesEmoji/colons/slashes break on Windows and some CI filesystems--restrict-filenames or .100B-truncated fields + [%(id)s]
Thin format list on a fresh machineNo JS runtime — YouTube player JS now needs one (EJS); runtime-less extraction is deprecated and may offer only low-res premuxedInstall deno, or --js-runtimes node; see failure-triage
git-bash (MSYS) path mangling/tmp/...-style args convert per-arg — templates containing %(...)s skip conversion while plain paths convert, scattering outputsUse Windows-style paths (X:/dir/...) for -o/-P/--download-archive under git-bash
pip-installed yt-dlp -USelf-update doesn't work for pip/uv installs (silently a no-op with a warning)uv tool upgrade yt-dlp; -U is for the standalone binary only

Failure triage

The ladder — run in order, stop at the first fix:

  1. Stale binary? check-ytdlp-version.sh --live → exit 10 → update. This closes most "nsig extraction failed", missing-format, and throttling cases.
  2. Reproduce verbosely: yt-dlp -v URL — read the actual extractor error, don't guess from the summary line.
  3. 403 / "Sign in to confirm you're not a bot" → identity problem: --cookies-from-browser firefox, or a different network/IP.
  4. 429 / sudden slowdowns mid-run → rate limited: add the politeness flags, reduce --concurrent-fragments, back off and resume later (archive files make every run resumable).
  5. Geo block ("not available in your country") → --proxy URL through an allowed region; the old --geo-bypass header tricks rarely work anymore.

Full decision tree with error-message → cause mapping, --extractor-args escape hatches, and when to file upstream: references/failure-triage.md.

Scripts

Follows the Skill Resource Protocol: --help with examples, stdout = data only, --json envelope (claude-mods.ytdlp-ops.version-check/v1), semantic exit codes (0 clean, 2 usage, 7 network/yt-dlp unavailable — advisory, 10 drift finding).

ScriptJobWorked invocation
check-ytdlp-version.shStaleness verifier: --offline structural (CI gate), --live = installed-version age vs latest GitHub release + documented-flag existence in yt-dlp --help + metadata-only smoke extractioncheck-ytdlp-version.sh --live --json | jq '.data.days_behind' — exit 10 = >60 days behind, a documented flag vanished, or an extractor-internal smoke failure; 7 = advisory (network/API unreachable, or the smoke test was blocked/unverifiable — normal on a bot-gated datacenter IP)

References

Load on demand — one concept per file:

ReferenceLoad when
format-selection.mdAny -f/-S decision, codec targeting, filter grammar, avoiding transcodes
playlists-archives.mdPlaylists, channels, --download-archive, incremental sync jobs
auth-cookies.mdPrivate/members/age-gated content, browser cookie matrix, ban avoidance
output-templates.md-o field catalog, paths, sanitization, per-type routing
subtitles-metadata.mdSub download/convert/embed, transcript workflows, metadata/thumbnail embedding
sponsorblock.mdSponsorBlock categories, mark vs remove, chapter workflows
failure-triage.mdAny download failure — 403/429/geo/nsig/throttling decision tree

Assets: format-presets.json — canonical, date-stamped -S/flag presets per destination (web MP4, STT audio, archival, clip, mobile-small).

Self-test

bash skills/ytdlp-ops/tests/run.sh   # fully offline; no network, no yt-dlp needed

Structural assertions plus the verifier's 60-day age logic exercised through its CM_YTDLP_INSTALLED/CM_YTDLP_LATEST test seams. Real --live runs happen only in the scheduled freshness workflow — a network blip must never fail a PR.

Skills enregistrées