alpha2 for Python
Build, validate and render alpha2 project documents from
Python. 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.
pip install alpha2
On Linux (glibc 2.35 or later; not musl) x86_64 and aarch64, macOS arm64 and Windows x64 this
also installs alpha2-host, the native renderer, as a platform wheel of the same version.
Elsewhere build alpha2-host from source (cargo install alpha2-host with FFmpeg 8 development
files) and set ALPHA2_HOST_PATH; see Finding alpha2-host.
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"])
alpha2.export,alpha2.conformandalpha2.capabilitiesrun on a sharedHostClient; make your own (alpha2.HostClient(concurrency=2)) to bound or separate them.- An existing
outputis refused unless you passoverwrite=True, which replaces it only once the export is whole. An output that is one of the export's inputs is always refused. - Setting a
cancelevent (threading.Event) or pressing Ctrl-C cancels the request: 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. A host that does not answer within 5 s is ended; a second Ctrl-C ends it at once. The host also exits when the process that started it dies. - Every failure is an
alpha2.Alpha2HostErrorwith a machine-readablecodeand a classifiedfailuredict for retry policies. A host that writes nothing withinhost_start_timeout(seconds, aHostClientargument, 30 by default,Noneto wait forever) is killed and the request fails withHOST_START_TIMEOUT; a host macOS has quarantined is refused before it starts withHOST_QUARANTINED, one Windows refuses by policy isHOST_BLOCKED, and a Linux host whose program interpreter is missing (musl) or whose glibc is too old isHOST_LOAD_FAILED. - Options you write use snake_case (
loudness={"target_lufs": -14, ...}); what the host reports keeps the protocol's camelCase keys (result["effectiveRange"]).
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 a helper and a hand-written dict
mix freely, and validate_project checks the result with the engine's messages.
from alpha2 import effect, fx, key, keyframes, visual
from alpha2.layout import box, layout, text
from alpha2.shapes import circle, fill, shapes
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")))
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.- An effect parameter is typed in the document.
fx.param(whicheffectapplies) lifts afloat, abool, an(x, y)pair and a hex color; anintor a 4-tuple is ambiguous and raisesTypeErrornaming the constructors (fx.scalar(1.0)orfx.enum(1),fx.vec4orfx.color). alpha2.layoutis the TypeScriptalpha2/layoutbuilder:box(*children, **props),text(content, **props),shape(items, **props). Length and CSS strings are not parsed in Python; the engine reports a bad one.
Units
| 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 (the canvas for a shapes generator,
the content box for a shape inside a layout box), so it has no default.
Time bases
| Owner | Keyed in | Why |
|---|---|---|
A clip's visual, its masks, a transition |
Timeline time, from the project's start | They place the clip on the timeline. |
A composition's settings (a clip's effects) |
Composition time, from the clip's start | The composition moves with the clip that shows it. |
| Shapes and layout generators | Picture time: the source time a clip selects | A generated picture is a source, trimmed and placed by its clips as a video file is. |
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.
Layers
A track shows one clip at a time: clips on one track may not overlap, except where a transition
joins them. Overlapping layers are one track each, the later track drawn on top. For many layers in
one place, use compose.Stack, a composition merge node (up to 32 layers in one clip), or one
shapes or layout generator that draws them all.
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.
Measure text
alpha2.TextMeasurer(fonts) measures text and layout generators with the engine's own placement
code and without a GPU. Give it the font files an export gets, in the same order: it registers the
same faces (every face of a .ttc).
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; baselines
holds the first and last lines' alphabetic baselines as fractions of its height, to line a text up
with another or with a shape by arithmetic. Measure at the size the picture is drawn at: at another
size lines can break elsewhere. The measurer reads its fonts once, so a loop that shrinks a text
until it fits is quick; measuring releases the GIL.
Check a frame
alpha2.still renders one frame of a project 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;frameTimesays which. alpha2.stills(project, [(0, "a.png"), (2.5, "b.png")], ...)renders up to 256 frames with one host, decoding once;size=(width, height)renders the project at another canvas size.- The PNG is 8-bit RGBA, the picture before any video encoding.
overwrite=Truereplaces an existing file, as forexport. - From a shell:
python -m alpha2 still project.json check.png --at 1.5 --asset title=title.png.
Renderer and video decode
renderer= ("auto", "gpu", "cpu"; typed as alpha2.client.RendererMode) chooses what draws
the frames, and video_decode= ("software", "auto", "hardware-required") how video is
decoded, on HostClient(...) for every request or per call to export, still and
capabilities. Unset, the host reads ALPHA2_RENDERER and ALPHA2_VIDEO_DECODE, else decides:
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 (lavapipe, llvmpipe, WARP, SwiftShader).gputakes whatever adapter the host finds and fails withGPU_INIT_FAILEDwithout one;cpuopens no graphics driver.- Results and capabilities report
renderer("gpu"or"cpu"),rendererReason(why the host renders on the CPU) and, on the CPU,rendererThreads; the capability report's["capabilities"]["features"]hasrenderer.gpu,renderer.cpuandeffects.runtime. 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.
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 project 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); componentsSlideshow,CaptionedVideo,Title,TitleCard,LowerThird,EndCard,ComparisonGrid,Reframe,Logo,MusicBed. Themesclean-darkandclean-light, or your own (theme={"base": "clean-light", "accent": "#e63946"}). - Motion:
enter=andexit=on visual nodes and containers takeFade,Rise,SlideorScale, timed from the node's own start and back from its own end;StackandGridtakestagger=to move children one after another;Texttakesreveal=Reveal("word")(or"line");Counter(0, 1200, thousands=",")counts on the frame grid. Easings are CSS names ("ease-out") or easing objects. All of it compiles to ordinary keyframes. - Helpers for hand-built projects:
alpha2.frames(30).snap(1.234)(frame-exact seconds),alpha2.EASING_PRESETS,alpha2.linear_gradientandalpha2.radial_gradient. - Compiling needs each file's size and length.
compileasks the host'sconformfor them (no GPU), or takes them asfacts=; with every fact given it runs without a host. The same spec, facts and fonts compile to the same project. - Templates are Python: loops, conditions and arithmetic build the lists the nodes take. A spec is
JSON (
video.to_json(), typed asalpha2.compose_types.VideoSpec) that every binding compiles the same way. - An error names where in the spec it lies (
alpha2.Alpha2ComposeError,.path == "spec.body.items[3].path").
Finding alpha2-host
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 (ALPHA2_HOST_CACHE_DIR,
else ~/.cache/alpha2, ~/Library/Caches/alpha2 or %LOCALAPPDATA%\alpha2), the one
@alpha2-sh/node uses, so one download serves both. It checks the archive against the SHA-256
the published wheel pins for it, so a mirror cannot substitute one; a source checkout, which has no
pin, checks against the release's checksums.sha256 instead (InstallResult.verified_by says
which). Or unpack the archive from the GitHub release
yourself and point ALPHA2_HOST_PATH at its bin/alpha2-host. On macOS an archive downloaded
with a browser is quarantined, and the client refuses it (HOST_QUARANTINED): run
xattr -dr com.apple.quarantine <unpacked dir>, or download with curl -LO. To use a system
FFmpeg 8, build the host from source (cargo install alpha2-host).
Downloads honour HTTPS_PROXY (and NO_PROXY) and verify with the operating system's certificate
store on macOS and Windows (through truststore), and with OpenSSL's store on Linux, or certifi's
roots where the system has none. A proxy that inspects TLS needs its root certificate in the system
store (or in SSL_CERT_FILE on Linux). ALPHA2_HOST_DOWNLOAD_URL (or the download_url argument)
names a mirror laid out like https://github.com/alpha2-sh/alpha2/releases/download. On Linux the
command refuses musl systems and glibc older than 2.35 before downloading anything. The Microsoft
Store's Python keeps what it writes under %LOCALAPPDATA% private to itself, so the command warns
there: set ALPHA2_HOST_CACHE_DIR to a folder outside %LOCALAPPDATA% to share the install.
Software H.264 (OpenH264)
Where no hardware H.264 encoder is available, alpha2-host can encode H.264 in software with
Cisco's OpenH264 library, which it loads at run time. Neither this package nor the alpha2-host
wheel includes that library, and nothing downloads it unless you ask: Cisco's patent licence covers
its binary only when the user fetches it from Cisco.
python -m alpha2 install --openh264
This downloads Cisco's OpenH264 2.7.0 binary for the platform from
https://ciscobinary.openh264.org, checks the download and the library in it against their
SHA-256s, and puts the library in the shared cache, in openh264-2.7.0-<target>/, where
alpha2-host finds it. It installs only the library, not alpha2-host. --force downloads again,
alpha2.install_openh264() does the same from code, and ALPHA2_OPENH264_DOWNLOAD_URL (or the
download_url argument) names a mirror. Every run prints Cisco's notice:
OpenH264 Video Codec provided by Cisco Systems, Inc.
The library is Cisco's binary, under Cisco's terms.
Set ALPHA2_OPENH264=0 to keep alpha2-host from using it.
Development
The extension module is the Rust crate bindings/alpha2-python; maturin builds it into the package.
cd python/alpha2
uv venv -p 3.11 && uv pip install --group dev
uv pip install --no-deps -e . # rebuild after changing the Rust crate
.venv/bin/pytest # end-to-end tests use ALPHA2_HOST_BINARY or target/debug/alpha2-host
.venv/bin/mypy && .venv/bin/ruff check
--no-deps because the pinned alpha2-host wheel exists only once a release is published; the
tests find a host through ALPHA2_HOST_BINARY, ALPHA2_HOST_PATH or the cache instead.
src/alpha2/types.py and src/alpha2/compose_types.py are generated from the project and compose
schemas by npm run generate:schema.
python/alpha2-host builds the alpha2-host wheel from a release archive.
Metadata
Release files for alpha2 0.5.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Actions Reset | |||
|---|---|---|---|---|
| alpha2-0.5.2-cp311-abi3-win_amd64.whl | CPython 3.11 | abi3 | Windows x86-64 | Details for alpha2-0.5.2-cp311-abi3-win_amd64.whl |
| alpha2-0.5.2-cp311-abi3-manylinux_2_34_x86_64.whl | CPython 3.11 | abi3 | Linux glibc 2.34+ x86-64 | Details for alpha2-0.5.2-cp311-abi3-manylinux_2_34_x86_64.whl |
| alpha2-0.5.2-cp311-abi3-manylinux_2_34_aarch64.whl | CPython 3.11 | abi3 | Linux glibc 2.34+ ARM64 | Details for alpha2-0.5.2-cp311-abi3-manylinux_2_34_aarch64.whl |
| alpha2-0.5.2-cp311-abi3-macosx_11_0_arm64.whl | CPython 3.11 | abi3 | macOS 11.0+ ARM64 | Details for alpha2-0.5.2-cp311-abi3-macosx_11_0_arm64.whl |
Total release size: 14.6 MB
Release files / alpha2-0.5.2-cp311-abi3-win_amd64.whl
| Download URL | alpha2-0.5.2-cp311-abi3-win_amd64.whl |
|---|---|
| Size | 3.5 MB |
| Tags | CPython 3.11 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
c2060823a2a8a0e354d0f57f88dc425b0bd940ac48de287748fabbead01da6b6
|
|
BLAKE2b-256 checksum How to use checksums |
f261616c26034ffa90a823b86bec1bb4aca20c4665aeed71687b01afc0c73453
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / alpha2-0.5.2-cp311-abi3-manylinux_2_34_x86_64.whl
| Download URL | alpha2-0.5.2-cp311-abi3-manylinux_2_34_x86_64.whl |
|---|---|
| Size | 3.8 MB |
| Tags | CPython 3.11 Linux glibc 2.34+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
aa690555ed8392cd85814a7d60ff5a074bc71f3ba064f1788290950003f60583
|
|
BLAKE2b-256 checksum How to use checksums |
83d6e72971c1e04623ecbba31507433c69b6c543fadbc40cada2e95c7e3252ba
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / alpha2-0.5.2-cp311-abi3-manylinux_2_34_aarch64.whl
| Download URL | alpha2-0.5.2-cp311-abi3-manylinux_2_34_aarch64.whl |
|---|---|
| Size | 3.8 MB |
| Tags | CPython 3.11 Linux glibc 2.34+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
b82532754c82fbcfc31814f3ad72aef1311a1fe301f1d58a002e741f031323b7
|
|
BLAKE2b-256 checksum How to use checksums |
1a5b95405769a2872b7781425b83cc4465ed02a344e9fb3661a8e2b1bad2e507
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / alpha2-0.5.2-cp311-abi3-macosx_11_0_arm64.whl
| Download URL | alpha2-0.5.2-cp311-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 3.5 MB |
| Tags | CPython 3.11 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
6c500d8dc2d4dc9da345f3ecfcac59d764eacd4cbd20d0db50d9072d9a9bc730
|
|
BLAKE2b-256 checksum How to use checksums |
dbff53de48b9fea15b00feca80e5a44d876a9e547e3ef5b011c5b63264e38a31
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for alpha2 0.5.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.