首页
/ OpenMontage 字幕管线:使用 @remotion/captions 的 parseSrt() 将 .srt 字幕导入 Remotion

OpenMontage 字幕管线:使用 @remotion/captions 的 parseSrt() 将 .srt 字幕导入 Remotion

2026-09-09 20:58:25作者:宣利权Counsellor

本文是 OpenMontage 仓库中 remotion-best-practices 技能规则 下的《Importing .srt subtitles into Remotion》一文的深度展开,讲解如何把已有 .srt 字幕文件解析为 Remotion 的 Caption 结构化数据,并接入 OpenMontage 自带的 CaptionOverlay 组件实现逐词高亮字幕,适合需要为 Remotion 合成器接入外部字幕、或在 Agent 驱动的视频生产管线中复用现成 SRT 的开发者。

如果你的手头已经有一份 .srt 字幕文件(例如由第三方字幕工具、人工翻译或旧项目导出),你完全不需要再跑一遍语音转写——Remotion 官方生态包 @remotion/captions 提供了 parseSrt() 函数,可以把 .srt 文本直接解析成统一的 Caption[] 数据结构,进而使用 @remotion/captions 的全部后续工具链(分页、逐词高亮等)。OpenMontage 的 Remotion 合成器 remotion-composer 已在依赖中引入 @remotion/captions@^4.0.484,并在 CaptionOverlay 组件 中实现了完整的字幕渲染方案,本文将带你从安装到渲染走通整条链路。

一、什么时候该用 parseSrt()

字幕进入 Remotion 有两条路径,本规则文档(import-srt-captions.md)明确划清了边界:

  • 已有 .srt 文件 → 使用 parseSrt()@remotion/captions 导入,这是本文的主题;
  • 没有字幕文件 → 阅读 Transcribing audio,使用 @remotion/install-whisper-cpp 包对音频做 Whisper.cpp 转录生成字幕。

两条路径殊途同归:转录路径最终会通过 toCaptions() 把 Whisper 输出转成 Caption[] 写入 JSON;而 SRT 导入路径则用 parseSrt() 直接得到同样的 Caption[]。这意味着无论字幕来源如何,下游渲染逻辑完全一致——这正是 Caption 统一格式的价值所在。

二、环境准备:安装 @remotion/captions

parseSrt() 位于 @remotion/captions 包中,首先需要确保它已被安装。规则文档给出四种包管理器对应的命令:

npx remotion add @remotion/captions      # 如果项目使用 npm
bunx remotion add @remotion/captions     # 如果项目使用 bun
yarn remotion add @remotion/captions     # 如果项目使用 yarn
pnpm exec remotion add @remotion/captions  # 如果项目使用 pnpm

提示:remotion add 会同步注册到项目的 package.json 依赖中,并自动处理与当前 Remotion 版本的兼容性。

在 OpenMontage 仓库中,该依赖已经就位。查看 remotion-composer/package.json 可以看到:

"dependencies": {
  "@remotion/captions": "^4.0.484",
  "@remotion/cli": "^4.0.484",
  "remotion": "^4.0.484"
}

^4.0.484 表示基于 Remotion 4.0.x 系列版本,与 remotion 核心包版本保持一致。若你基于该合成器二次开发,直接复用即可;若从零搭建,则按上文任一命令安装。

三、读取并解析 .srt 文件:完整组件实现

规则文档给出的核心思路是:把 .srt 文件放进 Remotion 项目的 public 目录,用 staticFile() 引用它,通过 fetch() 拿到文本,再交给 parseSrt() 解析。因为 fetch() 是异步操作,必须配合 useDelayRender() 阻塞首帧渲染,直到字幕加载完成。

以下是规则文档中的完整组件实现(可直接复制运行):

import { useState, useEffect, useCallback } from "react";
import { AbsoluteFill, staticFile, useDelayRender } from "remotion";
import { parseSrt } from "@remotion/captions";
import type { Caption } from "@remotion/captions";

export const MyComponent: React.FC = () => {
  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("subtitles.srt"));
      const text = await response.text();
      const { captions: parsed } = parseSrt({ input: text });
      setCaptions(parsed);
      continueRender(handle);
    } catch (e) {
      cancelRender(e);
    }
  }, [continueRender, cancelRender, handle]);

  useEffect(() => {
    fetchCaptions();
  }, [fetchCaptions]);

  if (!captions) {
    return null;
  }

  return <AbsoluteFill>{/* Use captions here */}</AbsoluteFill>;
};

3.1 逐行拆解:为什么必须用 useDelayRender()

Remotion 的渲染器在首帧前会"收集"所有异步资源,useDelayRender() 返回的 delayRender() 会生成一个句柄(handle)并向渲染器声明"此处有尚未就绪的资源":

  • const [handle] = useState(() => delayRender()):在组件挂载时立刻调用 delayRender() 挂起渲染,并用 useState 的惰性初始化保证句柄稳定、不会因重渲染重复创建;
  • continueRender(handle):字幕解析成功后调用,通知渲染器"资源已就绪,可以继续渲染";
  • cancelRender(e):任何异常(文件缺失、格式错误、网络失败)都调用 cancelRender(e) 终止渲染并抛出错误,避免渲染出缺字幕的半成品视频;
  • 未解析完成前返回 null,保证字幕数据到位后才挂载内容组件。

这一模式与 display-captions.md 中"Fetching captions"一节完全一致——那边的示例是 fetch(staticFile("captions123.json")) 读取 JSON,本质都是"异步加载 → delayRender 挂起 → 解析完成 → continueRender 放行"。

3.2 staticFile() 与 remote URL

staticFile("subtitles.srt") 指向 public/subtitles.srt,这是 Remotion 加载静态资源的官方方式,在渲染(Remotion Studio、CLI render、Lambda)期间均可正确解析。

规则文档特别强调:远程 URL 同样受支持。你完全可以把 fetch() 的目标换成完整的 https://.../subtitles.srt 远程地址,跳过 staticFile()——适合字幕由远端服务(如翻译平台、S3/CDN)托管的生产场景。需要注意的是,真实网络请求在渲染时可能受网络环境与跨域策略影响,稳定性不如本地 public 目录文件。

四、parseSrt() 的产物:Caption 统一格式

解析完成后,parseSrt({ input: text }) 返回对象中的 captions 字段即为 Caption[]。文档强调:一旦进入 Caption 格式,就可以使用 @remotion/captions 的所有工具函数——包括 createTikTokStyleCaptions() 分页、逐词时间戳高亮、toCaptions() 等。这意味着你的字幕数据从此与来源解耦:无论是 SRT 导入、Whisper 转录还是手工 JSON,下游一律按 Caption 处理。

Caption 的核心字段(含时间戳语义)包括:

  • text:该条字幕的文本内容(@remotion/captions 约定单词前保留空格,见下文"空白保留"一节);
  • startMs / endMs:该条字幕在视频时间轴上的起止毫秒时间戳;
  • timestampMs:可选,标记该条字幕的锚点时间;
  • tone:可选,字幕的情感/语气标记。

时间戳以毫秒为单位,因此在换算成帧时需要用 fps 转换(startMs / 1000 * fps),这也是下一节 CaptionOverlay 组件换算逻辑的基准。

五、仓库纵深:OpenMontage 的 CaptionOverlay 渲染链路

规则文档在"Using imported captions"一节明确指出解析后的 Caption 可直接接入所有工具函数。OpenMontage 仓库为此提供了一个完整的、生产可用的参考实现——CaptionOverlay 组件

5.1 CaptionOverlay 的输入模型

组件定义的 WordCaption 接口与 Caption 高度同构,展示了如何把解析后的字幕组织成逐词结构:

export interface WordCaption {
  word: string;
  startMs: number;
  endMs: number;
  // 强制在该词后换页(如句子或场景边界)。
  // 对 CJK 字幕特别有用:页面应与分句边界对齐。
  pageBreakAfter?: boolean;
}

组件通过 wordsPerPage(默认 6 词/页)和 pageBreakAfter 把单词流分组成 CaptionPage[],与 @remotion/captionscreateTikTokStyleCaptions() 分页思想一致——区别在于 OpenMontage 自实现了 buildPages(),并额外支持了 CJK 场景的强制分页。

5.2 分页渲染与逐词高亮

CaptionOverlay 的渲染核心与规则文档"Rendering with Sequences"一节异曲同工:将每一页映射为一个 <Sequence>,通过 startMs 换算起始帧、下一页起始时间换算时长:

const fromFrame = Math.round((page.startMs / 1000) * fps);
const nextStart = pages[i + 1]?.startMs ?? page.endMs + 500;
const duration = Math.max(1, Math.round(((nextStart - page.startMs) / 1000) * fps));

页内逐词高亮则用 currentMs 与每个词的 [startMs, endMs) 区间比对,当前正在朗读的词使用高亮色 #22D3EE,已读过的词恢复正文色,未读到的词以半透明色呈现(源码 CaptionOverlay.tsx 第 109-131 行)。这正对应规则文档 display-captions.md 中"Word highlighting"一节的 token.fromMs <= absoluteTimeMs && token.toMs > absoluteTimeMs 判定逻辑——SRT 导入后即可无缝享受这套高亮效果。

5.3 空白保留与 CJK 适配

规则文档"White-space preservation"一节强调:Caption.text空白敏感的,单词前应保留空格,渲染时使用 whiteSpace: "pre" 以保留空白。CaptionOverlay 对此做了两层处理:

  • 页内每个 <span>whiteSpace: "nowrap" 防止单词内部断行,外层容器设 whiteSpace: "pre-wrap" 在词边界换行;
  • 提供 wordSeparator 属性:空格分隔语言(英文等)传默认 " ",CJK 语言(无词间空格)传 "",并在 PageRenderer 中按 wordSeparator 拼接单词。

这一设计对中文、日文等字幕尤其重要——中文没有词间空格,若照搬英文的空格分隔渲染会出现额外空隙。

5.4 组件在合成器中的接入点

CaptionOverlay 已被 OpenMontage 的三个核心合成器复用:

同时 Root.tsx(第 257-258 行)注册了独立的 CaptionOverlayOnly composition,可在 Remotion Studio 中单独预览字幕效果,非常适合验证导入的 SRT 数据。

六、工程落地:字幕对比度与管线接入

在 OpenMontage 中,字幕不仅是视觉效果,还受到工程约束与测试保护:

  • 主题对比度契约:测试 test_theme_text_contrast_contract.py 专门回归验证"CaptionOverlay 字幕词色与主题背景对比度"问题——例如浅色主题下如果遗留了深色主题默认的字幕颜色,会被该测试拦截。这提示我们在接入 SRT 字幕时,color / highlightColor / backgroundColor 必须随主题联动,而不是写死。
  • 字幕烧录工具tools/video/remotion_caption_burn.py 展示了字幕落地的另一条路径——优先走 Remotion CaptionOverlay 渲染,不可用时回退到 FFmpeg 字幕烧录,体现了"Remotion 优先、FFmpeg 兜底"的工程策略。
  • 技能协同:技能索引 remotion.md 明确指出词级字幕在合成器内由 Remotion CaptionOverlay 负责,优于 FFmpeg SRT 烧录方案;explainer 管线的 compose-director.md 也要求词级字幕走 CaptionOverlay 组件。这意味着:在 OpenMontage 的 Agent 生产管线中,parseSrt() 导入的字幕应统一送入 CaptionOverlay 渲染,而不是直接依赖 FFmpeg。

七、与转录路径衔接:完整的字幕工作流

把规则文档的两条路径串起来,就得到 OpenMontage 中完整的字幕工作流:

  1. 已有 SRTfetch(staticFile("subtitles.srt"))parseSrt({ input: text })Caption[]
  2. 无 SRT:参照 transcribe-captions.md,用 @remotion/install-whisper-cpp 安装 Whisper.cpp 与模型(如 medium.en),将音频转为 16kHz WAV 后调用 transcribe(),再用 toCaptions() 后处理并写入 public/captions.json
  3. 统一渲染:无论 Caption[] 来自 SRT 还是 Whisper JSON,都交给 CaptionOverlay(或 createTikTokStyleCaptions() 分页 + <Sequence> 逐页渲染)完成逐词高亮展示。

由此,parseSrt() 成为整条字幕管线的"进口关"——它让历史 SRT 资产在 OpenMontage 的 Remotion 渲染体系中得以复用,并自动获得词级高亮、主题适配、对比度契约测试等全套能力。

八、常见问题与注意事项

  • 字幕未显示但渲染成功:检查 fetch 路径是否正确(staticFile 对应 public/ 根目录),以及是否遗漏 continueRender(handle)——漏掉会导致渲染永远挂起或超时。
  • 解析失败parseSrt() 对标准 SRT 格式(序号 + 时间轴 HH:MM:SS,mmm --> HH:MM:SS,mmm + 文本 + 空行分隔)敏感,请确认文件为 UTF-8 编码且无 BOM 干扰。
  • 中文/日文排版异常:为 CaptionOverlay 传入 wordSeparator="",并在生成 WordCaption 时善用 pageBreakAfter 对齐分句边界。
  • 主题切换后字幕看不清:同步调整 color / highlightColor / backgroundColor,以通过 test_theme_text_contrast_contract.py 的对比度校验。
  • 远程字幕不稳定:生产渲染优先把 SRT 放进 public/ 目录随项目一起部署,远程 URL 仅用于开发或受控网络环境。

相关资源

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.16 K
2.78 K
kernelkernel
deepin linux kernel
C
34
18
docsdocs
暂无描述
Markdown
904
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
932
1.86 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
862
1.36 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.95 K
1.03 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.38 K
1.47 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
535
606
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
549
398
leetcodeleetcode
🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解
Markdown
77
23