DocDoeAI / src /video-engine /engine /VideoSceneRenderer.tsx
asnannp's picture
deploy: sync backend to Space root (learn-lesson HF cache fix)
65d68f5
Raw History Blame Contribute Delete
3.61 kB
/**
* 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>
);
}