---
title: The video file
description: 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

```tsx title="videos/launch/video.tsx" lineNumbers
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.

```tsx
<Scene id="proof" duration="6s">
  <CodeProof code={source} />
</Scene>
```

For continuous motion, use Odori frame primitives inside the video:

```tsx
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.
