Skip to content
Esc
navigateopen⌘Jpreview
On this page

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:

  1. a static metadata export that Odori can read without rendering frames
  2. 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.

Was this page helpful?