VidTSX 2D 视频生成器
根据镜头或视频的描述,为 VidTSX 生成生产就绪的 2D TSX 视频文件。这些文件是 Remotion 合成组件,VidTSX 会逐帧渲染它们,因此正确性比精巧更重要:单个非单调的 interpolate 范围或一个多余的 useState 都会导致渲染崩溃。
此技能涵盖 2D 运动图形。3D 工作(@remotion/three / React Three Fiber)不在范围内。
如何使用此技能
- 明确的单个镜头 → 直接生成
.tsx文件。 - 复杂、多镜头或视觉描述模糊的请求 → 先简短确认镜头拆分或视觉方向,然后再生成。不要过度询问,一行确认就足够了。
- 交付为实际的
.tsx文件,而非内联代码。这些 TSX 组件通常超过 20 行,用户会直接编辑这些文件。默认每个文件一个镜头(原子化,易于编辑),除非用户要求一个统一的交付物。 - 文件仅包含组件代码 — 欢迎添加下面标注的结构性节注释,但文件内不要有 Markdown 围栏或散文式解释。
- 渲染并查看后再宣布完成 — 请参阅 验证渲染结果。仅凭推理得出的镜头不算完成;静帧(对于亮相和滚动动画,还需每个提示点的帧)是验收标准。
输出规范
尺寸预设
| 格式 | 宽度 | 高度 | 用途 |
|---|---|---|---|
| 横屏 | 1920 | 1080 | YouTube、演示文稿 |
| 竖屏 | 1080 | 1920 | TikTok、Reels、短视频 |
| 正方形 | 1080 | 1080 | Instagram 信息流 |
默认值
- 格式: 横屏 (1920×1080)
- 时长: 5 秒
- 帧率: 30
- 风格: 极简(参见
references/style-presets.md)
强制性文件结构
每个生成的文件都遵循此骨架。带标签的节注释是有意为之的——它们能让大文件保持可导航性。
import React from 'react';
import {
useCurrentFrame,
useVideoConfig,
interpolate,
Easing,
AbsoluteFill,
Sequence,
} from 'remotion';
// =============================================================================
// 合成配置
// =============================================================================
export const compositionConfig = {
id: 'ComponentName', // 仅限帕斯卡命名法 — 不能包含连字符或下划线
durationInSeconds: 5,
fps: 30,
width: 1920,
height: 1080,
};
// =============================================================================
// 样式常量
// =============================================================================
const COLORS = {
primary: '#6366f1',
secondary: '#8b5cf6',
accent: '#06b6d4',
background: '#0f0f23',
text: '#ffffff',
} as const;
const TYPOGRAPHY = {
fontFamily: 'Inter, system-ui, sans-serif',
} as const;
const EASINGS = {
easeOut: Easing.bezier(0.33, 1, 0.68, 1),
easeIn: Easing.bezier(0.32, 0, 0.67, 0),
easeInOut: Easing.bezier(0.37, 0, 0.63, 1),
overshoot: Easing.bezier(0.34, 1.56, 0.64, 1),
} as const;
// =============================================================================
// 预生成数据(在模块级别计算一次,而非渲染期间)
// =============================================================================
const seededRandom = (seed: number): number => {
const x = Math.sin(seed * 9999) * 10000;
return x - Math.floor(x);
};
// =============================================================================
// 主组件
// =============================================================================
const ComponentName: React.FC = () => {
const frame = useCurrentFrame();
const { fps, durationInFrames, width, height } = useVideoConfig();
return (
<AbsoluteFill style={{ backgroundColor: COLORS.background }}>
{/* 内容 */}
</AbsoluteFill>
);
};
export default ComponentName;
将 ComponentName 替换为描述镜头的帕斯卡命名名称,从所选风格预设设置 COLORS 块,并根据请求设置 width/height/durationInSeconds/fps。
正确性规则
这些不是风格偏好——它们是渲染成功的关键。VidTSX(通过 Remotion)通过在固定的帧号调用组件来渲染每一帧。没有事件循环,帧之间没有持久性,也没有挂钟时间。任何假设这些存在的代码都会出错。
动画始终基于帧
- 所有运动必须由
useCurrentFrame()+interpolate()驱动。 - 切勿使用
useState、useEffect、setTimeout、setInterval或 CSS 动画/过渡。它们不适合逐帧渲染模型,会产生错误或非确定性的输出。 - 对于任何随机或程序化内容,使用
seededRandom(或等效的种子化函数),以确保给定帧的每次渲染都相同。在模块级别预生成粒子/随机数组,切勿在渲染函数内部——逐帧重新计算会导致闪烁。 - 交错动画,使元素按顺序进入而非同时出现。这样效果更好,也是预期的外观。
interpolate — 输入范围必须严格递增
interpolate 的输入范围必须严格单调递增。重复或递减的值会在运行时抛出错误。
// ✅ 正确
interpolate(frame, [0, 30, 60], [0, 1, 0]);
// ❌ 抛出错误 — 输入范围递减
interpolate(frame, [60, 30, 0], [0, 1, 0]);
要反转映射,请翻转输出范围,切勿翻转输入范围:
// ✅ 正确 — 映射 0→100, 1→0
interpolate(value, [0, 1], [100, 0]);
// ❌ 错误
interpolate(value, [1, 0], [100, 0]);
对于基于索引的计时,确保 startFrame < endFrame 并始终进行钳位:
const startFrame = index * 30;
const endFrame = startFrame + 30;
interpolate(frame, [startFrame, endFrame], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
始终传递 extrapolateLeft: 'clamp' 和 extrapolateRight: 'clamp',除非确实需要无界值——没有它们,值会在关键帧之前/之后超出范围。
Easing — 使用 Easing.bezier(),切勿使用包装语法
像 Easing.out(Easing.cubic) 这样的包装形式会崩溃。只定义一次命名贝塞尔曲线(请参见骨架中的 EASINGS 块)并引用它们:
// ❌ 崩溃
Easing.out(Easing.cubic);
Easing.in(Easing.quad);
// ✅ 正确
interpolate(frame, [0, 30], [0, 1], {
easing: EASINGS.easeOut,
extrapolateRight: 'clamp',
});
chroma-js — 仅限命名空间导入
// ✅ 正确
import * as chroma from 'chroma-js';
const color = chroma('#00bfff').brighten(0.5).hex();
// ❌ 错误 — "chroma is not a function"
import chroma from 'chroma-js';
@remotion/paths — 仅存在实际函数
这些辅助函数不存在且绝不能使用:
makeCircle()、makeRect()、makeTriangle()、makeLine()、makePie()、makePolygon()、makeEllipse()、makeStar()。
只有这些导入是有效的:
import { evolvePath, getLength, getPointAtLength, getTangentAtLength } from '@remotion/paths';
手动编写 SVG 路径字符串并使用 evolvePath 对其进行动画处理:
const circlePath = 'M 50 10 A 40 40 0 1 1 49.99 10 Z';
const rectPath = 'M 0 0 L 100 0 L 100 50 L 0 50 Z';
const linePath = 'M 0 0 L 100 100';
const { strokeDasharray, strokeDashoffset } = evolvePath(progress, rectPath);
合成 ID
compositionConfig.id 仅限帕斯卡命名法 — 不能包含连字符,不能包含下划线(例如 ProductReveal,而不是 product-reveal 或 product_reveal)。
布局
安全区域
- 顶部 10%: 为平台 UI 预留。
- 底部 15%: 为字幕/按钮预留。
- 将主要内容保持在垂直方向的 25%–75% 之间。
居中对齐辅助
const centered: React.CSSProperties = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
};
排版
| 元素 | 字号 | 字重 |
|---|---|---|
| 标题 | 72–120px | 700–900 |
| 副标题 | 36–48px | 500–700 |
| 正文 | 28–36px | 400–500 |
始终在文本元素上设置 margin: 0 — 浏览器默认值会使布局偏离中心。
风格预设
在此仓库中,默认使用品牌风格,而非某个预设。 镜头从 remotion/src/brand.ts 导入 COLORS、GRADIENT、RADIUS、SHADOW、EASINGS,并从 remotion/src/fonts.ts 导入 FONT_HEADLINE、FONT_DISPLAY、FONT_BODY、FONT_MONO,而非使用骨架中的本地 COLORS/TYPOGRAPHY 块,并遵循 brand.md。FONT_HEADLINE (Instrument Serif) 只有一个字重:决不要给它设置 fontWeight。除非镜头的 compositionConfig 指定了 ground: 'night',否则镜头位于日间底板上;工具包组件(lib/shorts.tsx、BrandBg)从中选取颜色,因此对于您自己设置的任何颜色,请从 brand.ts 中读取 useGround()。
当用户指定风格时,从 references/style-presets.md 加载其调色板和特性,并将 COLORS 对象放入骨架中。六种预设为:极简、孟菲斯、新粗野主义、玻璃拟态、霓虹/赛博朋克和企业商务。如果未指定风格,则使用上述品牌风格。
验证渲染结果(不可跳过)
在查看过之前,镜头不算完成。编写完 .tsx 后,至少渲染一张静帧并打开它——永远不要交付仅凭推理得出的镜头。渲染成功且数字看起来正确,但文本溢出其卡片、图像裁剪位置错误、元素超出屏幕或两个元素重叠。只有截图才能捕获这些问题。
- 渲染一张静帧(例如
node scripts/render-all.mjs --still --scale=1 <ShotId>)并读取 PNG。 - 对于基于时间的呈现(元素在提示点进入、滚动、分段阶段),仅渲染一张 60% 时长处的静帧是不够的——渲染完整剪辑并在每个关键时刻提取帧(
ffmpeg -ss <t> -i out/<id>.mp4 -frames:v 1 f.jpg),或在多个偏移处渲染静帧。验证每个提示点的状态,而不仅仅是一帧。 - 特别检查:图像适合其槽位(没有重要内容被裁剪;对于异构真实图像,优先使用匹配背景上的
objectFit: contain),文本未被裁剪或溢出,没有元素超出安全区域,并且每个动画元素在其应该可见时确实可见。 - 如果它与其他素材合成,请也抽查最终合成输出的一帧,而不仅仅是孤立的镜头。
修复截图揭示的问题,然后重新渲染并再次查看。将“我渲染了它,看起来正确”并附上显示的帧视为完成的验收标准。
最终输出
生成完整的 .tsx 文件,文件内不包含其他任何内容——文件本身中没有周围的 Markdown、没有代码前后的注释。在聊天中移交文件时,可以附带简短的一行说明。