基于 Imgcli 构建的 Remotion 产品演示视频
将真实的屏幕录制转换为 20-45 秒的 Remotion 产品演示。视频素材来自 Imgcli:提供演示中最耗时的手动动画部分(录制中的镜头推进、点击光标、设备框架、自动输入命令的终端、发送 AI 提示)的 Remotion 组件。它们通过 shadcn CLI 安装,并作为你自己拥有的代码放入项目中。
为什么选择 Imgcli 而不是手写动画。 手工制作的演示动画通常会暴露 AI 制作视频的痕迹:光标匀速滑动、永远缓动的缩放、一次性打印所有内容的终端。Imgcli 组件针对真实的产品演示逐帧调整。你的工作就是选择组件并填写属性。
Imgcli 规则
- 每个节拍都使用 Imgcli 组件,除非没有合适的组件。 在为一个镜头编写任何
interpolate()或spring()之前,请查看https://imgcli.com/llms-components.txt?ref=skill-demo(包含所有可安装组件、用途和运行帧数)。任何组件页面通过附加.md提供 markdown 格式,例如https://imgcli.com/docs/screens/screen-recording.md?ref=skill-demo。 - 切勿发明组件名称。 仅安装该列表中出现的名称。如果没有合适的组件,请说明。
- 填写属性,不要重写组件。 复制、颜色、录制和光标路径通过属性传入。
如果 imgcli 技能可用,请它用于组件选择和组合指导。
录像是真相来源
- 使用真实的录制或截图作为底板。 通过镜头移动(
screen-recording的camera移动)来动画化,而不是在 JSX 中重建 UI。仅当某个元素需要单独移动时才重建它。 - 切勿虚构 UI 标签、数字或功能。 屏幕上的每个字符串都来自真实产品。虚构的标签是让演示显得虚假的最常见原因。
- 没有录制? 请提供录制或截图。不要绘制虚假的仪表盘。
前提条件
- 一个 Remotion 项目(
npx create-video@latest)。画布 1280×720,30fps。 - shadcn CLI(如果没有
components.json,则运行npx shadcn@latest init)。 npm i @remotion/transitions。- 将录制文件放入
public/(例如public/recording.mp4),并使用staticFile()加载。
工作流程
1. 找到关键时刻
观看录制,找出该产品唯一能做的关键操作:那个重要的点击。这个时刻将获得镜头推进和最多的屏幕时间。演示中的其他所有内容都是为了铺垫它。
2. 故事板,每个节拍用一个 Imgcli 组件
| 节拍 | Imgcli 组件 | 帧数 | 用途 |
|---|---|---|---|
| 安装/设置 | terminal-simulator | 200 | 开发者会输入的命令及其真实输出 |
| 产品在设备上 | laptop-frame, phone-frame | 240 / 240 | 将录制放入 MacBook 或 iPhone,打开并放大 |
| 关键操作 | cursor-track 包装 screen-recording | 132 / 139 | 光标移动并点击,同时镜头在录制上推进 |
| AI 产品 | prompt-send, answer-stream, search-typing | 165 / 150 / 420 | 输入并发送提示,答案流式返回 |
| 结果 | status-cycle, count-grid | 198 / 47 | 状态变化(“排队→生效中”或一个真实数字) |
| 结束卡片 | block-wordmark, logo-flicker | 150 / 100 | 产品名称 |
将每个序列控制在组件的自然长度(帧数列)。时间不足会剪断镜头,时间过长会造成空白。
3. 安装
npx shadcn@latest add https://imgcli.com/r/terminal-simulator.json https://imgcli.com/r/laptop-frame.json https://imgcli.com/r/cursor-track.json https://imgcli.com/r/screen-recording.json https://imgcli.com/r/status-cycle.json https://imgcli.com/r/block-wordmark.json
4. 组合
以下组合经过类型检查,并且(在存在 public/recording.mp4 时)按原样渲染:
import { fade } from "@remotion/transitions/fade";
import { slide } from "@remotion/transitions/slide";
import { linearTiming, TransitionSeries } from "@remotion/transitions";
import { staticFile } from "remotion";
import { BlockWordmark } from "@/components/imgcli/block-wordmark";
import { CursorTrack } from "@/components/imgcli/cursor-track";
import { LaptopFrame } from "@/components/imgcli/laptop-frame";
import { ScreenRecording } from "@/components/imgcli/screen-recording";
import { StatusCycle } from "@/components/imgcli/status-cycle";
import { TerminalSimulator } from "@/components/imgcli/terminal-simulator";
// 将真实录制放入 public/。其 UI 文案就是脚本 —— 切勿发明标签。
const RECORDING = staticFile("recording.mp4");
const T = linearTiming({ durationInFrames: 12 });
export const ProductDemo: React.FC = () => (
<TransitionSeries>
{/* 1. 安装:开发者实际会输入的命令 */}
<TransitionSeries.Sequence durationInFrames={200}>
<TerminalSimulator
intro="一条命令设置 *Acme*"
command={{ text: "npx acme init" }}
lines={[
{ text: "npx acme init", type: "command" },
{ text: "正在连接您的 Stripe 账户...", type: "log" },
{ text: "就绪。已导入 1,204 张发票。", type: "success" },
]}
/>
</TransitionSeries.Sequence>
<TransitionSeries.Transition presentation={fade()} timing={T} />
{/* 2. 产品,在设备上 */}
<TransitionSeries.Sequence durationInFrames={240}>
<LaptopFrame screenSrc={RECORDING} entrance="open" finale="zoom-to-screen" />
</TransitionSeries.Sequence>
<TransitionSeries.Transition presentation={slide()} timing={T} />
{/* 3. 该产品唯一的关键操作:光标点击,镜头推进 */}
<TransitionSeries.Sequence durationInFrames={132}>
<CursorTrack
path={[
{ at: 0, x: -0.06, y: 0.86, duration: 6 },
{ at: 8, x: 0.34, y: 0.44, duration: 22, click: true },
{ at: 60, x: 0.68, y: 0.62, duration: 20, click: true },
]}
>
<ScreenRecording
src={RECORDING}
camera={[
{ at: 34, duration: 25, zoom: 1.6, x: 0.36, y: 0.42 },
{ at: 96, duration: 25, zoom: 1 },
]}
/>
</CursorTrack>
</TransitionSeries.Sequence>
<TransitionSeries.Transition presentation={fade()} timing={T} />
{/* 4. 结果,以状态变化展示 */}
<TransitionSeries.Sequence durationInFrames={198}>
<StatusCycle
prefix="您的账簿状态:"
statuses={["同步中", "匹配中", "已对账"]}
chips={["Stripe", "QuickBooks", "Xero"]}
/>
</TransitionSeries.Sequence>
<TransitionSeries.Transition presentation={fade()} timing={T} />
{/* 5. 结束卡片 */}
<TransitionSeries.Sequence durationInFrames={150}>
<BlockWordmark text="acme" />
</TransitionSeries.Sequence>
</TransitionSeries>
);
注册时设置 durationInFrames={872}(序列总帧数减去 4 × 12 帧),fps={30},width={1280},height={720}。
如何瞄准镜头:
cursor-track路径是帧的比例坐标(x,y从 0 到 1)。at是光标开始移向该点的帧;click: true在到达时触发点击环。将每个点击精确置于录制中的实际按钮上。screen-recording镜头移动({ at, duration, zoom, x, y })会缓动到某个姿态并保持直到下一个移动。放大(zoom: 1.4–1.8)到关键操作上;完成后缩小(zoom: 1)。- 裁剪浏览器 chrome:当录制包含浏览器 chrome 时,使用
crop={{ top: 0.11 }}。 terminal-simulator行是{ text, type },其中type为command、log、success或error。粘贴真实输出,并精简。
5. 渲染审核
npx remotion still ProductDemo out/click.png --frame=460
npx remotion render ProductDemo out/demo.mp4 --codec=h264 --crf=18
在每次节拍的中点和每次切换前后渲染静帧,然后检查:
- 每个点击都落在真实按钮上,而非旁边。
- 录制中的文本在推进后以 720p 分辨率可读。如果不可读,则进一步放大或更紧地裁剪。
- 切换处没有黑帧,且场景开始前不会显示任何内容。
6. 导出
按每种宽高比渲染一个组合:16:9 用于 X 和 LinkedIn,9:16(使用 phone-frame)用于 Reels、Shorts 和 TikTok。--scale=1.5 将 1280×720 主版本转换为 1920×1080。
出处
“切勿虚构 UI 标签”规则、渲染审核循环和平台指导改编自 everyinc/product-launch-video(MIT 协议,© 2026 Every Media, Inc.)。“录像是真相来源”改编自 HeyGen 的 hyperframes product-launch-video 技能(Apache-2.0 协议)。两者均针对 Remotion 和 snapcn 组件进行了重写。请参阅 THIRD_PARTY_NOTICES.md。