Skip to content

feat(video): read a YouTube video as a citable, timestamped transcript - #8

Merged
maxgfr merged 3 commits into
mainfrom
issue-7-video-engine
Sep 30, 2026
Merged

maxgfr merged 3 commits into
mainfrom
issue-7-video-engine

Conversation

@maxgfr

@maxgfr maxgfr commented Sep 30, 2026

Copy link
Copy Markdown
Owner

First of four PRs for #7 (spec: docs/superpowers/specs/2026-09-30-video-engine-design.md).

fetchAndExtract now sends a YouTube video URL to a transcript ladder instead of the watch page:

  1. manual subtitles — requested language, else the video's own, else English;
  2. auto-captions in the video's own language (<lang>-orig), never a machine translation; rolling captions de-duplicated;
  3. local whisper (uvx whisper-ctranslate2, PyAV pinned <18), budgeted by WEBINDEX_WHISPER_MAX.

Each rung sits behind a words-per-minute gate. Everything goes through yt-dlp; its failures (403 / sign-in / PO token, private, members-only, age-restricted, removed, unavailable) come back as notes. Output is Markdown with a metadata header, a ## heading per chapter and a [mm:ss] stamp per paragraph; segments never straddle a chapter start.

  • src/video/{url,ytdlp,vtt,whisper,ladder,markdown}.ts behind src/video.ts
  • fetch / webindex_fetch read videos with no change on their side; documentType: "video", extractors manual-subs | auto-subs | whisper, own cache namespace
  • doctor gains video rungs (yt-dlp version and age, flagged past 60 days; uvx and ffmpeg for whisper), rungs.video in JSON
  • New variables: WEBINDEX_VIDEO_ENGINES, WEBINDEX_WHISPER_MODEL|MAX|TIMEOUT_MS, WEBINDEX_YTDLP_ARGS

Tested with real VTT fixtures (manual, translated, rolling auto-captions) and an injected runner — no network in the suite. Checked live on manual-subtitle, auto-caption-only (French) and whisper transcriptions.

fetchAndExtract sends a YouTube video URL to a transcript ladder instead of
the watch page: manual subtitles, then the video's own auto-captions (never a
machine translation, rolling repetition undone), then a local whisper
transcription through uvx, each behind a words-per-minute gate. Everything
goes through yt-dlp; its failures come back as reasons, and doctor shows its
age and what whisper needs.

Refs #7
- a caller's signal reaches every command (shAsync kills the tree on abort),
  so an MCP client that gives up stops yt-dlp, ffmpeg and whisper
- WHISPER_TIMEOUT_MS is one deadline for download, conversion and
  transcription, and a slot no transcription used is refunded
- only a recognised video URL is read; yt-dlp gets the canonical watch URL
  after --, so no caller string can reach it as an option
- live and upcoming streams are left until they have ended
- rolling-caption dedupe drops only the lines a cue carries over, so a line
  said twice on purpose survives; manual tracks never roll
- whisper is told a bare language code; the header names the subtitle track
  and flags a translation; every chapter heading is kept

Refs #7
@maxgfr
maxgfr merged commit d394af7 into main Sep 30, 2026
3 checks passed
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 1.22.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@maxgfr
maxgfr deleted the issue-7-video-engine branch September 30, 2026 12:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant