diff --git a/remotion-composer/src/TalkingHead.tsx b/remotion-composer/src/TalkingHead.tsx index 05156e2a..c6077e6d 100644 --- a/remotion-composer/src/TalkingHead.tsx +++ b/remotion-composer/src/TalkingHead.tsx @@ -302,6 +302,11 @@ export interface TalkingHeadProps { wordsPerPage?: number; fontSize?: number; highlightColor?: string; + captionColor?: string; + captionBackgroundColor?: string; + captionFontFamily?: string; + // Pass "" for CJK captions (no inter-word spacing); defaults to " ". + captionWordSeparator?: string; } export const TalkingHead: React.FC = ({ @@ -311,6 +316,10 @@ export const TalkingHead: React.FC = ({ wordsPerPage = 4, fontSize = 52, highlightColor = "#22D3EE", + captionColor = "#FFFFFF", + captionBackgroundColor = "rgba(0, 0, 0, 0.65)", + captionFontFamily, + captionWordSeparator, }) => { const { fps } = useVideoConfig(); @@ -345,8 +354,10 @@ export const TalkingHead: React.FC = ({ wordsPerPage={wordsPerPage} fontSize={fontSize} highlightColor={highlightColor} - backgroundColor="rgba(0, 0, 0, 0.65)" - color="#FFFFFF" + backgroundColor={captionBackgroundColor} + color={captionColor} + {...(captionFontFamily ? { fontFamily: captionFontFamily } : {})} + {...(captionWordSeparator !== undefined ? { wordSeparator: captionWordSeparator } : {})} /> ); diff --git a/remotion-composer/src/components/CaptionOverlay.tsx b/remotion-composer/src/components/CaptionOverlay.tsx index 1f9cf207..0142d682 100644 --- a/remotion-composer/src/components/CaptionOverlay.tsx +++ b/remotion-composer/src/components/CaptionOverlay.tsx @@ -12,6 +12,9 @@ export interface WordCaption { word: string; startMs: number; endMs: number; + // Force a page break after this word (e.g. sentence or scene boundaries). + // Useful for CJK captions where pages should align with clause boundaries. + pageBreakAfter?: boolean; } type CaptionOverlayProps = { @@ -23,6 +26,9 @@ type CaptionOverlayProps = { highlightColor?: string; backgroundColor?: string; fontFamily?: string; + // Separator rendered between words. Space-delimited languages want the + // default " "; CJK languages (no inter-word spacing) should pass "". + wordSeparator?: string; }; interface CaptionPage { @@ -33,15 +39,21 @@ interface CaptionPage { function buildPages(words: WordCaption[], wordsPerPage: number): CaptionPage[] { const pages: CaptionPage[] = []; - for (let i = 0; i < words.length; i += wordsPerPage) { - const pageWords = words.slice(i, i + wordsPerPage); - if (pageWords.length === 0) continue; + let pageWords: WordCaption[] = []; + const flush = () => { + if (pageWords.length === 0) return; pages.push({ words: pageWords, startMs: pageWords[0].startMs, endMs: pageWords[pageWords.length - 1].endMs, }); + pageWords = []; + }; + for (const w of words) { + pageWords.push(w); + if (pageWords.length >= wordsPerPage || w.pageBreakAfter) flush(); } + flush(); return pages; } @@ -52,7 +64,8 @@ const PageRenderer: React.FC<{ highlightColor: string; backgroundColor: string; fontFamily: string; -}> = ({ page, fontSize, color, highlightColor, backgroundColor, fontFamily }) => { + wordSeparator: string; +}> = ({ page, fontSize, color, highlightColor, backgroundColor, fontFamily, wordSeparator }) => { const frame = useCurrentFrame(); const { fps } = useVideoConfig(); @@ -100,6 +113,11 @@ const PageRenderer: React.FC<{ - {w.word}{i < page.words.length - 1 ? " " : ""} + {w.word}{i < page.words.length - 1 ? wordSeparator : ""} ); })} @@ -125,6 +143,7 @@ export const CaptionOverlay: React.FC = ({ highlightColor = "#22D3EE", backgroundColor = "rgba(15, 23, 42, 0.75)", fontFamily = "Space Grotesk, Inter, system-ui, sans-serif", + wordSeparator = " ", }) => { const { fps } = useVideoConfig(); const pages = buildPages(words, wordsPerPage); @@ -148,6 +167,7 @@ export const CaptionOverlay: React.FC = ({ highlightColor={highlightColor} backgroundColor={backgroundColor} fontFamily={fontFamily} + wordSeparator={wordSeparator} /> );