changelog-video
Turn a weekly changelog .md into a finished branded changelog video (square 1080, ~45-60s, Annie VO, animated brand background, mock-UI visualizations, lowkey captions). Use when the user provides a changelog/digest markdown and wants the weekly video, or says "changelog video". Self-contained — fonts, background, lexicon, and scripts ship in this skill.
1How It Works & Mechanism
The input is a changelog markdown file (topics and items, structured like a weekly HyperFrames digest), and the output is a lint-clean, seam-gate-green HyperFrames project located under `projects/active/weekly-changelog-<range>/`; rendering is executed only when explicitly requested.
Input: a changelog .md (themes + items, like the weekly HyperFrames digest). Output: a lint-clean, seam-gate-green HyperFrames project in `projects/active/weekly-changelog-<range>/`. Render only when asked.
The loading sequence is strictly non-negotiable: `motion-doctrine` (supplemented with `cut-the-curve`, `oversized-cursor`, and `seam-craft` if a cursor is present) and `captions-overlay` must be loaded prior to this skill; this skill solely provides the changelog-specific pipeline, while motion rules are delegated to the doctrine.
**Load first, non-negotiable:** `motion-doctrine` (+ `cut-the-curve`, `oversized-cursor` if a cursor appears, `seam-craft`) and `captions-overlay`. This skill supplies the changelog-specific pipeline; the doctrine supplies the motion law.
The prime directive is "Visualize, do not list": Every topic must showcase the change experience through an animated mock of a real UI or a faithful analog, strictly avoiding text bullet points; furthermore, routing via `references/visualization-registry.md` (which resolves to `ui-recreate`, `ui-analog`, `terminal`, or `checklist`) must occur prior to script writing. A text checklist serves as a last resort, reserved exclusively for truly non-visual items.
Every theme is illustrated by an **animated mock of the actual UI or a faithful analog** acting out the change in experience — never text bullets. Route every theme/item through `references/visualization-registry.md` BEFORE writing the script; the registry decides ui-recreate / ui-analog / terminal / checklist. Text checklist is the LAST resort, reserved for genuinely non-visual items (reliability fix lists).
Step 0 is non-optional brand bootstrapping: fonts, BGM, and skeleton assets bundled in this skill must be copied first, `bg-pattern.mp4` must be loop-encoded and dimmed to the exact duration using `ffmpeg`, and `build-spec.md` must be read in full before any writing begins. The guide provides self-check and rollback rules: if you find yourself copying `index.html` from a previous video, authoring `@font-face` manually, or designing a WebGL background, STOP, delete the current `index.html`, and restart from the skeleton.
**Do this before writing any composition HTML. Skipping it always produces a video that looks like a similar project you built before, NOT this skill's brand — that's the single most common way this skill goes off-brand.**
Duration and item budgets are strict figures: total runtime is 45-60s, intro ≤2s, outro ≤3.5s, and 4 topics run ~9-12s each; each topic retains only ONE primary visualization and at most 3 spoken items, with the rest relegated to the outro reference for the "full digest"; changelogs with 30 items must be trimmed to ≤14 spoken beats; topics are sequenced narrative-style (headline feature → surface area → performance → reliability).
- **Budget: 45-60s total.** Title ≤2s, outro ≤3.5s, 4 themes ≈ 9-12s each. - Per theme keep ONE hero visualization + at most 3 spoken items. Everything else exists only as the outro's "full digest" pointer.
The script operates as a two-tier contract (token rows): the voiceover reads the `spoken` layer, while captions render the `display` layer; every technical term must include a `spoken` pronunciation guide (sourced from `references/lexicon.json`), and any term not in the lexicon requires pausing to ask the user for pronunciation and updating the dictionary rather than guessing.
conversational register, every technical term carrying a `spoken` phonetic form from `references/lexicon.json` while `display` keeps standard spelling. Captions show `display`; the VO reads `spoken`. Any term not in the lexicon: STOP and ask the user how it's pronounced, then add it to the lexicon.
Voiceover is locked to Annie's voice ID (`330290724a1b470fb63153f34d4c0183`, strictly marked "do not substitute"), calling TTS via HeyGen CLI (≥0.3.0, authenticated via OAuth), and must produce `vo-words.json` (word-level timestamps for spoken text); subsequently, `align-captions.mjs` maps spoken timestamps back to display tokens to generate `captions.json` (serving as input to caption-rail with display spelling and spoken timing).
node skills/hyperframes-media/scripts/heygen-tts.mjs ./vo-spoken.txt \ -o voiceover.mp3 --words vo-words.json \ --voice 330290724a1b470fb63153f34d4c0183 # Annie — lifelike (do not substitute)
Word-level timestamps serve as a hard gate: before proceeding to Step 5, you must verify that `vo-words.json` is non-empty and contains a `words[]` array where each word has `start`/`end`; if empty or missing the array (a known failure mode where TTS returns audio without the timestamp payload), proceeding without timestamps is forbidden; the fallback is running forced alignment on the generated audio via local Whisper (`uvx --from openai-whisper`) and feeding the result into `align-captions.mjs`.
**Word-timings are a hard gate.** Before moving on to step 5, verify `vo-words.json` is non-empty and has a `words: [...]` array with `start`/`end` per word. If it's empty (0 bytes) or missing the array — a known failure mode when the TTS provider returns audio but no timestamp payload — DO NOT proceed without them.
Captions are mandatory: the skeleton includes a caption rail IIFE that reads the `LINES` array, and leaving this array empty is treated as a "shipped bug" rather than a stylistic choice; Step 4 must produce `captions.json`, Step 5 must inject it into the IIFE, and Check 5 of Step 6 must verify that text is physically rendered on sampled frames.
**Captions are non-optional.** The master-skeleton ships a caption-rail IIFE that reads a `LINES` array — leaving that array empty is a shipped bug, not a style choice.
The 5 gates in Step 6 are mandatory: ① Run repository-internal CLI `check` (explicitly banning `npx hyperframes@latest` because the internal CLI serves as the source of truth for composition contracts) with `caption-zone` sampling 26 timestamps with 0 errors; ② `seam-gate verify` passes with 0 failures; ③ Restart the preview server (which caches bundles) and spot-check 3-4 beats using `__player.seek`; ④ Do not render unless requested, and if rendered, perform frame sampling (verify captions are visible, background video is not black, and no frozen/micro-frames exist); ⑤ Hard caption presence check: cross-sample 3-4 frames during voiceover intervals; any missing caption triggers a red gate, requiring tracing back to the `LINES` population in Step 5.
5. **Caption presence gate — hard fail.** Sample 3-4 frames spread across the VO's spoken window (e.g. `t=3`, `t=15`, `t=30`, `t=42` for a 48s VO) and confirm the caption rail at `top: 990` renders visible text on each.
Project layout and seam mechanics are strictly codified (`build-spec.md` serves as the authoritative blueprint): single-document `index.html` (scenes as slides, seams stamped) + `ledger.json` + `script-tokens.json` + `vo-spoken.txt`/`voiceover.mp3`/`vo-words.json`/`captions.json` + `bgm.mp3` + `assets/fonts` and `assets/bg-pattern-<dur>s.mp4`; normal transitions uniformly use `cut-the-curve LEFT`, and exit/entry selectors must target inner `.swrap` (`#w-*`) rather than `#s-*` clip elements (animating clip elements violates error-level lint rules); internal beats must align with word timestamps in `vo-words.json`.
- `ledger.json`: every ordinary seam `cut-the-curve LEFT` (x, dir −1), exit and entry selectors = the inner `.swrap` wrappers (`#w-*`), NEVER the `#s-*` clip element — `gsap_animates_clip_element` is an error-severity lint rule and every stamped seam writes `autoAlpha`.
Caption rail specifications are cross-defined by `script-voice.md` and `build-spec.md`: on a 1080 square canvas, `top:990` / `height:52` / TT Norms Pro 500 32px / ink .94 / subtle dark `text-shadow`; words fade in over 0.12s based on individual timestamps, while phrases transition as whole units; essential small text must clear the bottom ~100px center zone (aligning with soft avoidance rules in `captions-overlay`).
Caption rail per `script-voice.md` (top: 990, font-size: 32px, height: 52). Mandatory — populate the master-skeleton's `LINES` array from `captions.json` before render; see SKILL.md step 5.
Visualization routing criteria: four categories ranked by precedence (`ui-recreate` highest, followed by `ui-analog`, `terminal`, and `checklist`), mapped against a "known surfaces" registry (Studio editor/timeline, Inspector, Canvas + elements, variant rendering, Storyboard, terminal, render panel) and "proven choreography"; analogs must stage behaviors (speed, batching, caching) rather than inventing non-existent UI screens; the outro checklist dictates appending a row upon first mock creation for future reuse.
The routing table for "show, don't tell". Classes, strongest first: 1. **ui-recreate** — the change lives in a surface we can faithfully mock.
Post-release actions: S3 replacement must be followed by invalidating the designated CloudFront distribution, otherwise the CDN will continue serving stale assets (this requirement is listed under "Don't" in anti-patterns, defining delivery without invalidation as an anti-pattern).
Run `aws cloudfront create-invalidation` on distribution `E2BSLVSZ7FG3U0` for the exact path after any S3 replace — CDN caches the old file otherwise
The semantics and execution order of the five gates are fixed: `check` (0 errors) → `seam-gate` (0 failures) → preview spot-check → (optional) render frame sampling → hard caption presence check; restarting the preview server is explicitly required because it caches bundles, preventing verification against stale builds.
3. Restart the preview server (it caches the bundle), spot-check 3-4 beats via `__player.seek` on the raw comp page.
2Core Capabilities
3External Dependencies
| Kind | Dependency / Endpoint |
|---|---|
| cli | heygen CLI ≥0.3.0(OAuth 登录,用于 TTS;SKILL.md 以仓库内 heygen-tts.mjs 为调用封装) |
| cli | ffmpeg(背景 pattern 循环编码 + 时长压暗;渲染后抽帧核验) |
| cli | 仓库内 hyperframes CLI(check --caption-zone 与 preview;SKILL.md 明确禁止用 npx hyperframes@latest 代替) |
| cli | seam-stamp.mjs / seam-gate.mjs(来自 motion-doctrine,作为构建与验证闸门) |
| cli | aws CLI(交付后对固定 CloudFront distribution 做 invalidation) |
| package | uvx / openai-whisper(备用强制对齐:下载包与模型,仅当 TTS 未返回词级时间戳时) |
| network | 产物期 CDN 引用:骨架 HTML 引 jsdelivr 的 GSAP(浏览器端拉取,执行期不外发) |
| package | 上游/兄弟 skill 依赖(被指名加载):motion-doctrine / cut-the-curve / oversized-cursor / seam-craft / captions-overlay |
4Recommended Governance & Evidence
- scripts/: Executes local scripts or commands: `node <SKILL_DIR>/scripts/align-captions.mjs --tokens script-tokens.json --w`
- 网络端点: Declares external network access: HeyGen TTS service (via `heygen` CLI with OAuth authentication; transmits spoken text from `vo-spoken.txt` to retrieve audio + word-level timestamps, consuming credits)
- 本地工作目录: File read/write and artifact output: project directory `projects/active/weekly-changelog-<range>/`: `index.html`, `ledger.json`, `scrip`
5Second-Pass Verification
- [discrepancy] SKILL.md Step 4 点名的仓库内 TTS 封装路径在 pin 上是否存在(照抄会不会立刻失败) — 不存在。SKILL.md 写 `node skills/hyperframes-media/scripts/heygen-tts.mjs`,并在注释里说明「the changelog-video skill runs from the hyperframes repo root, so it uses the tracked hyperframes-media TTS helper directly」;但 pin 内 skills/ 下没有 hyperframes-media 目录,全仓 heygen-tts* 唯一命中是 `skills/media-use/audio/scripts/heygen-tts.mjs`。同一过期路径还出现在 `.claude/skills/README.md`(TTS uses the tracked …)。结论:Step 4 命令按字面执行会 ENOENT,需改用 media-use 侧脚本(SKILL.md 自己留了 swap in your own path 的余地,故不算虚假声明)。
- [discrepancy] 自带 bg-pattern.mp4 / bgm.mp3 是否真的可用(Step 0 的 cp 与 ffmpeg 是否拿到媒体) — 在未拉 LFS 的检出里是 LFS 指针,不是媒体。实检:assets/bg-pattern.mp4 = 133 字节、assets/bgm.mp3 = 132 字节,内容为 `version https://git-lfs.github.com/spec/v1 / oid sha256:… / size 12817182`(mp4)与 `size 5318438`(mp3);仓库 .gitattributes 明写 `.claude/skills/**/*.mp4|*.mp3 filter=lfs`;`git cat-file -s HEAD:<path>` 亦返回 133(blob 即指针)。因此 Step 0 的 cp 与 `ffmpeg -i <SKILL_DIR>/assets/bg-pattern.mp4` 在未 git lfs pull / 未装 git-lfs 的环境会失败或产出坏文件(本工作副本的 git 甚至没有 lfs 子命令)。这是 pin 检出相关的真实差异,不是文档错误——但照本文档流程跑的人必须先确保 LFS 已拉取。
- [ok] 「字幕非可选」是否真有连锁校验(是否只是口号) — 三处闭环可查:① 骨架 HTML 里确有读 LINES 的字幕 IIFE(`const LINES = /* paste captions.json lines … */ [];` 与 #cap-line 容器、tl.set/tl.to 逐词淡入);② SKILL.md Step 5 给出把 captions.json 贴进 IIFE 的具体写法;③ Step 6 第 5 道闸要求跨旁白采样 3-4 帧确认字幕可见,并点名 Jul 13-20 v4 的失败。口号有实现支撑。
- [ok] align-captions.mjs 的行为是否与 script-voice.md 的描述一致 — 脚本注释与实现一致:tokens {lines:[{id,tokens:[str|{display,spoken}]}]} + words [{text,start,end}] → captions.json {lines:[{id,end,w:[[display,start],…]}]};贪婪 consume 支持「一个 display token 覆盖多个 spoken 词」;用 norm(去非字母数字)+ Levenshtein close 做模糊匹配;有 MISMATCH 计数并 `process.exit(mismatches ? 1 : 0)`。无网络、无子进程,只 readFileSync ×2 + writeFileSync ×1。
- [ok] check 的 --caption-zone 与 preview 缓存提醒是否自洽(是否存在互相矛盾的验收) — Step 6 第 1 条用仓库内 CLI 跑 caption-zone(x0=0;y0=.90;x1=1;y1=1;severity=error,26 个 seek 点)拿 0 error;第 3 条要求重启预览服务后再用 __player.seek 抽查——两处不冲突(前者静态闸、后者运行期抽查),且都与 build-spec 的「字幕轨 top:990(=1080×0.9167,落在 .90–1 带内)」一致。
- [discrepancy] Stage ground 与本 skill 骨架是否一致(跨 skill 一致性) — seam-craft / cut-the-curve 教义要求把不透明底色刷在 #root(`#root { background: var(--canvas-deep, var(--canvas, #000)) }`)。本 skill 的 master-skeleton.html 实测把底色放在 `html, body { background: #0a0c0b }`,#root 只有 data-* 属性、没有 background 声明;#root 内首个子元素是满幅 bg-video(track0)+ scrim。对本骨架而言白闪仍被 body 底色挡住(body 也是不透明深色),观感风险低;但与教义字面要求的形式不一致,且未用 --canvas-deep/--canvas 变量(该变量在 pin 内零命中)。记录为形式差异,不改档位。
- [ok] CloudFront invalidation 是否只是可选建议(影响档位判断) — 它写在 Anti-patterns 表的「Don't」列:`Delivered without CloudFront invalidation` → `Run aws cloudfront create-invalidation on distribution E2BSLVSZ7FG3U0 …`。即「交付却不做 invalidation」被定义为反模式,属交付流程的必需步骤而非可选花絮——这是判橙(触碰用户云凭据)的依据。
- [ok] 是否存在被忽略的其他外发(例如字体/CDN 在构建期拉取) — 字体是本地 woff2(5 个文件实检非 LFS 指针,约 488KB),build-spec 明确要求 `local @font-face with format('woff2') embeds at render`;唯一 CDN 引用是 master-skeleton 的 jsdelivr GSAP(产物期,浏览器端)。未发现构建期遥测、上报或未知端点。
6Verdict & Scenarios
f474b43e1565756e…