关于 wrapped-video
使用 Remotion 将个人或账户数据转为个性化竖屏回顾视频的技能,适用于需要基于统一模板批量制作多条视频的创作者和团队。
主要功能
- 01回顾数据的类型化架构
- 02可复用的回顾场景序列
- 03由数据驱动文本、统计值和配色
- 049:16 竖屏视频布局指导
- 05按数据记录批量渲染 MP4
- 06基于帧的预览与验证
适用场景
- 01制作年度用户活动回顾
- 02生成个性化健身或阅读视频
- 03制作销售或学生进度总结
- 04从数据行批量渲染可分享视频
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ iart-ai/ explainer-video-skills --skill wrapped-video安装后在 Claude Code 中输入/wrapped-video确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
README.mdreferences/batch-pipeline.mdremotion-recipes.mdscene-grammar.md详细介绍
Wrapped 视频
打造类似“Spotify Wrapped”的年度回顾:取一行关于某个人(或账户、团队、年份)的数据,将其制作成引人注目、便于分享的竖屏视频。核心理念是一个模板 × 一张数据表 → 多个个性化视频。模板只需编写一次,然后为每一行渲染一部独特的视频。
适用场景
- 面向任何能提供用户个人统计数据的产品,制作年度回顾/“你的 2026 年度总结”。
- 个性化数据视频:健身年度回顾、阅读年度回顾、消费总结、游戏统计、销售代表回顾、学生进度。
- 任何交付内容都是“同一部视频,只是每个人的数据不同”的场景,无论制作 1 份还是 100,000 份。
这是一种数据 → 引人分享的内容模式,而非手工剪辑的视频。如果没有数据表(或没有逐条记录的数据),请使用其他技能。
两项不可妥协的原则
- 所有内容都由数据驱动。 每个标题、数字、名称和颜色都必须来自 props,不能硬编码。无法根据一行数据填充的场景,不属于 Wrapped。
- 专为截图而设计。 每个场景都必须能在 2 秒内读懂,定格时也要好看——被分享至动态的正是这张定格画面。设计时要考虑暂停,而非播放。
Wrapped 场景语法
Wrapped 由一系列固定顺序的短场景类型组成,每个场景约 2.5–4 秒。选取 5–7 个场景并按递进方式排列。每年使用相同的语法;只有数据和配色会变化。
| 场景类型 | 作用 | 数据形态 |
|---|---|---|
| 开场/“你的 2026 年度回顾” | 打造品牌氛围,设定配色 | 姓名、年份 |
| 大数字揭晓 | 突出一项核心统计数据,以大号数字递增显示 | 一个数字 + 单位 + 标签 |
| Top-X 列表 | 按排名 1→5 排列,依次显示 | {rank, label, value} 数组 |
| 最高级/人物类型 | “你位列前 1%”,一个原型类别 | 计算得出的层级/标签 |
| 对比 | “超过了 92% 的听众” | 百分位或比率 |
| 时间/热力图 | “你最忙碌的月份是 3 月” | 序列或峰值 |
| 结尾/分享卡片 | Logo + 用户名 + CTA,画面静止停留 | 姓名、用户名 |
按渐强顺序排列:先展示少量背景信息,以最大/最个人化的统计数据作为高潮,最后呈现静止的分享卡片。完整的 7 场景分镜及时间安排,请参阅 references/scene-grammar.md。
回顾视频的故事弧线
场景语法是骨架;故事弧线 才能让 Wrapped 像一份礼物,而不是仪表盘。回顾视频是一部围绕一位观众展开、由五个节拍组成的短剧——按顺序编排,让每次揭晓都比上一次更有分量、更贴近个人。
| 节拍 | 作用 | 对应内容 |
|---|---|---|
| 铺垫 | 烘托时刻氛围,承诺这是专属于他们的 | 开场:「{name},你的 {year}」 |
| 逐步揭晓 | 逐层展示分量不断上升的数据 | Top-X、音乐流派、时间规律 |
| 「大数字」 | 一个主打数据,数值最大、展示时间最长 | 大数字/递增计数高潮 |
| 个性化称号 | 说出 他们是什么样的人,而不只是做过什么 | 人格/百分位等级 |
| 值得分享的收尾 | 一张他们想要发布的静态海报 | 分享卡片收尾 |
按影响力递增的顺序排列数据——从最小到最大。 根据情感分量(数值大小、稀有程度、令人欣喜的程度)为每项数据排序,并按升序展示。绝不要一开场就亮出主打数字:之后就无从递进,影片剩下的部分也会显得虎头蛇尾。让高潮数据以最大尺寸展示最久;此前的一切都是铺垫。
关键是「这是关于你」。 Wrapped 之所以出彩,是因为观众就是主角。前 2 秒就要让这一点深入人心:先展示他们的名字,使用他们专属的 accent,并让每一行文案都以第二人称书写(「你听过 412 位艺人的作品」)。泛泛的文案(「最爱流派:独立音乐」)会破坏这种代入感——改写成「你就是独立音乐那一挂的人」。如果两个不同用户看到的某一帧完全相同,那它就没有发挥应有的作用。
把最终分享画面设计成终点。 整条故事弧线都是为了呈现一张值得发布的海报。收尾不是滚动字幕,而是最终回报:重新呈现醒目的称号或主打数字、姓名 + @handle、Logo、一条简短的 CTA,并且完全静止至少 2 秒,让截图或自动循环都能干净利落地呈现。先决定这一帧,再围绕它逐步推向高潮。文案层级和完整分镜见 references/scene-grammar.md。
数据 → 视频结构
为一条记录定义类型化架构。整段视频都是它的纯函数。
// src/schema.ts
import { z } from "zod";
export const wrappedSchema = z.object({
name: z.string(),
year: z.number(),
minutesListened: z.number(),
topArtists: z.array(z.object({ rank: z.number(), label: z.string(), value: z.number() })),
topGenre: z.string(),
percentile: z.number(), // 0–100, "top X%"
accent: z.string(), // per-user palette, e.g. "#1DB954"
});
export type Wrapped = z.infer<typeof wrappedSchema>;
将它作为 defaultProps + schema 注册到合成中,这样每次渲染只需替换 props。
// src/Root.tsx
import { Composition } from "remotion";
import { Wrapped as Recap } from "./Wrapped";
import { wrappedSchema } from "./schema";
export const Root = () => (
<Composition
id="Wrapped"
component={Recap}
schema={wrappedSchema}
durationInFrames={30 * 22} // 22s @ 30fps
fps={30}
width={1080} height={1920} // 9:16 vertical — the share format
defaultProps={{ name: "Sam", year: 2026, minutesListened: 41203,
topArtists: [{ rank: 1, label: "Phoebe Bridgers", value: 312 }],
topGenre: "Indie", percentile: 3, accent: "#1DB954" }}
/>
);
标志性手法:大数字计数器
递增计数是每个 Wrapped 的心跳。使用 useCurrentFrame() 驱动(绝不要用 setState/setInterval——这会导致渲染时闪烁),通过弹簧效果缓动,并用 toLocaleString() 格式化。
import { useCurrentFrame, useVideoConfig, spring, interpolate } from "remotion";
export const BigNumber: React.FC<{ value: number; label: string; accent: string }> =
({ value, label, accent }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const progress = spring({ frame, fps, config: { damping: 200 } }); // 0→1, settles
const shown = Math.round(interpolate(progress, [0, 1], [0, value]));
const pop = interpolate(progress, [0, 1], [0.6, 1]); // overshoot-free scale-in
return (
<div style={{ display: "flex", flexDirection: "column", alignItems: "center",
justifyContent: "center", height: "100%", transform: `scale(${pop})` }}>
<span style={{ fontSize: 220, fontWeight: 900, color: accent, lineHeight: 1,
fontVariantNumeric: "tabular-nums" }}>{shown.toLocaleString()}</span>
<span style={{ fontSize: 48, color: "#fff", marginTop: 24 }}>{label}</span>
</div>
);
};
使用 tabular-nums,让数字滚动时宽度不会跳动。对于 Top-X 列表,为每项复用一个弹簧效果,并通过错开的 delay 延迟展示(排名第 1 的先出现)——参见 references/remotion-recipes.md。
竖屏 9:16,为停留一刻而设计
渲染 1080×1920。Stories/Reels/TikTok 会裁切画面并叠加 UI,因此所有文字和关键数字都应放在高度中央 80% 的范围内,避开顶部 12% 和底部 18%。每帧只传达一个鲜明想法,使用超大字号,并在纯色/渐变背景上搭配高对比度强调色。每位用户专属的 accent 颜色能让每次分享都更具个人感。完整的安全区域示意图见 references/scene-grammar.md。
批量处理:一个模板 → 多个视频
这是最终回报。使用 renderMedia,将每行数据作为 inputProps 传入,为每行数据分别渲染一个 MP4。生成任务 CSV,循环处理,并按每条记录命名文件。
// render-all.ts — run with: npx tsx render-all.ts
import { bundle } from "@remotion/bundler";
import { renderMedia, selectComposition } from "@remotion/renderer";
import { wrappedSchema } from "./src/schema";
import users from "./users.json"; // array of rows matching the schema
const serveUrl = await bundle({ entryPoint: "./src/index.ts" });
for (const user of users) {
const props = wrappedSchema.parse(user); // validate the row
const comp = await selectComposition({ serveUrl, id: "Wrapped", inputProps: props });
await renderMedia({
composition: comp, serveUrl, codec: "h264",
inputProps: props,
outputLocation: `out/wrapped-${user.id}.mp4`,
});
console.log("rendered", user.id);
}
对于数千行数据,可将任务分发到多台机器/Lambda,并对相同的 props 集合去重。流水线、扩展方案,以及 Node + CLI 批量处理变体见 references/batch-pipeline.md。
构建检查清单
- 所有可见字符串/数字都来自 props;不硬编码任何内容。
- 5–7 个场景,按递增顺序推进,以最大/最具个人特色的数据作为高潮。
- 数字通过
useCurrentFrame+ 弹簧效果递增,使用tabular-nums,并按区域设置格式化。 - 9:16,1080×1920;关键内容位于中央 80% 的范围内,避开顶部 12%/底部 18%。
- 每个场景都能在 <2 秒内读完,定格时也足够清晰(观众会截图)。
- 使用一个架构验证渲染前的每一行数据;批处理脚本按每条记录命名文件。
交付与验证(渲染静帧 → MP4)
打包的辅助脚本(
scripts/):使用scripts/contact-sheet.sh sheet.png f-hook.png f-mid.png f-end.png将静帧拼成图集,然后使用scripts/probe-mp4.sh out.mp4 [WxH] [fps]验证编码结果。详见scripts/README.md。
Wrapped 是按每行数据分别渲染的 Remotion 合成内容,帧完全确定,因此无需寻帧工具,即可在无头环境中精确渲染任意帧。交付物是包含每个人精确数据的 MP4(通常有很多个);批量渲染前,先用静帧验证一个有代表性的行。
输出要求:
- 一个已注册合成内容的 Remotion 项目(
<Composition>+ zodschema+defaultProps),所有动画均由帧驱动(不使用计时器 /Date.now()/Math.random();数字递增使用useCurrentFrame+ spring)。 - 交付物 = 每行数据对应的已渲染
out/wrapped-*.mp4(以及项目本身,以便重新渲染任意行)。 - 将每位用户的统计数据写入 props,并在渲染前通过 schema 验证;画面比例为 9:16,分辨率为 1080×1920。
- 时长取决于数据?在
calculateMetadata中计算,不要手动设置。
验证流程——同一行的静帧 → 检查 → 批量渲染。 先渲染一个有代表性的用户的帧(成本低,无需编码);及早发现布局或数据问题,避免重复 N 次。
# Frame-exact stills at start / mid / end for ONE representative row — pass that row as props
npx remotion still Wrapped out/f-start.png --frame=0 --props='{...one user...}'
npx remotion still Wrapped out/f-mid.png --frame=N --props='{...one user...}'
npx remotion still Wrapped out/f-end.png --frame=L --props='{...one user...}' # L = durationInFrames - 1
# Inspect: every visible string/number comes from that row and is EXACT (big-number reveal lands on the
# real value, top-X ranks/labels correct); key content inside center 80%, clear of top 12% / bottom 18%.
# Only after the representative stills check out, batch-render every row:
npx tsx render-all.ts
npx remotion compositions会读取durationInFrames/fps,以确定结束帧和大数字的定格帧。- 免费生成 README 演示 GIF:
npx remotion render Wrapped out/demo.gif --codec=gif --props='{...}'。
完成前:
- 使用
npx remotion still渲染代表性行的第 0 帧、中间帧和最后一帧,均无错误且没有缺失资源或字体。 - 大数字在其定格帧显示与 prop 值完全一致的数值;前 X 名的排名 / 标签 / 数值均与该行数据匹配。
- 9:16:关键数字和文字位于中央 80% 区域内,避开顶部 12% / 底部 18%;检查过的每一帧都能清晰读取。
- 仅由帧驱动——不使用
Date.now()/Math.random()/ 计时器;批量渲染前验证每一行的 schema。 - 编码并播放代表性 MP4;然后批量处理所有行(每条记录生成一个文件);(可选)生成 README 用的 GIF。
参考文件
references/scene-grammar.md— 完整的 7 场景分镜,包含帧时序、构建 / 渐强逻辑、最高级与百分位文案模式,以及 9:16 安全区域示意图。references/remotion-recipes.md— 可运行的场景组件:交错显示的前 X 名列表、百分位条形图、使用<Series>编排场景、转场、字体,以及避免闪烁的动画规则。references/batch-pipeline.md— CSV/JSON → 多个 MP4:schema 验证、渲染循环、并发、Lambda 扇出和文件命名。