用代码做产品宣传片 / 推广片 / 演示短片。 —— 片子里那台机器是按产品真实 数值重画的(或者 Playwright 抓的真机截图),全程进版本库,逐帧可复现。
概览
用代码做产品宣传片 / 推广片 / 演示短片。 —— 片子里那台机器是按产品真实 数值重画的(或者 Playwright 抓的真机截图),全程进版本库,逐帧可复现。 一支片子在这里是一份可以编译的源码,不是一条时间线。每一帧画面都是「全局帧号」的纯函数: 文件树长出几行、agent 日志到第几条、指针在哪、主题插值到哪一帧 —— 全在 state(f) 里。 ,下游全部是相对量,动作跟着一起挪。 于是节奏能改、镜头能算、字能 diff,而且今天渲和三个月后渲逐字节一样。 :要把它装进来 → 读下面的「安装」;已经装好、要拿它干活 → 读 SKILL.md,那是操作手册,references/ 是它按需展开的细节, 本文件只是说明书。 剪辑软件里,「把第三场缩短两秒」意味着手动拖后面每一个关键帧。截图配转场的那种片子, 观众读到的是「一堆图」,不是「有人在用这个软件」。 这里反过来:改一个数字,后面所有动作跟着挪,窗口全片不切镜,一台摄影机推、拉、摇过去。 代价是你得写代码,而且**(见下面「做不到什么」)。 颜色逐个从产品自己的 token 文件抄过来(_tokens.css / theme.css / tailwind config), 片子里不允许出现第二套。一支片子里混三条缓动曲线,观众读到的是三只手,不是一个产品的性格。 栏宽、字号、圆角、图标字重全按产品真实数值搭, —— 不是把 13px 的字放大成 20px 的示意图。图标重画产品同一套字形,不引第三方图标库。
README
promo-film
用代码做产品宣传片 / 推广片 / 演示短片。Remotion + React 出片 —— 片子里那台机器是按产品真实 数值重画的(或者 Playwright 抓的真机截图),全程进版本库,逐帧可复现。
一支片子在这里是一份可以编译的源码,不是一条时间线。每一帧画面都是「全局帧号」的纯函数:
export const B = { // 节拍边界,帧(60fps)
open: 0, files: 190, cli: 540, theme: 1000,
push: 1400, cross: 1850, cta: 2150, end: 2420,
} as const;
export const state = (f: number): State => { /* 只读 f,返回这一帧窗口里的一切 */ };
文件树长出几行、agent 日志到第几条、指针在哪、主题插值到哪一帧 —— 全在 state(f) 里。
想重排节奏,改 B 里那几个数字,下游全部是相对量,动作跟着一起挪。
于是节奏能改、镜头能算、字能 diff,而且今天渲和三个月后渲逐字节一样。
如果你是 agent:要把它装进来 → 读下面的「安装」;已经装好、要拿它干活 → 读
SKILL.md,那是操作手册,references/是它按需展开的细节, 本文件只是说明书。
为什么不是剪辑软件
剪辑软件里,「把第三场缩短两秒」意味着手动拖后面每一个关键帧。截图配转场的那种片子, 观众读到的是「一堆图」,不是「有人在用这个软件」。
这里反过来:改一个数字,后面所有动作跟着挪,窗口全片不切镜,一台摄影机推、拉、摇过去。 代价是你得写代码,而且只能拍界面(见下面「做不到什么」)。
三条硬约束
先记住这三条,references/ 里所有细节都是它们的推论。
① 只有一套颜色、一套字体、一条缓动。
颜色逐个从产品自己的 token 文件抄过来(_tokens.css / theme.css / tailwind config),
片子里不允许出现第二套。一支片子里混三条缓动曲线,观众读到的是三只手,不是一个产品的性格。
② 那台机器是真的。 栏宽、字号、圆角、图标字重全按产品真实数值搭,按逻辑像素画完再整体缩放 —— 不是把 13px 的字放大成 20px 的示意图。图标重画产品同一套字形,不引第三方图标库。
③ 不做观众察觉不到的缓慢运动。 一个 270 帧涨 2.2% 的推近,折算下来每帧 0.005px:读不出是运镜,只看得出字在抖 (每帧按新的亚像素边界重新光栅化)。要让画面「活着」,用入场动画,不用缓慢缩放。
第三条是本 skill 唯一一个会毁掉整支片子、而且在时间轴上完全看不出来的坑。
references/camera.md 整篇都在讲怎么躲开它,动手改镜头之前先读那一篇。
配乐是可选的,而且默认不做
先问用户「这支片要不要配乐」,说要才做。 多数推广片在社交平台上是静音自动播放的, 信息本来就必须全在画面里;而配错了乐的推广片,比没配乐伤害大。 无声是一个完整的交付,不是半成品。
用户说要,本 skill 自带一个纯代码合成器(assets/scripts/lib/synth.mjs,452 行,
只用 Node 标准库):木质拨弦、有节奏的贝斯、沙锤、软底鼓、响指,外加一层压得很低的铺底。
没有采样、没有第三方素材、没有版权问题。里面没有随机数(噪声来自定种子的 LCG),
所以同一份配置永远产出逐字节相同的音频,配乐和片子一样能进版本库。
要点在 references/music.md:调性先定(第一节有一条明确的品牌禁忌)、
「涨」靠加声部不靠加音量、重音落在与剪辑相同的帧上。想用买来的曲子也行:
覆盖 public/bgm.mp3,长度对齐总时长即可。
用户说不要,工程里就不留任何配乐的痕迹 —— `` 那一层、music 脚本、
make-music.mjs、synth.mjs、public/bgm.mp3 全都不要。
安装
没有安装脚本。这个 skill 就是一份 SKILL.md + 六篇 references + 一批可以拷进工程的模板文件,
任何「能读文件 + 能跑 shell」的 agent 都能驱动。所以「装」只有两件事:把目录放到位,
让 agent 知道 SKILL.md 在哪。
有技能目录的客户端(Claude Code / Claude Desktop)放进约定目录就会被自动发现:
git clone https://github.com/whyubel1eve/promo-film-skill.git ~/.claude/skills/promo-film
跟着某个项目走、团队都能用:
git clone https://github.com/whyubel1eve/promo-film-skill.git .claude/skills/promo-film
目录名要用 promo-film,和 SKILL.md 里的 name 对上;仓库名叫 promo-film-skill 不影响。
自动发现靠的是 SKILL.md 顶部那段 YAML frontmatter(name / description),
客户端拿它判断什么时候该加载;不支持的客户端会把它当普通 Markdown 读,不影响使用。
装完要新开一个会话才认得到 —— 技能列表是会话启动时加载的。
其他客户端:放到那个客户端约定的位置,然后把下面这段抄进它启动时会读的配置文件:
产品宣传片 / 推广片 / 演示视频:要做片子、改片子的文案节奏镜头、出竖版时,
先读 /SKILL.md,按它说的做。片子用 Remotion 写代码出,不要去找模板或剪辑软件。
| 客户端 | 抄到哪 |
|---|---|
| Codex CLI / 支持 AGENTS.md 的 | 项目根的 AGENTS.md,或 ~/.codex/AGENTS.md |
| Cursor | .cursor/rules/promo-film.mdc |
| Windsurf | .windsurfrules |
| 只想临时用一次 | 哪都不用抄,直接在对话里说「读 /SKILL.md」 |
位置随你的客户端:文档之间全是相对引用,没有一处硬编码的绝对路径
(SKILL.md 里的 $SKILL 指克隆下来的这个目录),克隆到哪个目录都照样跑。
所以该由你客户端的约定决定,不该由这份 README 替你定。
依赖
skill 目录本身零依赖 —— 依赖是它生成的那个 video/ 工程要的:
- Node.js ≥ 18
- Remotion 4 + React 19 ——
video/自己一份package.json和node_modules, 不跟应用共用(Remotion 会拖进一整个 Chromium,应用不该为它变重)。remotion和@remotion/*必须同一个精确版本(不加^),混版本会在渲染时报奇怪的错。 装之前npm view remotion version看一眼,别照抄references/setup.md里的数字。 - ffmpeg —— 抖动校验里裁窗口区域;配了乐的话还用它转 mp3。
- Playwright(可选)—— 只有走「抓真机截图」那条路才需要。
skill 本身没有可以「跑一下验证」的东西 —— 装好的标志是你的 agent 认得出 promo-film
这个名字。不确定就直接让它读 SKILL.md,那份读得懂就是装好了。
从零到出片
搭工程 —— 照 references/setup.md,从 assets/ 拷现成的($SKILL 是你克隆到的目录):
mkdir -p video/src/lib video/src/components video/src/scenes video/scripts video/public
cp -r $SKILL/assets/src/lib/* video/src/lib/
cp $SKILL/assets/scripts/frames.mjs video/scripts/
cp $SKILL/assets/{remotion.config.ts,tsconfig.json} video/
配乐的那两份(synth.mjs / make-music.mjs)只在用户说要配乐时才拷,见 references/setup.md。
写 design.ts(抄产品 token)→ 窗口组件 → story.ts(节拍表 + 镜头表 + state)→
scenes/S*.tsx(只画文案,一行产品 UI 都不画),然后开 Studio 拖时间轴看动作和时序:
npm run dev
审构图 —— 批量出关键帧,比反复 render 快一个数量级:
SCALE=0.5 node scripts/frames.mjs 90 430 760 1290 1620 2050 2260
抖动校验 —— 停稳的两帧,窗口区域必须逐像素相同,改完镜头每次都要跑:
SCALE=1 node scripts/frames.mjs 460 461 462 463
cd out/frames && for f in 0460 0461 0462 0463; do ffmpeg -y -loglevel error -i Film-$f.png -vf "crop=600:400:1150:400" /tmp/k.png; md5 -q /tmp/k.png; done
四个哈希必须相同。这个 bug 在视频里看是「字有点毛」,在时间轴上看什么都正常,
只有这行命令能指认它。最后出片(npm run music 只在配了乐时才有):
npm run render && npm run render-square && npm run still
目录里有什么
| 文件 | 讲什么 |
|---|---|
SKILL.md |
操作手册:三条硬约束 + 七步流程 + 出片前的收尾清单。agent 的入口 |
references/setup.md |
video/ 的目录结构、package.json、分层的 Film.tsx、竖版与方版怎么办 |
references/product.md |
design token 全部有出处、按逻辑像素画、图标重画、重画 vs 抓真机截图 |
references/camera.md |
镜头表为什么不能写成关键帧轨道、改镜头要自己算的边界、运动模糊、三条禁令 |
references/story.md |
state(f) 怎么推、文案层的交接、每场多长、文案怎么写 |
references/review.md |
批量出帧、抖动校验、半分辨率预览、十条毛病清单、发布前三问 |
references/music.md |
只在要配乐时读:调性、「涨」靠加声部、两个调过的合成器坑、母带 |
assets/ 是拷进工程就能用的模板,一般不用改:
| 文件 | 是什么 |
|---|---|
src/lib/anim.ts |
缓动词汇:t() / enter() / pop() / typed() / track(),两条全片通用曲线 |
src/lib/camera.ts |
镜头表 → 摄影机:makeCamera() / stageTransform() / edges(),保持段自动生成 |
src/lib/scene.tsx |
文案层的进退场交接(出去往上、进来从下,不叠双影) |
scripts/frames.mjs |
批量出关键帧:一次打包、一个浏览器、多张图 |
remotion.config.ts tsconfig.json |
CRF 15、jpeg 中间帧、120s delayRender 超时;strict TS |
scripts/lib/synth.mjs |
○ 只在配乐时拷,配乐引擎,452 行,无随机数 |
scripts/make-music.example.mjs |
○ 只在配乐时拷,写「弹什么」的样板,拷成 make-music.mjs 改 |
做不到什么
- 要真人出镜、要实拍素材 → 这是拍摄,不是渲染
- 只是把现成视频剪一剪加个字幕 → 直接 ffmpeg,别搭工程
- 只要一张图(封面 / OG 图)→ 那是静态图的活,别为它搭一个视频工程
- 产品还没有稳定的视觉 token → 先把设计系统定下来,否则片子里那台机器是假的
- 配乐只有一种编制(拨弦 / 电钢 / 铺底 / 贝斯 / 轻打击)→ 要人声、管弦、失真吉他、
EDM,去买授权,覆盖
public/bgm.mp3就行,别用合成器硬凑 - 9:16 竖版不是免费的:1:1 可以把内容层整体缩到 0.62(只缩内容层,背景保持满幅),
但竖版的「左边一句话、右边一台机器」必须改成上下堆叠,镜头表的
cx/cy要另写一份。 要做就当第二支片子的排版工作量估。
授权
代码按 MIT 授权。
assets/ 里的模板可以直接拷进你自己的工程改,不用署名。配乐引擎产出的音频是从零算出来的,
不含任何第三方采样或曲库素材 —— 商用、改编都可以。(这说的是本仓库产出的东西。
你自己往片子里放的字体、图标、截图,权利归属还是得你自己确认。)
收尾清单
出片前逐条过,任一条不过就别发:
- [ ] 停稳段落里,窗口区域逐像素帧帧相同
- [ ] 文案没有压在窗口上:算过
cx - px·k和cx + (shellW - px)·k - [ ] 摄影机到位之后文案才进场
- [ ] 每一场最后一件事做完之后留 0.7~1.3 秒收口,不留第二秒
- [ ] 全片只有一套颜色、一条缓动;片里的数字(体积、版本、域名)跟真实产品对得上
- [ ] 要不要配乐问过用户了;配了的话重音与节拍起始帧对齐,改过节奏就重新
npm run music - [ ] 静音播放看不看得懂 —— 信息必须全在画面里,配乐(如果有)只负责情绪
- [ ]
video/README.md写清楚「想改什么去改哪个文件」、这支片子已经讲过什么、 以及踩过的坑(原样保留,别删 —— 删掉的坑三个月后会原样再踩一次)
最后一条不是客套:下一个改这支片子的大概率还是个 agent,它只会读那一份。
推荐工具
换一个关键词,或者移除筛选条件。
安装
npx skillfish add whyubel1eve/promo-film-skill