File size: 3,612 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
104
105
106
107
108
/**
 * DocDoe Cinematic Video Engine β€” VideoSceneRenderer
 *
 * Dispatches to the correct scene component based on `scene.type`.
 * Wraps with SceneCamera and cross-dissolve opacity.
 *
 * This is the single entry point for rendering one scene inside a
 * Remotion Series.Sequence. Frame is already local (0-based) here.
 *
 * NO framer-motion. All motion via Remotion interpolate() / spring().
 */

import { AbsoluteFill, useCurrentFrame } from "remotion";
import type { VideoPlan, VideoScene } from "../types";
import { SceneCamera } from "../motion/camera";
import { sceneDissolve } from "../motion/animations";
import { ExplainScene } from "../scenes/ExplainScene";
import { HookScene } from "../scenes/HookScene";
import { KeywordScene } from "../scenes/KeywordScene";
import { RecapScene } from "../scenes/RecapScene";

// ── Shared prop type for all scene components ─────────────────────────────

export type SceneComponentProps = {
  plan: VideoPlan;
  scene: VideoScene;
  sceneIndex: number;
  totalScenes: number;
  durationInFrames: number;
};

// ── Scene dispatcher ──────────────────────────────────────────────────────

/**
 * Renders the correct scene component for a given scene.type.
 * All unimplemented types fall through to ExplainScene until their
 * dedicated component is built.
 *
 * TODO: Replace each TODO comment with its dedicated scene component.
 */
function renderSceneComponent(props: SceneComponentProps) {
  switch (props.scene.type) {
    case "explain":
      return <ExplainScene {...props} />;

    case "hook":
      return <HookScene {...props} />;

    case "keyword":
      return <KeywordScene {...props} />;

    case "pyq":
      // TODO: PYQScene β€” exam paper style, marks badge, answer reveal
      return <ExplainScene {...props} />;

    case "quiz":
      // TODO: QuizScene β€” question + multiple choice or fill-in-blank
      return <ExplainScene {...props} />;

    case "recap":
      return <RecapScene {...props} />;

    case "mentor":
      // TODO: MentorScene β€” warm close framing, personal coaching tone
      return <ExplainScene {...props} />;

    default:
      return <ExplainScene {...props} />;
  }
}

// ── Main renderer ─────────────────────────────────────────────────────────

/**
 * VideoSceneRenderer β€” renders one scene with camera motion and transitions.
 *
 * Mount inside a Remotion Series.Sequence. The frame passed to child
 * components is automatically local (reset to 0 at sequence start).
 *
 * @example
 * <Series.Sequence durationInFrames={durationInFrames}>
 *   <VideoSceneRenderer
 *     plan={plan}
 *     scene={scene}
 *     sceneIndex={index}
 *     totalScenes={scenes.length}
 *     durationInFrames={durationInFrames}
 *   />
 * </Series.Sequence>
 */
export function VideoSceneRenderer(props: SceneComponentProps) {
  const frame = useCurrentFrame();

  // First scene fades in without a leading black frame.
  // All subsequent scenes cross-dissolve for a smooth cut.
  const opacity = props.sceneIndex === 0
    ? sceneDissolve(frame, props.durationInFrames, 4, 6)
    : sceneDissolve(frame, props.durationInFrames, 8, 6);

  return (
    <AbsoluteFill style={{ opacity }}>
      <SceneCamera sceneType={props.scene.type}>
        {renderSceneComponent(props)}
      </SceneCamera>
    </AbsoluteFill>
  );
}