import React from "react"; import { useCurrentFrame, useVideoConfig, interpolate } from "remotion"; import type { WordTimestamp } from "@pipeline/shared"; interface SubtitleBarProps { wordTimestamps: WordTimestamp[]; style?: React.CSSProperties; } export const SubtitleBar: React.FC = ({ wordTimestamps, style, }) => { const frame = useCurrentFrame(); const { fps } = useVideoConfig(); const currentTime = frame / fps; // Find current word index let currentWordIndex = -1; for (let i = 0; i < wordTimestamps.length; i++) { if ( currentTime >= wordTimestamps[i].startSeconds && currentTime <= wordTimestamps[i].endSeconds ) { currentWordIndex = i; break; } } // Dynamic window size: fewer words when there's lots of text const totalWords = wordTimestamps.length; const windowSize = totalWords > 50 ? 8 : totalWords > 30 ? 12 : 16; const start = Math.max(0, currentWordIndex - Math.floor(windowSize / 2)); const end = Math.min(wordTimestamps.length, start + windowSize); const visibleWords = wordTimestamps.slice(start, end).map((w, i) => ({ text: w.word, globalIndex: start + i, })); const opacity = interpolate(frame, [0, 8], [0, 1], { extrapolateRight: "clamp", }); // Continuous font scaling based on total word count const baseFontSize = 42; const minScale = 0.5; const scaleAt50 = 0.65; const fontSize = totalWords <= 15 ? baseFontSize : totalWords <= 50 ? baseFontSize * (1 - (1 - scaleAt50) * ((totalWords - 15) / 35)) : baseFontSize * (minScale + (scaleAt50 - minScale) * Math.max(0, 1 - (totalWords - 50) / 100)); // Dynamic max height: smaller when text is dense const maxHeight = totalWords > 50 ? 80 : totalWords > 30 ? 100 : 120; return (
{visibleWords.map((w, i) => { const isCurrent = w.globalIndex === currentWordIndex; const isPast = currentTime > wordTimestamps[w.globalIndex].endSeconds; return ( {w.text} ); })}
); };