Remotion 字幕转写实战:用 @remotion/install-whisper-cpp 将音频转成 Caption JSON
本指南聚焦 Remotion 中"语音转字幕"的完整管线:如何下载并编译 Whisper.cpp、拉取模型、将音频转写为带词级时间戳的 JSON,再通过 toCaptions() 归一化为标准 Caption 结构供 Remotion 渲染使用。这是 AI 配音/口播类视频、TikTok 风格逐词高亮字幕项目的前置数据环节,读完你可以独立写出可复现的音频转写脚本并产出可用于 Remotion 的字幕 JSON 文件。
整体流程概览
字幕数据的生成不发生在 Remotion 组件内部,而是发生在"渲染之前"的一次性离线步骤中。典型链路为:
- 安装 Whisper.cpp 可执行文件(
installWhisperCpp()); - 下载对应的 GGML 量化模型(
downloadWhisperModel()); - 准备符合要求的 16kHz WAV 音频;
- 调用
transcribe()让本地 Whisper.cpp 引擎完成语音转写,返回TranscriptionJson; - 用
toCaptions()做推荐的后处理,得到标准Caption[]; - 将结果写入
public/或作为 JSON 文件落盘,供 Remotion 组件fetch(staticFile(...))加载展示。
其中第 5 步产出的 Caption 类型定义在 @remotion/captions 中,是整个 remotion-captions 系列能力(展示、分页、逐词高亮、SRT 导入)的公共数据契约。本技能配套的 remotion-captions 已完整描述了从转写到显示、再到 .srt 导入的完整工作流,本文聚焦其中的"转写"环节。
前置准备:安装 @remotion/install-whisper-cpp
本指南对应的转写能力由 @remotion/install-whisper-cpp 包提供。该包位于 packages/install-whisper-cpp,内部实现为"帮助安装与调用 Whisper.cpp 的一组合法方法",依赖 @remotion/captions(用于输出标准的 Caption 类型,见 package.json)。
在 Remotion 项目中通过以下命令安装:
npx remotion add @remotion/install-whisper-cpp
其对外导出的 API 集合(见 src/index.ts)包括:
| 导出 | 说明 |
|---|---|
installWhisperCpp |
下载、编译(或解压)Whisper.cpp 可执行文件 |
downloadWhisperModel |
从 Hugging Face 下载指定 GGML 模型到本地目录 |
transcribe |
调用 Whisper.cpp 二进制对 WAV 执行转写,返回完整 JSON |
toCaptions |
官方推荐的转写结果 → Caption[] 归一化后处理 |
convertToCaptions |
旧版后处理 API(自 Remotion v4.0.216 起已弃用,请改用 toCaptions()) |
三步拿到字幕 JSON
官方文档给出的是一个"一段式"的 Node.js 脚本(可放 .mjs / .ts 中通过 tsx、bun 等运行),依次完成安装引擎、下载模型、转写并后处理:
import path from "path";
import {
downloadWhisperModel,
installWhisperCpp,
transcribe,
toCaptions,
} from "@remotion/install-whisper-cpp";
import fs from "fs";
const to = path.join(process.cwd(), "whisper.cpp");
await installWhisperCpp({
to,
version: "1.5.5",
});
await downloadWhisperModel({
model: "medium.en",
folder: to,
});
// 若音频不是 16kHz WAV,先用 ffmpeg 做一次转换:
// import {execSync} from 'child_process';
// execSync('ffmpeg -i /path/to/audio.mp4 -ar 16000 /path/to/audio.wav -y');
const whisperCppOutput = await transcribe({
model: "medium.en",
whisperPath: to,
whisperCppVersion: "1.5.5",
inputPath: "/path/to/audio123.wav",
tokenLevelTimestamps: true,
});
// 可选用官方推荐的后处理,得到标准 Caption 结构
const { captions } = toCaptions({
whisperCppOutput,
});
// 写入 public/ 目录,便于 Remotion 通过 staticFile() 获取
fs.writeFileSync("captions123.json", JSON.stringify(captions, null, 2));
如果有多段素材,就分别对每一段音频执行转写、生成各自的 JSON 文件——字幕与视频一一对应,每个视频应有一份独立的字幕 JSON。生成 JSON 之后如何把字幕真正渲染到画面上(分页、逐词高亮等),见本技能系列的 display-captions.md。
步骤一:安装 Whisper.cpp 引擎
installWhisperCpp() 负责把 Whisper.cpp 本体弄到本地目录。签名(见 install-whisper-cpp.ts):
await installWhisperCpp({
to: string; // 目标安装目录(相对/绝对均可,内部会 path.resolve 到 process.cwd())
version: string; // Whisper.cpp 版本号,如 "1.5.5"
printOutput?: boolean; // 默认 true,是否透传编译/下载输出
signal?: AbortSignal | null; // 用于取消操作
});
平台差异是这里最容易踩的坑,从源码可以确认内部行为分为两条路径:
- macOS / Linux:先
git clone https://github.com/ggerganov/whisper.cpp.git到to,再用git checkout切到v{version},最后执行make编译出二进制(见 install-whisper-cpp.ts)。因此这类平台需要本机装有git与make。 - Windows:只支持语义化版本号,且版本为
1.5.5时从 Remotion 维护的二进制镜像站下载whisper-bin-x64-1-5-5.zip,其余版本从 whisper.cpp release 下载,然后调用 PowerShellExpand-Archive解压(见 install-whisper-cpp.ts)。
版本影响可执行文件的查找路径:getWhisperExecutablePath() 中,当版本 >= 1.7.4 时使用新目录 build/bin/whisper-cli(旧版 main 已弃用),否则使用根目录的 main 可执行文件(见 install-whisper-cpp.ts)。
installWhisperCpp() 返回 { alreadyExisted: boolean }:若目标目录已存在且可执行文件完好,则跳过安装并返回 true;若目录存在但缺少可执行文件,会抛出错误提示删除该目录重试,避免使用残缺安装。这与下载模型时的行为互相配合,使脚本具备可重复执行的特性——第二次运行会直接跳过已完成的下载与编译步骤。
提示:Whisper.cpp 的编译下载属于重操作,建议把上述脚本作为独立的"数据准备"任务在渲染前运行一次,而不是在 Remotion 渲染流程内反复执行。
步骤二:下载 Whisper 模型
downloadWhisperModel() 会从 https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-${model}.bin 拉取 GGML 格式模型:
await downloadWhisperModel({
model: "medium.en", // 模型标识,见下方列表
folder: to, // 模型存放目录(须与 installWhisperCpp 的 to 一致)
printOutput?: boolean, // 默认 true
onProgress?: (progress: {downloaded: number; totalSize: number}) => void;
signal?: AbortSignal | null;
});
可用的模型列表在 download-whisper-model.ts 中硬编码为常量:
tiny、tiny.en、base、base.en、small、small.en、medium、medium.en、large-v1、large-v2、large-v3、large-v3-turbo
命名规律直观:.en 后缀表示仅英文专用模型(体积更小、英文场景精度更高),不带后缀的为多语言模型。模型越大精度越高、同时下载体积与推理耗时也越大——上述列表对应文件的字节数(modelSizes 表,见 download-whisper-model.ts)范围从约 74 MB(tiny)到约 2.9 GB(large-v3),medium.en 约 1.4 GB。官方示例使用 medium.en 是精度与资源消耗的常见折中。
值得注意的幂等设计:源码中若目标文件已存在且字节数与 modelSizes 记录的期望大小完全一致,则直接返回 { alreadyExisted: true } 跳过下载;若大小不符会抛错提示删除文件重试(见 download-whisper-model.ts)。这为批量生成多段字幕的重复执行提供了安全检查。
模型文件会以 ggml-${model}.bin 命名落盘(getModelPath(),download-whisper-model.ts)。模型目录与后续 transcribe() 的 whisperPath 一致时,transcribe() 会自动找到模型;也可以单独用 transcribe({ modelFolder }) 指向其他目录。
步骤三:准备 16kHz WAV 音频(关键约束)
transcribe() 对输入文件有硬性校验,违反即抛错。从 transcribe.ts 可以确认两条规则:
- 必须是
.wav文件:源码通过扩展名判断(isWavFile),否则抛Invalid inputFile type. The provided file is not a wav file!; - 采样率必须是 16kHz:whisper.cpp 在处理时若检测到非 16kHz 会报
wav file must be 16 kHz,该错误会在转写进程退出后被识别并转成可读的异常信息(见 transcribe.ts)。
因此任意来源的素材(mp4、mp3、m4a……)都需先经 ffmpeg 转成 16kHz 单声道 WAV:
ffmpeg -i input.mp4 -ar 16000 output.wav -y
官方脚本把这一步以注释形式嵌在示例里;也可以在管道上游用 @remotion/media-parser / WebCodecs 的音频重采样能力处理。
步骤四:执行转写并理解返回结构
const whisperCppOutput = await transcribe({
model: "medium.en", // 与下载的模型保持一致
whisperPath: to, // installWhisperCpp 的安装目录
whisperCppVersion: "1.5.5", // 与安装时版本一致(决定可执行文件路径)
inputPath: "/path/to/audio123.wav", // 16kHz WAV
tokenLevelTimestamps: true, // 关键:生成词级时间戳,是逐词高亮的前提
});
完整的参数列表与默认值可以从 transcribe.ts 的类型定义中确认:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
inputPath |
string |
必填 | 待转写音频,须为 16kHz WAV |
whisperPath |
string |
必填 | Whisper.cpp 安装目录 |
whisperCppVersion |
string |
必填 | 与安装时一致的版本号,决定二进制路径 |
model |
WhisperModel |
必填 | 模型标识,须已下载 |
tokenLevelTimestamps |
boolean |
必填 | 是否启用词级时间戳(DTW);true 时返回结构更丰富 |
modelFolder |
string |
undefined |
模型目录,缺省用 whisperPath |
translateToEnglish |
boolean |
false |
转写为英文(对应 whisper -tr) |
printOutput |
boolean |
true |
是否向终端输出转写过程日志 |
tokensPerItem |
number | null |
与时间戳模式联动 | 每 JSON item 容纳的 token 数;开启词级时间戳时强制为 1 |
language |
Language | null |
null |
指定音频语言(自动检测不可用时使用) |
splitOnWord |
boolean |
false |
是否按词切分 |
signal |
AbortSignal |
null |
取消转写 |
onProgress |
(progress: number) => void |
null |
进度回调,参数为 0~1 的浮点 |
flashAttention |
boolean |
false |
是否启用 Flash Attention 加速 |
additionalArgs |
string[] | [string, string][] |
无 | 追加透传给 whisper.cpp 的额外参数 |
底层实现细节(见 transcribe.ts)值得了解,有助于你排错:
- 它通过
spawn启动 whisper.cpp 二进制,参数构造包含--output-json(JSON 输出)、-ojf(full JSON)、-pp(输出进度)等;若开启tokenLevelTimestamps还会追加--dtw {modelDtw},其中modelToDtw()负责把large-v3等模型名映射成 DTW 所需的large.v3这类点分命名(见 transcribe.ts); - 转写采用"先写临时文件再读取"的方式:输出写到
process.cwd()/tmp目录下的 JSON,成功后读取并unlinkSync删除(见 transcribe.ts); - 进度解析来自 stdout/stderr 中的
progress =字样(数值归一化为 0~1),并针对 macOS Metal 初始化结束行(ggml_metal_free: deallocating)做特殊处理以避免进程卡死(见 transcribe.ts)。
返回的 TranscriptionJson 结构(transcribe.ts)包含 systeminfo、model、params、result 与核心的 transcription 数组;开启 tokenLevelTimestamps 时每个 transcription item 会携带 tokens(词级 token,含 t_dtw 时间戳、p 置信度与起止时间),这是后续逐词高亮的原始数据来源。类型上,transcribe 是泛型函数:tokenLevelTimestamps: true 会让返回类型推断为 TranscriptionJson<true>,从而在下一步提供给 toCaptions() 时获得完整类型检查。
后处理:toCaptions() 归一化
Whisper.cpp 原始输出的 JSON 结构并不直接等于 Remotion 的 Caption 结构,因此需要后处理。官方推荐的是 toCaptions():
const { captions } = toCaptions({ whisperCppOutput });
从实现(to-captions.ts)看,它会遍历 transcription,跳过空文本 item,并把每个 item 映射成:
{
text: string; // 首个 item 会 trimStart() 去除前导空格
startMs: number; // item.offsets.from(毫秒)
endMs: number; // item.offsets.to(毫秒)
timestampMs: number | null;// 首个 token 的 t_dtw * 10;t_dtw 为 -1 时取 null
confidence: number | null; // 首个 token 的 p(模型置信度)
}
输出即 @remotion/captions 中定义的 Caption 类型——即 text、startMs、endMs、timestampMs、confidence 五字段(可含可选的 pageBreakAfter),这也是整个 captions 生态的统一中间格式。
兼容性说明:早期版本暴露的
convertToCaptions()(按毫秒阈值把 token 合并为"句子",见 convert-to-captions.ts)自 Remotion v4.0.216 起被标记为 deprecated(index.ts),新代码一律使用toCaptions()。
从 JSON 到画面:下一步衔接
本步骤产出的 captions123.json 应放入 public/(或任意可被 fetch 的位置)。Remotion 组件内通过 useDelayRender() 挂起渲染、fetch(staticFile("captions123.json")) 加载并 continueRender() 释放渲染锁,再交由 @remotion/captions 的 createTikTokStyleCaptions() 分组、用 <Sequence> 逐页渲染并配合 token 级 fromMs/toMs 实现逐词高亮。
完整展示链路见 display-captions.md;若你手上已是现成的 .srt 字幕,则无需本地转写,可直接用 parseSrt() 导入(见 import-srt-captions.md)。转写、显示、导入三份指南共同构成 remotion-captions 技能的完整知识面,SKILL 索引见 SKILL.md。
常见问题速查
- 报错
Whisper does not exist at ...:whisperPath目录不存在或未执行installWhisperCpp();请先安装再转写。 - 报错
Model ... does not exist:模型未下载,或modelFolder/whisperPath与downloadWhisperModel({folder})的目录不一致。 - 报错
must be 16 kHz/Invalid inputFile type:输入不是 16kHz WAV,用ffmpeg -i in.mp4 -ar 16000 out.wav -y先转换。 - 重复运行重复下载:不会。安装与模型下载均有幂等检查(按目标可执行文件是否存在 / 模型文件字节数是否与预期一致判定)。
- 中文等多语言音频:使用不带
.en的模型(如medium、large-v3-turbo),并可通过language参数指定语种。
需要澄清的是,Whisper.cpp 的安装需要目标环境具备 git、make(Linux/macOS)或 Windows PowerShell;大型模型下载较耗时且占用数 GB 磁盘,请在资源充足的环境中执行本流程。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00