DocDoeAI / src /video-engine /VideoEngineComposition.tsx
asnannp's picture
deploy: sync backend to Space root (learn-lesson HF cache fix)
65d68f5
Raw History Blame Contribute Delete
3.6 kB
/**
* DocDoe Cinematic Video Engine β€” Remotion Composition
*
* Sequences all scenes through VideoSceneRenderer using Remotion's Series.
* This component is passed to @remotion/player's `component` prop β€” it does
* NOT need to be registered in Root.tsx for browser preview use.
*
* For server-side rendering (remotion render CLI), register in Root.tsx.
*
* NO framer-motion. All animation is Remotion-native.
*/
import { AbsoluteFill, Series } from "remotion";
import type { CSSProperties } from "react";
import type { VideoPlan } from "./types";
import { normalizePlan } from "./engine/sceneBuilder";
import { buildVideoTimeline } from "./engine/timeline";
import { VideoSceneRenderer } from "./engine/VideoSceneRenderer";
// ── Props ─────────────────────────────────────────────────────────────────
export type VideoEngineProps = {
plan: VideoPlan;
};
// ── Composition ───────────────────────────────────────────────────────────
/**
* VideoEngineComposition renders a full VideoPlan as a Remotion composition.
*
* @example β€” in @remotion/player:
* <Player
* component={VideoEngineComposition}
* inputProps={{ plan }}
* durationInFrames={getTotalDurationInFrames(plan.scenes)}
* fps={VIDEO_ENGINE_FPS}
* compositionWidth={1920}
* compositionHeight={1080}
* controls
* />
*/
export function VideoEngineComposition({ plan }: VideoEngineProps) {
// Always normalize β€” handles legacy shapes and fills safe defaults
const safePlan = normalizePlan(plan);
const timeline = buildVideoTimeline(safePlan.scenes);
return (
<AbsoluteFill
style={{
background: "#000000",
overflow: "hidden",
// Base font stack β€” Remotion loads this from the browser/render environment
fontFamily:
"'Outfit', 'Inter', ui-sans-serif, system-ui, -apple-system, sans-serif",
}}
>
<Series>
{timeline.map((entry) => (
<Series.Sequence
key={`scene-${entry.scene.scene_id}-${entry.sceneIndex}`}
durationInFrames={entry.durationInFrames}
// layout="none" preserves the AbsoluteFill inside VideoSceneRenderer
layout="none"
>
<VideoSceneRenderer
plan={safePlan}
scene={entry.scene}
sceneIndex={entry.sceneIndex}
totalScenes={timeline.length}
durationInFrames={entry.durationInFrames}
/>
</Series.Sequence>
))}
</Series>
{/* Preview watermark β€” shown when plan.show_preview_watermark is true */}
{safePlan.show_preview_watermark ? (
<div style={watermarkStyle}>Preview</div>
) : null}
</AbsoluteFill>
);
}
// ── Watermark style ───────────────────────────────────────────────────────
const watermarkStyle: CSSProperties = {
position: "absolute",
top: 46,
right: 58,
zIndex: 200,
background: "rgba(255, 255, 255, 0.10)",
border: "1px solid rgba(255, 255, 255, 0.18)",
borderRadius: 10,
padding: "8px 18px",
color: "#ffffff",
fontSize: 22,
fontWeight: 900,
letterSpacing: "0.14em",
textTransform: "uppercase",
pointerEvents: "none",
backdropFilter: "blur(4px)",
};