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.
Install
Section titled “Install”pip install alpha2On 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.
Export your first project
Section titled “Export your first project”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_rateis anintor a(num, den)tuple.source(kind, locator, id=None, has_audio=None)adds avideo,audioorimagesource and returns its id. When locators are file paths,assets()maps each source id to its file, which is whatexportneeds.video_track(),audio_track()andcaption_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 exactalpha2.Ticks.build()returns the document and raises when it does not validate.alpha2.validate_project(document)returns the problems as a list of strings.
Export options
Section titled “Export options”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
outputis refused unless you passoverwrite=True. An output that is one of the export’s inputs is always refused. on_progressreceives anExportProgresswithphase(framesorfinalizing),completedandtotal.
Cancel an export
Section titled “Cancel an export”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.
Clients and concurrency
Section titled “Clients and concurrency”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.
Authoring helpers
Section titled “Authoring helpers”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 alpha2from alpha2 import effect, key, keyframes, visualfrom alpha2.layout import box, layout, textfrom 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)takesatin seconds oralpha2.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(whicheffectapplies) lifts afloat, abool, an(x, y)pair and a hex color. Anintor a 4-tuple is ambiguous and raisesTypeErrornaming the constructors to use (fx.scalar(1.0)orfx.enum(1),fx.vec4orfx.color).alpha2.layoutis the same builder as the TypeScriptalpha2/layout:box(*children, **props),text(content, **props),shape(items, **props). See layout.alpha2.shapesdraws 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.
Time bases
Section titled “Time bases”| 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.
Animated text
Section titled “Animated text”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.
Check a frame
Section titled “Check a frame”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, andalpha=Truedraws without the background.- The PNG is 8-bit RGBA, the picture before any video encoding.
Measure text
Section titled “Measure text”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.
Compose videos by intent
Section titled “Compose videos by intent”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 alpha2from 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 dictalpha2.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-darkandclean-light, or your own (theme={"base": "clean-light", "accent": "#e63946"}). - Compiling needs each file’s size and length.
compileasks the host’sconformfor them (no GPU), or takes them asfacts=. 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 asalpha2.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.Alpha2ComposeErrorwith.path == "spec.body.items[3].path".
Renderer and video decode
Section titled “Renderer and video decode”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.
autorenders on a hardware GPU when there is one and on the CPU otherwise, never on a software GPU adapter.gputakes whatever adapter it finds and fails withGPU_INIT_FAILEDwithout one.cpuopens no graphics driver.- Results and capabilities report
renderer,rendererReasonand, on the CPU,rendererThreads. A runtime WGSL effect cannot run on the CPU renderer: such an export fails withEFFECT_ERROR. - Under
autodecode, an export whose hardware decoder stalls runs once more decoding in software.
Errors
Section titled “Errors”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, aHostClientargument, 30 by default,Noneto wait forever) is killed withHOST_START_TIMEOUT. - A host macOS has quarantined is refused before it starts with
HOST_QUARANTINED. One Windows refuses by policy isHOST_BLOCKED. A Linux host whose program interpreter is missing (musl) or whose glibc is too old isHOST_LOAD_FAILED.
From a shell
Section titled “From a shell”python -m alpha2 has the same operations as commands:
python -m alpha2 export project.json out.mp4 --asset title=title.png --video-quality highpython -m alpha2 still project.json check.png --at 1.5 --asset title=title.pngpython -m alpha2 capabilitiespython -m alpha2 install # download alpha2-host into the shared cachepython -m alpha2 install --openh264 # Cisco's OpenH264, for software H.264Finding alpha2-host
Section titled “Finding alpha2-host”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.
- Host protocol: every export field and error code.
- CLI: the native programs, OpenH264 and hardware decode.
- Project model and generators: what the builder writes.
