Skip to content

Host protocol

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.

  1. Start alpha2-host serve with pipes for standard input and output.
  2. Read the first line. The host writes {"protocolVersion":1,"type":"ready"} before it reads anything.
  3. Write requests, one per line. Each has "protocolVersion": 1, a requestId (a non-empty string) and an operation: capabilities, conform, export, still or cancel.
  4. Read lines until the response with your requestId arrives. While an export runs, progress lines arrive first.
  5. Close standard input when you have no more requests. The host answers every request it has read, 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":"..."}}

Every message, both ways, is one JSON object on one line of UTF-8 text, with "protocolVersion": 1. What the host writes is ASCII: every character past ASCII in a string is a \uXXXX escape, so a reader that decodes it in a legacy code page still reads the same text.

  • 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 usable requestId.
  • A request line is at most 128 MiB, and the project in 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.

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. A line with "restarted": true and completed 0 says the export started over on the readback path, because the GPU-resident encoder rejected a frame. The export is finished only when its response arrives.

  • When the host cannot start its GPU 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. conform needs 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 host that writes nothing for long is being held before it runs (a Gatekeeper check on macOS, an antivirus scan or App Control policy on Windows). The official clients give up after 30 seconds with HOST_START_TIMEOUT; do the same.

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_client.py
"""Export a project through `alpha2-host serve`, using only the standard library."""
import json
import subprocess
import 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",
)
# The host writes {"type": "ready"} before it reads anything.
ready = read(host)
assert ready.get("type") == "ready", ready
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 once it has answered
host.wait()
if not message["ok"]:
error = message["error"]
raise SystemExit(f"{error['code']}: {error['message']}")
print("wrote", message["result"]["output"])

The project it reads:

project.json
{
"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:

Terminal window
python3 export_client.py ./alpha2-0.5.0-linux-x64-bundled/bin/alpha2-host

The 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.

Takes no other fields. The result reports what this host can do:

  • capabilities.gpu: the adapter, its tier, and memory.budgetBytes.
  • 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, and encode.h264.status (available, gpu-only or unavailable, with reason and remedy).
  • capabilities.features and capabilities.adapters (which GPU adapter the host renders on, and why; null on the CPU renderer).
  • effectCatalog: the built-in effects and transitions.
  • The adapter, FFmpeg and operating system versions.

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.

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, prores.
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
alpha false Keep transparency: vp9 in webm or mkv (8-bit), or prores 4444.
bitDepth 10 for prores, else 8 10-bit pictures for hevc, av1, vp9 and prores.
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, prores 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>.a2partial.<ext> beside output and moves it into place when it is complete. A finished render that cannot be moved there is kept as <stem>.a2kept.<ext>, and the request fails with OUTPUT_PUBLISH_FAILED, whose details.keptPath names the kept file.

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.
alpha false Draw without the project background.
proxies, frames, media, resources, effectAssets, fonts, effects As for export.

The result is { "stills": [{ "output", "frameTime", "width", "height", "publish" }], "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.

To cancel a request, write a cancel line naming it in target:

{"protocolVersion":1,"requestId":"1:cancel","operation":"cancel","target":"1"}

It is answered at once with {"protocolVersion":1,"requestId":"1:cancel","ok":true,"result":{"target":"1","state":"running"}}:

  • running: the target is the request being handled. An export or still stops, removes its partial file, and is answered CANCELLED. The host goes on serving.
  • pending: no request with that id is being handled. When one with that id is read or starts later, it is answered CANCELLED without running.

Keep standard input open until the cancelled request is answered: closing it means “no more requests”, not “stop”. SIGTERM, SIGINT and SIGHUP (a console control event on Windows) cancel the running export the same way and then end the process. When the process that started serve exits, the host cancels what it runs and exits too, so a killed client does not leave an export running.

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, a cancel without a target; 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_DEVICE_HUNG The GPU device was lost to the export’s own work; running it on the GPU again stops the GPU again.
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.
ENCODER_BUSY No video encoder opened, and a hardware one refused because other exports hold its sessions.
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.
OUTPUT_PUBLISH_FAILED The output was rendered in full but could not be moved into place; details.keptPath names where it was kept.
CANCELLED The request 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. An ENCODER_BUSY failure also has retryAfterMs. The type is ExportFailure in alpha2/export-failure, and alpha2/export-retry decides from it whether to retry. See validation and errors.