首页
/ HyperFrames changelog-video Skill:从周报 Changelog 到品牌化 1080 方屏视频的完整制作管线

HyperFrames changelog-video Skill:从周报 Changelog 到品牌化 1080 方屏视频的完整制作管线

2026-09-05 10:09:22作者:曹令琨Iris

HyperFrames 仓库内置的 changelog-video skill(位于 .claude/skills/changelog-video/SKILL.md)把一份周报 changelog Markdown(主题 + 条目清单,即 HyperFrames 每周 digest 的格式)转化为一条成品品牌视频:1080×1080 方屏、总长 45–60 秒、Annie 女声旁白、动画品牌背景、mock-UI 可视化演绎、低存在感字幕轨。读完后你能掌握这条管线的全部环节:从 skill 资产引导(bootstrap)、可视化路由、双层脚本(spoken/display)编写,到 HeyGen TTS 配音、词级时间戳对齐算法(align-captions.mjs)、主骨架 HTML 构建,直至最终的质量门禁(lint 检查、seam-gate、字幕帧抽检)——并理解每个环节背后"为什么必须这样做"的约束来源。

技能定位与自包含资产

SKILL.md 的 frontmatter 定义了这个技能的触发条件:当用户提供 changelog/digest Markdown 并想要"每周视频"、或说出 "changelog video" 时使用。它明确声明 self-contained——字体、背景、术语表和脚本都随 skill 一同发布,不需要外部资源:

输入是一个 changelog .md,输出是一个 lint-clean、seam-gate-green 的 HyperFrames 项目,落在 projects/active/weekly-changelog-<range>/ 目录;渲染(render)只在用户明确要求时执行。

SKILL.md 同时规定了一个不可协商的前置条件:先加载 motion-doctrine(若出现游标则加载 cut-the-curveoversized-cursor,另加 seam-craft),以及 captions-overlay。分工是:motion-doctrine.claude/skills/motion-doctrine/SKILL.md)提供"运动定律"——向量法则(怎么出场决定怎么入场)、film current(默认向左的主方向)、seam-gate 门禁;changelog-video 只提供 changelog 专属管线。而 captions-overlay.claude/skills/captions-overlay/SKILL.md)确立字幕的核心模型:字幕是叠加在画面之上的 overlay,永远不是预留的底部色带——因此构图保持完整画幅,内容围绕真正的画面中心布局。

第一准则:visualize, don't list

每个主题(theme)必须用真实 UI 的动画 mock 或忠实类比(faithful analog)来"演"出这个变化,绝不使用文字 bullet 列表。在写脚本之前,每个主题/条目都必须先过一遍 references/visualization-registry.md——路由表决定四类落点:

优先级 类别 含义
1 ui-recreate 变化发生在我们能忠实 mock 的界面上
2 ui-analog 没有精确界面,但存在一个诚实的 UI 隐喻(面板、仪表、管线),其行为本身就是这个变化
3 terminal 变化是一条 CLI 命令/标志:把它打出来,展示结果
4 checklist 纯非视觉项(修复清单、依赖升级),最后手段

路由表里已经沉淀了两张"已验证"的资产表。ui-recreate 的已知 surface 包括 Studio 编辑器/时间线(玻璃应用框:标题栏带 traffic dots、mono 应用名、Export 药丸;clips 落轨/堆叠、拖拽到边缘吸附时绿色吸附线闪光、marquee 框选后组移动)、Inspector/设计面板{{ var }} 绑定药丸飞入属性槽、值通过 mask slide 互换)、Canvas + 元素(面板编辑时文字同帧更新,绿色下划线脉冲标记 live-preview 瞬间)、Variant 渲染(小卡片对角级联飞出,间隔 ≤0.15s)、Storyboard 视图(缩略图瀑布式入场,其一被拖拽重排)、Terminal/CLI(mono 19–20px,字符逐个打出,间隔 0.02s,结果行在 0.3–0.5s 的 beat 后落地)、Render 面板(大号 tabular-nums 帧计数器 + 绿色进度条用 power2.in 缓动,"慢变快"读作"更快")。ui-analog 已验证类比则覆盖:调色/LUT(每个旋钮移动同帧重新过滤画面,因果同帧)、渲染速度(渲染面板计数 + before/after 时间 chip)、批处理(一排小刻度线聚簇)、缓存(第二行请求短路到 ✓ + cache chip)、Figma→HF 导入管线(纯 DOM/CSS mock 的源卡片变形成 comp 卡片,提取出的 chip 是载体,不碰任何真实 Figma API)、并发上限(chip 队列过闸,前 k 个通过其余挂起)、错误上报(角落长出 toast 卡片)。

两条硬性规则贯穿始终:绝不发明暗示了不存在的屏幕的假 UI——analog 必须表现"行为"(速度、批处理、缓存),而不是假产品页;checklist 是最后手段,仅限真正无法可视化的条目(如可靠性修复清单),glass 卡片 ≤6 行 mono 条目,绿色 ✓ 落在对应 VO 词上(back.out(1.5), 0.3s)。另外路由表本身是活的:每当新 UI 区域上线、第一次被 mock 时,就把它的 anatomy + choreography 加一行进表,让下一期 changelog 直接复用。

Step 0:从 skill 资产引导项目(不可跳过)

SKILL.md 将 bootstrap 列为"单一最常见的 off-brand 方式"的解法:跳过它,产出永远是"一个类似你以前做过的项目",而不是这个品牌。规则很直白——skill 的资产、字体和脚手架就是 skill 本身;SKILL.md 只是路由器

mkdir -p project/assets/fonts
cp <SKILL_DIR>/assets/fonts/*.woff2 project/assets/fonts/
cp <SKILL_DIR>/assets/bgm.mp3 project/bgm.mp3
ffmpeg -y -stream_loop 15 -i <SKILL_DIR>/assets/bg-pattern.mp4 -t <TOTAL> \
  -vf "scale=1080:1080,fps=30,eq=saturation=0.72,drawbox=c=black@0.5:t=fill" \
  -an -c:v libx264 -crf 20 -pix_fmt yuv420p project/assets/bg-pattern-<TOTAL>s.mp4
cp <SKILL_DIR>/examples/master-skeleton.html project/index.html

其中 ffmpeg 编码有讲究:-stream_loop 15 循环源片到影片总时长 <TOTAL>eq=saturation=0.72 降饱和 + drawbox=c=black@0.5:t=fill 压暗——references/build-spec.md 说明原始图案"far too loud",必须保留这层暗化,且编码时长必须 ≥ 总时长(渲染编译器会把视频轨道截短到媒体长度)。<SKILL_DIR>.claude/skills/changelog-video 目录。

引导完成后必须通读 references/build-spec.md——它定义了每个场景继承的品牌 token:TT Norms Pro + ABC Solar Display + TT Norms Mono 三字体体系、米色墨字 #f5f6f4(二级 rgba(245,246,244,.72)、dim rgba(245,246,244,.66)——不能低于 .66,否则对比度门禁对玻璃面上的小字 4.5:1 会失败)、配给制的绿色 #5ef17c(每个场景只允许一次"绿色时刻")、玻璃卡片(rgba(10,12,11,.78) 填充、1px rgba(190,255,205,.32) 绿调边框、圆角 22、box-shadow: 0 24px 60px rgba(0,0,0,.5),禁用 backdrop-filter)、kicker/sec-chip 药丸形状、位于 top: 990 的 32px 字幕轨。安全边距 x/y ∈ [76, 1004]。

只有完成 Step 0,才开始 1–6 步:Step 1–4(解析、路由、脚本、VO)规划脚手架里要填什么;Step 5 填充 <RANGE><TOTAL><CUT_N><DUR_N>、场景主体等占位符——不重写脚手架的 chrome、字体、配色或布局外壳。SKILL.md 给出了三个"自诊断停止信号":如果你想去 cp 以前视频的 index.html、手写 @font-face、或用 WebGL shader 背景替代已编码的 bg-pattern MP4——停,删掉当前 index.html,从 master-skeleton 的 cp 重新开始。"在正确的脚手架上重建场景内容,比把品牌回改到错误的脚手架上便宜。"

Step 1–2:解析、编辑裁剪与可视化路由

Step 1(解析 + 编辑裁剪) 从 changelog 提取周区间、头条统计(发布数、提交数)、主题和条目,并按预算硬约束裁剪:

  • 总时长 45–60 秒:标题 ≤2s,outro ≤3.5s,4 个主题各约 9–12s;
  • 每个主题保留 1 个 hero 可视化 + 至多 3 个口播条目,其余只作为 outro 的 "full digest" 指针存在——"裁剪就是这个工作本身":30 条目的 changelog 也要压到 ≤14 个口播 beat;
  • 主题按叙事排序:marquee feature → product surface → performance → reliability(digest 通常已是这个读序)。

Step 2(可视化路由) 对每个主题从路由表选一个 surface,并写出一行三要素:theme → surface → mock 依次执行的 2–4 个动作,每个动作绑定一句脚本短语。如果路由表没有合适的 surface、也不存在忠实类比,就老实做 checklist 场景——不为了凑视觉效果而造假 UI

Step 3:双层脚本——spoken 与 display 分离

脚本是 VO 和字幕的唯一事实来源,按 references/script-voice.md 写成分行 token。契约是硬质量门禁:字幕显示 "jay-sawn" 或 VO 读出 "juh-son"(把 JSON 当字母拼读)都算构建失败。

口吻(register)要求:会话式而非 release-notes("The big one this week —" 优于 "Theme 1:")、允许缩写和第二人称;信息性而非推销性,不用 changelog 撑不起的最高级;每句 ≤ 约 14 词让标点控制节奏;有意义的数字保留("fifteen releases"),但 commit hash、PR 号、版本细节从不口播;开场是周 + marquee,收尾是 digest 指针。有一条特色规则是"教那条简单的命令":功能若有一行式调用(slash 命令、CLI one-liner),脚本逐字念出("start your prompt with /figma…"),mock 演示它被敲出来——命令是结果的可见原因。

Token 行格式(script-tokens.json,项目内保存):

{
  "lines": [
    {
      "id": "l1",
      "tokens": ["This", "week", "at", "HyperFrames,", { "display": "JSON", "spoken": "jay-sawn" }]
    }
  ]
}
  • 裸字符串 = display 与 spoken 相同;对象 = 两层分叉:display 保留标准拼写与字幕应显示的标点,spoken 是 TTS 实际朗读的形式(完整示例见 examples/script-tokens.json,含 { "display": "CLI", "spoken": "C L I" }{ "display": "hyperframes.heygen.com", "spoken": "hyperframes dot hey-jen dot com" });
  • 一行 = 一个字幕短语(display 文本 ≤ 约 40 字符),行分组是创作期决策,不在下游做;
  • vo-spoken.txt 由所有 token 的 spoken 形式以空格连接、行按标点拼成句子生成。

术语发音规则针对 HeyGen TTS 只吃纯文本(无 SSML)这一现实,用拼写、连字符和空格控制发音:

  1. 首字母缩略词(逐字母读):CLI → "C L I"CDP → "C D P"API → "A P I"
  2. 读作单词的缩写:改写成音形——JSON → "jay-sawn"GSAP → "jee-sap"
  3. 混合/可拼读的复合词:连字符小写音形、一口气读完——ffmpeg → "ff-mpeg"(实测 TTS 把 "ff" 读成流畅的 "eff-eff")、WebM → "web em"OAuth → "oh-auth"。此处绝不用空格大写:TTS 会把空格大写读成带硬停顿的孤立字母名;空格大写只留给真正需要逐字母读的缩略词。候选接近时,对真实句子生成 A/B 两版由人耳挑选;
  4. 版本/数字:展开——v0.7.36 → "version zero point seven point thirty-six"(通常干脆不念版本);
  5. URLhyperframes.heygen.com → "hyperframes dot hey-jen dot com"
  6. 文件扩展名.mp4 → "dot em pee four",或改写句子避免念扩展名;
  7. 强调/停顿:用逗号与破折号,不用大写;省略号在 TTS 中不可靠,改用 em-dash。

共享词表是 references/lexicon.jsondisplay → spoken 映射,仓库现有 60 余个词条,如 "Linux": "linnucks""vite": "veet""OOM": "out of memory")。规则:每个技术术语都先查词表;词表缺失就停下问用户怎么读,然后加词条——绝不猜、绝不发布未听过的发音;新词条须先听一遍生成 VO 中那一行才接受。

Step 4:Annie 配音与词级时间戳硬门禁

配音使用 HeyGen 的 Annie 音色(lifelike),voice ID 固定、不可替换:

# spoken 层文本;words JSON = 所读文本的词级真值时间戳
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)

要求 heygen CLI ≥0.3.0 且已认证(heygen auth login --oauth)。SKILL.md 注释说明:该 skill 从 hyperframes 仓库根运行,直接使用仓库内被跟踪的 TTS helper(不需要 npx hyperframes skills 安装步骤);仓库中实际被跟踪的对应文件是 skills/media-use/audio/scripts/heygen-tts.mjs,skill 文档中的 hyperframes-media 路径若拷贝到其他仓库需自行替换。

然后运行对齐器,把 spoken 时间戳映射回 display token:

node <SKILL_DIR>/scripts/align-captions.mjs \
  --tokens script-tokens.json --words vo-words.json --out captions.json

captions.json 是字幕轨输入(display 拼写 + spoken 时间)。对齐器会打印 MISMATCH 警告——进入 Step 5 之前必须全部解决(通常是词表里某个拼写被 TTS 读成了多个词)。核心原则:音频就是钟——所有 beat 时间都来自 vo-words.json,一旦重生成 VO,所有 seam 都要重新打开。

词级时间戳是硬门禁:进入 Step 5 前必须验证 vo-words.json 非空、含 words: [...] 数组且每个词有 start/end。若为空(0 字节)或缺数组——这是 TTS 提供方"给了音频但没给时间戳 payload"的已知失败模式——不允许继续。回退方案是用本地 whisper 对产出的音频强制对齐:

uvx --from openai-whisper whisper voiceover.mp3 \
  --model base.en --language en --word_timestamps True \
  --output_format json --output_dir .
# 然后用 --words voiceover.json(相同结构)再跑 align-captions.mjs

注意 whisper 会听错 TTS 的音形("gee-sap" → "gsap"、"heyjen" → "hey Jen")——字幕仍然使用 script-tokens.jsondisplay 拼写,whisper 只供时间戳,join 由 align-captions 完成。SKILL.md 把这一步定位为"有字幕的构建和悄悄无字幕构建之间的区别"。

align-captions.mjs 的实现解析

scripts/align-captions.mjs(约 127 行)把 spoken 流的词时间戳映射回 display token。核心难点:一个 display token 可能覆盖多个 spoken 词("C L I" 是三个词,"hey-jen" 可能是一个词也可能被拆成多个),因此需要一个能"贪婪吞词"的匹配器:

  • 规范化与模糊匹配L39–58):norm() 去标点转小写;close() 先试前缀关系,再算 Levenshtein 距离,阈值是 max(1, min(len)/3)——足够吸收 TTS/时间戳的怪癖;
  • consume(from, spokenNorm)L67–88):从游标起逐个拼接 spoken 词,只要累计串还是目标的前缀或模糊匹配就继续吞,记录第一个词的开始时间作为该 display 词的时间;
  • 主循环L90–115):每个 token 先在游标处尝试,再向前最多重同步 4 个词(off 0–4);仍失败则打印 MISMATCH line=… display=… expected~… heard=… @…s 并计错;
  • 行结束时间L117–120):一行 end = 下一行首个词的开始时间;最后一行 = 最后一个 spoken 词结束 + --tail(默认 0.6s 的尾部余量);
  • 输出契约{ lines: [{ id, end, w: [[display, start], …] }] },即字幕轨的输入格式;退出码非零当且仅当存在 mismatch("resolve before building")。

Step 5:构建——脚手架、品牌 token 与字幕轨

构建严格遵循 references/build-spec.md:品牌 token + 字体(打包在 skill 的 assets/)、背景动画编码、场景脚手架、chrome、字幕轨、每场景一次绿色时刻。随后按 motion-doctrine 的顺序:ledger.json(普通 seam 一律 cut-the-curve LEFT)→ seam-stamp → 场景内部 beat 落在 VO 词上 → seam-gate 验证。

master-skeleton(examples/master-skeleton.html)的关键机制值得逐条看:

  • 构图声明<div id="root" data-composition-id="main" data-start="0" data-duration="<TOTAL>" data-width="1080" data-height="1080">,GSAP timeline 必须命名为 tl 并注册到 window.__timelines["main"](seam-stamp 向 tl.* 生成代码),暂停、补满总时长;
  • 背景<video id="bg-video" class="clip" src="assets/bg-pattern-<TOTAL>s.mp4" muted> 挂 track 0 + 静态 rgba(8,10,9,.25) scrim;<video> 必须有 id(否则静音/黑屏),且必须保持平面 2D(不能位于 3D 祖先下);
  • Chrome(z 6,不计时):左上 kicker chip HYPERFRAMES WEEKLY · <RANGE>,右上进度点每主题一个,激活态用 tl.set 在每次 cut 时刻改背景色(active #f5f6f4、done .45)——绝不 tl.call 做状态
  • 场景解剖:标题(≤2s,mono kicker 日期 + ABC Solar h1 ~104px + 绿色横线扫过);主题场景 = sec-chip 0N · THEME(top 128)+ 54px 标题(top 186)+ mock 填充 y ∈ [288, 944];outro(≤3.5s)= FULL DIGEST kicker + "See what shipped." + URL chip,结尾前 ~0.5s 整体淡出;
  • 内部 beat:每个 beat 落在 vo-words.json 时间戳上(局部时间 = 主时间 − 场景开始),场景壳在局部 t=0 组装,内部 beat 距 cut ≥0.4s、距下一 cut ≥0.45s 完成;
  • 字幕轨 IIFEL89–112):读一个 LINES 数组,为每行生成 .cap-phrase、每词生成 .cap-w,短语在首词时刻 tl.set 显隐、在 line.end 消失,每个词在自己的时间戳上以 0.12s power1.out 淡入。

字幕不可省略:skeleton 里 LINES = [] 是待填占位——把它留空是"发布的 bug",不是风格选择。必须把 captions.json 的内容粘贴进去:

// 替换 caption-rail IIFE 中的 "const LINES = /* … */ []":
const LINES = /* captions.json 的内容 */ [
  { id: 0, end: 2.74, w: [["This", 0.0], ["week,", 0.30], …] },
  …
];

若跳过了 align-captions.mjsLINES 仍为 [],Step 6 的帧检查会失败——不要用删掉脚手架里的 #cap-line 来掩盖问题

Step 6:五道门禁(全绿才交付)

  1. lint 检查(仓库本地 CLI 为契约事实来源,不用 npx hyperframes@latest):

    bun run --cwd packages/cli hyperframes check --caption-zone \
      "x0=0;y0=.90;x1=1;y1=1;severity=error;seek=.02,.06,.10,.14,.18,.22,.26,.30,.34,.38,.42,.46,.50,.54,.58,.62,.66,.70,.74,.78,.82,.86,.90,.94,.98"
    

    25 个采样点覆盖全片,要求 0 error:暗色文字 alpha ≥ .66(对比度),场景内容保持在字幕轨上方(caption zone 是底部 y0=.90 起的全宽区域)。

  2. seam-gate.mjs verify.claude/skills/motion-doctrine/scripts/seam-gate.mjs)——0 fail,seam 连续性由门禁强制。

  3. 重启预览服务器(它缓存 bundle),在原始 comp 页面上用 __player.seek 抽查 3–4 个 beat。

  4. 未获要求不渲染。用户要求渲染后,用 ffmpeg -ss <t> … -frames:v 1 从 MP4 抽帧验证:字幕存在、背景视频非黑、无微小/冻结帧。

  5. 字幕存在性门禁——硬失败:跨 VO 口播窗口均匀抽 3–4 帧(例如 48s 的 VO 取 t=3/15/30/42),确认 top: 990 的字幕轨每帧都渲染出可见文字。任何口播区间内抽到无字幕的帧,就按红灯处理,回查 Step 5 的 LINES 填充。SKILL.md 明确这是 7 月 13–20 那期 v4 构建实际踩过的坑。

项目目录结构与反模式清单

交付的项目布局:

projects/active/weekly-changelog-<range>/
├── index.html            # 单文档 master(场景为 slide,seam 已 stamped)
├── ledger.json           # 向量台账(seam-stamp 输入)
├── script-tokens.json    # 双层脚本(VO + 字幕的事实来源)
├── vo-spoken.txt         # 生成物:spoken 层,一行
├── voiceover.mp3 + vo-words.json + captions.json
├── bgm.mp3               # 从 <SKILL_DIR>/assets/bgm.mp3 复制(house track),除非用户提供
└── assets/fonts/ + assets/bg-pattern-<dur>s.mp4

SKILL.md 收尾的反模式表把整条管线的纪律浓缩为 11 条,值得作为检查单使用:

不要做 而是
用 bullet 幻灯片讲 UI 变化 mock 出该界面"演"出变化
给无法表现的东西做假 UI 诚实的 checklist 场景
TTS 文本里直接写 "JSON"/"CLI" 用词表 spoken 形式;display 保持标准拼写
字幕出现音形拼写 字幕永远渲染 display 层
猜一个未知术语的发音 先问,再扩充词表
口播每一条 changelog 条目 每主题 ≤3 条;digest 链接承载其余
到处用绿色强调 每场景只留一个 #5ef17c 时刻
从以前的视频 index.html 起步 Step 0——永远从本 skill 的 examples/master-skeleton.html 复制
手写 @font-face / WebGL shader / 自制 BGM Step 0——逐字复制本 skill 的 assets/;skill 的资产就是品牌
交付而未做 CloudFront invalidation S3 替换后对分发 E2BSLVSZ7FG3U0 的精确路径跑 aws cloudfront create-invalidation——否则 CDN 会一直缓存旧文件
LINES 数组为空就交付 Step 4 产出填充的 captions.json → Step 5 粘入 IIFE → Step 6 第 5 道门禁确认渲染帧有字幕;空 LINES = 无字幕交付 = 重跑
没有 vo-words.json 就跳过字幕照常交付 回退到 whisper 强制对齐;字幕不可选

延伸阅读

这条管线把"changelog → 视频"做成了有门禁约束的确定性工程:编辑裁剪有预算、可视化有路由表、发音有词表、时间戳有对齐算法与回退、交付有五道门禁。它的上层运动规则(向量法则、film current、seam-gate)在 .claude/skills/motion-doctrine/SKILL.md(seam 门禁细则见 seam-gate.md,工具链为 seam-stamp.mjsseam-gate.mjs);cut-the-curve 曲线目录在 .claude/skills/cut-the-curve/SKILL.md,字幕 overlay 模型在 .claude/skills/captions-overlay/SKILL.md,品牌构建规范全量在 build-spec.md。若要理解这条 skill 产出的"lint-clean"由什么定义,可继续看 packages/cli 的检查实现与 packages/core 的组合契约。

登录后查看全文
热门项目推荐
相关项目推荐