Skip to content

Coming from FFmpeg

If you build videos with FFmpeg filtergraphs, this page shows the alpha2 way to do the same things, and where each tool fits.

alpha2 doesn’t replace FFmpeg: the native programs decode and encode with FFmpeg 8. What changes is the middle. Instead of a filtergraph string, you write a project document, a JSON timeline of sources, tracks and clips that a GPU engine renders. The same document previews in a browser and renders on a server, and you can edit it, diff it and check it against a schema.

FFmpeg alpha2
Inputs (-i) sources, each with an id. Files are given at export time: --asset <id>=<path>.
A filtergraph of labelled streams tracks of clips on one timeline. Later tracks draw on top.
Per-input timestamps, -ss, -t, trim A clip’s timeline_range (where it plays) and source_range (what part of the source plays), in ticks
Output size from the last filter The project’s settings.width and height. Every source is fitted into it.
Pixel coordinates (x=W-w-40) Normalized positions: 0 to 1 across the canvas
Filters on a stream Effects in a composition graph on a clip
-filter_complex with sendcmd or expressions over t Keyframes on any animatable value

One project with a crossfade between two photos, a grade on the second, and a line of text, the FFmpeg command beside it:

Terminal window
ffmpeg -loop 1 -t 3 -i coast.jpg -loop 1 -t 3 -i forest.jpg -filter_complex "
[0:v]scale=1280:720,setsar=1[a];
[1:v]scale=1280:720,setsar=1,eq=contrast=1.2:saturation=1.3[b];
[a][b]xfade=transition=fade:duration=1:offset=2,
drawtext=fontfile=HankenGrotesk.ttf:text='Day two':fontsize=58:fontcolor=white:
x=w*0.05:y=h*0.92-th:enable='gte(t,3)'" -r 30 out.mp4
{
"schema_version": 1,
"id": "from-ffmpeg",
"name": "From FFmpeg",
"settings": {
"width": 1280,
"height": 720,
"frame_rate": {
"num": 30,
"den": 1
},
"sample_rate": 48000
},
"sources": [
{
"id": "coast",
"locator": "asset://coast",
"kind": "image"
},
{
"id": "forest",
"locator": "asset://forest",
"kind": "image"
},
{
"id": "caption",
"kind": "generated",
"generator": {
"type": "text",
"text": "Day two",
"style": {
"font_family": "Hanken Grotesk",
"font_size": 0.08,
"align": "start",
"position": {
"x": 0.05,
"y": 0.92
},
"anchor": {
"x": 0,
"y": 1
}
}
}
}
],
"compositions": [
{
"id": "graded",
"output": "eq",
"nodes": [
{
"id": "clip",
"op": {
"type": "clip"
}
},
{
"id": "eq",
"op": {
"type": "effect",
"input": "clip",
"effect": {
"id": "alpha2.effect.color-controls",
"version": 1,
"params": {
"contrast": {
"mode": "constant",
"value": {
"type": "float",
"value": 1.2
}
},
"saturation": {
"mode": "constant",
"value": {
"type": "float",
"value": 1.3
}
}
}
}
}
}
]
}
],
"timeline": {
"tracks": [
{
"id": "v1",
"name": "Picture",
"kind": "video",
"clips": [
{
"id": "a",
"source_id": "coast",
"timeline_range": {
"start": 0,
"end": 360000
},
"source_range": {
"start": 0,
"end": 360000
}
},
{
"id": "b",
"source_id": "forest",
"timeline_range": {
"start": 240000,
"end": 600000
},
"source_range": {
"start": 0,
"end": 360000
},
"composition": {
"id": "graded"
}
}
],
"transitions": [
{
"from_clip": "a",
"to_clip": "b",
"timeline_range": {
"start": 240000,
"end": 360000
},
"easing": {
"type": "linear"
},
"effect": {
"id": "alpha2.transition.fade",
"version": 1
}
}
]
},
{
"id": "text",
"name": "Text",
"kind": "video",
"clips": [
{
"id": "caption-clip",
"source_id": "caption",
"timeline_range": {
"start": 360000,
"end": 600000
},
"source_range": {
"start": 0,
"end": 240000
}
}
],
"transitions": []
}
]
}
}
Terminal window
alpha2-host export --project graded.json \
--asset coast=coast.jpg --asset forest=forest.jpg \
--font HankenGrotesk.ttf --out out.mp4
Terminal window
[a][b]xfade=transition=fade:duration=1:offset=2

The two clips overlap on one track from 2 s to 3 s, and a transition in the track’s transitions joins them over that range. effect.id names one of 122 built-in transitions (alpha2.transition.fade, directional-wipe, circle-open and so on), and easing shapes its progress. Unlike offset, the range is explicit on the timeline, and both clips keep their own times.

Terminal window
eq=contrast=1.2:saturation=1.3

The graded composition runs the second clip through alpha2.effect.color-controls with contrast 1.2 and saturation 1.3. It also has exposure, temperature and tint. The math is the engine’s own, so the same numbers give a similar look to eq, not the same pixels. levels, lift-gamma-gain, hue-saturation and lut-3d (a .cube file) cover the rest of FFmpeg’s color filters.

Terminal window
drawtext=fontfile=HankenGrotesk.ttf:text='Day two':fontsize=58:x=w*0.05:y=h*0.92-th:enable='gte(t,3)'

Text is a generated source with a text generator, shown by a clip from 3 s to 5 s, which replaces enable. font_size is a fraction of the picture’s shorter side (0.08 × 720 ≈ 58 pixels). position places the text’s anchor point: anchor (0, 1) is its bottom-left corner, so this is x=w*0.05:y=h*0.92-th. Lines are aligned within the text’s wrapping width, max_width (0.9 of the canvas width by default), so align: "start" keeps them at the left. The font comes from --font, as fontfile does. For boxes that size themselves around text, use a layout generator.

Terminal window
[1:v]scale=iw*0.32:-1[pip];[0:v][pip]overlay=W-w-50:H-h-50

A clip on a higher track draws over the ones below. Its visual.scale scales it from its fitted size, and position places its anchor point. The picture-in-picture recipe has the full project, with rounded corners and a shadow, which take more than one filter in FFmpeg.

scale, pad and crop → fitting the canvas

Section titled “scale, pad and crop → fitting the canvas”

Every source is first fitted inside the canvas, centred, keeping its aspect ratio, like scale=...:force_original_aspect_ratio=decrease,pad=.... The bars are the project’s settings.background color (black by default). To fill the frame instead (force_original_aspect_ratio=increase,crop=...), scale the clip up and move it with position: see Vertical video.

alpha2.effect.crop cuts edges off without changing the frame size: top_left and bottom_right are how much to cut from each side, as fractions, with a feather. A clip’s masks cut it to any shape.

FFmpeg alpha2
concat Clips one after another on one track
fade=in, fade=out opacity keyframes on a clip, or a transition
setpts, atempo A source_range longer or shorter than the timeline_range: the clip plays faster or slower, with its sound time-stretched or resampled
reverse "time_map": { "type": "reverse" }
boxblur, gblur alpha2.effect.gaussian-blur
unsharp alpha2.effect.unsharp-mask, alpha2.effect.sharpen
vignette alpha2.effect.vignette
chromakey, colorkey alpha2.effect.chroma-key
lut3d alpha2.effect.lut-3d with a .cube effect resource
amix, volume, pan Audio tracks; a clip’s audio.gain and audio.pan
afade A clip’s audio.fade_in and audio.fade_out
loudnorm --loudness-target <LUFS> on export (EBU R128)
subtitles A caption track: see Captions from SRT
FFmpeg alpha2-host export
-ss, -to --start-ticks, --end-ticks
-r 30 --frame-rate 30/1
-c:v libx264, -c:v libvpx-vp9 --video-codec h264, --video-codec vp9
-crf, -b:v --video-quality high (a level) or --video-quality 8000000 (bits per second)
-c:a, -b:a --audio-codec aac, --audio-quality
-an --no-audio
-f mp4 --container mp4, or the output’s extension
-y --overwrite

The CLI reference lists every flag.

The release archives have a second program, alpha2, for single files. It covers the everyday ffprobe and ffmpeg jobs an editing app needs around a project:

Job FFmpeg alpha2
Inspect a file ffprobe clip.mp4 alpha2 probe clip.mp4
A poster frame ffmpeg -ss 2 -i clip.mp4 -frames:v 1 poster.png alpha2 thumbnail clip.mp4 poster.png --at 2
A representative frame ffmpeg -i clip.mp4 -vf thumbnail -frames:v 1 poster.png alpha2 thumbnail clip.mp4 poster.png
An editing proxy ffmpeg -i clip.mp4 -vf scale=iw/2:-2 proxy.mp4 alpha2 transcode clip.mp4 proxy.mp4 --proxy half
Audio to WAV ffmpeg -i clip.mp4 -ar 48000 audio.wav alpha2 export-audio clip.mp4 audio.wav
Waveform data ffmpeg ... astats alpha2 waveform clip.mp4 (JSON)

Without --at, thumbnail picks the sharpest of nine frames spread over the video that is neither black nor white. The commands that decode take --decode-policy tolerant to skip damaged input instead of failing. See CLI reference.