---
title: Odori vs Remotion vs HyperFrames
description: How the three authoring models in this repository differ in timeline, discovery, components, preview, and export.
---

All three tools render frame-accurate video with Chrome and FFmpeg, and all
three are deterministic: a frame number in, the same pixels out. They differ in
**what they own**. HyperFrames owns a document and a capture loop. Remotion owns
a React frame runtime. Odori owns the frame runtime *and* the conventions above
it: discovery, layout inheritance, a component registry, contract tests, and an
export protocol.

The comparisons below are drawn from building the same product video three ways.

## At a glance

| | HyperFrames | Remotion | Odori |
| --- | --- | --- | --- |
| Authoring surface | HTML, CSS, GSAP | React and frame arithmetic | React with `<Video>` and `<Scene>` |
| Composition exists because | A `.html` file is in the project | You registered it in `Root.tsx` | A `videos/**/video.tsx` file exists |
| Timeline | `data-start` / `data-duration` attributes | `<Sequence from durationInFrames>` | `<Scene duration="4s">`, offsets derived |
| Timing unit | Seconds | Frames | Durations (`"4s"`) resolved to frames |
| Format and fps | Attributes on the composition element | Props on each `<Composition>` | Inherited from `videos/layout.tsx` |
| Brand tokens | CSS custom properties you maintain | Whatever you build | `defineBrand`, read with `useBrand` |
| Component library | Registry blocks copied in | npm packages you assemble | `odori add`, source copied to `videos/components/` |
| Typed inputs | None | Zod schema per composition | `schema` on `defineVideoMetadata`, `--input` on every command |
| Preview | CLI preview server | Remotion Studio | Studio, with component fixtures alongside videos |
| Automated checks | `hyperframes check` / `lint` | Your own tests | `odori test`: blank frames, overflow, unreadable text, contracts |
| Export | `hyperframes render` | `remotion render` | `odori export`, jobs and a frozen manifest |
| Build step | None | Bundler | Bundler |

## HyperFrames: the raw-web option

The timeline lives in the document. Sections carry their own timing, and a
paused GSAP timeline is handed to the renderer to seek:

```html title="index.html, HyperFrames"
<section id="opening" class="clip scene" data-start="0" data-duration="3">…</section>
<section id="proof"   class="clip scene" data-start="3" data-duration="3.5">…</section>
```

```js
const tl = gsap.timeline({ paused: true });
tl.fromTo("#opening h1", { opacity: 0, y: 42 }, { opacity: 1, y: 0, duration: 0.7 }, 0.15);
window.__timelines.main = tl;
```

**Strengths.** No build step, no framework, and any web technique is available
immediately. A designer who knows CSS can ship a cut.

**Costs.** Timing is duplicated between markup attributes and the GSAP
timeline, so a scene can drift out of sync with its own animation. There is no
type checking across scenes, no component contract, and reuse is copy-paste.

## Remotion: React on a frame clock

Remotion gives you `useCurrentFrame`, `interpolate`, and `<Sequence>`, and you
build the rest. Composition metadata is registration, not a file convention:

```tsx title="src/Root.tsx, Remotion"
<Composition id="DirectRemotion" component={DirectRemotion} durationInFrames={270} fps={30} width={1920} height={1080} />
```

```tsx title="src/Composition.tsx, Remotion"
<Sequence durationInFrames={90} name="Opening">…</Sequence>
<Sequence from={90} durationInFrames={105} name="Proof">…</Sequence>
<Sequence from={195} durationInFrames={75} name="Resolution">…</Sequence>
```

**Strengths.** A mature, well-documented runtime with a large ecosystem, a
capable studio, and hosted rendering options.

**Costs.** Absolute frame offsets are computed by hand, so inserting a scene
means renumbering the ones after it. Format, fps, and brand are per-composition
props rather than an inherited layout, and there is no built-in component
system or authored-frame linting. You assemble those yourself.

## Odori: the framework layer

A video exists because its file exists. Metadata is static, so discovery never
runs your component:

```tsx title="videos/launch/video.tsx"
export const metadata = defineVideoMetadata({
  id: "launch",
  duration: "12s",
  layout: productLayout,
  schema: launchInput,
});

export default function LaunchVideo({headline}: {headline: string}) {
  return (
    <Video>
      <Audio src="/audio/product-cinematic.m4a" gain={0.75} duckUnder />
      <Scene id="opening" duration="4s">
        <Stage>
          <TitleReveal title={headline} />
        </Stage>
      </Scene>
      <Scene id="proof" duration="5s">…</Scene>
    </Video>
  );
}
```

What the framework adds on top of a frame clock:

- **Derived offsets.** Scenes are ordered, not numbered. Reordering or
  retiming a scene never touches its neighbours.
- **Inherited layout.** Format, fps, and brand come from `videos/layout.tsx`,
  so one change re-formats every video.
- **Source-owned components.** `odori add @odori/title-reveal` copies real
  source into `videos/components/`. You edit it; `odori diff` and
  `odori update` compare it with upstream later.
- **Temporal contracts.** Registry components declare aspect ratios, minimum
  and recommended durations, entrance and exit frames, and content limits, and
  `odori test` fails a cut that violates them.
- **Authored-frame checks.** `odori test` samples representative frames and
  reports blank frames, content escaping the canvas, and text too small to read
  at delivery size.
- **Typed inputs.** Every command takes `--input`, validated against the
  video's schema, so one composition renders many variants.
- **One frozen manifest.** Preview and export resolve the same manifest, so the
  approved cut is the rendered cut.

## Choosing

- Choose **HyperFrames** for a one-off cut, when the team is CSS-first and no
  build step is worth more than reuse.
- Choose **Remotion** when you want an established ecosystem and are happy to
  own the conventions above the runtime yourself.
- Choose **Odori** when video is ongoing work: many cuts, several formats, a
  brand to hold, components to share, and agents or teammates who need the
  structure to be discoverable and checkable.

The three are not mutually exclusive ideas. Odori's position is that the
runtime was never the hard part; the conventions around it are. See [Alternative structures](/docs/reference/alternatives) for the models Odori
considered before settling on `videos/` plus JSX.
