Skip to content
Esc
navigateopen⌘Jpreview
On this page

Preview and render lifecycle

One prepared manifest drives live preview, deterministic stills, tests, and on-demand encoded exports.

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

odori dev mounts the video component in Odori Studio or an embedded Odori Player. The player owns a seekable frame clock and passes deterministic local frame state to React. No screenshot sequence, headless browser job, encoder, or MP4 upload is needed for preview.

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;
};

odori inspect <id> --json prints the manifest for any video. The same inputs always hash to the same manifest, so network drift, mutable URLs, and database changes cannot silently produce a different MP4 from the approved cut.

Render jobs

An export records the video ID, manifest hash, output path, progress, attempts, logs, and result under .odori/builds/. Jobs run through a single-lane queue, so an export started from Studio and one started from the CLI take exactly the same path and never fight for the machine. Every write to a job record is serialized and lands atomically, so progress updates cannot corrupt it.

Odori’s render worker opens the same compiled video in render mode, seeks one frame at a time through a readiness handshake, captures those frames, then mixes any audio cues and encodes with FFmpeg. Frames are captured by several browser workers in parallel; each shard walks an interleaved slice of the timeline, so the work spreads evenly.

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
odori export --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.

Was this page helpful?