Programmatic definitions
A video is typed source: static metadata, JSX scenes, and inputs validated against a schema. Diff it, review it, generate it.
Define videos in code, discover them from the filesystem, preview them in the built-in Studio, and compose them from components you own.
A video.tsx file is all you need to make a video. Layouts, schemas, components, and configuration are optional building blocks.
A video.tsx file is a complete composition. Arrange typed scenes in JSX, then preview it immediately.
import { Scene, Video, defineVideoMetadata } from "odori";
import { CodeProof, EndCard, TitleReveal } from "@/components/video";
export const metadata = defineVideoMetadata({
id: "launch",
duration: "18s",
});
export default function LaunchVideo() {
return (
<Video>
<Scene duration="4s">
<TitleReveal title="Ship the story." />
</Scene>
<Scene duration="10s">
<CodeProof code={source} />
</Scene>
<Scene duration="4s">
<EndCard title="Available today." />
</Scene>
</Video>
);
}import type { ReactNode } from "react";
import { VideoTheme } from "odori";
export default function VideoLayout({ children }: { children: ReactNode }) {
return <VideoTheme mode="dark">{children}</VideoTheme>;
}import { z } from "zod";
export const launchInput = z.object({
headline: z.string(),
productUrl: z.string().url(),
accent: z.string().default("#0070f3"),
});
export type LaunchInput = z.infer<typeof launchInput>;import { animate, useFrame } from "odori";
export function TitleReveal({ title }: { title: string }) {
const frame = useFrame();
const entrance = animate(frame, { from: 0, to: 1, duration: 24 });
return <h1 style={{ opacity: entrance }}>{title}</h1>;
}import { defineConfig } from "odori";
export default defineConfig({
videos: "./videos",
output: "./dist",
defaults: { fps: 30, width: 1920, height: 1080 },
});A video is typed source: static metadata, JSX scenes, and inputs validated against a schema. Diff it, review it, generate it.
A video exists because videos/launch/video.tsx exists. Layouts cascade down the tree, so one file reformats a whole subtree.
odori dev serves a workspace that plays every composition and component fixture at any frame. Preview needs no encode.
odori add copies real source into your repository, with a declared timing contract odori test enforces.