/** * MainComp.tsx - Main Remotion composition with Ken Burns effect * * Supports three scene types: * 1. kinetic: Text animation (Sequence-based) * 2. screenshot: Image with Ken Burns pan/zoom effect * 3. caption: Text overlay on current scene * * VideoConfig: 1920x1080, 60 fps */ import React, { useMemo } from 'react'; import { Composition, Sequence, interpolate, useCurrentFrame, useVideoConfig, AbsoluteFill, Img, staticFile, } from 'remotion'; import { Scene, KineticScene, ScreenshotScene, CaptionScene } from '../types/Scene'; // VideoConfig constants export const VIDEO_WIDTH = 1920; export const VIDEO_HEIGHT = 1080; export const VIDEO_FPS = 60; /** * Converts duration in seconds to frame count at 60 fps */ const secondsToFrames = (seconds: number, fps: number = VIDEO_FPS): number => { return Math.ceil(seconds * fps); }; /** * Ken Burns Effect Component * Applies subtle zoom and pan animation to an image * - Starts at scale 1.0, ends at scale 1.05 (5% zoom) * - Vertical pan: 0 to full height (creates movement illusion) */ interface KenBurnsProps { imagePath: string; alt?: string; duration: number; // in frames } const KenBurnsImage: React.FC = ({ imagePath, alt = 'Scene', duration }) => { const frame = useCurrentFrame(); // Scale interpolation: 1.0 → 1.05 const scale = interpolate(frame, [0, duration], [1.0, 1.05]); // Vertical pan: 0 → full height (in pixels) const translateY = interpolate(frame, [0, duration], [0, VIDEO_HEIGHT * 0.08]); return ( {alt} ); }; /** * Kinetic Scene Component - Text animation */ interface KineticSceneComponentProps { scene: KineticScene; duration: number; // in frames } const KineticSceneComponent: React.FC = ({ scene, duration, }) => { const frame = useCurrentFrame(); // Fade in and scale effect const opacity = interpolate(frame, [0, duration * 0.1], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', }); const scale = interpolate(frame, [0, duration * 0.15], [0.8, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', }); return (
{scene.text}
); }; /** * Caption Overlay Component */ interface CaptionComponentProps { scene: CaptionScene; duration: number; // in frames } const CaptionComponent: React.FC = ({ scene, duration }) => { const frame = useCurrentFrame(); // Fade in/out effect const opacity = interpolate(frame, [0, duration * 0.2, duration * 0.8, duration], [0, 1, 1, 0], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', }); const positionMap = { top: '5%', middle: '50%', bottom: '85%', }; const topPosition = positionMap[scene.position || 'bottom']; return (
{scene.text}
); }; /** * Main Composition - orchestrates all scenes */ interface MainCompProps { scenes: Scene[]; } const MainComp: React.FC = ({ scenes }) => { const { durationInFrames } = useVideoConfig(); // Calculate cumulative frame positions const sequenceLayout = useMemo(() => { let currentFrame = 0; return scenes.map((scene) => { const frameDuration = secondsToFrames(scene.duration); const fromFrame = currentFrame; currentFrame += frameDuration; return { scene, fromFrame, frameDuration }; }); }, [scenes]); return ( {sequenceLayout.map(({ scene, fromFrame, frameDuration }) => { return ( {scene.type === 'kinetic' && ( )} {scene.type === 'screenshot' && ( )} {scene.type === 'caption' && ( )} ); })} ); }; /** * Example usage with sample scenes */ const exampleScenes: Scene[] = [ { id: 'intro', type: 'kinetic', text: 'Welcome to Video Magic', duration: 3, fontSize: 72, color: '#ffffff', backgroundColor: '#1e1e2e', }, { id: 'screenshot-1', type: 'screenshot', imagePath: staticFile('sample.jpg'), duration: 5, alt: 'Sample Screenshot', }, { id: 'caption-1', type: 'caption', text: 'Amazing Ken Burns Effect Applied', duration: 5, fontSize: 48, color: '#ffffff', position: 'bottom', }, { id: 'outro', type: 'kinetic', text: 'Thank You!', duration: 2, fontSize: 64, color: '#00ff00', backgroundColor: '#000000', }, ]; // Calculate total duration from scenes const getTotalDurationFrames = (scenes: Scene[]): number => { return scenes.reduce((total, scene) => total + secondsToFrames(scene.duration), 0); }; export default MainComp; /** * Composition registration configuration */ export const mainCompositionConfig = { id: 'MainComp', component: () => , durationInFrames: getTotalDurationFrames(exampleScenes), fps: VIDEO_FPS, width: VIDEO_WIDTH, height: VIDEO_HEIGHT, }; // Export helper hook for custom compositions export const useMainCompConfig = (scenes: Scene[]) => ({ id: 'MainComp', component: () => , durationInFrames: getTotalDurationFrames(scenes), fps: VIDEO_FPS, width: VIDEO_WIDTH, height: VIDEO_HEIGHT, });