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)",
};