OpenMontage Remotion API 完全参考:从核心 Hooks 到 Lambda 云端渲染的实战指南
本指南以 OpenMontage 仓库中的 Remotion API 参考文档 为骨架,系统梳理 Remotion 的核心 Hooks、插值动画、媒体组件、渲染器、Player 与 Lambda 云端渲染 API,并结合 remotion-composer 目录下的真实实现(Explainer、CinematicRenderer、TalkingHead 等 13 个 Composition 及 20+ 个可复用组件)逐项印证每个 API 在生产级视频生成流水线中的用法。读完本文,你将掌握 Remotion 从"写一个帧动画"到"数据驱动批量渲染"再到"无服务器云端出片"的完整 API 能力栈,并能在 OpenMontage 中直接复用这些模式。
文档定位:OpenMontage 为什么需要一份 Remotion API 参考
OpenMontage 将 Remotion 设为所有最终渲染的默认合成引擎:视频片段(<OffthreadVideo>)、静态图片、动画场景、组件化图表、转场与混合内容,全部在一次 React 渲染通道中完成(见 skills/core/remotion.md)。而 .agents/skills/remotion/SKILL.md 明确指出:"核心 Remotion 框架知识(hooks、动画、渲染等)以 .agents/skills/remotion/reference.md 为准",它服务于两类读者:
- Agent 技能调用者:AI 编码助手在生成 Remotion 合成时,按此参考查 API 签名、参数默认值与约束;
- 人工开发者:在 remotion-composer 中编写或扩展 Composition 时的速查手册。
仓库的 package.json 锁定了 Remotion 4.0.484 与配套的 @remotion/transitions、@remotion/player、@remotion/google-fonts 等版本,本文所有 API 均以该版本为基准。
一、核心 Hooks:帧驱动的一切起点
1.1 useCurrentFrame()
const frame = useCurrentFrame();
返回当前帧号(从 0 开始计数)。在 <Sequence> 内部,返回的是相对该 Sequence 起始帧的帧号,而非全局帧号——这是理解 Remotion 时间模型的关键。
仓库证据:Explainer.tsx 中的 AnimatedBackground 组件用 useCurrentFrame() 驱动背景渐变角度与浮动光球:
const frame = useCurrentFrame();
const angle1 = 135 + Math.sin(frame / (fps * 8)) * 30;
每个场景被包在 <Sequence> 中(Explainer.tsx),所以场景组件里拿到的 frame 天然是"本场景内第几帧",做入场动画时无需自己减去 from 偏移。
1.2 useVideoConfig()
const { width, height, fps, durationInFrames, id, defaultProps } = useVideoConfig();
返回当前 Composition 的全局配置。width/height/fps/durationInFrames 来自 <Composition> 注册值与 calculateMetadata 的动态覆盖。
关键陷阱(OpenMontage 在源码中专门标注为 #1 常见坑):durationInFrames 返回的是整个 Composition 的时长,而不是当前 <Sequence> 的时长。AnimeScene.tsx 的注释原样记录了这一坑:
CRITICAL: useVideoConfig().durationInFrames returns the FULL composition duration, not the Sequence duration.
修复方案是父组件显式传入 sceneDurationSeconds,子组件自行换算:Math.round(sceneDurationSeconds * fps)。在 Explainer.tsx 中即为 sceneDurationSeconds={cut.out_seconds - cut.in_seconds}。
仓库证据:TitledVideo.tsx 中 EditorialTagline 同时消费 frame、fps、durationInFrames 三者,实现片尾标语在最后 10 帧内淡出。
二、interpolate():帧到数值的万能映射
interpolate(
input: number,
inputRange: number[],
outputRange: number[],
options?: {
extrapolateLeft?: 'extend' | 'clamp' | 'identity' | 'wrap',
extrapolateRight?: 'extend' | 'clamp' | 'identity' | 'wrap',
easing?: (t: number) => number
}
): number
inputRange 与 outputRange 必须等长且单调。四个外推策略的含义:
| 策略 | 行为 |
|---|---|
extend |
输入超出范围时按斜率继续延伸(默认) |
clamp |
超出范围时钉死在端点值,防止数值跑飞 |
identity |
超出范围时返回输入值本身 |
wrap |
按周期回绕(较少用) |
示例(来自原文档):
// Basic interpolation
interpolate(15, [0, 30], [0, 100]); // 50
// With clamping
interpolate(50, [0, 30], [0, 1], { extrapolateRight: 'clamp' }); // 1
// Multiple keyframes
interpolate(frame, [0, 20, 40, 60], [0, 1, 1, 0]);
// With easing
interpolate(frame, [0, 30], [0, 100], { easing: Easing.bezier(0.42, 0, 0.58, 1) });
OpenMontage 实践:Explainer.tsx 中 ImageScene 用 interpolate 实现帧级淡入淡出与 Ken Burns 缩放,且双端全部 clamp,这是仓库在 skills/core/remotion.md 中反复强调的硬性约束:"Always clamp interpolate() — use extrapolateLeft: 'clamp', extrapolateRight: 'clamp' to prevent values shooting past endpoints." 音乐淡入淡出音量曲线同样由 interpolate 计算(Explainer.tsx)。
三、spring():物理弹簧动画
spring({
frame: number,
fps: number,
config?: {
damping?: number, // Default: 10
mass?: number, // Default: 1
stiffness?: number, // Default: 100
overshootClamping?: boolean
},
from?: number, // Default: 0
to?: number, // Default: 1
durationInFrames?: number,
durationRestThreshold?: number,
delay?: number,
reverse?: boolean
}): number
弹簧物理参数决定动画"性格":
| 配置 | 效果 |
|---|---|
{ damping: 5, stiffness: 200 } |
高回弹(bouncy),适合活泼入场 |
{ damping: 20, stiffness: 100, overshootClamping: true } |
无回弹,干净利落,适合专业/科技感 |
{ damping: 20, mass: 2 } |
慢速沉稳,适合厚重感标题 |
仓库证据:
- TitledVideo.tsx 用
spring({ frame: frame - 12, fps, config: { damping: 18, stiffness: 70 } })让下划线在文字出现 12 帧后"弹"出来——delay通过把传入的frame减去 12 实现; - Explainer.tsx 用
spring({ frame, fps, config: { damping: 18, stiffness: 80 } })做图片淡入,营造丝滑入场; - 主题系统把弹簧参数下沉到配置:
THEMES中每个主题都带springConfig: { damping, stiffness, mass }(见 Root.tsx),例如clean-professional用{ damping: 20, stiffness: 120, mass: 1 }(低回弹、专业感),anime-ghibli用{ damping: 18, stiffness: 60, mass: 1 }(松弛、梦幻感)。
四、measureSpring():测量弹簧动画时长
import { measureSpring } from 'remotion';
const duration = measureSpring({ fps: 30, config: { damping: 10 } });
// Returns number of frames until spring settles
spring() 在数学上理论上限无穷(只是振幅衰减到可忽略),因此若要把它放进 durationInFrames、或与转场时长对齐,就必须用 measureSpring 预先计算"到稳定需要多少帧"。典型用法:<Sequence durationInFrames={measureSpring({ fps, config })}>,确保动画播完再切场,避免被截断。
五、interpolateColors():颜色插值
interpolateColors(
input: number,
inputRange: number[],
outputRange: string[], // Hex, rgb(), rgba(), hsl()
options?: { extrapolateLeft?, extrapolateRight? }
): string
与 interpolate 用法一致,但输出端是颜色字符串(支持 #RRGGBB、rgb()、rgba()、hsl())。常用于根据进度在两种主题色之间渐变,例如图表强调色随动画进度从 #2563EB 过渡到 #F59E0B。注意 outputRange 各值必须同一颜色格式(不能混用 hex 与 rgba)。
六、Easing:缓动函数库
import { Easing } from 'remotion';
// Basic
Easing.linear
Easing.ease
Easing.quad
Easing.cubic
// In/Out/InOut variants
Easing.in(Easing.quad)
Easing.out(Easing.cubic)
Easing.inOut(Easing.ease)
// Cubic bezier
Easing.bezier(x1, y1, x2, y2)
// Other
Easing.circle
Easing.back(s?) // Overshoot
Easing.elastic(bounciness?)
Easing.bounce
Easing.sin
Easing.exp
Easing.poly(n) // Power of n
分类速记:
- 多项式族:
quad/cubic/poly(n),n越大加速越陡; - In/Out/InOut 包装器:
Easing.in(fn)慢入、Easing.out(fn)慢出、Easing.inOut(fn)两头缓; - 曲线化:
Easing.bezier(x1,y1,x2,y2)自定义贝塞尔,等价于 CSScubic-bezier; - 特效族:
back(s)(过冲回弹,s控制幅度)、elastic(bounciness)(橡皮筋弹性)、bounce(落地弹跳)、circle/sin/exp。
OpenMontage 的主题系统将缓动名写入 playbook:animationEasing: "easeInOutCubic" 等参数由样式 YAML(如 styles/anime-ghibli.yaml、styles/clean-professional.yaml)驱动,最终映射到组件内的 Easing 选择。
七、核心组件:搭建时间轴与画面
7.1 Composition:注册一个可渲染条目
<Composition
id="MyVideo"
component={MyComponent}
// OR lazyComponent={() => import('./MyComponent')}
durationInFrames={150}
fps={30}
width={1920}
height={1080}
defaultProps={{ title: 'Hello' }}
calculateMetadata={async ({ props }) => ({
durationInFrames: props.items.length * 30,
props: { ...props, computed: true }
})}
/>
要点:
id是渲染 CLI 与 Studio 中查找该合成的唯一标识;defaultProps提供 Studio 预览与渲染兜底值,运行时 props 会被--props传入的 JSON 覆盖;calculateMetadata在渲染前异步执行,可动态计算时长/分辨率/新 props。
仓库证据:Root.tsx 注册了 Explainer 合成,defaultProps 为 { cuts: [], overlays: [], captions: [], audio: {} },calculateMetadata 根据 cuts 中最后一个 out_seconds 加 1 秒余量计算总时长(Root.tsx)。同一文件还注册了 CinematicRenderer、TalkingHead、TitledVideo、HeroTitle、ProductReveal、CaptionOverlayOnly、CollageBurst、LyricOverlay、EndTag、EndTagOverlay 共 13 个 Composition,覆盖解释视频、电影感渲染、口播竖屏、片尾标签等场景。
7.2 Sequence:局部时间偏移
<Sequence
from={30} // Start frame
durationInFrames={60} // Optional duration
name="Intro" // Label in Studio timeline
layout="none" // "none" | "absolute-fill"
>
<Child />
</Sequence>
layout 语义:"none"(默认)子元素按普通文档流排布;"absolute-fill" 子元素绝对定位填满全屏。在 OpenMontage 的 Explainer 中,每个场景即一个 Sequence:
{cuts.map((cut) => {
const from = Math.round(cut.in_seconds * fps);
const duration = Math.round((cut.out_seconds - cut.in_seconds) * fps);
return (
<Sequence key={cut.id} from={from} durationInFrames={duration}>
<SceneRenderer cut={cut} theme={theme} />
</Sequence>
);
})}
(Explainer.tsx)——scene_plan.json 中的秒级时间戳在此换算为帧号,正是 skills/core/remotion.md 中"Artifacts Map to Remotion Props"映射表的落地实现。
7.3 Series:无空隙顺序排布
<Series>
<Series.Sequence durationInFrames={30} offset={-5}>
<A /> {/* Frames 0-29 */}
</Series.Sequence>
<Series.Sequence durationInFrames={60}>
<B /> {/* Frames 25-84 (offset caused overlap) */}
</Series.Sequence>
</Series>
Series 自动把子 Sequence 首尾相接,offset 可为负值制造重叠(用于交叉淡入淡出)。与手写 from 相比,Series 避免了"前一个时长改了、后一个 from 忘了改"的连锁错误。
7.4 Loop:循环播放
<Loop
durationInFrames={30}
times={3} // Or Infinity
layout="none"
>
<Animation />
</Loop>
times={Infinity} 时循环至父级时长耗尽。适合背景动画、粒子层、循环水印等"不需要关心总时长"的内容。
7.5 AbsoluteFill:全屏绝对定位容器
<AbsoluteFill style={{ backgroundColor: '#000' }}>
{/* Position: absolute, full width/height */}
</AbsoluteFill>
这是 Remotion 的 position: absolute; inset: 0 快捷容器,OpenMontage 几乎所有场景的最外层都使用它(例如 Explainer.tsx 的 backgroundColor: theme.backgroundColor 根容器,以及 CinematicRenderer.tsx 的场景背景层)。
7.6 媒体组件
Img(等待加载完成后才进入画面,避免闪烁):
<Img src={staticFile('photo.jpg')} style={{ width: '100%' }} />
Video / Html5Video:
<Video
src={staticFile('clip.mp4')}
volume={0.5} // 0-1, or callback: (f) => f / 100
playbackRate={1.5}
muted={false}
loop={false}
startFrom={30} // Skip first 30 frames of source
endAt={120} // Stop at frame 120 of source
acceptableTimeShiftInSeconds={0.2}
/>
startFrom/endAt 是源素材的时间裁剪(对应 OpenMontage 中 source_in_seconds),与 Sequence 的 from(时间轴位置)是两回事。volume 支持函数形式 (frame) => number,可实现逐帧音量曲线——OpenMontage 的音乐淡入淡出正是这样做的。
OffthreadVideo(推荐,性能更优):
<OffthreadVideo
src={staticFile('clip.mp4')}
volume={0.5}
transparent={false} // For videos with alpha
toneMapped={true} // HDR tone mapping
/>
<Video> 在渲染时通过 Chromium 实时播放视频并逐帧截屏,<OffthreadVideo> 则用 FFmpeg 预抽取帧,更快、更省内存、更确定。OpenMontage 的硬性最佳实践即"Use OffthreadVideo — Better performance than <Video> for complex compositions"。仓库证据:Explainer.tsx 的视频场景、CinematicRenderer.tsx 的电影场景均使用 OffthreadVideo,后者还配合 trimBefore/trimAfter/playbackRate 实现源素材裁剪与变速。此外 skills/core/remotion.md 提醒:playbackRate 必须为常数,极端变速需先用 FFmpeg 预处理。
Audio:
<Audio
src={staticFile('music.mp3')}
volume={0.8}
startFrom={0}
endAt={300}
playbackRate={1}
/>
仓库证据:Explainer 用两个 <Audio> 叠放解说与音乐(Explainer.tsx),音乐的 startFrom 由 offsetSeconds * fps 换算(跳过安静前奏)、loop 支持、音量是 (f) => Math.min(fadeIn, fadeOut) 的插值函数,完整对应 skills/core/remotion.md 中 audio.music 配置的 offsetSeconds/loop/fadeInSeconds/fadeOutSeconds 四个字段。
AnimatedImage(GIF/APNG):
<AnimatedImage src={staticFile('animation.gif')} />
八、异步处理:delayRender / continueRender / cancelRender
Remotion 渲染是"按帧同步求值"的,异步数据(TTS 音频时长、远程资源、AI 生成结果)必须先阻塞再放行:
import { delayRender, continueRender, cancelRender } from 'remotion';
// Block render
const handle = delayRender('Loading data...');
// Unblock when ready
continueRender(handle);
// Cancel on error
cancelRender(new Error('Failed to load'));
// With timeout
const handle = delayRender('Loading...', { timeoutInMilliseconds: 30000 });
三条规则:
- 任何 fetch/异步计算开始前先
delayRender,完成后再continueRender; - 出错必须
cancelRender,否则渲染会悬挂直到超时; - 可选
timeoutInMilliseconds显式设超时(默认 30 秒),防止静默卡死。
最佳实践:delayRender 的调用点必须在渲染进程的同步阶段(模块顶层或组件首次渲染时),不能在 setTimeout 回调里注册——否则 Remotion 可能已判定"没有 pending delay"直接渲染出空帧。
九、静态文件与预取
import { staticFile, prefetch, getStaticFiles } from 'remotion';
// Reference file in public/
const url = staticFile('video.mp4');
// Prefetch for faster playback
const { free, waitUntilDone } = prefetch(url);
await waitUntilDone();
// Later: free() to release memory
// List all static files
const files = getStaticFiles(); // ['video.mp4', 'image.png', ...]
staticFile('x')将public/x解析为可用的 URL/路径,是引用静态资产的唯一正确方式;prefetch预取媒体加速<Video>播放,完成后务必free()释放内存;getStaticFiles在渲染期列出 public 下全部文件,可用于校验staticFile()引用是否有效(OpenMontage 的composition_validator正是做这类前置校验)。
仓库证据:OpenMontage 的资产解析不止于 public/,还需支持远程 URL 与绝对路径。resolveAsset.ts 实现了三级路由:远程 URL(http(s)://、data:)原样返回;绝对路径转为 file:// 协议;其余走 staticFile(clean) 落入 public/。
十、Input Props:渲染期注入数据
import { getInputProps } from 'remotion';
const props = getInputProps(); // Data passed via --props CLI flag
在 npx remotion render 时通过 --props=xxx.json 传入的数据,可在组件内用 getInputProps() 读取。与 <Composition defaultProps> 的关系:默认 props 兜底,--props 覆盖。OpenMontage 的渲染调用方式(skills/core/remotion.md):
npx remotion render Explainer \
--props="public/demo-props/my-video.json" \
--output=output/final.mp4 \
--codec=h264 --crf=18
十一、环境检测:getRemotionEnvironment()
import { getRemotionEnvironment } from 'remotion';
const env = getRemotionEnvironment();
// { isStudio: boolean, isRendering: boolean, isPlayer: boolean }
用于区分代码运行在 Studio 预览、实际渲染、还是 <Player> 嵌入环境。典型用途:仅在渲染期执行昂贵的预取、在 Player 中跳过某些渲染专用逻辑、或在 Studio 中显示调试信息。
十二、random():确定性随机
import { random } from 'remotion';
const value = random('my-seed'); // 0-1, same every render
const value2 = random('seed', 0, 100); // 0-100
const value3 = random(null); // Different each render
Remotion 渲染每帧都会重新执行组件,普通 Math.random() 会在不同帧/不同机器上产生不同结果,导致闪烁与不可复现。random(seed) 是按 seed 确定的伪随机数:同一 seed 在任何帧、任何机器上结果一致。传 null 则退化为每次调用都不同(适合 Player 交互场景)。OpenMontage 的粒子系统、图表散点等需要"看起来随机但每次渲染完全一致"的效果都依赖它。
十三、@remotion/renderer:程序化渲染
import { bundle } from '@remotion/bundler';
import {
renderMedia,
renderStill,
selectComposition,
getCompositions,
renderFrames,
stitchFramesToVideo
} from '@remotion/renderer';
13.1 完整渲染流程
// Bundle project
const bundleLocation = await bundle({
entryPoint: './src/index.ts',
webpackOverride: (config) => config,
});
// Get composition
const composition = await selectComposition({
serveUrl: bundleLocation,
id: 'MyComp',
inputProps: {},
});
// Render video
await renderMedia({
composition,
serveUrl: bundleLocation,
codec: 'h264', // h264, h265, vp8, vp9, gif, prores
outputLocation: 'out.mp4',
inputProps: {},
onProgress: ({ progress }) => console.log(`${progress * 100}%`),
imageFormat: 'jpeg', // jpeg or png
jpegQuality: 80,
scale: 1,
frameRange: [0, 59], // Optional: specific frames
muted: false,
audioBitrate: '128k',
videoBitrate: '5M',
crf: 18, // Quality (lower = better, bigger)
concurrency: 4,
});
参数决策速查:
| 参数 | 说明 | 建议 |
|---|---|---|
codec |
输出编码 | 通用 h264;透明通道用 prores 或带 alpha 的 WebM |
crf |
质量/体积权衡 | 数字越小质量越高文件越大;18 是高质量默认 |
imageFormat/jpegQuality |
中间帧格式 | 有透明需求用 png,否则 jpeg 更快 |
frameRange |
只渲染部分帧 | 预览、并行分片渲染时用 |
concurrency |
并行度 | 每并发会起一个 Chromium,注意内存 |
13.2 渲染静态帧
// Render still image
await renderStill({
composition,
serveUrl: bundleLocation,
output: 'thumbnail.png',
frame: 30,
imageFormat: 'png',
});
OpenMontage 的视觉质量检查流程(skills/core/remotion.md 的 Post-Render Verification Protocol)会在每个场景中点抽取静态帧做人工/视觉模型审查,renderStill 正是该协议的程序化实现基础。
十四、@remotion/lambda:无服务器云端渲染
import {
deployFunction,
deploySite,
renderMediaOnLambda,
renderStillOnLambda,
getRenderProgress,
downloadMedia,
} from '@remotion/lambda';
14.1 三步流程
第一步:部署 Lambda 函数与站点
// Deploy function
const { functionName } = await deployFunction({
region: 'us-east-1',
timeoutInSeconds: 120,
memorySizeInMb: 2048,
});
// Deploy site
const { serveUrl } = await deploySite({
entryPoint: './src/index.ts',
region: 'us-east-1',
siteName: 'my-video',
});
第二步:触发渲染并轮询进度
// Render
const { renderId, bucketName } = await renderMediaOnLambda({
region: 'us-east-1',
functionName,
serveUrl,
composition: 'MyComp',
codec: 'h264',
inputProps: {},
framesPerLambda: 20, // 每个 Lambda 实例渲染的帧数,决定并行度
});
// Check progress
const progress = await getRenderProgress({
renderId,
bucketName,
region: 'us-east-1',
functionName,
});
第三步:下载成品
// Download when done
if (progress.done) {
await downloadMedia({
bucketName,
renderId,
region: 'us-east-1',
outPath: 'video.mp4',
});
}
framesPerLambda 是关键并行参数:越小并发切分越细、出片越快,但冷启动成本越高。OpenMontage 在本地渲染的定位是"CPU 密集型但零 API 成本"(成本追踪器里 reserve: 0),而 Lambda 路径适合需要水平扩展、无固定渲染机的生产环境。
十五、@remotion/player:网页内嵌播放器
import { Player, PlayerRef } from '@remotion/player';
const playerRef = useRef<PlayerRef>(null);
15.1 声明式配置
<Player
ref={playerRef}
component={MyComp}
// OR lazyComponent={() => import('./MyComp')}
durationInFrames={150}
fps={30}
compositionWidth={1920}
compositionHeight={1080}
inputProps={{}}
style={{ width: '100%' }}
controls={true}
autoPlay={false}
loop={false}
showVolumeControls={true}
allowFullscreen={true}
clickToPlay={true}
doubleClickToFullscreen={true}
spaceKeyToPlayOrPause={true}
playbackRate={1}
renderLoading={() => <div>Loading...</div>}
errorFallback={({ error }) => <div>Error: {error.message}</div>}
numberOfSharedAudioTags={5}
initiallyShowControls={3000}
renderPlayPauseButton={() => null}
moveToBeginningWhenEnded={true}
/>
compositionWidth/compositionHeight与width/height(CSS 尺寸)分离:前者是逻辑分辨率,后者是显示尺寸,二者可不同(响应式);numberOfSharedAudioTags控制浏览器并行音频标签数量,多个<Audio>场景需要调大以避免音画不同步;moveToBeginningWhenEnded播放结束后回到开头(而非停在最后一帧)。
15.2 命令式 API(PlayerRef)
playerRef.current?.play();
playerRef.current?.pause();
playerRef.current?.toggle();
playerRef.current?.seekTo(30);
playerRef.current?.getCurrentFrame();
playerRef.current?.isPlaying();
playerRef.current?.getVolume();
playerRef.current?.setVolume(0.5);
playerRef.current?.isMuted();
playerRef.current?.mute();
playerRef.current?.unmute();
playerRef.current?.requestFullscreen();
playerRef.current?.exitFullscreen();
playerRef.current?.isFullscreen();
15.3 事件回调
<Player
onPlay={() => {}}
onPause={() => {}}
onEnded={() => {}}
onError={(e) => {}}
onSeeked={(frame) => {}}
onTimeUpdate={({ frame }) => {}}
onFullscreenChange={(isFullscreen) => {}}
/>
Player 是"同一份 Composition 代码,既渲染成视频文件又嵌入网页实时播放"的关键桥梁——OpenMontage 用它做效果画廊、交互式预览与最终交付页展示。
十六、calculateMetadata:动态化合成属性
export const calculateMetadata: CalculateMetadataFunction<Props> = async ({
props,
abortSignal,
defaultProps
}) => {
const data = await fetch('/api/data', { signal: abortSignal });
return {
durationInFrames: data.items.length * 30,
fps: 60,
width: 1920,
height: 1080,
props: { ...props, items: data.items },
};
};
<Composition
id="Dynamic"
component={MyComp}
calculateMetadata={calculateMetadata}
// Base values (can be overridden by calculateMetadata)
durationInFrames={1}
fps={30}
width={1920}
height={1080}
/>
要点:
abortSignal用于在渲染被取消时中止内部 fetch,避免泄漏;- 返回对象的字段全部可选——只返回需要覆盖的字段即可;
- 若
props变了(比如远程数据注入),必须显式props: { ...props, newField }合并返回。
仓库证据:Explainer 合成的 calculateMetadata 根据 cuts 计算时长(Root.tsx),CinematicRenderer 与 TitledVideo 各有自己的 metadata 函数(calculateCinematicMetadata、calculateTitledVideoMetadata),后者还会用 @remotion/media-utils 的 getVideoMetadata 探测源视频时长(TitledVideo.tsx),实现"视频多长,合成多长"的自动适配。
十七、从参考到落地:OpenMontage 的组合用法
17.1 主题系统与 springConfig
Root.tsx 的 THEMES 把"clean-professional / flat-motion-graphics / minimalist-diagram / anime-ghibli"四个主题各绑定一套 springConfig 与 transitionDuration,对应仓库 styles 目录下的四个样式 playbook。渲染时 resolveTheme 依据 props 中的 theme 或 playbook 名称选中主题(Root.tsx),也可直接传入 themeConfig 对象整体覆盖——这正是 defaultProps 与运行时 props 协作的典型场景。
17.2 场景时长陷阱的标准解法
AnimeScene 是"Sequence 时长坑"的标准解:父组件把 sceneDurationSeconds 作为 prop 传入(Explainer.tsx),组件内部以 Math.round(sceneDurationSeconds * fps) 作为插值上限(AnimeScene.tsx),所有 crossfade、镜头运动、灯光渐变都基于这个"真实场景时长",而不是 useVideoConfig().durationInFrames。任何新增的 Remotion 场景组件都应复刻该模式。
17.3 渲染前置校验
在调用 renderer API 或 CLI 之前,OpenMontage 强制先跑 tools/analysis/composition_validator.py,校验资产文件存在、旁白音频不超视频时长、音乐不短于视频、cut 时间合法——把"渲染到一半才发现缺图"的失败前置化。
十八、渲染调用与质量门禁
CLI 方式(skills/core/remotion.md):
# Standard render (composition name is "Explainer", no entry point needed)
npx remotion render Explainer \
--props="public/demo-props/my-video.json" \
--output=output/final.mp4 \
--codec=h264 --crf=18
# With specific media profile
npx remotion render Explainer \
--width=1080 --height=1920 --fps=30 \
--props="public/demo-props/my-video.json" \
--output=output.mp4
注意:不要指定 src/index.ts 作为入口点,Remotion 会自动发现 Composition;--width/--height/--fps 会覆盖合成默认值,实现 YouTube 横屏 / TikTok 竖屏等媒体配置档的切换。Python 侧则由 video_compose.py 在 backend="remotion" 时通过 subprocess 调用。
渲染后的验证协议(所有流水线强制):先用 ffprobe -show_format -show_streams 确认视频流分辨率/帧率正确、音频流存在、时长偏差在 ±5% 内;再抽取各场景中间帧做视觉审查;最后用 WhisperX 转写成品音频,核对字幕词数与脚本匹配度。只有全部通过,才把成品交给用户。
结语
这份 API 参考覆盖了 Remotion 的完整能力面:useCurrentFrame/useVideoConfig 定义时间与配置上下文,interpolate/spring/interpolateColors/Easing 构成帧驱动动画语言,Composition/Sequence/Series/Loop/媒体组件搭建时间轴,delayRender 桥接异步数据,staticFile/getInputProps/getRemotionEnvironment/random 解决资产、数据注入、环境判断与确定性随机,而 @remotion/renderer/@remotion/lambda/@remotion/player 三件套分别覆盖本地渲染、云端渲染与网页播放三种交付形态。在 OpenMontage 中,这些 API 已被 Explainer.tsx、CinematicRenderer.tsx、Root.tsx 等真实组件充分验证——阅读本文时对照这些源码,即可看到每个 API 在生产流水线中的最佳实践形态。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust4.21 K636- DDeepSeek-V4.1-FlashDeepSeek-V4.1-Flash 是一个多模态混合专家(MoE)模型,拥有 5520 亿骨干参数,并支持最多一百万 token 的上下文长度。该模型原生支持图像和文本输入,并以自回归方式生成文本Python100
jforgamejforgame是一个一站式游戏服务器开发框架。包含游戏服务器开发所需要的各种组件,比如网关,socket服务端与客户端,自定义高效消息编解码,游戏热更新,游戏通用工具等等。包含游戏服,跨服,匹配服,后台管理系统等实现,同时提供大量业务案例以供学习。亦可用于其他socket应用,例如及时聊天等。Java271
fizz-gateway-nodeAn Aggregation API Gateway in Java . FizzGate 是一个基于 Java开发的微服务聚合网关,是拥有自主知识产权的应用网关国产化替代方案,能够实现热服务编排聚合、自动授权选择、线上服务脚本编码、在线测试、高性能路由、API审核管理、回调管理等目的,拥有强大的自定义插件系统可以自行扩展,并且提供友好的图形化配置界面,能够快速帮助企业进行API服务治理、减少中间层胶水代码以及降低编码投入、提高 API 服务的稳定性和安全性。Java160
certd开源SSL证书管理工具;全自动证书申请、更新、续期;通配符证书,泛域名证书申请;证书自动化部署到阿里云、腾讯云、主机、群晖、宝塔;https证书,pfx证书,der证书,TLS证书,nginx证书自动续签自动部署JavaScript150
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python300