Skip to content

Python

The alpha2 Python package builds, validates and renders alpha2 projects. After reading this page you can write a project with the builder and the authoring helpers, export it to a video file with progress and cancellation, check single frames as PNGs, measure text without a GPU, and describe whole videos with alpha2.compose.

A project is a plain dict in the engine’s project.v1 shape, typed as alpha2.types.Project. Validation runs the engine’s own validator in-process. Rendering runs the native alpha2-host process on the GPU, or on the CPU where there is none.

Terminal window
pip install alpha2

On Linux x86_64 and aarch64 (glibc 2.35 or later; not musl), macOS arm64 and Windows x64, this also installs alpha2-host as a platform wheel (alpha2-host) of the same version. Nothing else to download.

import alpha2
project = alpha2.ProjectBuilder("Intro", width=1280, height=720, frame_rate=30)
title = project.source("image", "title.png")
project.video_track().clip(title, at=0, duration=3)
result = alpha2.export(
project.build(),
"intro.mp4",
assets=project.assets(),
on_progress=lambda p: print(f"{p.completed}/{p.total}"),
)
print(result["output"], result["videoPipeline"], result["frames"])
  • ProjectBuilder(name, width=, height=, frame_rate=, id="project", sample_rate=48000, background=None, ids=None). frame_rate is an int or a (num, den) tuple.
  • source(kind, locator, id=None, has_audio=None) adds a video, audio or image source and returns its id. When locators are file paths, assets() maps each source id to its file, which is what export needs.
  • video_track(), audio_track() and caption_track() add tracks. clip(source, at=, duration=, source_in=0, source_duration=None, effects=None, ...) places a clip; extra keyword arguments set any other clip field. Times are seconds or exact alpha2.Ticks.
  • build() returns the document and raises when it does not validate. alpha2.validate_project(document) returns the problems as a list of strings.

alpha2.export(project, output, **options) takes the host protocol’s export fields in snake_case:

Option Default
assets None {source_id: path}.
start_ticks, end_ticks 0, the end of the project The range, in ticks.
frame_rate the project’s An int, a (num, den) tuple, or {"num", "den"}.
container the output’s extension’s mp4, mov, mkv, webm.
video_codec, audio_codec the container’s
video_quality, audio_quality high A level from alpha2.QUALITY_LEVELS, or {"bitrate": ...}.
include_audio True
alpha, bit_depth False, the codec’s Transparency and 10-bit output.
loudness None {"target_lufs": -14, "true_peak_ceiling_dbtp": -1}.
fonts, effects, effect_assets None Font files in fallback order; runtime effects and their resources.
proxies, media, frames, resources Proxies, image sequences and files SVG or Lottie sources reference.
render_cache, memory None Reuse rendered frames; byte budgets.
strategy, input_policy, video_encoder render, auto, None
renderer, video_decode the client’s See below.
overwrite False Replace an existing file once the export is whole.
on_progress, cancel None A callback, and a threading.Event.

Options you write are snake_case; what the host reports keeps the protocol’s camelCase keys (result["effectiveRange"]).

  • An existing output is refused unless you pass overwrite=True. An output that is one of the export’s inputs is always refused.
  • on_progress receives an ExportProgress with phase (frames or finalizing), completed and total.

Set a cancel event, or press Ctrl-C. The client writes a cancel line, the host stops the export, removes its partial output and answers, and the host ends, all before the call returns.

import threading
cancel = threading.Event()
threading.Timer(10, cancel.set).start() # give up after 10 seconds
try:
alpha2.export(document, "long.mp4", assets=assets, cancel=cancel)
except alpha2.Alpha2AbortError:
print("cancelled")

A host that does not answer within 5 seconds is ended; a second Ctrl-C ends it at once. The host also exits when the process that started it dies.

alpha2.export, alpha2.still, alpha2.conform and alpha2.capabilities run on a shared HostClient. Make your own to bound or separate them:

client = alpha2.HostClient(concurrency=2, renderer="auto", host_start_timeout=30.0)
client.export(document, "a.mp4", assets=assets)

concurrency is how many host processes the client runs at once.

The document is plain dicts, and the helpers write them: values, effects, a clip’s visual, layouts and shapes. They write the document’s own field names, so helpers and hand-written dicts mix freely.

import alpha2
from alpha2 import effect, key, keyframes, visual
from alpha2.layout import box, layout, text
from alpha2.shapes import circle, fill, shapes
project = alpha2.ProjectBuilder("Card", width=1280, height=720, frame_rate=30)
card = project.generated(
layout(
box(
text("Hello", font_size="40px", line_height=1.4),
padding="12px 20px",
background="#1e2230",
border_radius="16px",
)
)
)
project.video_track().clip(
card,
at=0,
duration=3,
visual=visual(opacity=keyframes(key(0, 0.0), key(0.5, 1.0)), scale=1.2),
effects=[effect("alpha2.effect.gaussian-blur", radius=0.002)],
)
dot = project.generated(shapes(circle((0.5, 0.5), 0.2, picture=(1280, 720)), fill("#ffb020")))
project.video_track().clip(dot, at=0, duration=3)
document = project.build()
assert alpha2.validate_project(document) == []
  • key(at, value) takes at in seconds or alpha2.Ticks. keyframes(*keys) refuses keys out of order instead of sorting them. Every other field takes a plain value, which becomes a constant. See animation.
  • effect(id, **params) adds an effect. fx.param (which effect applies) lifts a float, a bool, an (x, y) pair and a hex color. An int or a 4-tuple is ambiguous and raises TypeError naming the constructors to use (fx.scalar(1.0) or fx.enum(1), fx.vec4 or fx.color).
  • alpha2.layout is the same builder as the TypeScript alpha2/layout: box(*children, **props), text(content, **props), shape(items, **props). See layout.
  • alpha2.shapes draws shapes generators: shapes(*items), circle, fill, and more. See generators.
What Unit
Positions: a clip’s position and anchor, a shape’s center, path vertices Per axis: x a fraction of the width, y of the height
Sizes of shapes (rect, ellipse) Per axis, as positions: (0.5, 0.5) is round only on a square picture
Lengths: corner radii, stroke widths, dashes, blur radii, layout numbers A fraction of the picture’s shorter side
Font sizes (text generators, layouts) A fraction of the picture’s shorter side, or "40px" of the canvas in a layout
Letter spacing, text stroke widths, text shadow offsets and blur A fraction of the font size

circle(center, diameter, picture=(w, h)) gives a round ellipse: the diameter is a shorter-side fraction, and picture is the size of what the shapes draw on, so it has no default.

Owner Keyed in
A clip’s visual, its masks, a transition Timeline time, from the project’s start
A composition’s settings (a clip’s effects) Composition time, from the clip’s start
Shapes and layout generators Picture time: the source time a clip selects

A keyframe in the wrong base usually falls outside the time its owner is shown. alpha2.lint_project(document) reports each keyframe no frame reaches as unreached_keyframe, with its owner, property and time.

A layout text takes keyframes of strings, so a count-up is one clip:

count = keyframes(*(key(i / 30, str(i)) for i in range(101)))
counter = project.generated(layout(box(text(count, font_size=0.2), justify_content="center")))

Text holds from one key to the next: text writes key’s default linear as hold, and refuses any other interpolation.

alpha2.still renders one frame to a PNG, the frame an export draws there, without encoding a video:

result = alpha2.still(project.build(), 1.5, "check.png", assets=project.assets())
print(result["stills"][0]["frameTime"]) # the grid frame that holds 1.5 s, in ticks
  • Times are seconds or exact alpha2.Ticks. The frame drawn is the one of the project’s frame grid that holds the time.
  • alpha2.stills(project, [(0, "a.png"), (2.5, "b.png")], ...) renders up to 256 frames with one host, decoding once. size=(width, height) renders at another canvas size, and alpha=True draws without the background.
  • The PNG is 8-bit RGBA, the picture before any video encoding.

alpha2.TextMeasurer(fonts) measures text and layout generators with the engine’s own placement code, without a GPU. Give it the font files an export gets, in the same order.

measurer = alpha2.TextMeasurer(["fonts/Inter.ttc"])
bounds = measurer.layout(card_layout, picture=(1280, 720))
title = bounds["nodes"][1]
print(title["border"], title["lineCount"], title["overflow"], title["baselines"])
words = measurer.text({"type": "text", "text": "Hi"}, canvas=(1280, 720))

Rectangles are fractions of the picture, (0, 0) top-left to (1, 1) bottom-right. Measure at the size the picture is drawn at: at another size lines can break elsewhere. The measurer reads its fonts once and releases the GIL while it measures, so a loop that shrinks a text until it fits is quick.

alpha2.compose describes a video by what it is made of and compiles it into a project. Times are seconds, lengths are pixels of the canvas, and files are paths. The compiled result carries its assets, fonts and loudness to alpha2.export.

from pathlib import Path
import alpha2
from alpha2 import compose as c
video = c.Video(
canvas="youtube-1080p",
body=c.Slideshow(
sorted(Path("photos").glob("*.jpg")), seconds=6, transition=c.Crossfade(1), ken_burns=1.15
),
overlays=[c.Title("Summer 2026", at=0.5, duration=4)],
audio=c.MusicBed("music.m4a", fade_out=3),
)
compiled = video.compile(fonts=["DejaVuSans-Bold.ttf"]) # compiled.project is a plain dict
alpha2.export(compiled, "summer.mp4")
  • Nodes: Sequence, Stack, Grid, Media, Text, Color, Captions (SubRip, WebVTT, cues or word timestamps), Layout, Audio, Raw (part of a project, as an overlay).
  • Components: Slideshow, CaptionedVideo, Title, TitleCard, LowerThird, EndCard, ComparisonGrid, Reframe, Logo, MusicBed.
  • Themes: clean-dark and clean-light, or your own (theme={"base": "clean-light", "accent": "#e63946"}).
  • Compiling needs each file’s size and length. compile asks the host’s conform for them (no GPU), or takes them as facts=. A video with no media files compiles without a host. The same spec, facts and fonts compile to the same project.
  • A spec is JSON (video.to_json(), typed as alpha2.compose_types.VideoSpec), described by the compose schema. Every binding compiles it the same way.
  • An error names where in the spec it lies: alpha2.Alpha2ComposeError with .path == "spec.body.items[3].path".

renderer= ("auto", "gpu", "cpu") chooses what draws the frames, and video_decode= ("software", "auto", "hardware-required") how video is decoded. Set them on HostClient(...) for every request, or per call to export, still and capabilities. Unset, the host reads ALPHA2_RENDERER and ALPHA2_VIDEO_DECODE, else uses the auto renderer, and auto decode on Linux and macOS, software on Windows.

  • auto renders on a hardware GPU when there is one and on the CPU otherwise, never on a software GPU adapter. gpu takes whatever adapter it finds and fails with GPU_INIT_FAILED without one. cpu opens no graphics driver.
  • Results and capabilities report renderer, rendererReason and, on the CPU, rendererThreads. A runtime WGSL effect cannot run on the CPU renderer: such an export fails with EFFECT_ERROR.
  • Under auto decode, an export whose hardware decoder stalls runs once more decoding in software.

Every failure is an alpha2.Alpha2HostError with a machine-readable code and a classified failure dict for retry policies. Subclasses: Alpha2ProjectValidationError, Alpha2ConformError, Alpha2GpuInitializationError and Alpha2AbortError.

  • A host that writes nothing within host_start_timeout (seconds, a HostClient argument, 30 by default, None to wait forever) is killed with HOST_START_TIMEOUT.
  • A host macOS has quarantined is refused before it starts with HOST_QUARANTINED. One Windows refuses by policy is HOST_BLOCKED. A Linux host whose program interpreter is missing (musl) or whose glibc is too old is HOST_LOAD_FAILED.

python -m alpha2 has the same operations as commands:

Terminal window
python -m alpha2 export project.json out.mp4 --asset title=title.png --video-quality high
python -m alpha2 still project.json check.png --at 1.5 --asset title=title.png
python -m alpha2 capabilities
python -m alpha2 install # download alpha2-host into the shared cache
python -m alpha2 install --openh264 # Cisco's OpenH264, for software H.264

The client looks in this order: the host_path argument, ALPHA2_HOST_PATH, the alpha2-host wheel of this version, the archive of this version in the shared cache, then PATH.

python -m alpha2 install downloads the release archive into the cache that @alpha2-sh/node also uses (ALPHA2_HOST_CACHE_DIR, else ~/.cache/alpha2, ~/Library/Caches/alpha2 or %LOCALAPPDATA%\alpha2), so one download serves both. It checks the archive against the SHA-256 the published wheel pins for it; a source checkout, which has no pin, checks against the release’s checksums.sha256 (InstallResult.verified_by says which). alpha2.install_host() does the same from code.

Downloads honour HTTPS_PROXY and NO_PROXY. ALPHA2_HOST_DOWNLOAD_URL (or the download_url argument) names a mirror laid out like https://github.com/alpha2-sh/alpha2/releases/download.