animate 是做什么的?
您用代码制作短动画视频:一个 <canvas>,程序化绘制和配乐,逐帧确定,通过无头浏览器和 ffmpeg 渲染。故事遵循从优秀短动画中总结的语法;外观是一种风格——一个插件套件,从 styles/ 中选择,或根据用户的参考图使用 new-style.md 创建。
要求: Node 18+、带 Chromium 的 Playwright(npm i -g playwright && npx playwright install chromium)、路径上的 ffmpeg。首先检查它们;如果缺少某个,告诉用户安装命令并停止。对于画外音,使用带有 faster-whisper 的 Python(pip install faster-whisper)来计时每个单词;没有它,单词时间将被估算。
以下所有路径都相对于此技能的基础目录。作品存放在用户的项目中,位于 pieces/<name>/(如有需要请创建 pieces/);用户创建的风格存放在 <project>/styles/<name>/。
不妥协事项
- 故事板批准前不得进行动画制作。 存在确认环节是因为错误的故事或外观会导致整个构建失败。
- 在绘制任何内容之前查看参考——无论是用户的,还是风格的
sample.png和演示。然后对照 grammar/FRAME.md 和风格自己STYLE.md中的检查表检查每个关键帧。 - 屏幕上的每个事实性声明都通过网络搜索验证并在简报中列出其来源。切勿在没有来源的情况下声称“第一个”。
- 单文件,所有代码: 一个
index.html;一切都在代码中绘制和合成——没有图像/音频/视频文件,没有data:URI,没有 URL,没有代码中的网络字体。tools/build.mjs强制执行。有两个可选的例外,都是用户自己的:资源(<piece>/assets/中的屏幕截图和标志,由构建嵌入)和音乐轨道(在导出时加入)。请参阅下面的资源和音乐部分。 - 确定性: 仅使用种子随机数(
RNG(...)),沸腾索引B用于每步变化。切勿使用Math.random()或挂钟时间。 - 参考媒体保持本地。 切勿提交它、复制其代码、或在发布的输出中提及艺术家的名字,除非用户要求注明出处。
- “完成”是可测量的:
tools/review.mjs通过并且您已查看联系表。报告数字,而不是“看起来不错”。 - 用选项提问。 使用 AskUserQuestion,并首先推荐默认选项,以便用户一键回答。
流程
0. 输入
询问 intake.md 中的问题——仅限于那些您无法推断的问题。对于外观,构建画廊(node tools/gallery.mjs <out.png> [<project>/styles],约 3 秒),展示它并询问:这些中的哪一个,或者给我看一个参考图? 然后您自己对照 FRAME.md 和风格的检查表研究参考图。
1. 故事确认(文本,快速)
从 grammar/FORMATS.md 中选择格式——情节引擎决定它(历史是通过形态连接的年表;任务在节拍上切换;列表是目录)。阅读 grammar/STORY.md。通过网络检查事实。然后向用户展示:
- 一句话的创意和可叙述的技巧(“火花在每个时代获得一道光芒,最终成为答案”)
- 格式及其原因、风格、长度、BPM
- 编号的节拍表:时间、发生的事件、进入下一个节拍的过渡、声音的作用(沉默在哪里,最响亮的节拍在哪里)
询问:批准 / 更改节拍 / 不同角度。 修改直到批准。在这里,“我指的是 X,而不是 Y”会被低成本地捕获。
2. 外观确认(2–4 帧风格帧)
在 pieces/<name>/ 开始作品:从风格的演示(styles/<name>/demo/)——它已经使用了风格的套件并通过形态连接时代——或从 templates/beat-cut/(用于在节拍上硬切的作品:8 分音符、奇数帧上的 16 分音符冲刺、每个镜头一个时代、使用摄像机取景)或 templates/piece/(用于最简单的剪纸形态)复制 piece.json 和 src/。在 piece.json 中设置 "style",然后重新编写 brief.md 和 LOG.md(模板中的是占位符)。
- 已发布的风格: 阅读其
STYLE.md和演示,并使用其套件进行绘制。 - 用户的参考图: 遵循 new-style.md。首先测量参考图(
tools/measure/refs.mjs);作品自己的风格帧就是匹配的帧,所以这只是一次确认:比较表和帧一起。在作品批准后保存风格文件夹(该作品成为其“已验证”的证明)。
绘制 2–4 个最不同节拍的全尺寸帧。每个必须通过 FRAME.md 和风格的检查表。使用 node tools/still.mjs pieces/<name> 2.25,9.75,16.5 look.png --scale 0.5(或 tools/storyboard.mjs 并使用 TIMELINE.board 设置为那些节拍)将它们并排渲染,并显示图像。询问:逐帧点赞 / 更改外观 / 尝试另一种风格。
3. 故事板确认(每个节拍)
以批准的外观绘制每个节拍。用每个节拍的一个关键时间填充 TIMELINE.board({ t, title, sound, next })并渲染板子。要求按面板编号上下点赞和注释;重新绘制被点踩的;重复。面板就是视频自己的场景函数,因此之后不会重新绘制。
4. 构建
- 编写
pieces/<name>/brief.md(规格、风格、节拍、过渡、声音计划、声明+来源)。保持LOG.md。 - 动画场景:使用
TT、ev(t, d)、popS(t)、书写动画(参见 craft.md → 运动)的定时动作。在src/bridges.js中连接时代、摄像机和过渡(对于形态连接的格式)或保留BRIDGES = []用于硬切。 node tools/build.mjs pieces/<name>→index.html(风格套件从piece.json的"style"中拉入)。- 构建时测试图块:
node tools/tile.mjs pieces/<name> tile.png <frames...>——查看它们;平铺覆盖每个形态和切分。 - 在
src/score.js中配乐(参见 craft.md → 声音)。 node tools/export.mjs pieces/<name> --share→ 帧、audio.wav、音轨分轨、renders/final.mp4、renders/share.mp4(--formats 9:16,1:1,16:9用于每种格式;--blur 4用于 1s 风格的运动模糊)。使用--only-audio迭代混音(重新渲染配乐并重新混合,速度提高约 4 倍)。node tools/review.mjs pieces/<name>→ 每个镜头的联系表、切分网格、形态网格、故事弧、锚点、文本(截断/重叠/在手机 UI 下方)和声音(最响亮的时刻、之前的沉默、LUFS)。修复直到通过,查看联系表,在LOG.md中编写每个镜头的 PASS 表。相信文本检查胜过您的眼睛:裁剪的标签在联系表上看起来没问题。
5. 交付
发送 renders/share.mp4。用几句话说明:它是什么,检查的数字,审查捕获并修复了什么,最弱的镜头,以及您未验证的内容(例如,您无法收听配乐)。询问应该更改什么:故事、外观、过渡、节奏、声音。
6. 学习
每次运行后,追加到作品的 LOG.md。如果出现了新的错误或有效的方法,为 craft.md、风格的 STYLE.md 或套件更改提出一条一条的规则,并在用户同意时添加。
画外音(可选)
在输入时询问(问题 3)。有了声音,声音就是时钟:每个节拍在前一句结束后约 0.3 秒开始,重要时刻落在口述单词上。脚本就是故事板:每个节拍一行。
- 将脚本写入
pieces/<name>/voice/script.json:{ "lead": 1.2, "tail": 1.8, "lines": [{ "id": "open", "text": "As it is spoken.", "after": 0.45 }, ...] }。按照应该说的方式编写数字和名称。保持在 ≤ 3.5 词/秒;在需要图片时间落地的位置给行一个更长的after(计数、说明文字、回报之前的沉默)。 - 在声音存在之前构建:
node tools/voice.mjs pieces/<name> --scratch使用操作系统自己的语音制作一个时序声音(切勿发布它),以便整个作品可以被动画化和审查。 - 录音: 每行一个文件,保存为
voice/<id>.mp3,并在行上设置"file"。- ElevenLabs 连接器(如果用户有:Claude 的设置 → 连接器 → ElevenLabs,通过 OAuth 登录,无需 API 密钥):使用其语音搜索找到 2–3 个合适的叙述者,为每个生成一行(每个一个变体,不是四个),让用户选择,然后使用该语音和模型为每行生成一次。生成结果提供每个录音的短期下载链接:将每个保存到
voice/。每个字符大约花费一个积分;在全面运行前告知总数。如果在会话期间添加了连接器,其工具可能仅在应用重启后出现。 - 用户自己的录音,每行一个文件(或从一个录音中剪切)。
- 切勿从用户未指向的站点获取语音,并且未经所有者同意切勿克隆语音。
- ElevenLabs 连接器(如果用户有:Claude 的设置 → 连接器 → ElevenLabs,通过 OAuth 登录,无需 API 密钥):使用其语音搜索找到 2–3 个合适的叙述者,为每个生成一行(每个一个变体,不是四个),让用户选择,然后使用该语音和模型为每行生成一次。生成结果提供每个录音的短期下载链接:将每个保存到
node tools/voice.mjs pieces/<name>→voice/voice.wav(带有暂停的行)和voice.json(每行和每个单词的开始和结束,由 faster-whisper 在音频中本地听到)。构建将其注入为VOICE。- 在头部:
VL(id)是一行,VW(id, word, n)当其第 n 个word开始时;节拍在前一行的t1 + 0.3处开始;TIMELINE.narration来自VOICE.lines,DURATION来自VOICE.duration。在命名它们的单词上提示书写和闪烁。将回报节拍放在命名它的行之前的停顿中:语音下的节拍会被闪避。 piece.json:"voice": { "file": "voice/voice.wav", "music": -12 }。导出将语音混音到配乐之上(配乐在musicdB,语音说话时进一步闪避)到 −14 LUFS。审查仅基于配乐(renders/audio-score.wav;语音比任何节拍都响)和语音及其 NARRATION 检查判断故事弧和回报窗口。- 重新录音和脚本编辑: 替换行的文件(或编辑其文本),重新运行
voice.mjs,重新构建:从VOICE提示的场景随之移动。更改的单词可能导致指向它的提示孤立;检查那些节拍。
音乐:用户自己的轨道(可选)
在输入时询问(问题 6)。没有轨道时,配乐在 120 BPM 网格上在代码中合成。有轨道时:
- 将其放在作品中:
pieces/<name>/audio/track.wav(WAV、MP3 或 M4A),并在piece.json中:"music": { "file": "audio/track.wav", "start": 12.0, "gain": 0, "fade": 0.5 }(start= 作品中歌曲开始的位置)。 node tools/beats.mjs pieces/<name>/audio/track.wav pieces/<name> --start 12 --dur <作品长度>→beats.json:速度、第一个节拍、每个节拍和小节、最强的节拍、最响亮的时刻和下降。如果用户知道速度,传递--bpm 128;否则检查其“其他速度”行(双倍/一半/三分之二时间通常是常见的错误)。- 构建将其注入为
BEATS;起始头部从它读取BPM和GRID0,并且onBeat(t)将时间捕捉到歌曲的 8 分音符网格。将切分、形态和提示放在onBeat(...)上。歌曲决定弧线: 将转折放在下降(BEATS.quiet)上,将回报放在其最响亮的节拍(BEATS.loudest、BEATS.hits)上,而不是合成配乐会放置它们的位置。 - 配乐(
src/score.js)只保留音效(to = 's'):导出用轨道替换音乐总线,将音效分轨混音到其上,并归一化到 −14 LUFS。审查的网格使用TIMELINE.gridOffset;其 SOUND 部分说明最响亮的时刻是否落在回报幕中。 - 浏览器预览仍播放合成的配乐(页面中没有音频文件);导出有轨道。
资源:真实的屏幕截图和标志(可选)
在输入时询问(问题 7),当主题是产品时。然后:
node tools/capture.mjs <url> pieces/<name>/assets/home.png [--selector "css"] [--full] [--dark] [--hide "css"]截取用户自己网站(页面、部分、标志)的屏幕截图。在捕获他们不拥有的任何内容之前先询问。他们提供的标志文件也放入assets/(PNG、SVG、JPG、WEBP)。tools/build.mjs将assets/中的所有内容嵌入到index.html中(作品保持一个文件,并且画布保持可由检查读取);一旦图像解码,window.renderFrame就会出现,并且每个工具都等待它。- 使用
drawAsset('home.png', x, y, w, h, { fit: 'cover' | 'contain', r })在风格自己的框架内(撕纸照片、墨水框架、印刷卡片)绘制它们,使它们属于外观。asset(name)返回图像。 - 仅真实产品 UI: 对真实屏幕进行动画(裁剪、推进、揭示、用主角指向它们);切勿绘制假屏幕,假装是产品。从屏幕截图中采样品牌颜色。
格式:一个作品,多种形状(可选)
在输入时询问(问题 2,多选)。在 piece.json "formats": ["9:16", "1:1", "16:9"] 中列出它们(第一个是主要的)。起始头部读取 ?format= 并设置 W、H、SAFE 和 CX;场景使用 LX(fraction)、LY(fraction) 放置事物,并使用 UNIT 调整它们的大小,在需要形状的地方使用 PORTRAIT / WIDE 选择不同的排列(9:16 上三列,16:9 上三行)。切勿将 9:16 渲染裁剪为 16:9。
node tools/export.mjs pieces/<name> --formats 9:16,1:1,16:9 --share→renders/final.mp4(主要)和其他格式的final-<w>x<h>.mp4。- 审查每个:
node tools/review.mjs pieces/<name> --format 16:9(并且tile/still/textcheck/storyboard也接受--format)。文本检查在这里最重要:适合 16:9 的宽说明文字可能跑出 9:16。
运动:弹簧和模糊
- 弹簧(
kit/core.js):springMove(t0, a, b, SPRING.snappy)以轻微过冲和稳定移动一个值;springTrack([[t, v], ...])无跳跃地跟随一个值经过多个目标。预设:snappy(UI)、smooth(卡片、摄像机)、heavy(大号字体、标志)、playful(吉祥物)。封闭形式,因此帧保持确定性。在 2s 风格上,它们每 2 帧步进一次,如应做的。 - 运动模糊:
export.mjs --blur 4每帧平均 4 个子帧——仅适用于 1s 风格(STYLE.ones:数学、等距);手绘外观在 2s 风格上故意清晰(导出会警告)。
并行代理(长作品)
一个 45–60 秒的作品有 10–15 个场景;将工作分配到 3–4 个代理之间:一个收集参考和样张,一个编写每个场景,一个审查和修正。每个代理使用 buildAgentPiece("<name>", 2, 4) 等工具。
要拆分:1)输入和故事(代理 0),2)场景 #1–5(代理 1),3)场景 #6–10(代理 2),4)场景 #11–15(代理 3),5)最终组装:构建、导出、审查。代理 1–4 各自编写其场景文件(src/scenes/scene-{n}.mjs),填充 TIMELINE.board 中其节拍的部分,并运行 node tools/build.mjs pieces/<name> --scene {range} 进行测试。最终代理运行完整的 build.mjs、export.mjs 和 review.mjs。确保所有代理就图形和过渡达成一致(先编写 brief.md 和 src/bridges.js)。
停止并检查:在进入第 5 步之前,穿过所有场景进行故事板检查(在第 3 步处)。让用户一次性批准所有节拍。
工具
tools/build.mjs pieces/<name>— 构建一个作品;--watch在文件更改时重新构建。tools/export.mjs pieces/<name>— 渲染、音轨、最终视频。tools/review.mjs pieces/<name>— 联系表和数值检查。tools/tile.mjs pieces/<name> <out.png> <frames>— 来自场景的时间(例如 1.2,2.0)的瓷砖。tools/still.mjs pieces/<name> <times> <out.png>— 大尺寸关键帧。tools/storyboard.mjs pieces/<name>— 来自 TIMELINE 的故事板。tools/gallery.mjs pieces/<name>— 所有注册风格的拼贴画。tools/capture.mjs <url> <out.png>— 屏幕截图以嵌入。tools/measure/refs.mjs <ref-path>— 分析参考图像。tools/beats.mjs <track> <piece>— 节拍检测。tools/voice.mjs pieces/<name>— 处理画外音。tools/finalise.mjs pieces/<name>— 将作品标记为已完成。
调试
- 使用
window.RNG查看随机状态。 - 使用
window.drawList查看所有绘制调用。 - 使用
drawOverlay: true显示边界框和锚点。 - 使用
window.TIMELINE查看完整的时间线。 - 使用
window.renderFrame(n)跳转到特定帧。 - 使用
tools/tile.mjs快速查看多个帧。
相关技能
new-style.md— 根据参考图像创建新样式。grammar/— 叙事语法和帧布局。kit/core.js— 核心动画原语。templates/— 用于不同格式的起始点。