Skip to content

Coming from Remotion

If you know Remotion, this page maps its ideas to alpha2’s, shows the same video written both ways, and says where the two differ.

In Remotion, a video is a React component. Remotion renders it frame by frame in a browser, so anything the browser can draw (HTML, CSS, SVG, canvas, WebGL) can be in the picture, and your code runs on every frame.

In alpha2, a video is a JSON document: sources, tracks and clips, with effects, transitions, generators for text, shapes and flexbox or grid layouts, captions and an audio mixer. A GPU engine renders it. No code of yours runs per frame: motion is written as keyframes in the document. That makes the document something you can store, diff, validate and generate, and lets the same frames come out of a browser, Node.js, Python, Rust or a CLI.

Remotion alpha2 Notes
<Composition> (width, height, fps, durationInFrames) The project and its settings (width, height, frame_rate) The duration is where the timeline ends. frame_rate is a rational, { "num": 30000, "den": 1001 } for 29.97.
A frame number A time in ticks, 120,000 per second Ticks are exact at every common frame rate, so a project’s times don’t depend on its frame rate.
<AbsoluteFill> A clip on a track, or a layout generator’s root A clip’s picture is the full canvas. A layout’s root box is the canvas, placed with justify_content, align_items and padding.
<Sequence from durationInFrames> A clip’s timeline_range source_range chooses which part of the source plays.
<Series> Clips one after another on one track
Layering by JSX order Track order Later tracks draw on top. One track shows one clip at a time.
useCurrentFrame() + interpolate() Keyframes: { "mode": "keyframes", "keyframes": [...] } Each keyframe’s interpolation (linear, hold, cubic_bezier) shapes the segment after it.
Easing.bezier(...) { "type": "cubic_bezier", "x1", "y1", "x2", "y2" } Same curve as CSS cubic-bezier().
<Img>, <Video>, <OffthreadVideo> image and video sources Your app or the host decodes them.
<Audio> Audio tracks and clips Gain, pan, fades, effects, ducking and a master bus.
CSS transform, opacity A clip’s visual (position, anchor, scale, rotation_degrees, opacity) Or translate, scale, rotation_degrees, opacity on a layout box.
CSS filters, custom shaders Effects (62 built in), or runtime WGSL effects
@remotion/transitions Transitions between two clips on a track (122 built in)
<Player> The browser player, or @alpha2-sh/react’s canvas
renderMedia() exportProject() in @alpha2-sh/node, or alpha2-host export
renderStill() still() in @alpha2-sh/node, or alpha2.still in Python
Input props Generate the JSON with your own code A template is a function that returns a document.

A white background and a title that fades in and rises from 0.5 s to 1.5 s, in a 3-second 1080p video:

import { AbsoluteFill, Composition, Easing, interpolate, Sequence, useCurrentFrame } from "remotion";
const Title = () => {
const frame = useCurrentFrame();
const opacity = interpolate(frame, [0, 30], [0, 1], { extrapolateRight: "clamp" });
const rise = interpolate(frame, [0, 30], [54, 0], {
easing: Easing.bezier(0.2, 0, 0, 1),
extrapolateRight: "clamp",
});
return (
<AbsoluteFill style={{ justifyContent: "center", alignItems: "center" }}>
<h1 style={{ opacity, transform: `translateY(${rise}px)` }}>Hello from alpha2</h1>
</AbsoluteFill>
);
};
export const Root = () => (
<Composition
id="Title"
width={1920}
height={1080}
fps={30}
durationInFrames={90}
component={() => (
<AbsoluteFill style={{ backgroundColor: "white" }}>
<Sequence from={15}>
<Title />
</Sequence>
</AbsoluteFill>
)}
/>
);

In the alpha2 version the title is a text generator on its own track, over a solid fill. The clip starts at 0.5 s (60000 ticks), like <Sequence from={15}>. Its opacity and position keyframes do what the two interpolate calls do. A clip’s visual keyframes are in timeline time, so they start at the clip’s start, 0.5 s. Before the first keyframe and after the last, a value holds the nearest key’s value, like extrapolateRight: "clamp". y 0.55 to 0.5 is 5% of the canvas height, the 54 pixels of the Remotion version.

import { exportProject } from "@alpha2-sh/node";
await exportProject({
project, // the document above
assets: {}, // no files: every source is generated
fonts: ["HankenGrotesk.ttf"],
startTicks: 0,
endTicks: 360_000,
frameRate: { num: 30, den: 1 },
output: "title.mp4",
});

Where Remotion drives a headless browser, alpha2-host is a native program that renders on the GPU (Vulkan, Metal or Direct3D 12), or on the CPU where there is none, and encodes with FFmpeg. See Render on a server.

  • It does not render React or the DOM. There is no HTML, no CSS cascade, no SVG DOM and no canvas API in the picture. Layout generators cover flexbox and grid boxes with text and shapes, and SVG and Lottie files can be sources, but arbitrary web content cannot.
  • No code runs per frame. You can’t compute a value from the frame number at render time. Compute it ahead and write it as keyframes (a layout text takes keyframes of strings, so a counter is a list of held keys), or write a runtime WGSL effect for per-pixel work.
  • No data fetching during a render. Everything the render needs is in the document or given with it (asset files, fonts, effect resources).
  • Fonts are files you give it. The engine never reads system fonts. A text that names a family none of the supplied fonts has draws in the fallback order of the fonts given, and a native export lists that family in unmatchedFontFamilies.

What you get in exchange: a schema-checked document that loads the same everywhere, frames that are the same in the browser preview and the server export, and GPU rendering without a browser on the server.