Remotion LabRemotion Lab
資源動態字幕模板

動態字幕模板

使用 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);

延伸閱讀