| name | compositing |
| description | 使用 Remotion 合成最终视频。当需要将片头、录屏、配音、片尾组合成完整视频时使用。包含动画效果、时间线管理、多尺寸模板和故障处理。 |
视频合成技能
快速开始:使用模板
项目提供了可复用的配置和组件模板,位于 templates/ 目录:
templates/
├── config/
│ ├── scenes.ts
│ ├── theme.ts
│ ├── types.ts
│ ├── videoPresets.ts
│ └── index.ts
└── components/
├── SubtitleDisplay.tsx
├── AnimatedText.tsx
├── BackgroundEffects.tsx
├── BrandElements.tsx
├── useResponsive.ts
└── index.ts
使用方法
- 复制模板到项目目录:
cp -r templates/config src/config
cp -r templates/components src/components
-
根据项目需要修改配置(颜色、场景时间等)
-
在组件中导入使用:
import { SCENES, FPS, VIDEO_DURATION } from "./config";
import { THEME, getGlowStyle } from "./config";
import { SubtitleDisplay, FadeInText, ParticleField } from "./components";
多尺寸视频模板
预设尺寸
| 名称 | 分辨率 | 比例 | 适用平台 |
|---|
| 1080p (默认) | 1920×1080 | 16:9 | YouTube, 官网 |
| 720p | 1280×720 | 16:9 | 快速预览, 低带宽 |
| vertical | 1080×1920 | 9:16 | 抖音, 小红书, Reels |
| square | 1080×1080 | 1:1 | Instagram, 微信 |
| 4K | 3840×2160 | 16:9 | 高端展示 |
Remotion 多尺寸配置
export const VIDEO_PRESETS = {
"1080p": { width: 1920, height: 1080, name: "Full HD" },
"720p": { width: 1280, height: 720, name: "HD" },
"vertical": { width: 1080, height: 1920, name: "Vertical" },
"square": { width: 1080, height: 1080, name: "Square" },
"4k": { width: 3840, height: 2160, name: "4K" },
} as const;
export type VideoPreset = keyof typeof VIDEO_PRESETS;
Root.tsx 多尺寸定义
import { Composition } from "remotion";
import { MainVideo } from "./MainVideo";
import { VIDEO_PRESETS, VideoPreset } from "./videoPresets";
const FPS = 30;
const DURATION_SECONDS = 85;
export const RemotionRoot: React.FC = () => {
return (
<>
{/* 为每个尺寸创建 Composition */}
{Object.entries(VIDEO_PRESETS).map(([key, preset]) => (
<Composition
key={key}
id={`Video-${key}`}
component={MainVideo}
durationInFrames={DURATION_SECONDS * FPS}
fps={FPS}
width={preset.width}
height={preset.height}
defaultProps={{ preset: key as VideoPreset }}
/>
))}
</>
);
};
响应式组件适配
const ResponsiveLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const { width, height } = useVideoConfig();
const aspectRatio = width / height;
if (aspectRatio < 1) {
return (
<AbsoluteFill style={{ flexDirection: "column", padding: "60px 40px" }}>
{children}
</AbsoluteFill>
);
}
if (aspectRatio === 1) {
return (
<AbsoluteFill style={{ padding: "40px" }}>
{children}
</AbsoluteFill>
);
}
return (
<AbsoluteFill style={{ padding: "60px 120px" }}>
{children}
</AbsoluteFill>
);
};
字体大小适配
const getResponsiveFontSize = (baseSize: number): number => {
const { width, height } = useVideoConfig();
const scale = Math.min(width / 1920, height / 1080);
return Math.round(baseSize * scale);
};
const title = getResponsiveFontSize(72);
批量渲染脚本
#!/bin/bash
SIZES=("1080p" "720p" "vertical" "square")
OUTPUT_DIR="out"
for size in "${SIZES[@]}"; do
echo "渲染 $size..."
npx remotion render src/index.ts "Video-$size" "$OUTPUT_DIR/video_$size.mp4"
done
echo "所有尺寸渲染完成!"
Remotion 基础
Remotion 是 React-based 的视频渲染框架,使用 React 组件定义视频内容。
核心概念
| 概念 | 说明 |
|---|
| Composition | 视频组合定义(分辨率、帧率、时长) |
| Sequence | 时间序列,控制内容出现时机 |
| useCurrentFrame | 获取当前帧数 |
| interpolate | 数值插值,用于动画 |
| spring | 弹性动画 |
基本结构
import { Composition } from "remotion";
export const RemotionRoot = () => {
return (
<Composition
id="FinalVideo"
component={FinalVideo}
durationInFrames={2550} // 85秒 * 30fps
fps={30}
width={1920}
height={1080}
/>
);
};
故障处理 (重要)
浏览器下载失败
问题表现:
Error: Tried to download file xxx, but the server sent no data for 20 seconds
解决方案 A: 手动下载 Chrome Headless Shell
curl -L --connect-timeout 30 --max-time 300 \
"https://storage.googleapis.com/chrome-for-testing-public/134.0.6998.35/mac-arm64/chrome-headless-shell-mac-arm64.zip" \
-o /tmp/chrome-headless-shell.zip
mkdir -p ~/.cache/remotion
unzip /tmp/chrome-headless-shell.zip -d ~/.cache/remotion/
npx remotion render src/index.ts VideoId out/video.mp4 \
--browser-executable="$HOME/.cache/remotion/chrome-headless-shell-mac-arm64/chrome-headless-shell"
解决方案 B: 使用代理
export HTTP_PROXY=http://your-proxy:port
export HTTPS_PROXY=http://your-proxy:port
npx remotion browser ensure
其他常见问题
| 问题 | 解决方案 |
|---|
| Chrome 下载失败 | 手动下载或使用代理 |
| 视频文件找不到 | 确保在 public/ 目录,用 staticFile() |
| 渲染内存不足 | --concurrency=4 减少并发 |
| 字体不显示 | 使用 @remotion/google-fonts |
字体配置
使用 Google Fonts (推荐)
npm install @remotion/google-fonts
import { loadFont } from "@remotion/google-fonts/NotoSansSC";
const { fontFamily } = loadFont();
<div style={{ fontFamily }}>中文文字</div>
推荐中文字体
| 字体 | 包名 | 风格 |
|---|
| Noto Sans SC | NotoSansSC | 现代无衬线 |
| Noto Serif SC | NotoSerifSC | 经典衬线 |
| ZCOOL XiaoWei | ZCOOLXiaoWei | 手写风格 |
备选:系统字体栈
const fontFamily = `
"PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
"WenQuanYi Micro Hei", system-ui, sans-serif
`;
高级动画模式
1. Logo 弹性缩放 + 发光
const LogoWithGlow: React.FC<{ src: string }> = ({ src }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const scale = spring({
frame,
fps,
config: { damping: 12, stiffness: 100 },
});
const glowIntensity = interpolate(
Math.sin(frame * 0.08),
[-1, 1],
[0.3, 0.8]
);
return (
<div
style={{
transform: `scale(${scale})`,
filter: `drop-shadow(0 0 ${40 * glowIntensity}px #76B900)`,
}}
>
<Img src={src} style={{ width: 400 }} />
</div>
);
};
2. 文字淡入 + 上移
const FadeInText: React.FC<{ text: string; delay?: number }> = ({
text,
delay = 0,
}) => {
const frame = useCurrentFrame();
const opacity = interpolate(frame, [delay, delay + 30], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const translateY = interpolate(frame, [delay, delay + 30], [20, 0], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
return (
<div
style={{
opacity,
transform: `translateY(${translateY}px)`,
}}
>
{text}
</div>
);
};
3. 打字机效果
const TypewriterText: React.FC<{ text: string; speed?: number }> = ({
text,
speed = 3,
}) => {
const frame = useCurrentFrame();
const charsToShow = Math.floor(
interpolate(frame, [0, text.length * speed], [0, text.length], {
extrapolateRight: "clamp",
})
);
return <span>{text.slice(0, charsToShow)}</span>;
};
4. 年份大字弹入
const YearDisplay: React.FC<{ year: string; color?: string }> = ({
year,
color = "#76B900",
}) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const scale = spring({
frame,
fps,
config: { damping: 15, stiffness: 80 },
});
return (
<div
style={{
fontSize: 200,
fontWeight: "bold",
color,
transform: `scale(${scale})`,
textShadow: `0 0 60px ${color}`,
}}
>
{year}
</div>
);
};
5. 背景图渐变叠加
const BackgroundWithOverlay: React.FC<{
src: string;
opacity?: number;
}> = ({ src, opacity = 0.3 }) => {
return (
<>
<AbsoluteFill>
<Img
src={staticFile(src)}
style={{
width: "100%",
height: "100%",
objectFit: "cover",
opacity,
}}
/>
</AbsoluteFill>
{/* 渐变叠加层 */}
<AbsoluteFill
style={{
background: "linear-gradient(180deg, rgba(0,0,0,0.6) 0%, rgba(0,0,0,0.3) 50%, rgba(0,0,0,0.8) 100%)",
}}
/>
</>
);
};
6. 数据卡片动画
const DataCard: React.FC<{
value: string;
label: string;
delay: number;