Preview and render lifecycle

Odori separates authoring from encoding:

Discover source
      ↓
Resolve layout, props, data, fonts, and assets
      ↓
Freeze a render manifest
      ↓
Live React preview ── stills and visual tests
      ↓
Explicit export request
      ↓
Render worker → encoded media → durable storage

Live preview

Run odori dev to preview videos in Studio. The player advances the frame clock and renders React directly, without encoding a video file.

Prepared manifest

Before rendering, Odori resolves asynchronous inputs into a serializable, content-addressed manifest:

type RenderManifest = {
  videoId: string;
  sourceHash: string;
  manifestHash: string;
  input: unknown;
  prepared: unknown;
  format: {width: number; height: number; fps: number; duration: number; durationInFrames: number};
  scenes: Array<{id: string; start: number; durationInFrames: number}>;
  audio: Array<{src: string; fromFrame: number; durationInFrames: number; gain: number; integrity: string}>;
  assets: Array<{url: string; integrity: string}>;
  fonts: Array<{family: string; url: string; integrity: string}>;
  createdAt: string;
};

Run odori inspect <id> --json to inspect resolved inputs, scene timing, and asset hashes.

Render jobs

Exports record their status, inputs, output path, and logs under .odori/builds/. Studio and the CLI submit jobs to the same queue.

Browser workers capture frames in parallel. FFmpeg mixes the audio cues and encodes the output. An export reports its progress:

odori export launch
  job job-88e09129a0  manifest 88e09129a05765b874e102653aba07ed
  2 audio cue(s) at 360 frames
  captured 360 frames in 7.5s on 4 workers, encoded in 1.1s
  ok Exported launch to out/launch.mp4

Tune the workers with --concurrency, and the encoder with --preset.

Retries

A failed job keeps its frozen manifest, so a retry re-renders the approved cut without re-resolving inputs or rerunning prepare.ts.

odori jobs list
odori jobs retry job-88e09129a0-msw17lz9

Studio shows the same history and offers a retry button on a failed job.

Checks before export

odori test mounts every discovered video, reads the compiled timeline, and samples representative frames. It fails when a declared duration disagrees with the compiled scene total, when a frame is blank, when content escapes the canvas, or when text is too small to read at 1080p.

:::note An export URL means a job was accepted, not that rendering succeeded. Clients should follow job state until it is ready or failed. :::

On this page