Data and assets
Use prepare.ts for network requests, database queries, and file reads. Return the data your video needs before playback starts:
import {definePrepare} from "odori";
export const prepare = definePrepare(async ({input, assets, cache, signal}) => {
const release = await cache.getOrSet(
`release:${input.tag}`,
() => fetchRelease(input.tag, {signal}),
);
const logo = await assets.resolve("brand:product-mark");
return {release, logo};
});The prepared result becomes a prop of the video entry:
export default function ChangelogVideo({prepared}) {
return (
<Video>
<Scene duration="6s">
<ReleaseTitle release={prepared.release} logo={prepared.logo} />
</Scene>
</Video>
);
}Provide fallback data for previews where preparation hasn't run:
export default function WorkflowVideo({prepared}: {prepared?: {commands: Step[]}}) {
return <Terminal steps={prepared?.commands ?? FALLBACK} />;
}Inputs
Define serializable inputs to validate CLI arguments and generate Studio controls:
import {defineInputSchema} from "odori";
export const launchInput = defineInputSchema({
headline: {type: "text", defaultValue: "Author the story.", maxLength: 64, multiline: true},
});defineInputSchema validates, fills defaults, and describes itself so Studio can generate controls. Any zod-compatible object with a parse() method is accepted instead.
Readiness and integrity
Fonts and images resolve before the first frame is captured, and audio is collected into a track the encoder mixes. Assets declared in odori.config.ts are addressable by reference through useAssets().
Every font, asset, and audio source in the manifest carries a real content hash. Local files hash their bytes; remote files are fetched once and cached by URL under .odori/cache/integrity.json. A source that cannot be read is recorded as unresolved rather than pretending to be verified.
font Geist Sans /fonts/Geist-Variable.woff2 sha256-o2n89WKOoqpOG54uxqWzYk42W9pYjh8PLxK1ZPco+7g=Cache keys
Prepared data is cached on disk under .odori/cache/prepare/, keyed by video source hash, validated input, and prepare version. Changing scene styling does not refetch source data; changing a data dependency invalidates deterministically. Repeated stills and exports of an approved cut reuse the cached result, and a retry never reruns preparation at all because it replays the frozen manifest.