File size: 3,603 Bytes
65d68f5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | /**
* 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)",
};
|