Remotion 如何为视频添加 AI 自动生成的字幕?
Remotion 如何为视频添加 AI 自动生成的字幕【免费下载链接】remotion Make videos programmatically with React项目地址: https://gitcode.com/GitHub_Trending/re/remotion如果你的 Remotion 项目中有一段带人声的视频想通过 AI 自动识别语音、生成带时间轴的字幕并渲染到画面上Remotion 官方提供了完整的操作路径先用内置的转录方案把音频转成标准的Caption数据JSON再在 React 组件里按帧渲染字幕。本文以本地免费的 Whisper.cpp 方案为主路径给出从安装、转录、格式转换到上屏渲染的完整步骤并附云 API 的可选分支。选择转录方案Remotion 内置了五种把音频转成字幕的方式官方文档给出了对比remotion/install-whisper-cppremotion/whisper-webgpuremotion/whisper-webremotion/openai-whisperremotion/elevenlabsEnvironmentServer (Node.js)Client (Browser)Client (Browser)Cloud (API)Cloud (API)SpeedFast (depends on hardware)Fast (depends on GPU)Slow (WASM overhead)FastFastCostFreeFreeFreePaid (OpenAI API pricing)Paid (ElevenLabs API pricing)Offline support✅✅✅❌❌Convert functiontoCaptions()toCaptions()toCaptions()openaiWhisperApiToCaptions()elevenLabsTranscriptToCaptions()五种方案的输出都可以统一转换为Caption类型从而共用remotion/captions里的分页、序列化等 API。本文主路径选用remotion/install-whisper-cpp服务端本地运行、免费、离线可用如果不想在服务器上装任何东西可选用remotion/openai-whisper调用 OpenAI Whisper API但需要付费的 OpenAI API且不支持离线。安装依赖在 Remotion 项目根目录执行npx remotion add remotion/install-whisper-cpp remotion/captionsnpx remotion add会安装与你当前remotion版本匹配的包版本例如remotion4.0.100的项目会装上对应版本的remotion/install-whisper-cpp。remotion/captions提供createTikTokStyleCaptions()等字幕处理 API。版本要求方面remotion/install-whisper-cpp从 v4.0.115 可用transcribe()从 v4.0.131 可用toCaptions()和remotion/captions从 v4.0.216 可用如果要用large-v3-turbo模型还需要 Remotion v4.0.229 及以上。准备 16KHz 音频transcribe()对输入文件有硬性要求必须是16-bit、16KHz 的 WAVE 文件。如果手头是 mp4 或其他格式先用 ffmpeg 转码官方文档给出的转换示例ffmpeg -i /path/to/audio.mp4 -ar 16000 /path/to/audio.wav -y命令中的/path/to/audio.mp4和/path/to/audio.wav替换为你自己的输入和输出路径。-ar 16000把采样率设为 16KHz-y表示覆盖已存在的输出文件。关于在服务端如何重采样音频文档指向了 Resample audio to 16kHz 一节。安装 Whisper.cpp 与模型remotion/install-whisper-cpp提供了跨平台的函数可以直接把 Whisper.cpp 可执行文件和模型下载到本地文件夹无需手动编译。官方示例采用 Whisper.cpp1.5.5文档说明这是目前验证可用且支持 token 级时间戳的最新版本和medium.en模型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, }); // Convert the audio to a 16KHz wav file first if needed: // 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/audio.wav, tokenLevelTimestamps: true, }); // Optional: Apply our recommended postprocessing const {captions} toCaptions({ whisperCppOutput, }); fs.writeFileSync(captions.json, JSON.stringify(captions, null, 2));执行这个脚本例如bun run install-whisper.cpp或node运行 ESM 脚本会做三件事installWhisperCpp()把 Whisper.cpp1.5.5安装到whisper.cpp/文件夹downloadWhisperModel()把medium.en模型下载到同一文件夹文件名为ggml-medium.en.bin。如果文件已存在函数不做任何事并返回alreadyExisted: truetranscribe()转录音频toCaptions()做官方推荐的后期处理最后把结果写入captions.json。理解 transcribe() 的关键参数transcribe()的完整选项参见 transcribe() 文档inputPath待转录的 16-bit、16KHz WAVE 文件路径。whisperPathwhisper.cpp文件夹路径即installWhisperCpp()的to参数。model默认为base.en。可选tiny、tiny.en、base、base.en、small、small.en、medium、medium.en、large-v1、large-v2、large-v3、large-v3-turbo。用哪个模型前先确认它已存在于whisper.cpp/models文件夹downloadWhisperModel()可以确保模型在本地可用。tokenLevelTimestamps传true会给 Whisper.cpp 加--dtw标志生成更准确的时间戳返回在t_dtw字段。文档推荐开启以获得真正精确的时序但这要求 Whisper.cpp 为 1.5.5 或更新版本旧版本请设为false。language通过-l标志指定音频语种取值包括English、Chinese、auto等约 100 种语言或代码。translateToEnglish设为true可把外语翻译成英文字幕。注意此时不要用*.en后缀的模型它们无法做翻译文档建议至少用medium模型以获得可接受的翻译效果。onProgress进度回调参数是0到1之间的数字可以打印Transcription progress: ${progress * 100}%这类日志。signal传入AbortController的信号可取消转录。结果验证确认 captions.jsontoCaptions()会把transcribe()的原始输出转换成Caption[]数组。文档中给出的示例输出文档示例实际文本和时间取决于你的音频[ { text: William, startMs: 40, endMs: 420, timestampMs: 240, confidence: 0.813602 }, { text: just, startMs: 420, endMs: 650, timestampMs: 480, confidence: 0.990905 }, { text: hit, startMs: 650, endMs: 810, timestampMs: 700, confidence: 0.981798 } ]Caption类型的字段text字幕文本字符串。startMs/endMs起止时间毫秒。timestampMs单个时间戳毫秒或null使用remotion/install-whisper-cpp时它对应t_dtw值。confidence0 到 1 之间的置信度无法提供时为null。pageBreakAfter可选v4.0.517 起为true时强制该条字幕后换页。验证要点打开生成的captions.json检查text与startMs/endMs是否覆盖了你音频中实际有语音的时间段。注意text字段对空白敏感空格理想情况下每个词前的空格必须保留——createTikTokStyleCaptions()依赖空格作为分词分隔符缺失会导致整段文本合并成一行。在 Remotion 组件中渲染字幕把captions.json放进项目的public/目录后用useDelayRender()挂起渲染直到字幕加载完成参见 Displaying captionsimport {useState, useEffect, useCallback} from react; import {AbsoluteFill, staticFile, useDelayRender} from remotion; import type {Caption} from remotion/captions; export const MyComponent: React.FC () { const [captions, setCaptions] useStateCaption[] | 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]); if (!captions) { return null; } return AbsoluteFill{/* Render captions here */}/AbsoluteFill; };把字幕分成“页”用createTikTokStyleCaptions()把字幕按时间分组为页TikTok 风格逐词显示的基础。combineTokensWithinMilliseconds控制一次显示多少词值越大一页词越多越小越接近逐词动画import {useMemo} from react; import {createTikTokStyleCaptions} from remotion/captions; import type {Caption} from remotion/captions; // How often captions should switch (in milliseconds) // Higher values more words per page // Lower values fewer words (more word-by-word) const SWITCH_CAPTIONS_EVERY_MS 1200; const captions: Caption[] []; const {pages} useMemo(() { return createTikTokStyleCaptions({ captions, combineTokensWithinMilliseconds: SWITCH_CAPTIONS_EVERY_MS, }); }, [captions]);返回的每个TikTokPage包含text、startMs、durationMs和tokens每个 token 有text、fromMs、toMs可用于逐词高亮。如果想让停顿处自动换页可以传breakOnSilenceAfterMillisecondsv4.0.514 起两条字幕之间间隔达到该毫秒数就提前换页它只会让页变短、不会超过combineTokensWithinMilliseconds的上限。用 Sequence 按时间轴渲染对每一页计算起始帧和时长放进Sequence中import {Sequence, useVideoConfig, AbsoluteFill} from remotion; import type {TikTokPage} from remotion/captions; const pages: TikTokPage[] []; const CaptionPage: React.FC{page: TikTokPage} ({page}) div{page.text}/div; const CaptionedContent: React.FC () { const {fps} useVideoConfig(); return ( AbsoluteFill {pages.map((page, index) { const nextPage pages[index 1] ?? null; const startFrame (page.startMs / 1000) * fps; const endFrame Math.min(nextPage ? (nextPage.startMs / 1000) * fps : Infinity, startFrame (SWITCH_CAPTIONS_EVERY_MS / 1000) * fps); const durationInFrames endFrame - startFrame; if (durationInFrames 0) { return null; } return ( Sequence key{index} from{startFrame} durationInFrames{durationInFrames} CaptionPage page{page} / /Sequence ); })} /AbsoluteFill ); };逐词高亮的页组件每个page.tokens里的 token 带fromMs/toMs可以判断当前词是否正在被念出并改变颜色。字幕容器要加whiteSpace: pre以保留text中的空格import {AbsoluteFill, useCurrentFrame, useVideoConfig} from remotion; import type {TikTokPage} from remotion/captions; const HIGHLIGHT_COLOR #39E508; const CaptionPage: React.FC{page: TikTokPage} ({page}) { const frame useCurrentFrame(); const {fps} useVideoConfig(); // Current time relative to the start of the sequence const currentTimeMs (frame / fps) * 1000; // Convert to absolute time by adding the page start const absoluteTimeMs page.startMs currentTimeMs; return ( AbsoluteFill style{{ justifyContent: center, alignItems: center, }} div style{{ fontSize: 80, fontWeight: bold, textAlign: center, // Preserve whitespace in captions whiteSpace: pre, }} {page.tokens.map((token, tokenIndex) { const isActive token.fromMs absoluteTimeMs token.toMs absoluteTimeMs; return ( span key{${token.fromMs}-${tokenIndex}} style{{ color: isActive ? HIGHLIGHT_COLOR : white, }} {token.text} /span ); })} /div /AbsoluteFill ); };在 Remotion Studio 里预览或在命令行渲染成片时字幕会随时间轴出现文档给出的完整示例含加载、分页和渲染三部分的完整组件见 Displaying captions。可选分支用 OpenAI Whisper API 转录如果不想在本机/服务器上跑 Whisperremotion/openai-whisperv4.0.217 起提供把 OpenAI Whisper API 的返回转换成语料Caption[]的函数openaiWhisperApiToCaptions()装法同样是npx remotion add remotion/openai-whisper该方案的转换结果与本地方案共用同一套remotion/captionsAPI所以上面的渲染代码不用改。代价是需要按 OpenAI API 计费且必须联网remotion/elevenlabs是另一个云端选项转换函数为elevenLabsTranscriptToCaptions()。限制与下一步本地方案的输入必须是 16-bit、16KHz WAVE 文件其他格式先用 ffmpeg 转码。tokenLevelTimestamps: true需要 Whisper.cpp 1.5.5旧版本请设为false。large-v3-turbo模型要求 2024 年 11 月之后构建的 Whisper.cpp 版本和 Remotion v4.0.229。字幕text字段的空格不能丢渲染时容器需要white-space: pre。美化方面文档建议用remotion/layout-utils的fitText()自动缩放文字宽度、给文字加描边WebkitTextStrokepaintOrder: stroke提高可读性以及为字幕进出场加动画。除了自己渲染也可以把Caption[]用serializeSrt()导出为.srt文件或用parseSrt()解析现有 SRT。参考文档Transcribing audio、remotion/captions API、remotion/install-whisper-cpp、createTikTokStyleCaptions()。【免费下载链接】remotion Make videos programmatically with React项目地址: https://gitcode.com/GitHub_Trending/re/remotion创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考