关于 frame-capture
frame-capture 是一项通过虚拟时钟和逐帧截图录制网页视频的技能,适合需要精确控制滚动编排的开发者。即使场景渲染较慢,也能生成稳定帧率的视频,并可制作片段对比。
主要功能
- 01将 URL 录制为 MP4
- 02控制滚动和指针移动
- 03逐帧推进虚拟时钟
- 04使用 Chromium 或 WebKit 捕获
- 05制作带标签的片段对比
适用场景
- 01录制网页滚动过程
- 02避免重型动画场景录制时卡顿
- 03对比两个网页或模型的输出
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ moh4696/ open-3d-skills --skill frame-capture安装后在 Claude Code 中输入/frame-capture确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
agents/openai.yamlscripts/compare.pyrecord.pyserve.py详细介绍
帧捕获
屏幕录制工具会实时捕获 GPU 成功绘制的内容,因此场景一复杂就会卡顿,滚动过程看起来也像手持拍摄。本工具采用不同的录制方式。在页面自身的脚本运行之前,它会替换页面时钟:performance.now、Date.now 和 requestAnimationFrame 都会读取虚拟时间,CSS 动画和过渡效果也会固定在虚拟时间上,并将每个 <video> 暂停并定位到对应时间。每输出一帧,时钟就恰好前进 1/60 秒,运行一帧并截取屏幕。即使一帧需要两秒才能渲染,视频仍会以完美的 60 fps 播放。
工具
| 脚本 | 用途 |
|---|---|
scripts/record.py | 使用滚动按键、指针移动和就绪条件,将 URL 录制为 MP4 |
scripts/compare.py | 堆叠两个片段(或将它们并排放置),并添加标签和圆角面板 |
scripts/serve.py | 静态服务器,可响应 HTTP Range 请求,以便视频进行定位 |
需要 Python 3.9+、pip install playwright pillow、python3 -m playwright install chromium webkit,以及 PATH 中的 ffmpeg。
运行
-
使用
scripts/serve.py提供页面服务,切勿使用python3 -m http.server。内置服务器会忽略 Range 请求,导致浏览器无法定位它所提供的任何视频(video.seekable为空),录制的视频每次都会停留在首帧。record.py检测到此情况时会发出警告。python3 scripts/serve.py path/to/site --port 8000 -
先快速试跑一次。 每秒 10 帧可以在几秒钟内看完整个编排,而不是花上几分钟:
python3 scripts/record.py http://127.0.0.1:8000/ /tmp/check.mp4 --seconds 20 --fps 8 \ --scroll "0:0 2.5:0 9:40% 12:40% 19:100%" --pointer drift
观看效果、调整按键,再重复试跑。只有确认后才渲染完整片段。
-
渲染最终版本。
--size设置 CSS 视口大小,--dpr用于放大视口。--size 1920x1080 --dpr 1会生成 1080p;--size 1280x720 --dpr 1.5会生成相同的 1920x1080 画面,但布局更大。输出采用 H.264、CRF 16、yuv420p、BT.709 标签、faststart,且无音频。python3 scripts/record.py http://127.0.0.1:8000/ out.mp4 --seconds 20 --fps 60 \ --scroll "0:0 2.5:0 9:40% 12:40% 19:100%" --pointer drift --ready "window.__ready === true" -
交付前查看结果。 在关键时刻提取帧(
ffmpeg -ss 9 -i out.mp4 -frames:v 1 f.png)并检查:首帧、每次滚动停顿,以及末帧。
编排
--scroll的按键格式为秒数:位置。位置可以是像素值(1200)、滚动范围的比例(40%),也可以是视口高度数(2.5vh)。各段采用缓动效果(半程线性、半程 smootherstep,因此长距离移动不会在中途突然加速)。在线性移动的时间后加上~:9~:60%。- 需要让观众阅读的内容都应停留足够时间:为固定区块或计数器添加一组位置相同的按键。
--pointer drift会让真实鼠标缓慢地绕屏幕中心移动,使悬停状态、视差效果和跟随光标的装置动起来。--pointer 0.7,0.3会将光标固定在该位置。- 对于页面最了解如何编排的内容(如镜头飞行、导览),让页面自行驱动:在
?record=1标记下定义window.__recordFrame = (t) => { ... }。每帧开始前都会调用该函数,传入以秒为单位的虚拟时间。录制期间window.__recording为 true。 - 平滑滚动库会与设置滚动位置的录制器冲突。
--kill-lenis会阻止 Lenis 加载。对于其他库,请在?record=1下将其关闭。
让页面支持录制
-
时间必须来自
performance.now()或 rAF 时间戳,不能来自setTimeout或setInterval。 虚拟时钟运行时,计时器仍会按真实时间运行,因此setTimeout(next, 3000)会在三秒真实时间后触发,此时可能是第 20 帧,也可能是第 400 帧。应改为基于页面时钟安排:const pending = []; const later = (secs, fn) => pending.push({ at: performance.now() / 1000 + secs, fn }); // in the frame loop: const now = performance.now() / 1000; for (let i = pending.length - 1; i >= 0; i--) if (now >= pending[i].at) pending.splice(i, 1)[0].fn(); -
暴露一个就绪标记(首帧渲染完成、纹理解码、字体加载后设置
window.__ready = true),并通过--ready传入。资源加载完成前就开始录制,会让加载闪烁被烙进第 0 帧。 -
在录制模式下,隐藏影片中不会出现的界面元素:提示、光标、加载器和 Cookie 栏。
-
如果 WebGL 输出的帧是黑屏或过时画面,请在录制时使用
preserveDrawingBuffer: true创建渲染器。
浏览器
- **Chromium(默认)**会在无头模式下使用真实 GPU 运行 WebGL。在 macOS 上,脚本会添加
--use-angle=metal,以全速渲染;不加此参数时,Chromium 会回退到 SwiftShader。虽然效果正确,但速度会慢很多倍。 - WebKit(
--browser webkit)可展示 Safari 的渲染效果:字体平滑、色彩处理和视频解码都各不相同。它在 1080p 下偶尔会卡住截图(约每 100 次出现 1 次);脚本会重试,而且由于步骤之间时钟处于暂停状态,重试会返回完全相同的帧。 - 如果缺少 Playwright 自带的 Chromium(升级后版本不匹配),脚本会在 Playwright 缓存中查找最新版本的 Chromium。
CHROME_PATH可覆盖该路径。 - 切勿在你看不到的可见浏览器标签页中录制,也不要在隐藏的嵌入式预览窗格中录制。后台标签页会限制或冻结
requestAnimationFrame。无头模式则不会。
比较
python3 scripts/compare.py a.mp4 b.mp4 versus.mp4 --labels "Model A" "Model B" # stacked, for phones
python3 scripts/compare.py a.mp4 b.mp4 versus.mp4 --labels "Before" "After" --layout side # side by side
公平的比较应保持所有条件一致,只有被比较的内容不同:
- 使用相同的视口、相同的时长和相同的动画编排(原样复用
--scroll键)。 - 先将每个来源裁剪到只包含其内容。移除另一段视频中没有的水印、网络摄像头叠层和 UI 边框。
- 输出静音视频。两段音轨会相互干扰,音频也会分散对画面的注意力。
- 较短的视频停留在最后一帧,这样两段视频就会同时结束。
- 在标签中如实标明两侧的内容。把观众熟悉的那一侧放在上方。
交付
- 社交平台会重新编码所有内容。分辨率为 1920x1080 或 1080x1920、帧率为 60 fps、编码格式为 H.264、CRF 为 14 到 18 的视频最能经受重新编码。更高分辨率通常也会被缩小。
- 保留母版。使用 ffmpeg 从母版制作精简版(6 s 循环视频、竖屏裁剪版),而不要重新录制。
- 之后再添加声音,并根据录制时打印的帧数确定时间。切勿通过虚拟时钟录制音频。