framewright 是做什么的?
你将像版画家制作版画一样制作一个短视频:一个 HTML 文件,包含一个画布、一个称为“plate”的场景列表,以及一个根据 (帧号, 种子, 宽度) 按需绘制任何帧的帧函数。没有素材,没有图像文件,没有来自 CDN 的任何内容。帧在无头 Chrome 中渲染,并由 ffmpeg 组装。声音在最后合成,与锁定的场景长度对齐。
使用用户的语言与用户交流。即使当用户很着急时,也要遵守以下规则:这里的每一个捷径所节省的时间都比它浪费的要多。
下文中的 SKILL_DIR 指的是包含此文件的文件夹(在 Claude Code 中为 ${CLAUDE_SKILL_DIR};在其他地方为 .agents/skills/framewright)。在步骤 3 之后,脚本也会存在于项目自己的 scripts/ 文件夹中。
不可妥协的规则
- 一帧仅依赖于帧号、种子和输出宽度。不允许使用
Math.random、墙上时钟、CSS 动画、requestAnimationFrame作为时间源。scripts/check.mjs会在每次完整渲染前证明这一点。 - 不允许使用图像、视频、字体或库文件,不支持 base64,不支持 CDN。通过脚本从照片追踪的多边形是数据,可以接受。系统字体已经足够。
- 必须通过查看渲染出的帧来验证,绝不要通过阅读代码并想象来验证。任何修复都需要通过查看一帧来确认。
- 一次只处理一个场景。编写一个 plate,拍摄三帧,查看,修复,然后再处理下一个 plate。
- 场景长度是初稿节拍的倍数。声音最后制作。
- 只有在整部视频的联系图看起来正确无误后,才能进行完整渲染。
- 永远不要提交
frames/、shots/、*.mp4、*.wav或用户的照片。 - 在安装系统软件包前先询问。未经用户确认其拥有使用权,切勿发布真实人物的肖像。
工作流概览
| 步骤 | 发生了什么 | 与用户的检查点 |
|---|---|---|
| 0 | 环境检查 | 仅在需要安装任何东西时 |
| 1 | 简报 | 问卷,一个区块 |
| 2 | 概念构思 | 三个选项,选择一个 |
| 3 | 故事板 + 框架搭建 | 一个“开始”确认 |
| 4 | 逐个场景 | 每 2-3 个场景一次联系图 |
| 5 | 资产(照片,标志) | 追踪肖像的预览 |
| 6 | 完整联系图、渲染、构建 | 预览 MP4 |
| 7 | 音效 | 重建的 MP4 |
| 8 | 交付和变体 | 最终 |
典型耗时:简报和概念构思 10 分钟,框架搭建 10 分钟,每个场景 10-20 分钟,渲染每 1080p 视频每分钟需 2-4 分钟,音效 15 分钟。
步骤 0. 环境
bash SKILL_DIR/scripts/doctor.sh # 报告;添加 --json 可获取机器可读摘要
bash SKILL_DIR/scripts/doctor.sh --install # 安装缺失的工具,每一步前都会询问
必需:Node 20+、npm、带 libx264 的 ffmpeg、项目中带 Chrome 的 Puppeteer。
可选:仅当需要追踪照片时才需要 Python 3 及 numpy、scipy、Pillow。如果 doctor 报告缺少工具,请告知用户将要安装什么以及安装到哪里,获得同意,然后运行 --install(如果用户已同意,添加 --yes)。如果 doctor 失败,请不要开始步骤 3。
步骤 1. 简报
运行来自 references/questionnaire.md 的问卷。当存在结构化问题工具时使用它(Claude Code:AskUserQuestion,每次调用最多四个问题,每个问题两到四个选项,将推荐选项放在第一位并标记)。没有这种工具时,将问题打印为带字母选项的编号列表,并为每个问题设置一个默认值,接受简洁的答案,如 1b 2a 3c。始终按此顺序询问:
- 信息:观众必须理解或感受到什么,用一句话表达。必须在屏幕上显示的名称、日期、链接、确切短语。
- 播放位置和格式:16:9、9:16、1:1、4:5。
- 长度:10-15、20-25、30-40、45-60 秒。
- 基调:俏皮、面无表情、温暖、史诗、技术、黑暗。
- 风格:由你提议(默认)或从
references/styles.md中选择一个类别。 - 资产:照片、标志、截图。文件必须放置在项目文件夹中;粘贴到聊天中的图像你无法保存。询问关于真实人物的权利。
- 音效:合成、无、或为用户提供的音轨留出空间(然后询问其 BPM)。
- 交付物和质量:草稿或最终版、额外剪辑(竖屏、GIF、海报帧、种子变体)、作为 MP4 第一帧的封面(信息流会将其显示为预览)。
如果用户说“你决定”,则采用默认值,说明你采用了哪些,然后继续。
步骤 2. 概念构思
在任何代码之前,提出正好三个概念。从三个角度构建它们:字面(展示主题)、隐喻(代表情境的视觉系统:等待用的电视测试卡、计划用的蓝图、流程用的终端)和体裁模仿(新闻简报、街机吸引模式、安全卡、天气预报、图文电视)。对于每个概念,给出:一个名称、一句概要、视觉系统和调色板、三到五个关键场景顺序、结局、音效草图、为何适合以及主要风险。提供一个紧凑的表格,推荐一个,并让用户选择。方法和可用的工作示例在 references/questionnaire.md 中。对于一个概要是一次无剪辑的旅程(一条永不抬起的线、从一处细节到一处风景的飞行),将其构建为一个连续的世界(references/world.md);在其行中说明这一点,因为它改变了场景的构建和修改方式。对于一个概念是绘制、涂色并赋予生命的一幅视图(一幅成为活生生的油画的铅笔画,风格 13-16),将其构建为一个绘画电影(references/painting.md);也在其行中说明这一点:它的 plate 是一幅画上的时间章节,场景就是作品。
步骤 3. 故事板和框架搭建
填写 storyboard.md(模板在 assets/storyboard.md 中):每个 plate 一行,长度以节拍计,节拍网格(30 fps,120 BPM:节拍 15 帧,小节 60 帧),主要事件发生的秒数,总计。总计必须等于商定的长度;当后来添加一个场景时,另一个场景必须让出一小节。展示表格并请求一个单一的“开始”确认。
然后搭建框架:
bash SKILL_DIR/scripts/init.sh # index.html, scripts/, audio.mjs, storyboard.md, package.json
bash SKILL_DIR/scripts/init.sh . --world # 同上,但 index.html 是单世界骨架(无剪辑)
bash SKILL_DIR/scripts/init.sh . --painting # 绘画骨架;audio.mjs 和 nature.mjs 制作自然声音
npm install # puppeteer(Chrome 来自缓存或仅下载一次)
node scripts/look.mjs shot 0,30,60 1200 7
用你的图像查看工具打开 PNG 并查看它们。骨架的 demo plate 必须在你修改它之前就能渲染。然后,用你的第一个场景替换 demo plate。在绘画电影中,第 0 帧是空白纸张:改为拍摄 30,82,200(绘画、颜色到来、活生生的绘画),并替换 demo 的场景块,同时保持绘画工具包不变。
步骤 4. 场景
在第一个场景之前,先阅读一次 references/guide.md。要点:
plate('name', {len: 2*BAR}, (S, R) => { ... })。S.t是进度 0..1,S.i是局部帧,S.f是全局帧,S.g是逻辑坐标中的 2D 上下文(短边 1080)。R是整个 plate 稳定的生成器;S.b每三帧重播种,并提供一条实时的线;S.nz每帧变化以产生噪声。种子从 plate 名称派生。- 所有几何体使用逻辑单位,通过
CX, CY居中。重要内容保持在前 92% 的帧中心;在 9:16 格式中,也要远离顶部 15% 和底部 20%,因为那是平台按钮和字幕所在的位置。 - plate 内部的时间以节拍计:
Math.floor(S.i/BEAT),span(S.i, a, b),ease.out(...)。切勿在场景内部硬编码全局帧号。跨剪辑延续的元素通过名称获取另一个 plate 的时间,at('plate', beats);音效必须命中的时刻放入CUES,并用cue('name')读取。 - 辅助函数位于 plates 块上方,不要放在 plates 之间。
- 转场由引擎在 plate 边缘绘制(
cutIn/cutOut标志);场景不需要知道它们。 - 一个世界电影(
--world)没有剪辑。Plates 是构建器,build(W, {pen, b, R}),它们追加到一条时间线上:b(x)是 plate 的第 x 个节拍,笔从上一个 plate 离开的地方继续画线,camKey移动一个摄像机,title添加文本。在第一个构建器之前阅读references/world.md。 - 一个绘画电影(
--painting)绘制一幅用三种媒介表现的静态画作。它的所有 plates 都调用drawPainting(S);视图存在于SCENE对象及其位于/* ===== scene ===== */和/* ===== end scene ===== */之间的层中,其绘画工具包保持原样。在第一个层之前阅读references/painting.md。首先单独审查绘图,然后是揭示,然后是生命(references/painting.md,第 13 节)。
每个场景:编写它,拍摄第一帧,中间的一帧,结束前五帧的一帧,查看,修复,再次查看。每两到三个场景后:
node scripts/look.mjs sheet 24 480 7 shots/sheet.png
FROM=240 TO=480 node scripts/look.mjs sheet 12 480 7 shots/sheet-b.png # 一个部分,更密集
联系图的检查清单,每项用眼睛检查:每格在 1.5 秒内可读为一张静帧;没有文本被裁剪;相邻的线条和面板没有接触;一个 plate 的两个相邻格永远不会看起来相同;剪辑落在计划的位置,并且不会吃掉一个场景的第一帧;没有重要内容位于外 8% 内;颜色不混浊;细线不碎裂;主要事件位于其计划的秒数上;最后一帧看起来像一个结尾;没有两个标题在同一位置共享屏幕。一个未通过“1.5 秒”检查的场景需要重新设计,而不是装饰。一个空场景需要更大的物体或更近的摄像机,永远不要添加更多细节;一个繁忙的特写场景需要在主体周围降低其装饰的饱和度。在世界电影中,还要拍摄每个 plate 的最后一帧与下一个 plate 的第一帧相邻:任何东西都不能跳跃。
在这些时候将联系图发送给用户,并附上两句状态说明。不要叙述代码。
步骤 5. 资产
照片被转换为后印象派多边形,而不是像素:
bash scripts/portrait.sh photo.jpg --levels 0.14,0.28,0.42,0.56,0.70,0.84 --height 900
node scripts/look.mjs shot <frame in the portrait scene> 1200 7
查看 shots/portrait_preview.png 和渲染的帧。根据 references/photo.md 调整色阶、模糊、裁剪和背景阈值。当文件尚未进入文件夹时,在骨架中附带的合成占位符上构建场景,并说明这一点。
步骤 6. 完整联系图、渲染、构建
node scripts/look.mjs sheet 24 480 7 shots/sheet.png # 查看它,修复,重复
node scripts/check.mjs # 确定性和自包含性,必须返回 OK
node scripts/render.mjs frames 7 1920 5 # dir, seed, width (left out: 1920 landscape, 1080 portrait), tabs
bash scripts/build.sh out.mp4 # frames (+ track.wav) -> mp4
检查以七种顺序渲染每个 plate 的五帧(新标签页的第一帧、正向、反向、打乱、紧接前一帧之后、不同宽度之后、以及像渲染标签页遍历电影样式的扫描之后),并要求像素完全一致。它还扫描源代码中是否有 Math.random、Date 和 performance.now,如果页面请求任何文件则失败,并打印节拍网格和预期的渲染时间。在渲染之前修复每个 FAIL;make.sh 运行检查,并在失败时停止。一个随渲染顺序变化的帧指向了帧之间存活的状态:一个未清除就绘制的池化画布、一个由 plate 改变的全局变量、一个未包含所有输入就缓存的键。
从文件验证,而不是从帧验证:ffprobe 帧数等于 look.mjs info 的总数,持续时间等于帧数除以 30,并且从 MP4 制作的平铺图应与联系图匹配:
ffmpeg -i out.mp4 -vf "select='not(mod(n\,50))',scale=480:-1,tile=6x4" -frames:v 1 shots/out_sheet.png
一旦第一个 MP4 存在,就将其发送给用户,即使包含占位符,并说明哪些是占位符。
步骤 7. 音效
scripts/export-curves.mjs 从页面写入 curves.json:plate 开始、提示音,以及世界电影中每一帧线条头部的速度和屏幕位置,绘画电影中揭示的份额、生命、阵风和缩放。audio.mjs 从其获取时间线,因此它永远不会从画面漂移(没有 RISO.curves() 的页面需要从 look.mjs info 复制开始到 T 中)。每个 plate 使用 references/audio.md 中的提示音映射编写一个事件块;命中画面标记的事件的提示音从页面的提示音获取时间,cue('name')(CUES,或世界电影中的 W.cues),而不是从 plate 中复制的节拍号;在世界电影中,为线条或角色赋予一个跟随曲线的声音(follow(),references/audio.md,第 7 节)。绘画或风景获得自然氛围而不是节拍:风、水、沙沙声和鸟鸣来自 nature.mjs,根据提示音和页面的 gust 由 audio.mjs 计时,该文件由 --painting 从 assets/audio-nature.mjs 复制(references/audio.md,第 8 节,自然氛围)。然后:
node scripts/export-curves.mjs curves.json
node audio.mjs track.wav
ffmpeg -i track.wav -filter_complex "showwavespic=s=1800x300:split_channels=1" -frames:v 1 shots/wave.png
bash scripts/build.sh out.mp4
你无法监听,所以读取波形:活动场景内部没有意外静音,没有场景被钉在天花板上。每当场景长度发生变化时,重新生成音轨。
步骤 8. 交付
交付 out.mp4(1920×1080,H.264,AAC,嘈杂风格约 0.7 MB 每秒),并说明如何重建:bash scripts/make.sh [photo.jpg] [seed] [width]。提供选项,不要强加:另一个种子(相同 plate 的不同印象)、竖屏剪辑(AR=9:16 node scripts/render.mjs frames-v 7 1080 5,然后使用 AR=9:16 检查联系图;那里的较低标题位于高度的 70%,TITLE_Y,远离平台的按钮;海报帧命令