Communitygithub.com

smwbev/framewright

仅用代码生成短视频:一个 HTML 文件,每一帧都是 (帧号, 种子, 宽度) 的纯函数。支持 Claude / Codex / Gemini / Cursor。

framewright 是什么?

Framewright 是一个 AI 代理技能,它通过代码生成短视频,就像版画家制作版画一样:一个包含画布的 HTML 文件、一个称为“plate”的场景列表,以及一个根据 (帧号, 种子, 宽度) 按需绘制任何帧的帧函数。不依赖任何素材、图像文件或 CDN。帧在无头 Chrome 中渲染,并由 ffmpeg 组装。声音在最后合成,与锁定的场景长度对齐。 该技能强制执行严格的非协商规则:帧仅依赖于帧号、种子和输出宽度,不允许使用 Math.random、墙上时钟、CSS 动画或 requestAnimationFrame 作为时间源。不允许引入外部资源(图像、视频、字体、库文件、base64 或 CDN)。工作流程包括 9 个步骤:环境检查、简报(通过结构化问卷)、概念构思(建议三个方向)、故事板和框架生成、逐个场景开发、资产处理(照片矢量化)、完整渲染、声音合成以及最终交付。 Framewright 支持 Claude Code、Codex、Gemini 和 Cursor 等 AI 代理平台。它需要 Node 20+、npm、带 libx264 的 ffmpeg 以及项目中带 Chrome 的 Puppeteer。对于照片矢量化,可选依赖 Python 3 及 numpy、scipy、Pillow。典型的一次 30 秒 1080p 视频制作大约需要 1.5–2.5 小时的实际时间,以及几分钟的渲染时间。

兼容平台✓Claude Code✓Codex CLI✓Cursor✓Gemini CLI
npx skills add smwbev/framewright

在你喜欢的 AI 中提问

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

文档

framewright 是做什么的?

你将像版画家制作版画一样制作一个短视频:一个 HTML 文件,包含一个画布、一个称为“plate”的场景列表,以及一个根据 (帧号, 种子, 宽度) 按需绘制任何帧的帧函数。没有素材,没有图像文件,没有来自 CDN 的任何内容。帧在无头 Chrome 中渲染,并由 ffmpeg 组装。声音在最后合成,与锁定的场景长度对齐。

使用用户的语言与用户交流。即使当用户很着急时,也要遵守以下规则:这里的每一个捷径所节省的时间都比它浪费的要多。

下文中的 SKILL_DIR 指的是包含此文件的文件夹(在 Claude Code 中为 ${CLAUDE_SKILL_DIR};在其他地方为 .agents/skills/framewright)。在步骤 3 之后,脚本也会存在于项目自己的 scripts/ 文件夹中。

不可妥协的规则

  1. 一帧仅依赖于帧号、种子和输出宽度。不允许使用 Math.random、墙上时钟、CSS 动画、requestAnimationFrame 作为时间源。scripts/check.mjs 会在每次完整渲染前证明这一点。
  2. 不允许使用图像、视频、字体或库文件,不支持 base64,不支持 CDN。通过脚本从照片追踪的多边形是数据,可以接受。系统字体已经足够。
  3. 必须通过查看渲染出的帧来验证,绝不要通过阅读代码并想象来验证。任何修复都需要通过查看一帧来确认。
  4. 一次只处理一个场景。编写一个 plate,拍摄三帧,查看,修复,然后再处理下一个 plate。
  5. 场景长度是初稿节拍的倍数。声音最后制作。
  6. 只有在整部视频的联系图看起来正确无误后,才能进行完整渲染。
  7. 永远不要提交 frames/、shots/、*.mp4、*.wav 或用户的照片。
  8. 在安装系统软件包前先询问。未经用户确认其拥有使用权,切勿发布真实人物的肖像。

工作流概览

步骤发生了什么与用户的检查点
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。始终按此顺序询问:

  1. 信息:观众必须理解或感受到什么,用一句话表达。必须在屏幕上显示的名称、日期、链接、确切短语。
  2. 播放位置和格式:16:9、9:16、1:1、4:5。
  3. 长度:10-15、20-25、30-40、45-60 秒。
  4. 基调:俏皮、面无表情、温暖、史诗、技术、黑暗。
  5. 风格:由你提议(默认)或从 references/styles.md 中选择一个类别。
  6. 资产:照片、标志、截图。文件必须放置在项目文件夹中;粘贴到聊天中的图像你无法保存。询问关于真实人物的权利。
  7. 音效:合成、无、或为用户提供的音轨留出空间(然后询问其 BPM)。
  8. 交付物和质量:草稿或最终版、额外剪辑(竖屏、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,远离平台的按钮;海报帧命令

相关技能