显示字幕
🌐 Displaying captions
本指南解释了如何在 Remotion 中显示字幕,假设你已经拥有 Caption 格式的字幕——有关如何生成字幕,请参见 转录音频 。
🌐 This guide explains how to display captions in Remotion, assuming you already have captions in the Caption format - see Transcribing audio for how to generate them.
正在获取字幕
🌐 Fetching captions
首先,获取你的字幕 JSON 文件。使用 useDelayRender() 来保持渲染,直到字幕加载完成:
🌐 First, fetch your captions JSON file. Use useDelayRender() to hold the render until the captions are loaded:
Fetching captionsimport {useState ,useEffect ,useCallback } from 'react'; import {AbsoluteFill ,staticFile ,useDelayRender } from 'remotion'; import type {Caption } from '@remotion/captions'; export constMyComponent :React .FC = () => { const [captions ,setCaptions ] =useState <Caption [] | null>(null); const {delayRender ,continueRender ,cancelRender } =useDelayRender (); const [handle ] =useState (() =>delayRender ()); constfetchCaptions =useCallback (async () => { try { constresponse = awaitfetch (staticFile ('captions.json')); constdata = awaitresponse .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 >; };
创建页面
🌐 Creating pages
使用 createTikTokStyleCaptions() 将标题分组到页面中。combineTokensWithinMilliseconds 选项控制一次显示多少个单词:
🌐 Use createTikTokStyleCaptions() to group captions into pages. The combineTokensWithinMilliseconds option controls how many words appear at once:
Creating caption pagesconst {pages } =useMemo (() => { returncreateTikTokStyleCaptions ({captions ,combineTokensWithinMilliseconds :SWITCH_CAPTIONS_EVERY_MS , }); }, [captions ]);
使用序列进行渲染
🌐 Rendering with Sequences
遍历页面并在 <Sequence> 中渲染每一个。根据页面时间计算起始帧和持续时间:
🌐 Map over the pages and render each one in a <Sequence>. Calculate the start frame and duration from the page timing:
Rendering caption pagesconstCaptionedContent :React .FC = () => { const {fps } =useVideoConfig (); return ( <AbsoluteFill > {pages .map ((page ,index ) => { constnextPage =pages [index + 1] ?? null; conststartFrame = (page .startMs / 1000) *fps ; constendFrame =Math .min (nextPage ? (nextPage .startMs / 1000) *fps :Infinity ,startFrame + (SWITCH_CAPTIONS_EVERY_MS / 1000) *fps ); constdurationInFrames =endFrame -startFrame ; if (durationInFrames <= 0) { return null; } return ( <Sequence key ={index }from ={startFrame }durationInFrames ={durationInFrames }> <CaptionPage page ={page } /> </Sequence > ); })} </AbsoluteFill > ); };
呈现标题页
🌐 Rendering a caption page
标题页包含 tokens,你可以使用它来高亮当前正在说的单词。以下是一个在单词被说出时高亮的示例:
🌐 A caption page contains tokens which you can use to highlight the currently spoken word. Here's an example that highlights words as they are spoken:
Rendering a caption page with word highlightingimport {AbsoluteFill ,useCurrentFrame ,useVideoConfig } from 'remotion'; import type {TikTokPage } from '@remotion/captions'; constHIGHLIGHT_COLOR = '#39E508'; constCaptionPage :React .FC <{page :TikTokPage }> = ({page }) => { constframe =useCurrentFrame (); const {fps } =useVideoConfig (); // Current time relative to the start of the sequence constcurrentTimeMs = (frame /fps ) * 1000; // Convert to absolute time by adding the page start constabsoluteTimeMs =page .startMs +currentTimeMs ; return ( <AbsoluteFill style ={{justifyContent : 'center',alignItems : 'center', }} > <div style ={{fontSize : 80,fontWeight : 'bold',textAlign : 'center', // Preserve whitespace in captionswhiteSpace : 'pre', }} > {page .tokens .map ((token ) => { constisActive =token .fromMs <=absoluteTimeMs &&token .toMs >absoluteTimeMs ; return ( <span key ={token .fromMs }style ={{color :isActive ?HIGHLIGHT_COLOR : 'white', }} > {token .text } </span > ); })} </div > </AbsoluteFill > ); };
完整示例
🌐 Full example
显示完整示例
Full captioned video exampleimport {useState ,useEffect ,useCallback ,useMemo } from 'react'; import {AbsoluteFill ,Sequence ,staticFile ,useCurrentFrame ,useDelayRender ,useVideoConfig } from 'remotion'; import {createTikTokStyleCaptions } from '@remotion/captions'; import type {Caption ,TikTokPage } from '@remotion/captions'; constSWITCH_CAPTIONS_EVERY_MS = 1200; constHIGHLIGHT_COLOR = '#39E508'; constCaptionPage :React .FC <{page :TikTokPage }> = ({page }) => { constframe =useCurrentFrame (); const {fps } =useVideoConfig (); constcurrentTimeMs = (frame /fps ) * 1000; constabsoluteTimeMs =page .startMs +currentTimeMs ; return ( <AbsoluteFill style ={{justifyContent : 'center',alignItems : 'center', }} > <div style ={{fontSize : 80,fontWeight : 'bold',textAlign : 'center',whiteSpace : 'pre', }} > {page .tokens .map ((token ) => { constisActive =token .fromMs <=absoluteTimeMs &&token .toMs >absoluteTimeMs ; return ( <span key ={token .fromMs }style ={{color :isActive ?HIGHLIGHT_COLOR : 'white', }} > {token .text } </span > ); })} </div > </AbsoluteFill > ); }; export constCaptionedVideo :React .FC = () => { const [captions ,setCaptions ] =useState <Caption [] | null>(null); const {delayRender ,continueRender ,cancelRender } =useDelayRender (); const [handle ] =useState (() =>delayRender ()); const {fps } =useVideoConfig (); constfetchCaptions =useCallback (async () => { try { constresponse = awaitfetch (staticFile ('captions.json')); constdata = awaitresponse .json ();setCaptions (data );continueRender (handle ); } catch (e ) {cancelRender (e ); } }, [continueRender ,cancelRender ,handle ]);useEffect (() => {fetchCaptions (); }, [fetchCaptions ]); const {pages } =useMemo (() => { returncreateTikTokStyleCaptions ({captions :captions ?? [],combineTokensWithinMilliseconds :SWITCH_CAPTIONS_EVERY_MS , }); }, [captions ]); return ( <AbsoluteFill style ={{backgroundColor : 'black'}}> {pages .map ((page ,index ) => { constnextPage =pages [index + 1] ?? null; conststartFrame = (page .startMs / 1000) *fps ; constendFrame =Math .min (nextPage ? (nextPage .startMs / 1000) *fps :Infinity ,startFrame + (SWITCH_CAPTIONS_EVERY_MS / 1000) *fps ); constdurationInFrames =endFrame -startFrame ; if (durationInFrames <= 0) { return null; } return ( <Sequence key ={index }from ={startFrame }durationInFrames ={durationInFrames }> <CaptionPage page ={page } /> </Sequence > ); })} </AbsoluteFill > ); };
下一步
🌐 Next steps
你可以自定义字幕的外观:
🌐 You can customize the appearance of your captions:
<div
style={{
WebkitTextStroke: '4px black',
paintOrder: 'stroke',
}}
>
{text}
</div>另请参阅
🌐 See also
- 转录音频 - 从音频生成字幕
Caption- 字幕数据结构createTikTokStyleCaptions()- API 参考<Sequence>- 序列组件引用