Communitygithub.com

cxb2523/gsb-828-9f38b84355494457

通过代码生成每一帧的动画视频(Canvas 2D + 程序化 Web Audio),并在无头模式下渲染为 MP4,支持 16:9 / 9:16 / 4:5 比例,适合制作动画解说、技术演示、动态图表和程序化音效或音乐,最终输出为可编辑的代码而非生成的像素。

gsb-828-9f38b84355494457 是什么?

该技能允许开发者编写纯 JavaScript 文件(`index.html`),通过 Canvas 2D 绘制每一帧并结合 Web Audio 生成同步音效,最终在无头 Chrome 中渲染为高质量 MP4 视频。其核心设计理念是输出代码而非像素,因此视频中的任何时刻都可以被精确编辑、重新计时或重新渲染。技能借鉴了 Remotion、HeyGen HyperFrames 等项目,并提供了一个完整的工具链(`verify.mjs`、`render.mjs`、`lint.mjs`、`review.mjs`、`qa.sh`)来确保动画的确定性、同步性和质量。它支持多种宽高比(16:9、9:16、4:5、1:1),并从单一时间线生成不同布局,而非简单裁剪。该技能特别适合需要精确控制、程序化生成和版本管理的动画制作场景,如技术讲解、数据可视化与音乐视频。

兼容平台✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/cxb2523/gsb-828-9f38b84355494457/tree/HEAD

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

代码渲染动画

核心理念(Addy Osmani, LinkedIn 2026-09-24):编程模型读取源内容(文章、规格说明),并编写 JavaScript 代码来绘制每一帧及音效。交付物是一个程序,因此任何时刻都可以被编辑、重新计时、重新渲染或重新格式化。Osmani 未公开其提示语或工具。此技能的设计源自开源项目 riso-windowseat(MIT 许可证,仅作参考,请勿复制其说明):https://github.com/sevenevesai/riso-windowseat 。此外,它也借鉴了 HeyGen HyperFrames (https://hyperframes.heygen.com/guides/pipeline) 和 Remotion 的一些想法。

基本原则:以不低于 4 次/秒的采样率逐帧审查;绝不声称执行了未实际运行的检查;精准修复缺陷。

约定(一切皆以此为基础)

每个作品是一个独立的 index.html,包含一个 <canvas>,该 canvas 从 URL 中读取 ?w=&h= 参数,并暴露以下接口:

window.__anim = {
  duration, fps, width, height, canvas,
  ready,            // 在字体/资源加载完毕且 seek(0) 绘制完成之前为 false
  seek(t),          // 同步绘制时间 t 处的精确帧
  renderAudio(),    // Promise<base64 WAV>,48 kHz 立体声,时长严格等于 duration
  marks,            // 可见事件(弹出、切换)的时间点 - 音效必须落在这些点上
  shots,            // [{id,start,end}] - 验证时会检查边界
};

不变规则:

  • seek(t) 是 t 的纯函数。屏幕上的每个值都由 t 计算得到:prog(t,a,b)、缓动函数、pop(t,at)。帧之间不携带状态,不使用 requestAnimationFrame 时序,不使用 Date,不进行物理步进,除非从 0 开始重新计算或使用封闭形式。
  • 渲染时绝不调用 Math.random()。请使用 rngFor('stable-key')(基于 mulberry32 的种子随机数生成器),并在加载时通过固定键创建随机布局。更改键会改变输出结果,因此在批准后将键冻结。
  • 一个时间线对象 (TL) 包含所有场景范围和事件时间。画面和配乐都从这个对象读取。这就是同步的实现方式:音效在画面弹出的相同数字时间点被调度。
  • 在 ready=true 之前等待字体加载完成(document.fonts.load('700 40px "DejaVu Sans"'))。否则,前几帧会使用后备字体渲染。通过 fc-list : family(Linux/macOS)或 C:\Windows\Fonts(Windows;Segoe UI、Bahnschrift、Consolas 和 Georgia 始终存在,DejaVu 则不一定)检查已安装的字体。如果设计需要特定字体,请将其作为 base64 @font-face 嵌入,并以相同方式加载。
  • 作品中不能包含网络调用。将所有资源作为 data URI 嵌入。

可编辑元素(用于影片修复)

review/film-fix.html 允许审阅者直接移动和缩放元素,并将结果写回 index.html。希望使用此功能的影片(示例影片使用了此功能;请从该处复制)需要添加:

  • 一行 const TUNE = /*TUNE*/{}/*END-TUNE*/;,键值按格式再按元素 ID 组织:{"16x9":{"title":{"dx":0,"dy":0,"s":1}}}。之所以按格式键控,是因为每种宽高比有自己的布局。dx/dy 为画布像素。该工具只重写两个标记之间的文本。
  • 在每个主要元素周围使用 E(id, [x,y,w,h], draw)。它会在 TUNE[fmt][id] 中查找,然后保存上下文,按 dx/dy 平移,按 s 围绕盒子中心缩放,调用 draw() 并恢复上下文。它还会将 {id, box} 推入 __anim.elements,其中 box 是画布像素坐标下的变换后盒子(来自 ctx.getTransform(),因此摄像机移动会被考虑在内)。seek(t) 在每帧开始时清空此列表。
  • __anim.elements、__anim.format(16x9、9x16、4x5、1x1)和 __anim.tune(用于实时 TUNE 对象的 getter/setter,供工具的实时预览使用)。
  • 规则:ID 在整个影片中唯一;无随机性;在 TUNE 为空时,E 不应用任何变换,因此帧保持字节级一致。通过在添加 E 前后计算帧哈希来证明这一点,然后运行 verify.mjs。

没有使用 E 的影片仍然可以在 film-fix 中工作:其“微调”模式会对一个区域进行框选,并将拖拽或缩放转化为供 AI 使用的数值修复。

项目设置

需要 Node 18+、ffmpeg(libx264、aac、blackdetect、freezedetect、ebur128)以及本地 Chrome/Chromium。工具链会在 Windows、macOS 和 Linux 的常见安装位置查找 Chrome;设置 CHROME 环境变量可覆盖此路径。qa.sh 需要 bash(Windows 上使用 Git Bash),并会选择一个系统字体用于时间码显示;设置 QA_FONT 环境变量可覆盖。不会在系统中安装任何全局包;puppeteer-core 是唯一的 npm 依赖。

SKILL_DIR=<此技能文件夹的路径>
mkdir -p anim && cp -r "$SKILL_DIR/harness" anim/harness
cd anim/harness && npm install && cd ..
mkdir -p works/<名称>
cp "$SKILL_DIR/example/browser-10s/index.html" works/<名称>/index.html   # 从可工作的示例开始
# 所有工具命令都在 anim/ 目录下运行;输出文件位于 out/ 目录

工具命令

命令功能成功信号
node harness/verify.mjs works/<n>/index.html --format 16x9在采样点和镜头边界处向前、向后和冷跳查找,然后比较像素哈希值verify: OK, 退出码 0
node harness/shoot.mjs works/<n>/index.html --format 9x16 --times 1.5,3.4 或 --range 3:4:0.1 --out out/stills在精确时间点生成原生 Canvas PNG查看图片
node harness/audio.mjs works/<n>/index.html --twice仅渲染配乐(秒级),检查精确时长和运行间的确定性无 FAIL。Chrome Web Audio 在两次运行之间可能相差 1 LSB;容差为 1
node harness/render.mjs works/<n>/index.html --format 16x9 --fps 30 --out out/<n>-16x9.mp4逐帧查找并将 PNG 通过管道送入 ffmpeg(libx264 crf18, yuv420p, +faststart),然后将 renderAudio 作为 AAC 复用打印帧数和时间。页面错误时退出码为 2。--from/--to 或 --silent 可渲染不带音频的范围
node harness/lint.mjs works/<n>/index.html --all-formats对页面本身进行质量门禁检查(16:9 + 9:16;使用 --format 可指定单一格式):文本大小、安全区域、文本重叠、静态拉伸、空白帧、镜头列表、音效是否落在标记处、屏幕上的字数lint: OK (0 FAIL), 退出码 0
node harness/review.mjs works/<n>/index.html --format 16x9 --out out/review-16x9, 然后 --check out/review-16x9/REVIEW.md每个镜头生成一张联系页(第一帧、6 个均匀间隔的帧和最后一帧,带烧录的时间码)、lint JSON 以及 REVIEW.md,其中包含针对每个镜头的固定问题。--check 在答案为空白或 TODO、评分低于 4、lint 失败、镜头部分被删除或影片自联系页生成后发生更改时失败review check: OK, 退出码 0
bash harness/qa.sh out/<n>-16x9.mp4 4ffprobe、blackdetect、freezedetect、EBU R128 响度、音频/视频时长、以 N 次/秒采样并烧录时间码的联系页参见 QA 部分

格式:16x9 1920x1080、9x16 1080x1920、4x5 1080x1350、1x1 1080x1080。默认情况下,帧以 JPEG q95 质量送入 ffmpeg,在 1080p 下约为 16 fps,因此 40 秒的片段每个格式大约需要 60-75 秒。--png 是无损的,但速度大约慢 4 倍。

长渲染:代理的 shell 调用可能会超时(通常为 120 秒),并连带终止其子进程。要么提高调用的超时时间,要么分离运行完整渲染并轮询日志(Linux/macOS 示例如下;Windows 上请使用后台 shell 或 Start-Process):

setsid nohup bash -c 'node harness/render.mjs works/<n>/index.html --format 16x9 --out out/<n>-16x9.mp4; echo RENDER_DONE' > out/render.log 2>&1 < /dev/null & disown
tr '\r' '\n' < out/render.log | tail -2

另外,请将编写作品文件的 heredoc 与运行它的命令分开,以防止超时导致文件丢失。

完成标准

具体命令在 AGENTS.md(单一来源,与其他编码工具共享)中:在每个格式中运行 verify、audio --twice、lint --all-formats(0 FAIL)、review.mjs 然后通过 review.mjs --check、渲染、QA。切勿在门禁检查失败时声称完成,也切勿为了通过某项检查而削弱门禁或编辑工具阈值。

工作流程

  1. 简报。 源内容是什么、目标观众、时长、格式、是否需要旁白。从源文章中提取关键主张。每个场景都必须展示正在表述的内容。 先讲故事,再保证正确性。 工具链能使影片正确,但不能使其吸引人。在制作故事板之前,先回答简报中的四个问题:
    • 角色是谁?在每个场景中他们感受到什么?
    • 用什么日常物品来代表每个想法?(原作使用了带蜡封的信、沙盒和一座由房屋组成的城镇。)
    • 是什么持续存在的 HUD 将各个场景串联起来?通常是一个章节拨盘和一个打字机风格的标题。
    • 最后一帧如何循环回到第一帧? 一部不具备以上任何要素的影片,尽管能通过所有门禁检查,但观众仍会匆匆划过。
  2. 在写代码之前先写 STORYBOARD.md。 每行对应一个场景:时间范围 | 屏幕内容 | 动作 | 音频提示。列出每个事件时间(这些将成为 TL 和 marks)。每几秒规划一次切换或新的运动,避免静态拉伸。将过渡规划为推入或重叠,切勿让帧变为空白。除非得到批准,否则不要使用渐隐至黑屏的结尾。
  3. 时间线模块,然后每个场景一个 sceneN(t) 函数(关于 t 的纯函数),最后是一个合成器,负责绘制背景、活动场景和过渡。
  4. 先处理最难的帧。 构建密度最大的时刻,在每个格式中 shoot 它,查看它,修复它。然后再逐步构建其它部分。
  5. verify 每个格式,audio --twice,然后 render 每个格式。
  6. QA(如下),精准修复,仅重新渲染需要修改的部分,重新 QA。
  7. 交付 MP4 文件作为附件,并附上 QA 结果和已知的弱点。

使用旁白

先处理 TTS,然后获取单词级别的时间戳(使用任何能返回单词级时间的工具转录 WAV),然后根据单词设置 TL。场景、弹出和卡拉 OK 字幕高亮都基于单词时间点。规则:在框内显示卡拉 OK 式字幕,仅一个字幕轨道,说出数值时在屏幕上显示,通过听觉检查专有名词。将旁白 WAV 作为 data URI 嵌入,并在 renderAudio() 内部进行混音(使用 ctx.decodeAudioData 解码,在时间 0 调度),或者使用 ffmpeg 复用并在配乐下进行闪避处理。标准化整个视频中旁白的响度。

从单一时间线实现多格式(非裁剪)

  • 相同的 TL,相同的场景,不同的布局。计算 portrait = H > W、S = min(W,H)/1080(字体和形状缩放比例)、M = 0.06*min(W,H)(安全边距)。按宽高比布局:16:9 中的行在 9:16 中变为列,树形结构获得更窄的展开,等等。
  • 文本:自适应缩小(measureText 循环,对比 W-2M),确保没有任何内容被边缘裁剪。尺寸基于 S 计算。在竖屏模式下,使用更大的字体并填满高度。
  • 不要将“竖屏”视为一种情况。4:5(1080x1350)比 9:16 短得多。示例中 9:16 的列布局在 4:5 比例下运行时,像素网格和标签行会从底部被推出并重叠在一起。根据宽高比(H/W:约 0.56、1.25、1.78)或根据可用高度进行布局,并对交付的每个格式执行 shoot。
  • 在检查边距时,记住弹出时的过冲(easeOutBack ~1.1x)和“热”缩放效果。使用 shoot 验证每个格式密度最高的帧。

程序化音效 (OfflineAudioContext)

  • new OfflineAudioContext(2, 48000*duration, 48000),构建音频图,await ctx.startRendering(),编码为 16 位 PCM WAV,返回 base64(请参阅示例中的 wavB64)。
  • 示例中的模式:每个场景一个背景和弦(失谐的正弦/三角波对,慢起音,在切换处重叠),在每个 marks 事件处一个拨弦音(三角波 + 2 个泛音,5 毫秒起音,指数衰减),用于打字效果的种子噪音爆发点击声,用于撞击效果的音高下降重击声,以及主总线上的 DynamicsCompressor。
  • 噪声来自种子随机数生成器,绝不要使用 Math.random。
  • 响度:对于社交媒体推送,目标综合响度约为 -14 LUFS,峰值低于 -1 dBFS。原始文章测得 -14.3 LUFS;捆绑的示例较安静,为 -17.2 LUFS,峰值为 -1.4。通过在页面中设置主增益来控制,使其保留在代码中。
  • 对于比特级精确的音频,使用普通 JS 将样本合成为 Float32Array(使用封闭形式的包络累加振荡器),而不要使用 Web Audio 节点。
  • 如果需要更丰富的音乐 API,Tone.js 的 Tone.Offline (https://tonejs.github.io/docs/14.9.17/functions/Offline.html) 是一个选项。由于作品无网络访问权限,因此必须内联嵌入。

QA(审查是我们的责任,不是观众的)

自动门禁检查,全部针对最终 MP4 文件:

  • 每个格式的 verify 通过。页面错误 = 失败。
  • ffprobe:预期尺寸、30/1 fps、nb_frames = 时长 x fps、音频时长 = 视频时长。
  • blackdetect:无(除非有批准的戏剧性黑屏节拍)。freezedetect(>=2 秒静态):无。
  • ebur128:综合响度和真实峰值在目标范围内。剧集间保持一致。

Lint(质量门禁)

node harness/lint.mjs works/<n>/index.html --all-formats [--json out/lint.json] 每 0.1 秒(--step)对作品进行一次 seek,并从外部包裹 fillText/strokeText,因此它无需修改即可在任何遵循约定的作品上运行。在以下情况下失败:

  • text-size:静态文本小于 min(W,H) 的 2.6%(1080p 下为 28 像素);小于 3.3%(36 像素)为 WARN。因缩放至 0.5 以下而缩小的文本被视为概览地图,仅发出警告。
  • safe-area:静态文本距离边缘在 4% 以内;4-6% 为 WARN。
  • text-overlap:两个不同的可见字符串在 0.2 秒或更长时间内共享较小框面积的 15% 以上。更短的交叉为 WARN。
  • static-stretch:超过 3 秒无可见变化(2 秒为 WARN)。empty-frame:任何接近均匀的帧,包括第一帧和最后一帧。
  • shots-coverage:shots 中存在间隙或重叠,或某个镜头短于 1 秒。audio-sync:在 ±40 毫秒内具有声音起始点的 marks 占比低于 70%(90% 为 PASS);峰值超过 -1 dBFS 为 WARN。density:竖屏模式下屏幕上超过 18 个单词,或横屏模式下超过 30 个单词(仅 WARN)。

在您交付的每个格式中,lint 必须以 0 FAIL 通过,作品才算完成。它只能看到在作品自身 canvas 上使用 fillText/strokeText 绘制的文本,因此形状碰撞和绘制到离屏 canvas 的文本仍需人工视觉审查。example/browser-10s 在所有三种格式中均通过;example/browsers-40s 在门禁引入之前制作,因此未能通过(标签大小为 17-28 像素,9:16 格式中一个标签被裁剪,文本

…

[已截断以进行翻译]

相关技能