Skip to content
Esc
navigateopen⌘Jpreview
On this page

Odori vs Remotion vs HyperFrames

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:

<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>
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:

<Composition id="DirectRemotion" component={DirectRemotion} durationInFrames={270} fps={30} width={1920} height={1080} />
<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:

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 for the models Odori considered before settling on videos/ plus JSX.

Was this page helpful?