Communitygithub.com

benlaval23/product-launch-video

使用 Remotion 将真实产品真实帧与动态图形结合,制作约 40 秒的产品发布视频。

product-launch-video 是什么?

该技能解决了产品视频制作中常见的两难困境:纯动态图形看起来专业但无法证明产品真实存在,而原始屏幕录制能展示产品但显得业余。其核心解决方案是使用 Remotion 作为主时间线,通过无头浏览器驱动实际应用来捕获真实的产品帧。技能详细列出了捕获和渲染过程中的关键陷阱及修复方法,例如处理视网膜分辨率、无头浏览器无光标、GPU 缺失等问题。它支持通过 Remotion Studio 进行开发,并提供了一套严格的编辑规则,确保视频中的每一个声称都有对应的镜头支持。用户需要与产品代码共存建立一个 Remotion 项目,并遵循给定的流程:先验证产品功能,再通过最小测试降低风险,最后在用户的指导下完成编辑和交付。

兼容平台~Claude Code~Codex CLI✓Cursor
npx skills add benlaval23/product-launch-video

在你喜欢的 AI 中提问

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

文档

产品发布视频

制作一段约 40 秒的视频,其中动态图形承载叙事,而每个“产品”像素都由真实应用实际渲染。

这个技能的存在,是因为人们一直在做一个错误的选择:

  • 纯动态图形电影看起来专业,但证明不了任何东西——没有真实产品。
  • 原始屏幕录制能证明一切,但看起来业余——没有动态设计。

答案两者都不是:Remotion 是主时间线,产品镜头是通过在无头浏览器中驱动实际应用捕获的真实帧。

在项目旁边构建一个小型 Remotion 项目:共享捕获工具包 + 原语,每个应用的影片一个目录(品牌、镜头、捕获脚本、时间线)。参考 references/toolkit.md 获取推荐的布局。


不可妥协的要点

在编写任何代码之前阅读这些。每一条都花费了真实的调试时间,而且每一条都会静默失败——你会得到看起来合理但实际错误的输出。

捕获

陷阱发生了什么修复方法
setViewport({deviceScaleFactor: 2})page.screencast() 测量其 ffmpeg 滤镜链时使用系统 DPR,因此它构建了一个 1× 的链并丢弃了视网膜细节。没有错误。在浏览器层面强制设置:--force-device-scale-factor=2 + 视口中的 deviceScaleFactor: 0(0 = “系统默认”)。
无头模式不渲染光标page.mouse.move() 会发送页面能看到的 CDP 事件,但没有任何东西被绘制出来。通过 evaluateOnNewDocument 注入一个 DOM 光标。将其放置在页面中,而不是在 Remotion 中合成——这样每个 Remotion 变换(帧内嵌、缩放推近)都会像真实录制一样精确缩放它。使用精灵需要反转每个变换,而 6 像素的偏移就会被视为损坏。
无头模式没有 GPU任何 WebGL/three.js 内容都会录制为空白或黑色面板。--use-gl=angle --use-angle=swiftshader --enable-unsafe-swiftshader。通过像素回读验证,而不仅仅是“上下文是否存在”。
SwiftShader 也合成页面一个软件 GL 驱动程序合成 2560x1440 会将整个标签页限制在大约 8fps,因此每个动画镜头都会录制为幻灯片。它看起来像是一个缓慢的产品,而不是损坏的捕获,所以没有人怀疑这些标志。添加 --disable-gpu-compositing。Skia 的 CPU 光栅化器在处理纯 2D 内容时快得多,WebGL 仍然可以工作,并且 A/B 静态帧是相同的。在同一页面上测量:8.2fps → 60.1fps。
通过肉眼判断流畅度重复帧在联系表中看起来没问题,逐帧查看也没问题。只有在播放时才会暴露,但那时已经渲染完成了。计算不同的帧:ffmpeg -i take.mp4 -vf mpdecimate,showinfo -f null - 2>&1 | grep -c pts_time。远低于文件帧计数意味着卡顿。
page.mouse.click(x, y, {clickCount: 2})发送一次按下,携带 clickCount 元数据。Chrome 从未合成 dblclick。间隔 60ms 的两次离散点击也不行。如果应用需要真正的 dblclick,先点击以移动可见光标,然后在帧内以元素自身的坐标分发事件。
点击进入 iframe 后使用 page.keyboard.type()按键会发送到父页面聚焦的任何元素。如果父页面将按键转发给应用(箭头键导航、快捷键),你的打字会静默地驱动 UI 而不是输入文本。在帧内输入:frame.type(selector, text)。
使用 evaluateOnNewDocument 操作 DOM在 document-start 运行,此时 document.head 和 document.documentElement 都可能为 null。未受保护的 appendChild 会抛出异常并静默中止该脚本之后的所有内容。添加保护,并延迟到 DOMContentLoaded。
录制导航过程将有用的秒数埋藏在文件深处,并膨胀了中间片。将每个镜头拆分为 setup(不录制)和 action(录制)。
信任标记时间戳屏幕录制是可变帧率;编码后的时间线相对于挂钟时间会发生压缩,因此 22 秒处的标记在文件中并不在 22 秒处。使用标记来大致找到查看位置,然后通过提取帧并查看来选择 startAt。
远程/热链接资源第三方 CDN 偶尔会拒绝捕获浏览器的请求;图像网格会绘制为空透明度棋盘格,看起来像损坏的产品。等待图像加载并警告,然后构图以避免它们。不要使用带有空白艺术的镜头。
在应用 shell 中使用 window.scrollTo许多 shell 会滚动内部元素(<main class="overflow-y-auto">),而不是窗口。滚动窗口是静默的无操作,镜头永远不会移动。找到真正的滚动容器并设置其 scrollTop。
猜测点击坐标应用通常只接受特定节点上的交互——例如,一个只接受叶子元素(childElementCount === 0)的内联编辑器,而标题将其文本包裹在 span 中。点击视觉上明显的目标不会产生任何效果。阅读应用自身的源代码以了解规则,然后向页面询问其有效目标的位置(frame.evaluate)并通过 iframe 的测量框将帧坐标映射到页面坐标。

渲染

陷阱发生了什么修复方法
跳过中间片转码Remotion 任意寻找;使用标准 GOP,每次寻找到解码都需要从之前的 I 帧开始。一个 3 分钟的渲染会变成 40 分钟。转码为全帧内:-c:v libx264 -crf 14 -g 1 -keyint_min 1 -sc_threshold 0 -pix_fmt yuv420p,加上显式的 bt709 标签,这样 Chrome 就不会改变品牌颜色。
page.screencast({format:'mp4'})输出 VP9 封装在碎片化的 MP4 中——支持性差。使用 format: 'webm',然后转码。
使用带节拍网格的 <TransitionSeries>转场重叠,因此总持续时间 = 序列总和 − 转场总和。你的帧网格会静默漂移。对于帧精确的剪辑,使用显式的 <Sequence from/durationInFrames> 并在本地处理溶解。断言链的总和等于总时间。
<Sequence> 长于其源素材冻结在最后一个解码帧上——看起来像卡顿,而不是错误。将每个持续时间绑定到真实镜头的长度。在注释中记录源持续时间。
在重叠序列上使用 premountFor一个预安装的序列会在当前序列之上提前渲染——你会得到幽灵般的双重曝光文本。不要预安装共享屏幕空间的序列;只在硬切之间预安装。
在合成空间定位覆盖层如果镜头组件在内部裁剪和缩放版,那么在合成坐标中放置的蒙版或高亮将无法与帧中的任何内容对齐——边缘会穿过内容。将覆盖层放在与版相同的变换内部,或者原生地处理效果而不是在素材之上叠加。
在合成中使用 Math.random() / Date.now()每帧重新滚动;文本闪烁。Remotion 独立渲染每一帧。使所有随机性成为索引的纯函数(基于 sin 的哈希)。
Remotion 内部的 CSS 动画 / 过渡 / Tailwind animate-*渲染冻结或出现乱码——Remotion 通过 useCurrentFrame() 驱动时间,而不是挂钟时间。使用 useCurrentFrame() 重建运动。这也意味着你通常不能复用营销网站的动画组件。
通过偏移到影片中来构建循环两个接缝错误:如果范围在溶解处打开,则会出现黑色闪烁;在包装处会出现硬跳切。为循环提供自己的合成,并将其尾部交叉淡入到其自身头部的 <Freeze frame={0}> 中。选择一个连续运动的镜头;任何静止的镜头都接近静态,不利于自动播放。

流程

1. 在设计影片之前验证产品事实

最重要的一步。 阅读影片打算声称的每个功能的代码。营销页面展示的是愿景;不要假设产品能实现其登陆页面动画所暗示的功能。

这已经发生过一次:一部影片的计划核心镜头取自营销网站上的一个动画组件,显示一个文档在四种不同的宽高比之间变形。该产品硬编码了单一的宽高比和固定的导出阶段——该组件是一个想法的图示,而不是一个功能。基于它构建核心镜头会在首页上放置一个不支持的声明。

具体检查:

  • 哪些内容实际渲染?旧记录可能无法被当前引擎重新渲染。
  • 哪些路由需要认证?哪些在没有前置状态的情况下会重定向?
  • 每个交互功能实际需要什么才能触发?

2. 用尽可能小的测试降低管道风险

按顺序,在构建任何上层之前:

  1. 证明捕获几何(加了角标的页面 → 文件是否真的是 2×?)。
  2. 证明认证(浏览器能否访问已登录的路由?)。
  3. 如果涉及任何 3D 内容,证明 WebGL 正常工作。
  4. 端到端证明一个交互。

3. 侦察,不要猜测

对每个候选界面进行截图,并在花费时间拍摄视频之前向用户展示联系表。内容选择主导制作价值:平庸的演示内容会使一部漂亮的影片成为平庸产品的广告。

4. 有目的地选择演示内容

优先选择产品自身的品牌。展示知名第三方更有说服力,但会将他人的商标放在首页上,而且将一个第三方换成另一个并不能改变这一点。自我参照内容也能形成闭环:销售工具的制品是由该工具制作的。

如果内容还不存在,创建它是一种设计工作,而不是捕获工作——它占据了影片后半部分的每一帧。有目的地起草,制作几个候选方案,让用户选择。

5. 按网格剪辑

120 BPM ⇒ 1 小节 = 60 帧 = 2 秒(30fps 下)。让每个剪辑都落在 30 的倍数上,每个幕的边界落在 60 上。这无需任何成本,而且剪辑版本会自然产生。

6. 通过查看来验证

每次渲染后,提取帧并实际查看它们。构建一个带有标签的联系表。到目前为止发现的每个真实缺陷——空白标志、显示错误页面的镜头、在浅色面板上难以辨认的标题、因测量错误的裁剪而产生的暗带、穿过标签的蒙版接缝——都是通过查看帧发现的,而不是通过任何测试通过。

静音观看,因为这就是它自动播放的方式。


编辑规则

这些是区分一部有效影片和一部好影片的关键。

屏幕上的每个声称都必须得到旁边镜头的支持。 有一个剪辑在“分享链接”的文本下展示了没有任何分享操作的镜头。要么展示它,要么在卡片上明确说明它——不要暗示。

注意那些在上下文中诚实但在视频规模上却不是的声称。 一个标注为*“示意”*的小字数字,一旦变成营销影片中一个持久的英雄帧,就变成了一个硬性断言。将这些排除在持久帧之外。

引用产品的运动,不要重新发明它。 采用应用自身的缓动曲线和入场词汇。所有进入或落脚的元素使用同一种曲线。不要有弹跳效果——过冲看起来像犹豫。

两种底色,永远不要中灰色。 深色底色用于陈述,浅色底色用于解释。从产品自身的令牌中选择它们。

一种强调色,谨慎使用——光标、承载句子的那个词、状态的改变。不是装饰。

帧内不要有任何不是为观众准备的东西。 真实的电子邮件地址、组织名称、操作员与应用内助手的对话、开发者模式徽章。优先使用应用自身的机制(用于折叠侧边栏或关闭横幅的 cookie)而不是 CSS 技巧——不要在镜头中出现闪烁。

“核心镜头”获得最多时间。 选择那个展示了竞争对手结构上无法做到的事情的时刻,然后:锁定相机,不要剪辑,不要在它上面覆盖文字,让音乐淡出。静止告诉眼睛是主体在移动,而不是相机。将解释性的文字放在之后——反应之前的文字会告诉人们该想什么。

可读性优于覆盖面。 一个跨越四个屏幕的镜头可能使它们都无法阅读。停留在一个上面通常更好。

当 UI 无法执行时,构建节拍而不是拍摄它。 一个静态屏幕无法同时介绍三件事。如果节拍需要产品无法实现的顺序,使用产品的真实文案和令牌在 Remotion 中原生合成它——并且保持其视觉上与镜头不同,使其看起来像一个说明,而不是一个假的截图。

如果没有画外音,卡片承载 100% 的含义。 预留阅读时间。


与用户讨论,不要独自决定

  • 口号和屏幕上的文案。 提供按角度分组的几个选项,附带推荐和理由。注意,影片需要演示提示,而网站标题需要持久的定位陈述——它们不必是相同的句子。
  • 要展示哪些内容——展示联系表。
  • 音乐。 你不能获得曲目许可。设计影片使其静音工作,并提供一个提示表(BPM,任何跌落发生的位置),以便后续提供的曲目可以落入网格。当曲目到达时,在放置之前检查其形状:测量随时间的响度,而不是假设它有开场或跌落来对齐。
  • Remotion 许可。 对个人、非营利组织和员工不超过 3 人的公司免费;超过则需要商业许可。在发布前提示这一点。

命令(形式)

捕获从不启动服务器,也从不猜测端口——认证 URL 通常是固定的。

# 终端 1 — 产品已经在运行
pnpm --filter <app> dev   # 或等效命令

# 终端 2 — 影片项目
pnpm install
pnpm check-webgl          # 如果涉及任何 3D
pnpm capture:<app>        # 或单个镜头:pnpm capture:<app> <shot>
pnpm mezzanine:<app>      # 必需 — 参见陷阱
pnpm studio
pnpm render:<app>         # 根据需要输出 mp4 / webm / loop / poster

已登录界面的认证:优先借用实时会话 cookie,而不是注册一个一次性用户(空账户在每次拍摄前强制进行缓慢的内容生成)。


发布到页面

两种形式,由用户决定:

  • 短循环,点击播放完整影片。 最轻量——几百 KB 自动播放,完整影片仅在点击时加载。

…

[为翻译而截断]

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

相关技能