The video file
Author timelines as normal JSX while exporting static metadata for discovery and production rendering.
Every discovered entry is named video.tsx. The module has two surfaces:
- a static
metadataexport that Odori can read without rendering frames - a default React component that composes the timeline naturally
import {Scene, Video, defineVideoMetadata} from "odori";
import {BrowserDemo} from "../components/browser-demo/browser-demo";
import {EndCard} from "../components/end-card/end-card";
import {TitleReveal} from "../components/title-reveal/title-reveal";
import {launchInput} from "./schema";
export const metadata = defineVideoMetadata({
title: "Product launch",
duration: "24s",
schema: launchInput,
defaultProps: {
headline: "Meet the new workflow.",
productUrl: "https://example.com",
},
});
export default function LaunchVideo({headline, productUrl}) {
return (
<Video>
<Scene id="opening" duration="4s">
<TitleReveal title={headline} />
</Scene>
<Scene id="demo" duration="16s">
<BrowserDemo url={productUrl} />
</Scene>
<Scene id="resolution" duration="4s">
<EndCard title="Available today." />
</Scene>
</Video>
);
}
Why .tsx?
The entry is a visual React module. Components, conditionals, loops, context, and local composition should remain ordinary JSX rather than being encoded into a separate scene configuration language.
Static metadata is deliberately separate because video discovery must know the ID, format, schema, defaults, and duration before mounting React.
Structured scenes and direct React
Use <Scene> for most work. It gives Odori inspectable boundaries, local frame
zero, duration checks, scene names in Studio, and better diagnostics.
<Scene id="proof" duration="6s">
<CodeProof code={source} />
</Scene>
For continuous motion, use Odori frame primitives inside the video:
export default function GenerativeVideo() {
const frame = useFrame();
return (
<Video>
<GenerativeCanvas progress={frame / 360} />
</Video>
);
}
When Odori cannot derive scene duration, metadata.duration is authoritative.
The framework validates that structured scene totals agree with it.
Inputs
All preview and render inputs must be serializable and schema-valid. A single contract powers Studio controls, embedded Player props, CLI input, API requests, and export jobs.