動態字幕模板
使用 Remotion 的動態字幕模板,為影片自動生成帶有動畫效果的逐字字幕,支援 OpenAI Whisper 轉錄。
動態字幕模板
Animated Captions Template 是 Remotion 提供的字幕動畫起始模板,結合 AI 語音轉文字技術,自動為影片生成帶有視覺動畫效果的逐字字幕,廣泛應用於社群媒體短影音製作。
模板特色
- AI 自動轉錄:整合 OpenAI Whisper,自動將語音轉為帶時間戳記的字幕
- 逐字動畫:每個字詞依說話時間點依序出現,搭配縮放、淡入等動畫效果
- 高度客製化:字型、顏色、位置、動畫風格皆可調整
- 多語言支援:Whisper 支援 90+ 種語言,包含繁體中文
- 一鍵渲染:設定完成後直接輸出為 MP4
快速開始
npx create-video@latest --template captions
cd my-captions-video
npm install設定 OpenAI API Key:
# .env.local
OPENAI_API_KEY=your-openai-api-key放入你的影片並執行轉錄:
# 將影片放到 public/ 目錄,例如 public/speech.mp4
npm run transcribe啟動預覽:
npm run dev轉錄流程
步驟一:語音轉文字
模板使用 @remotion/captions 搭配 Whisper API 生成帶時間戳記的字幕資料:
// scripts/transcribe.ts
import { transcribe } from "@remotion/captions";
import { writeFileSync } from "fs";
const result = await transcribe({
inputPath: "public/speech.mp4",
apiKey: process.env.OPENAI_API_KEY!,
model: "whisper-1",
language: "zh", // 繁體中文
tokenizeByWord: true, // 逐字時間戳記
});
writeFileSync("src/captions.json", JSON.stringify(result.captions, null, 2));步驟二:字幕資料格式
轉錄後的字幕資料結構如下:
[
{
"text": "歡迎",
"startMs": 0,
"endMs": 480,
"confidence": 0.98
},
{
"text": "來到",
"startMs": 480,
"endMs": 850,
"confidence": 0.97
},
{
"text": "Remotion",
"startMs": 850,
"endMs": 1400,
"confidence": 0.99
}
]字幕動畫元件
基本逐字顯示
import { useCurrentFrame, useVideoConfig, interpolate } from "remotion";
import captions from "./captions.json";
interface Word {
text: string;
startMs: number;
endMs: number;
}
const WordDisplay: React.FC<{ word: Word }> = ({ word }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const startFrame = (word.startMs / 1000) * fps;
const endFrame = (word.endMs / 1000) * fps;
const opacity = interpolate(frame, [startFrame, startFrame + 3], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const scale = interpolate(frame, [startFrame, startFrame + 5], [0.8, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
// 已說完的字變暗
const dimOpacity = frame > endFrame + fps * 0.5 ? 0.4 : 1;
return (
<span
style={{
display: "inline-block",
opacity: opacity * dimOpacity,
transform: `scale(${scale})`,
transformOrigin: "bottom center",
margin: "0 4px",
fontSize: 64,
fontWeight: "bold",
color: "white",
textShadow: "0 2px 8px rgba(0,0,0,0.8)",
}}
>
{word.text}
</span>
);
};分段顯示(每次顯示一組字)
import { useCurrentFrame, useVideoConfig } from "remotion";
import captions from "./captions.json";
const WORDS_PER_GROUP = 4;
export const GroupedCaptions: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const currentMs = (frame / fps) * 1000;
// 找出目前應顯示的字詞群組
const currentWordIndex = captions.findIndex(
(word) => word.startMs <= currentMs && word.endMs > currentMs
);
if (currentWordIndex === -1) return null;
const groupStart =
Math.floor(currentWordIndex / WORDS_PER_GROUP) * WORDS_PER_GROUP;
const group = captions.slice(groupStart, groupStart + WORDS_PER_GROUP);
return (
<div
style={{
position: "absolute",
bottom: 120,
left: 0,
right: 0,
display: "flex",
justifyContent: "center",
flexWrap: "wrap",
padding: "0 80px",
}}
>
{group.map((word, i) => (
<WordDisplay key={groupStart + i} word={word} />
))}
</div>
);
};樣式客製化
螢光標記效果
const HighlightedWord: React.FC<{ word: Word; isActive: boolean }> = ({
word,
isActive,
}) => {
return (
<span
style={{
backgroundColor: isActive ? "#facc15" : "transparent",
color: isActive ? "#000" : "#fff",
borderRadius: 8,
padding: "2px 8px",
transition: "background-color 0.1s",
fontSize: 56,
fontWeight: "bold",
}}
>
{word.text}
</span>
);
};漸層背景字幕條
<div
style={{
position: "absolute",
bottom: 0,
left: 0,
right: 0,
padding: "60px 80px 80px",
background:
"linear-gradient(to top, rgba(0,0,0,0.9) 0%, transparent 100%)",
}}
>
{/* 字幕內容 */}
</div>搭配真實影片使用
import { AbsoluteFill, OffthreadVideo } from "remotion";
export const CaptionedVideo: React.FC = () => {
return (
<AbsoluteFill>
<OffthreadVideo src={staticFile("speech.mp4")} />
<GroupedCaptions />
</AbsoluteFill>
);
};匯出為 SRT 格式
若需要將字幕匯出為標準 .srt 格式以供其他平台使用:
import { srtToCaption, captionToSrt } from "@remotion/captions";
import captions from "./captions.json";
const srtContent = captionToSrt(captions);
writeFileSync("output/captions.srt", srtContent);