Host protocol
This content is for v0.4. Switch to the latest version for up-to-date documentation.
alpha2-host serve takes requests on standard input and answers on standard output, one JSON object per line. After reading this page you can write an alpha2 client in any language that can start a process and read and write lines: Go, Python, Ruby, C#, a shell script.
@alpha2-sh/node and the Python package are clients of this protocol. In Node.js or Python, use them instead. This page describes version 1 of the protocol. CLI says where alpha2-host comes from.
How a session goes
Section titled “How a session goes”- Start
alpha2-host servewith pipes for standard input and output. - Write requests, one per line. Each has
"protocolVersion": 1, arequestId(a non-empty string) and anoperation:capabilities,conform,exportorstill. - Read lines until the response with your
requestIdarrives. While an export runs,progresslines arrive first. - Close standard input when you have no more requests. The host serves requests until its input ends, then exits.
{"protocolVersion":1,"requestId":"1","operation":"capabilities"}The host answers each request with one response carrying its requestId:
{"protocolVersion":1,"requestId":"1","ok":true,"result":{...}}{"protocolVersion":1,"requestId":"1","ok":false,"error":{"code":"INVALID_INPUT","message":"..."}}Messages
Section titled “Messages”Every message, both ways, is one JSON object on one line of UTF-8 text, with "protocolVersion": 1.
- You can write requests before earlier ones are answered. Match each response to its request by
requestId. - A request the host cannot read is answered with
"requestId": "unknown"when it has no usablerequestId. - A request line is at most 128 MiB, and the
projectin it at most 64 MiB. - Standard error carries human-readable diagnostics, never protocol messages. Let it go to your log, or read it on its own thread: a pipe nobody reads can fill up and stall the host.
Progress
Section titled “Progress”While an export runs, the host also writes progress lines:
{"protocolVersion":1,"type":"progress","requestId":"1","phase":"frames","completed":12,"total":30}phase is frames while frames are encoded and finalizing once every frame is and the file is being completed. The export is finished only when its response arrives.
When the host stops
Section titled “When the host stops”- When the host cannot start its GPU device (or, in
hardware-requireddecode mode, its decode device) for the first request that needs one, it writes{"protocolVersion":1,"type":"fatal","error":{...}}and exits. That request and the ones after it get no response.conformneeds no GPU. - When the device is lost or a decoder or encoder thread is stuck, the host exits after its response. Start a new process for the requests after it.
A complete client in Python
Section titled “A complete client in Python”This client uses only Python’s standard library. It exports a two-second project with no media (a solid color), prints progress, and handles every kind of line the host writes.
"""Export a project through `alpha2-host serve`, using only the standard library."""import jsonimport subprocessimport sys
HOST = sys.argv[1] if len(sys.argv) > 1 else "alpha2-host"
def send(host, message): # One JSON object per line, protocol version 1. host.stdin.write(json.dumps({"protocolVersion": 1, **message}) + "\n") host.stdin.flush()
def read(host): line = host.stdout.readline() if not line: raise RuntimeError("alpha2-host exited without answering") return json.loads(line)
with open("project.json", encoding="utf-8") as file: project = json.load(file)
host = subprocess.Popen( [HOST, "serve"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True, encoding="utf-8",)
send(host, { "requestId": "export-1", "operation": "export", "project": project, "assets": {}, "startTicks": 0, "endTicks": 240_000, # two seconds: 120,000 ticks a second "frameRate": {"num": 30, "den": 1}, "output": "solid.webm", "overwrite": True,})
while True: message = read(host) if message.get("type") == "progress": print(f"{message['phase']}: {message['completed']}/{message['total']}", file=sys.stderr) elif message.get("type") == "fatal": raise RuntimeError(message["error"]["message"]) elif message.get("requestId") == "export-1": break
host.stdin.close() # no more requests: the host exits at the end of its inputhost.wait()
if not message["ok"]: error = message["error"] raise SystemExit(f"{error['code']}: {error['message']}")print("wrote", message["result"]["output"])The project it reads:
{ "schema_version": 1, "id": "solid", "name": "Solid", "settings": { "width": 1280, "height": 720, "frame_rate": { "num": 30, "den": 1 }, "sample_rate": 48000 }, "sources": [ { "id": "backdrop", "kind": "generated", "generator": { "type": "fill", "paint": { "type": "solid", "color": { "red": 0.17, "green": 0.36, "blue": 0.99, "alpha": 1 } } } } ], "timeline": { "tracks": [ { "id": "video-1", "name": "Main video", "kind": "video", "clips": [ { "id": "clip-1", "source_id": "backdrop", "timeline_range": { "start": 0, "end": 240000 }, "source_range": { "start": 0, "end": 240000 } } ] } ] }}Run it with the path to the host:
python3 export_client.py ./alpha2-0.4.6-linux-x64-bundled/bin/alpha2-hostThe output is WebM because VP9 encodes in software in every release archive, so the example runs on a machine without a GPU or an H.264 encoder. Set ALPHA2_RENDERER=cpu in the host’s environment to force the CPU renderer.
capabilities
Section titled “capabilities”Takes no other fields. The result reports what this host can do:
status:"ready".capabilities.gpu: the GPU and its tier.capabilities.decode: the decode mode and the hardware decoders.capabilities.encode: the containers with the codecs each offers (encode.containers), the video and audio encoders, the GPU-resident encode path, andencode.h264.status(available,gpu-onlyorunavailable, withreasonandremedy).capabilities.features.effectCatalog: the built-in effects and transitions.- The adapter and FFmpeg versions.
conform
Section titled “conform”Reports which media each source would read and what blocks it, without rendering and without a GPU.
| Field | Type | Default | |
|---|---|---|---|
project |
project document | required | |
assets |
{ "<source-id>": "<path>" } |
{} |
The original file of each source. |
proxies |
{ "<source-id>": [{ "path", "madeFrom"?, "colorPipeline"? }] } |
{} |
Proxies of each original, in order of preference. |
frames |
{ "<source-id>": ["<path>" or null, ...] } |
{} |
The frame files of each image sequence; null where it has a gap. |
purpose |
preview, preview-proxy, thumbnail, remote-edit, export-original, export-proxy |
export-original |
What the media is read for. |
The result is { "catalog": ..., "report": ... }. report.ready is false when an issue blocks. The types are AssetCatalog and ConformReport in alpha2/assets.
export
Section titled “export”Renders a range of a project and encodes it into an MP4, MOV, Matroska or WebM file.
| Field | Default | |
|---|---|---|
project |
required | The project document. |
assets |
required | { "<source-id>": "<path>" }, the file of each source the export reads. |
startTicks, endTicks |
required | The timeline range in ticks, widened to the frame grid. |
frameRate |
required | { "num", "den" }. |
output |
required | The file to write. Its extension (.mp4, .m4v, .mov, .mkv, .webm) names the container unless container does, and must match it. |
overwrite |
false |
Replace a regular file at output once the export is whole. |
container |
the output’s extension’s | mp4, mov, mkv, webm. |
videoCodec |
the container’s | h264, hevc, vp9, av1, mpeg4-part2. |
audioCodec |
the container’s | aac, opus. |
videoQuality |
high |
very-low, low, medium, high, very-high, or { "bitrate", "mode"?, "quantizer"? }. |
audioQuality |
high |
A level, or { "bitrate" }. |
includeAudio |
true |
|
loudness |
none | { "targetLufs", "truePeakCeilingDbtp" }. Needs includeAudio. |
fonts |
[] |
Font files in fallback order. A project that shows text needs them. |
effects |
[] |
Runtime effect definitions besides the built-in ones. |
effectAssets |
{} |
{ "<resource-id>": "<path>" }, such as .cube LUTs. |
proxies, frames |
{} |
As for conform. |
media |
original |
proxies reads fresh proxies where there are some. |
resources |
{} |
{ "<source-id>": { "<locator>": "<path>" } }: files an SVG or Lottie source references. |
videoEncoder |
chosen by the host | An encoder capabilities reports. |
inputPolicy |
auto |
auto, gpu-required, cpu-readback. |
strategy |
render |
render, prefer-copy, require-copy. |
renderCache |
none | { "directory", "quotaBytes"?, "backlog"? }. |
memory |
host defaults | Byte budgets: inFlightBytes, decodeQueueBytes, reverseCacheBytes, audioWindowBytes, providerSourceBytes, decoderSessionBytes, scale. |
Each container offers only some codecs; the first of each is the default:
| Container | Video | Audio |
|---|---|---|
mp4 |
h264, hevc, vp9, av1, mpeg4-part2 |
aac, opus |
mov |
h264, hevc, mpeg4-part2 |
aac |
mkv |
h264, hevc, vp9, av1, mpeg4-part2 |
aac, opus |
webm |
vp9, av1 |
opus |
The result reports, among others, the frames, the encoder and rate control used, how frames reached the encoder (videoPipeline), the absolute path written (output), what each source read (media), and timing (renderMs, fps). @alpha2-sh/node types the request and result as ExportProjectRequest and NativeExportResult.
The host writes <stem>.alpha2-partial.<ext> beside output and renames it into place when it is complete. A partial file another running export is writing makes the request fail with OUTPUT_IN_USE, with or without overwrite; one left by a process that was killed is taken over.
Renders frames to PNG files, each the frame an export draws at that time. One request decodes once for all its frames, and writes no progress lines.
| Field | Default | |
|---|---|---|
project, assets |
required | As for export. |
stills |
required | [{ "time": <ticks>, "output": "<path>" }, ...], 1 to 256 frames. |
size |
the canvas | { "width", "height" }, 1 to 16384 each. |
overwrite |
false |
As for export, for every output. |
proxies, frames, media, resources, effectAssets, fonts, effects |
As for export. |
The result is { "stills": [{ "output", "frameTime", "width", "height" }], "renderer", "renderMs", "timings", "media", "decodeDiagnostics" }, in the order the frames were asked for. Each PNG is 8-bit RGBA, display-encoded sRGB with straight alpha, and opaque over the project’s background.
Cancelling
Section titled “Cancelling”To cancel the running export, send the process SIGTERM or SIGINT (a console control event on Windows). The export stops, removes its partial file and is answered CANCELLED, and the process exits. An idle process exits at once. A second signal, or SIGKILL, ends the process immediately; an export ended that way leaves its partial file and gets no response.
Errors
Section titled “Errors”| Code | Meaning |
|---|---|
UNSUPPORTED_PROTOCOL |
protocolVersion is missing or not 1. |
INVALID_REQUEST |
The line is not a request: not UTF-8, not a JSON object, too long, an unknown or missing operation, a missing or mistyped field, an empty requestId; or an output is one of the request’s inputs. |
INVALID_INPUT |
A value is not accepted: an invalid project, range, rate or setting, or settings that do not go together. |
CONFORM_FAILED |
The export’s media is not ready; details is the conform report. |
MISSING_INPUT |
A file the render needs was not given: an asset, an effect resource, a font or a frame. |
INCOMPLETE_COMPOSITION |
A composition graph has an input or output that is not connected. |
EFFECT_ERROR |
A runtime effect’s definition, parameters or shader failed. |
GPU_INIT_FAILED |
The host could not create its GPU device. |
HARDWARE_DECODE_UNAVAILABLE |
The decode mode is hardware-required and the host cannot decode in hardware. |
GPU_DEVICE_LOST |
The GPU device was lost, or no longer runs the work it is given. |
GPU_TIMEOUT |
GPU work did not complete in time. |
DECODE_STALLED |
A decoder stopped making progress. |
ENCODE_STALLED |
The encoder or muxer stopped making progress. |
COPY_INELIGIBLE |
strategy: "require-copy" and the range cannot be copied. |
GPU_OUTPUT_UNAVAILABLE |
inputPolicy: "gpu-required" and the GPU-resident encode path cannot take the export. |
ENCODE_ERROR |
Encoding or muxing failed. |
IO_ERROR |
A file could not be read or written, or output exists and the request does not overwrite it. |
OUTPUT_IN_USE |
Another running export is writing the same output. |
CANCELLED |
The export was cancelled. |
RESOURCE_LIMIT |
A budget or limit refused the work, such as a memory budget too small for the export. |
INTERNAL |
A defect in the host failed the request. The host answers the requests after it. |
ALPHA2_ERROR |
A failure no other code describes. |
An export that fails also has error.failure: the failure’s class (code, such as OUT_OF_MEMORY, DEVICE_LOST, INVALID_INPUT or CANCELLED), the step and frame it failed at, the protocol code, and diagnostics. The type is ExportFailure in alpha2/export-failure, and alpha2/export-retry decides from it whether to retry. See validation and errors.
- CLI: the same host for one export at a time.
- Node.js and Python: clients of this protocol.
- Project model: what goes in
project.
