Remotion LabRemotion Lab
字幕匯出字幕檔案

匯出字幕檔案

學習如何將 Remotion 影片的字幕匯出為 SRT 或 VTT 等獨立字幕檔案,或直接燒入影片。

總覽

本指南介紹從 Remotion 影片匯出字幕的不同方式:

  1. 燒入字幕 — 字幕直接嵌入影片畫面
  2. 匯出為獨立 SRT 檔案 — 產生可與影片分離使用的字幕檔案

燒入字幕

如果你希望字幕成為影片畫面的一部分(燒入字幕),請依照顯示字幕的說明渲染字幕,然後照常渲染影片即可:

npx remotion render

燒入字幕的優點是字幕會永久附著在影片上,無需額外的字幕檔案。缺點是字幕無法被關閉或切換語言。

匯出為獨立 SRT 檔案

若要將字幕匯出為獨立的 .srt 檔案,搭配使用 <Artifact> 元件與 serializeSrt() 函式。

安裝相依套件

npm install @remotion/captions

基本 SRT 匯出

import { useCurrentFrame } from 'remotion';
import { Artifact } from 'remotion';
import { serializeSrt } from '@remotion/captions';
 
export const MyComp: React.FC = () => {
  const frame = useCurrentFrame();
 
  // 將字幕轉換為 SRT 格式
  // 每個字幕段落各自成為一行
  const srtContent = serializeSrt({
    lines: captions.map((caption) => [caption]),
  });
 
  return (
    <>
      {/* 只在第一幀輸出 artifact */}
      {frame === 0 ? (
        <Artifact filename="subtitles.srt" content={srtContent} />
      ) : null}
      {/* 影片其他內容 */}
    </>
  );
};

渲染後,字幕檔案會儲存至 out/[composition-id]/subtitles.srt

<Artifact> 元件說明

<Artifact> 是 Remotion 提供的特殊元件,用於在渲染過程中輸出額外的檔案。它只應在第一幀(frame === 0)渲染,避免重複輸出。

<Artifact
  filename="subtitles.srt"  // 輸出檔名
  content={srtContent}       // 檔案內容(字串)
/>

將字詞分組為字幕行

如果你的字幕是逐字拆分的(word-level),可能會想將多個字詞合併為單一字幕行。使用 createTikTokStyleCaptions() 建立分頁,再轉換回 serializeSrt() 所需的格式:

import { createTikTokStyleCaptions, serializeSrt } from '@remotion/captions';
import { Artifact, useCurrentFrame } from 'remotion';
 
export const MyComp: React.FC = () => {
  const frame = useCurrentFrame();
 
  // 將字詞分組,每 3 秒一組
  const { pages } = createTikTokStyleCaptions({
    captions,
    combineTokensWithinMilliseconds: 3000,
  });
 
  const srtContent = serializeSrt({
    lines: pages.map((page) => {
      // 將頁面中的 token 轉換回 Caption 格式
      return page.tokens.map((token) => ({
        text: token.text,
        startMs: token.fromMs,
        endMs: token.toMs,
        timestampMs: (token.fromMs + token.toMs) / 2,
        confidence: null,
      }));
    }),
  });
 
  return (
    <>
      {frame === 0 ? (
        <Artifact filename="subtitles.srt" content={srtContent} />
      ) : null}
      {/* 影片其他內容 */}
    </>
  );
};

SRT 格式說明

SRT(SubRip Text)是最常見的字幕格式之一。serializeSrt() 輸出的格式如下:

1
00:00:00,000 --> 00:00:02,500
第一段字幕文字

2
00:00:02,500 --> 00:00:05,000
第二段字幕文字

3
00:00:05,000 --> 00:00:07,800
第三段字幕文字

每個字幕條目包含:

  1. 序號(從 1 開始)
  2. 時間範圍(開始時間 --> 結束時間,格式為 HH:MM:SS,mmm
  3. 字幕文字(可多行)
  4. 空白行作為分隔

完整匯出範例

以下是一個完整範例,同時渲染燒入字幕的影片內容,並輸出 SRT 檔案:

import {
  useState,
  useEffect,
  useCallback,
  useMemo,
} from 'react';
import {
  AbsoluteFill,
  Artifact,
  staticFile,
  useCurrentFrame,
  useDelayRender,
  useVideoConfig,
} from 'remotion';
import {
  createTikTokStyleCaptions,
  serializeSrt,
} from '@remotion/captions';
import type { Caption } from '@remotion/captions';
 
const COMBINE_CAPTIONS_EVERY_MS = 3000;
 
export const VideoWithExportedCaptions: React.FC = () => {
  const frame = useCurrentFrame();
  const [captions, setCaptions] = useState<Caption[] | null>(null);
  const { delayRender, continueRender, cancelRender } = useDelayRender();
  const [handle] = useState(() => delayRender());
 
  const fetchCaptions = useCallback(async () => {
    try {
      const response = await fetch(staticFile('captions.json'));
      const data = await response.json();
      setCaptions(data);
      continueRender(handle);
    } catch (e) {
      cancelRender(e);
    }
  }, [continueRender, cancelRender, handle]);
 
  useEffect(() => {
    fetchCaptions();
  }, [fetchCaptions]);
 
  const srtContent = useMemo(() => {
    if (!captions) return '';
 
    const { pages } = createTikTokStyleCaptions({
      captions,
      combineTokensWithinMilliseconds: COMBINE_CAPTIONS_EVERY_MS,
    });
 
    return serializeSrt({
      lines: pages.map((page) =>
        page.tokens.map((token) => ({
          text: token.text,
          startMs: token.fromMs,
          endMs: token.toMs,
          timestampMs: (token.fromMs + token.toMs) / 2,
          confidence: null,
        }))
      ),
    });
  }, [captions]);
 
  if (!captions) {
    return null;
  }
 
  return (
    <AbsoluteFill style={{ backgroundColor: 'black' }}>
      {/* 輸出 SRT 字幕檔案(僅在第一幀) */}
      {frame === 0 && srtContent ? (
        <Artifact filename="subtitles.srt" content={srtContent} />
      ) : null}
 
      {/* 影片主體內容(此處省略實際影片元素) */}
      <AbsoluteFill
        style={{
          justifyContent: 'center',
          alignItems: 'center',
          color: 'white',
          fontSize: 40,
        }}
      >
        影片內容區域
      </AbsoluteFill>
    </AbsoluteFill>
  );
};

渲染並取得 SRT 檔案

渲染後,SRT 檔案會出現在輸出目錄中:

# 渲染影片
npx remotion render MyComposition
 
# 輸出檔案位置:
# out/MyComposition/
#   ├── out.mp4          ← 影片檔案
#   └── subtitles.srt   ← 字幕檔案

在伺服器端渲染時取得 Artifact

若使用 SSR API 渲染,可以從渲染結果取得所有 artifact:

import { renderMedia, selectComposition } from '@remotion/renderer';
 
const composition = await selectComposition({
  serveUrl: 'http://localhost:3000',
  id: 'MyComposition',
  inputProps: {},
});
 
const { artifacts } = await renderMedia({
  composition,
  serveUrl: 'http://localhost:3000',
  codec: 'h264',
  outputLocation: 'out/video.mp4',
});
 
// artifacts 包含所有透過 <Artifact> 輸出的檔案
for (const artifact of artifacts) {
  console.log(artifact.filename); // 'subtitles.srt'
  console.log(artifact.content);  // SRT 檔案內容
}

參考資料