Site Reels: 网站 → MP4 → 演示文稿
系统要求
| 用途 | 需要什么 | 检查方法 |
|---|---|---|
| 所有功能 | Windows、macOS 或 Linux,带 GPU 和可见桌面(Chrome 以有头模式运行) | 不适用 |
| 所有功能 | Node.js 18+ | node -v |
| 所有功能 | ffmpeg + ffprobe 位于 PATH(或设置 FFMPEG=<完整路径>) | ffmpeg -version |
| 所有功能 | Chrome、Chromium 或 Edge(自动检测,见下文) | node -e "import('./scripts/find-chrome.mjs').then(m=>console.log(m.findChrome()))" |
record.mjs | puppeteer-core + puppeteer-screen-recorder(npm,在项目中) | npm ls puppeteer-core puppeteer-screen-recorder |
snap.mjs | puppeteer-core + timesnap(附带了 timeweb) | npm ls timesnap |
| 展示片 | Remotion 项目 | npx remotion --version |
| 审计(可选) | ImageMagick | magick -version |
| Figma 上传 | Figma MCP(use_figma、upload_assets)+ 技能 figma-use、figma-use-slides | MCP 工具列表 |
Chrome 检测(scripts/find-chrome.mjs,由 record.mjs 和 snap.mjs 使用):如果设置了 CHROME 环境变量则使用该值;否则检测已安装的 Chrome/Edge(Windows:Program Files 和 LocalAppData;macOS:/Applications 和 ~/Applications;Linux:PATH 中的 google-chrome、chromium、microsoft-edge);否则使用最新的 ms-playwright/chromium-<N>(或 PLAYWRIGHT_BROWSERS_PATH);否则停止并提示如何设置 CHROME 或运行 npx playwright install chromium。在 Windows 上会添加 --use-angle=d3d11;其他操作系统 Chrome 保持默认 GPU 后端。
功能描述
录制本地运行的网站的脚本化场景,并输出可直接用于 Figma 的 MP4 文件(1920×1080,30fps,x264 编码,< 100MB)。已验证的引擎:puppeteer-core + puppeteer-screen-recorder(CDP 屏幕录制 + ffmpeg)。无需系统屏幕录制。
运行
mkdir <项目名>-reels && cd <项目名>-reels && npm init -y && npm i puppeteer-core puppeteer-screen-recorder
mkdir -p scripts && cp <技能目录>/scripts/{record,find-chrome}.mjs scripts/
cp <技能目录>/scripts/scenes.example.mjs scenes.mjs # 编辑场景配置
# 在网站仓库中:npx next build && npx next start -p 3100 (生产构建:更流畅的 WebGL)
OUT="<输出文件夹>/Videos" node scripts/record.mjs scenes.mjs [场景名 ...]
将脚本复制到项目中运行:ESM 模块解析从脚本所在文件夹查找 puppeteer-core,而非当前工作目录。
环境变量:BASE(默认 http://localhost:3100)、OUT、CHROME、FFMPEG、W、H、DPR、MOBILE=1(触摸仿真)、KEEP_UI=1(保留导航栏)、MIN_FPS(默认 24)。手机 4:5 比例:W=360 H=450 DPR=2 MOBILE=1 得到 720×900 分辨率。
每个场景 = { name, path, theme?, trim?, crf?, css?, prepare(page, ctx), act(page, ctx) }。prepare 在录制前运行(滚动到区域,等待 3D 加载);act 是录制的核心动作。ctx 包含 wait、scrollTo(page, y|selector, ms) 和 scrollThrough(page, selector|null, ms, from, to)(带缓动效果)。页面加载后立即运行的介绍:在 act 中使用 page.reload(),并通过 trim 裁切加载部分。
有效的品牌脚本结构:logo/英雄区介绍(浅色和深色模式)、英雄媒体切换、3D 区域(相机随滚动进入)、沉浸式路径浏览、页脚标志、一次完整页面滑动。5–7 个场景,每个 12–55 秒。
交付前审计(不要信任退出码)
- 每个视频的联系表:
ffmpeg -i x.mp4 -vf "fps=6/<时长>,scale=480:-1,tile=3x2" -frames:v 1 sheet.png并仔细查看。 - 19 秒的视频只有 1MB = 空白场景。> 100MB 的视频无法上传到 Figma(使用场景的
crf参数修复;对于带颗粒和拖尾的场景可设为 24)。 - 顶部条带(
crop=1920:120:0:0)在不同时间点检查,以捕捉泄漏的导航栏。 LOW-FPS且在开头/结尾有长时间停顿是误报(计数器统计的是静止帧);请检查联系表。
已知陷阱
- 隐藏窗口会冻结 rAF → 产生冻结帧。启动时已经传入了
--disable-backgrounding-occluded-windows --disable-renderer-backgrounding --disable-background-timer-throttling --disable-features=CalculateNativeWinOcclusion。有意使用headless: false:headless 模式会回退到 SwiftShader,导致 WebGL 卡顿。 - 隐藏 UI 的 CSS 在重新加载和 React 水合清除
<head>时会消失 → 脚本每 100ms 向 body 重新附加一个<style id="reels-hide">。 - 一个无效的选择器会破坏整个规则:JS 模板字面量中的
\[变成[,而.z-[60]会使header, nav, .z-[60]失效。每个有风险的选择器应单独写一条规则。 - 不要隐藏
[data-cursor]:网站将 3D Canvas 和轮播标记为此属性。应定位自定义光标元素(fixed + mix-blend + pointer-events-none)。 - 懒加载 3D(IntersectionObserver + 动态导入):到达该区域后等待 5–7 秒。如果没有
preserveDrawingBuffer,读取 WebGL Canvas 会返回空内容:使用固定等待或截图。 - 屏幕录制每秒传送约 32 个唯一帧 → 以 30fps 录制(60fps 会产生不规则的重复帧)。默认
trim0.3 秒丢弃首帧缓存。 - 在负载高的机器上屏幕录制会卡顿(Photoshop 打开、并行渲染):MP4 显示“30fps”,但实际只有 9–18 个唯一帧/秒。
record.mjs会测量(mpdecimate=hi=64:lo=24:frac=0.05)并在低于MIN_FPS(默认 24)时最多重新录制 2 次。对于 WebGL/3D 静态板,建议使用snap.mjs。 - Cookie 横幅:在
prepare中接受(通用点击“Accept/Aceitar/OK”)后再录制。 - 没有滚动操作的工具页面(生成器、效果编辑器)录制的视频会静止不动:场景必须进行交互(上传文件到
input[type=file],通过按钮文字点击预设)。自定义滑块不会响应合成的input事件。
60fps 锁定静态板:snap.mjs(确定性帧)
scripts/snap.mjs 注入 timeweb(timesnap 的虚拟时钟:rAF、performance.now、setTimeout 和 video),每帧前进 1/60 秒并截图。独立于机器负载。场景配置:{ name, path, theme, css, dur, wait(加载 3D 的真实毫秒数), pre(滚动前的虚拟毫秒数,默认 2000;**0 表示在加载时运行的介绍**), start(跳过的虚拟秒数), scroll: { sel, from, to, at, len } | { bottom: [from, to](屏幕高度倍数) }, frame(page, t)(例如鼠标移动) }。滚动是虚拟时间的函数。
npm i puppeteer-core timesnap && mkdir -p scripts
cp <技能目录>/scripts/{snap,find-chrome,patch-timeweb}.mjs scripts/
npm pkg set scripts.postinstall="node scripts/patch-timeweb.mjs" && npm run postinstall
FPS=60 OUT=... node scripts/snap.mjs scenes-snap.mjs [场景名]
- 不要直接使用
timesnap():在 Next + R3F + Lenis 组合下,它会录到空白页面,并在 WebGL 英雄区留下冻结片段(rAF 未运行)。自定义循环(goTo + scrollTo + 截图)在所有场景下都有效:60/60 唯一帧。 - 必须打 timeweb 补丁:它将
video.play()替换为不返回 Promise 且没有playing事件的版本 →video.play().catch()会抛出异常,页面变白。 - 强制设置
preserveDrawingBuffer: true到 getContext(可通过preserve: false关闭)。 - 帧数据写入操作系统临时目录,绝不写入同步驱动器(数千张 jpg 同步会导致问题)。
- 不要并行进行 GPU 密集型工作(Remotion 渲染、Photoshop):WebGL 会丢失上下文,录制出仅含背景的几秒内容。脚本会警告
FLASH(通过亮度检测帧闪烁);长段空白只有联系表能显示。请仔细查看。 - 真正的流畅度审计:使用
magick compare -metric MAE在全分辨率下比较相邻帧。0 = 重复帧。 - 交付:
scripts/deliver.example.sh(复制并重命名静态板;V=必需)可制作展开效果、循环(loop.sh)和干净的 3D,最多将 crf 增加 3,直到文件 < 95MB。
上传至 Figma Slides(隐藏,不干扰团队)
MP4 不通过 MCP 直接上传:use_figma 没有网络功能,Plugin API 也无法从字节创建视频;upload_assets 只接受图片(≤ 10MB)。因此:
- 为每个视频制作封面图:
ffmpeg -ss <时间点> -i x.mp4 -frames:v 1 -q:v 2 _capas/x.jpg。 - 使用
use_figma(技能figma-use+figma-use-slides):在最终幻灯片前调用createSlideRow(idx),设置row.name = 'WIP · Motion (hidden)',并为每个视频调用一次createSlide(idx, i),设置isSkippedSlide = true,黑色背景,一个 1920×1080 的矩形,命名为VIDEO · x.mp4,并在speakerNotes中添加操作说明。 - 使用
upload_assets上传封面图到矩形的nodeIds→ 多部分 POST。 - 用户将 MP4 拖放到矩形上(封面被视频替换)。请明确说明这一点;切勿承诺自动插入视频。
getSlideGrid() 会省略被跳过的幻灯片:通过 SLIDE_GRID.children[i].children 检查结构。
品牌展示片(Remotion):静态板 + 设计场景
对于品牌展示片(Logo、颜色、字体、布局、理念、模型),只需从网站录制干净的静态板(无文字:每个场景的 CSS 隐藏标题/导航),其余部分在 Remotion 中合成(逐帧渲染,无抖动)。值得拥有的可复用构建块位于 src/fx.tsx:Mist、MistWords、Grain、KenBurns、Plate、nevoa()(迷雾)过渡效果,以及 brand.ts 中的品牌数据。
- 视觉标识来自演示文稿,而非网站:在幻灯片中检查官方字体和调色板(网站可能偏离品牌手册)。
- 有效的场景:开场短语在迷雾中、录制的展开效果、Logo 构建(
evolvePath+ 在 Logo 光栅上测量的辅助线)、理念短语逐句出现、带有可变wght轴的柱子、色带调色板、排版、分层图形、带mix-blend-difference标题的 3D、滚动布局列、1/2/3 列模型蒙太奇、产品与名称同步、以标志收尾。 - 通过静帧审查(
npx remotion still ... --frame=N+ 蒙太奇)后再渲染。 - 使用
--concurrency=3渲染:并发数为 8 时,如果 Photoshop 打开,32GB 机器可能内存不足。 - 循环:
scripts/loop.sh 输入 输出 [开始] [时长] [交叉淡入]。使用magick compare -metric RMSE检查首尾帧接缝(< 0.5% = 不可见)。 - 坏的静态板直接丢弃:旋转实体显示不了运动;为竖屏画布设计的相机镜头在 16:9 中看起来很小。
- 快节奏展示片(经批准的节奏):25–35 秒,60fps,
Series在 120 BPM 网格(30 帧)上硬切,每 3–6 帧出现图像爆发,巨大 Logo 裁剪在网格上,字体粗细闪烁,调色板块重新排列,竖屏在圆形遮罩中,图形爆发,动态短语,一拍模型,最后 Logo 保持约 2.5 秒。零标签:文字仅作为图形。1 分 40 秒的慢节奏展示片已被拒绝。 - 柔和/烟雾展示片:录制英雄烟雾效果时不带 Logo 或照片,黑色背景配纯白(场景中使用
storage将参数存入 localStorage,以及block: ['assets/logo.png'];空白 PNG 必须是透明的;不要阻塞视频:损坏的视频会冻结 timeweb)。将静态板作为mix-blend-mode: multiply叠加层:在场景开头出现,反转版本(ffmpeg -vf reverse)结尾;剪辑落在黑色上。过渡大约 1.1 秒(速率 2),或半个场景变黑。 - Figma Slides 布局组装:克隆幻灯片(跳过),将每张卡片复制 3 份,只保留背景/Logo/文字可见,使用
download_assets(缩放 2)下载每个节点,然后删除克隆。Slides 不导出透明背景:先在黑色背景上导出 Logo/文字,再在白色背景上导出,通过差异提取 Alpha 通道(ImageMagick)。切勿通过use_figma返回 base64(会撑坏上下文)。
产品展示片(真实应用录制)
屏幕录制使用真实应用(Playwright + CDP 屏幕录制,2 倍速,步骤 goto/move/click/type/scroll/wait,光标轨迹保存为 <id>.cursor.json),光标在 Remotion 中重新绘制。
已被拒绝,请勿重复:
- 将设计好的展示片与真实屏幕拼接(插画开头 + 录制中间 + 绘制的 CTA):两种语言,角色消失又出现,行为之间的黑色卡片中断节奏。每个视频只使用一种语言。
- 在 16:9 窗口中显示小而加速的产品屏幕:Feed 和排名变得不可读。
- 裁剪屏幕的缩放和覆盖在视频上的文字印章(巨大动词、01/07 计数器、渐变):侵入式设计。
- 屏幕上的空白等待(“正在等待…”):直接切入动作。
- 模糊的文案:必须清晰明了,使用受众的日常语言。
有效的形式: 每个动作,先显示纯文字,居中且简短(2 拍),然后展示整个屏幕,不裁剪,在屏幕中央以捕获比例显示在一个中心窗口中,背景干净。文字低调(粗细 700,逐词进入,带轻微模糊),无计数器,无渐变。在轨道网格上剪辑(14 帧 = 128.57 BPM)。最长 40 秒。
有效的技术:
- 加速录制:使用
<OffthreadVideo trimBefore={开始时间} playbackRate={倍速}>并重新计算点击帧/ 倍速(声音和光标环)。 - 中途开始另一个合成:使用
<Sequence from={X}><Sequence from={-开始}><Comp/></Sequence></Sequence>。检查该段的第一个帧:共享元素“飞入”时可能显得松散。跳过几帧,重新放置落在前面的冲击音效。 - 每个合成为一个语言,使用模块级时间线:
globalThis标志由自己的入口文件设置,在 Root 之前导入。 - 跨剪辑的连续音轨:对背景音乐使用
trimBefore,使其从前一段停止处继续。 - 响度:渲染,测量并使用 2 遍
loudnorm(-14 LUFS / -1 dBTP)归一化,同时复制视频。只在最后测量:Remotion 的原始混音会超过 0 dBFS。 - 审计:为每个渲染制作带时间戳的联系表(
fps=2,drawtext=%{pts\:hms},tile=8x6),并在每段交界处制作密集表(fps=6)。 - 没有 git 的项目,另一个会话同时编辑:在新文件中进行新剪辑,编辑共享文件前备份
*.bak-*,并告知用户。
作品集卡片和工作室展示片
record.mjs使用KEEP_UI=1(卡片按原样显示网站)和上面的手机预设。- 场景从清单文件生成(仅限有实时 URL 的公开项目);无实时 URL = 使用截图进行 Ken Burns 效果。
- 卡片包:从结尾到开头的交叉淡入循环,2 遍码率控制到 KB 上限,同时输出 VP9 WebM,WebP 海报为第一帧。
- 带 Alpha 的 Logo 用于网页:-
…
[此处为截断内容]