Releases: muckybuzzwoo/claude-code-youtube-extract
Release list
v1.10.1
Fixed
- Screenshot extraction falsely reported as failed on long output paths.
extract_screenshots()checkedos.path.exists()/os.path.getsize()on
the raw constructed path to confirm each frame was written. On Windows,
paths beyond the legacy 260-characterMAX_PATHlimit silently fail that
check — ffmpeg had already written the frame correctly, but the stat call
returned as if the file didn't exist, so every frame was logged as
Frame at HH:MM:SS failed: unknown errorand the run reported0 successfully extracteddespite valid PNGs on disk. New_long_path()
helper applies the\?\extended-length prefix on Windows before the
check; no-op on macOS/Linux, which do not have this limit. Only manifests
with long--output-basetrees (e.g. deeply nested temp/session dirs) —
the default--output-base "."case was unaffected.
v1.10.0
Added
--visualflag: the summarizer worker looks at 4 evenly-spaced, ephemeral
keyframes so the summary can address on-screen content (diagrams, code,
slides). Frames are extracted to a temp dir, read by the worker, then
deleted — nothing is saved to the output folder. A "Visually checked at:"
line in the summary records which timestamps were inspected. Requires ffmpeg;
ignored in--full-transcript/--transcript-onlymodes. Adoption #3 from
the claude-video roadmap.
v1.9.0 - Perceptual frame-dedup for scene screenshots
Added
Perceptual frame-dedup for scene-detection screenshots. After extraction, scenes mode compares 16×16 grayscale thumbnails of consecutive captures (mean-absolute-difference against the last kept frame) and drops near-duplicates — a slide held across several detected scene changes now collapses to a single frame instead of many near-identical ones. Comparing against the last kept frame (not the previous one) keeps a gradual pan while removing static repeats.
- Runs only in scene-detection mode;
--screenshots chaptersand explicit-timestamp captures are intentional and left untouched. - No new flag; the threshold is fixed (mean-abs-diff ≤ 2.0 on a 0–255 scale).
- Pure ffmpeg + stdlib (no PIL). Approach ported from
frames.pyinbradautomates/claude-video.
Changed
### Screenshot Statusnow reports dedup explicitly: when frames are removed the count line readsN screenshots requested, M successfully extracted, D near-duplicate(s) removed (K kept). Previously deduped frames would have been indistinguishable from extraction failures in the count. With zero dedup the wording is unchanged.- Version bumped 1.8.3 → 1.9.0.
Tests
Added unit coverage for the pure helpers frame_delta and dedupe_perceptual_indices (drift-aware keep logic, threshold boundary, length-mismatch fail-open) and for the render_screenshot_status dedup clause. Suite: 81 passing. The ffmpeg-backed glue (compute_thumbnail, dedupe_screenshots) stays outside the unit suite per the project's I/O policy and was verified with an offline ffmpeg smoke check.
Full changelog: CHANGELOG.md
v1.8.3 - release workflow docs
Documentation-only release. No functional, output, or CLI change — extraction behavior, sentinels, and folder layout are identical to 1.8.2.
Docs
Added
SKILL.md"Extending this skill" now documents the release workflow: which files carry the version string, the CHANGELOG + SemVer convention, thepython -m pytest tests/gate, and the tag / GitHub Release /claude plugin updatesteps. The contributor section previously covered adding flags, install targets, sentinels, and Markdown sections but never how to cut a release.
Changed
- Version bumped 1.8.2 → 1.8.3 across
.claude-plugin/plugin.json,.claude-plugin/marketplace.json,CLAUDE.md, andREADME.md.
v1.8.2 — robust OUTPUT_FOLDER parse + scene threshold 0.04
Changed
- Scene-detection default threshold raised
0.025→0.04. On talking-head and slide-heavy videos the old default tripped on minor frame changes and often hit the 50-capture cap (then thinned);0.04yields fewer, more meaningful captures by default. Tune per video with--screenshots scenes=<0..1>(lower = more).
Fixed
- Robust
OUTPUT_FOLDER:parsing. When a worker subagent returns, the harness can append anagentId: <hex>trailer directly onto the trailingOUTPUT_FOLDER:line with no separating newline (e.g.…_my-slugagentId: a2ba…). The orchestrator now extracts the folder by cutting at the first whitespace or the literalagentIdsubstring instead of taking the raw last line, so the trailer can no longer be folded into the saved folder/file name. Documented in SKILL.md Step 3, mirrored in theCLAUDE.mdsentinel registry, and covered by a new static contract test.
Full changelog: https://github.com/muckybuzzwoo/claude-code-youtube-extract/blob/main/CHANGELOG.md
v1.8.1 — fix recursive subagent loop
Critical bugfix: runaway recursive subagent loop. /yt-extract <url> (default and --full-transcript modes — any path that dispatches a subagent) could spawn an ever-deepening chain of general-purpose subagents (~one new level every ~30 s), burning tokens and never producing output. Each dispatched worker's first action was Skill(yt-extract) — it re-invoked this skill instead of running the Python command it was handed, and Step 1 then dispatched the next worker, ad infinitum.
Root cause
The recursion needed two things, both introduced in v1.6.0:
- v1.6.0 removed
disable-model-invocation: trueto make the skill callable programmatically by other skills. That flag was also the hard structural guard that kept a subagent from reaching/yt-extractthrough the Skill tool. Its removal was compensated only by a tightened description — a soft, model-interpreted guard. But the worker's own task ("Extract all data for this YouTube video…") reads as an explicit extract request, so the soft guard matched exactly the case it was meant to exclude. - The skill dispatched
subagent_type: general-purpose, which inherits all tools — includingSkillandAgent. With the skill now model-invocable, the worker could (and did) re-invoke it.
Fixed
- New restricted worker agent
agents/extract-worker.md—toolsallowlist isBash, Read, Glob, Greponly, so the worker has noSkilland noAgenttool and physically cannot re-invoke the skill or spawn subagents. SKILL.mdStep 1 now dispatchessubagent_type: "yt-extract:extract-worker"instead ofgeneral-purpose. The skill stays model-invocable (programmatic invocation by other skills is preserved).- Both Step 1 subagent prompts gained a "you are a LEAF worker — run only the Bash command, never invoke a skill or dispatch a subagent" guard (defense-in-depth).
- Worker no longer asks the user directly. The subagent prompts used to instruct the worker to call
AskUserQuestionforFOLDER_EXISTSandSCREENSHOTS_ASK_USER, but subagents have noAskUserQuestiontool (it depends on the main-chat UI). The worker now returns these states; the orchestrator (main context) asks the user and re-dispatches with--force/ explicit timestamps. The--transcript-onlypath is unaffected. - New test
tests/test_skill_contract.py— static contract test locking the dispatch target (restricted worker, notgeneral-purpose), the worker tool allowlist (excludesSkill/Agent/Task), and that no worker prompt instructsAskUserQuestion. Closes the previously untested orchestration-layer gap.
Upgrade note
Adds a fourth plugin component type (an internal agent). No user-facing CLI change. To restore the hard block instead — at the cost of programmatic invocability — re-add disable-model-invocation: true to the SKILL.md frontmatter.
v1.8.0 — scene-detection screenshot mode
New screenshot mode: ffmpeg scene detection — capture a frame whenever the
screen changes (slide flips, screen shares, demo cuts). Built for tutorial
videos, where slides change far more often than chapter markers suggest.
⚠ BREAKING
- Bare
--screenshotsnow means scene detection, not chapter markers. Use
--screenshots chaptersto restore the pre-1.8.0 behavior. The legacy
internal valueautois kept as an alias forchapters, so direct script
callers passing--screenshots autoare unaffected.
yt-extract.py (backend)
Added
- Two-pass scene detection: pass 1 decodes a ≤360p stream once with ffmpeg's
select='gt(scene,T)'filter (default threshold 0.025) and collects only
timestamps; pass 2 extracts those frames at ≤1080p through the existing
extract_screenshots()path. Rendering, filenames, and markdown embedding
are unchanged (scene runs use the inline-with-heading layout). - Flood protection: 4s minimum gap between detections, then even thinning to
max 50 screenshots with a WARNING in### Screenshot Status(suggests a
higher threshold). Zero detections above threshold → opening frame only +
WARNING suggesting a lower threshold. --screenshots scenes=0.05— tunable detection threshold in(0, 1];
invalid values fall back to the default with a WARNING.- New stage marker
Detecting scene changes(stderr,[k/N]stays adaptive).
Detection decodes the whole video at low resolution and gets a
duration-scaled timeout (min 5, max 30 minutes) plus HTTP reconnect flags. - New pure helpers (unit-tested):
parse_screenshots_mode,
parse_scene_timestamps,apply_min_gap,thin_evenly; new subprocess
helpersget_lowres_stream_url,detect_scene_timestamps.
Fixed
- Screenshot seeks no longer truncate timestamps to whole seconds
(-ss int(ts)→-ss ts:.2f). For scene mode this is essential — a
truncated seek could land before the detected change and capture the
previous screen. Side effect: explicit fractional timestamps like1:30.5
now seek accurately instead of silently flooring to1:30.
yt-extract skill
Changed
- Step 0.4 flag grammar:
--screenshots [scenes[=t]|chapters|timestamps];
both subagent prompts pass the user's value through verbatim and announce
the new detection stage. SCREENSHOTS_ASK_USERnow only fires for--screenshots chapterson a
chapterless video — scene mode (the default) needs no chapters, so the
no-chapters Rückfrage disappears from the happy path.- No new sentinel: thinning/zero-detection conditions travel as
- WARNING:
lines inside### Screenshot Status.
Tests
Added
tests/test_scene_detection.py— pure-Python coverage for the four new
helpers, including an ffmpegmetadata=printfixture verified against real
output (pts_time:values may lack a decimal part).
Docs
Changed
- Version bumped 1.7.0 → 1.8.0 across
CLAUDE.md,README.md,
.claude-plugin/plugin.json, and.claude-plugin/marketplace.json;
README gets a breaking-change callout and a scene-mode row in the flags
and modes tables.
v1.7.0 — transcript-only mode + guided help
New lean --transcript-only mode: fetch and output just the raw transcript — no metadata, description, chapters, comments, screenshots, or summary. It runs the Python script directly (no subagent), since a subagent would only relay the raw transcript back into context and pay its token cost twice. It also skips the heavy yt-dlp --dump-json metadata call entirely, naming the output folder by the video ID parsed from the URL.
Plus: a no-URL invocation now always answers with a guided text message instead of failing.
Highlights
--transcript-only— raw transcript only, fast; no subagent; folder/file named by video ID. Combinable with--no-save. Multi-URL supported (no synthesis — it's raw data). The post-run "What next?" offers an in-context summary, so "transcript then summarize" needs no separate mode.- No-URL guard —
/yt-extractwith no URL (and without--check) prints the full guided help when no flags were given, or a short flag-preserving "paste a URL" message when flags were passed. Plain text only — never anAskUserQuestion— so it stays safe in programmatic skill-to-skill calls. extract_video_id()— parses the 11-char ID fromwatch?v=,youtu.be/,/shorts/,/embed/, and/live/URL forms.
The happy path is unchanged: /yt-extract <url> still runs the default structured summary immediately.
See CHANGELOG.md for full details.
v1.6.0 — skill invocable from other skills
The skill can now be invoked programmatically by other skills via the Skill tool, not only manually via /yt-extract. Removing disable-model-invocation: true is what unblocks this — the Skill tool cannot reach a skill that declares that flag. Because the skill is now model-invocable, its description was tightened to fire only on an explicit extract/analyze request.
Changed
SKILL.mdfrontmatter — removeddisable-model-invocation: true. Callable from another skill asyt-extract:yt-extract <url> [flags]. Stays user-invocable via/yt-extract.SKILL.mddescription— tightened to fire only on an explicit extract/analyze request; no auto-trigger on incidental YouTube URL mentions.SKILL.mdline-count-budget note — no longer references the removed flag.
Behavior change
- The hard block against model-invocation is gone. Auto-trigger restraint now relies on the tightened description + user-level gating, not a frontmatter flag.
Full changelog: https://github.com/muckybuzzwoo/claude-code-youtube-extract/blob/main/CHANGELOG.md
v1.5.0 — Windows PATH Recovery
Windows-only fix for the "freshly installed but not on PATH" UX dead-end. winget installs into %LOCALAPPDATA%\Microsoft\WinGet\Packages with a shim under \Microsoft\WinGet\Links\ — when that Links dir is empty or absent from the Bash tool's PATH, yt-dlp --version returned exit 127 even though the binary was on disk, and the skill's only response was "restart your terminal".
v1.5.0 adds an automatic two-stage PATH-recovery step that locates the binary via merged-registry PATH and copies it into a directory already on the current shell's PATH (Stage 1 — Python's Scripts dir) or, as a fallback, into WinGet\Links while permanently adding that directory to the user PATH (Stage 2). One AskUserQuestion confirmation covers the full recovery chain.
yt-extract skill
Added
references/install-helper.md— new Step W (Windows PATH Recovery) with two recovery stages:- Stage 1 copies the binary to Python's Scripts directory (already on PATH per the Step 0.3a Python check) — works in the current shell session, no Claude Code restart required.
- Stage 2 (auto-fallback when Stage 1 mechanically fails, or sole option when Stage 1 is unavailable, e.g. MS-Store Python) copies the binary to
%LOCALAPPDATA%\Microsoft\WinGet\LinksAND
adds that directory to the user PATH via Registry — fixes the winget Links shim permanently for all future winget installs. Requires a Claude Code restart. - Step W locates the binary via PowerShell merged-registry
Get-Commandlookup withGet-ChildItemfallback under%LOCALAPPDATA%\Microsoft\WinGet\Packages.
SKILL.mdStep 0.3b (yt-dlp) and Step 0.5 (ffmpeg) — Windows pre-check fallback that invokes Step W BEFORE loading the install-helper. On Stage 1 success, the install-helper is skipped entirely
(recovers the case where the binary is already on disk via winget but Bash cannot see it).install-helper.mdStep D — Windows post-install fallback that invokes Step W BEFORE Step E. On Stage 1 success, returns success without "restart your terminal".- ffmpeg recovery copies BOTH
ffmpeg.exeandffprobe.exe— yt-dlp invokes ffprobe internally for stream selection, so copying onlyffmpeg.exewould yield silent screenshot failures.
Fixed
- Windows + winget yt-dlp / ffmpeg: skill no longer asks the user to install something that is already present on disk (Fall A — empty or absent
WinGet\Linksshim). - Combined with the existing winget exit-code-43 normalization (install-helper.md Step C),
winget install yt-dlpnow resolves transparently when the binary is in the WinGet packages dir.
Docs
Changed
README.mdQuick Start and Troubleshooting — rewrote the "expected first-time experience on Windows" guidance. The Claude Code restart is now the rare-case fallback, not the default ritual.CLAUDE.md— version reference bumped 1.4.0 → 1.5.0.
Known limitations
- After a future
winget upgradeof a binary recovered via Stage 1 / Stage 2, the local copy goes stale until a fresh recovery runs. Mitigated by yt-dlp's built-in-Uself-update; ffmpeg upgrades
are rare./yt-extract --checkre-triggers recovery whenever needed. - MS-Store Python users — both
sysconfig.get_paths()['scripts']andos.path.dirname(sys.executable)resolve under\WindowsApps\with stub-redirect ACLs that rejectCopy-Item. Step W detects
this, suppresses Stage 1, and offers Stage 2 only (which writes toWinGet\Linksand is unaffected by the ACL issue).