Skip to content

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.

  1. Start alpha2-host serve with pipes for standard input and output.
  2. Write requests, one per line. Each has "protocolVersion": 1, a requestId (a non-empty string) and an operation: capabilities, conform, export or still.
  3. Read lines until the response with your requestId arrives. While an export runs, progress lines arrive first.
  4. 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":"..."}}

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 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. The export is finished only when its response arrives.

  • When the host cannot start its GPU device (or, in hardware-required decode 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. 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.

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",
)
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 input
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.4.6-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:

  • 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, and encode.h264.status (available, gpu-only or unavailable, with reason and remedy).
  • capabilities.features.
  • effectCatalog: the built-in effects and transitions.
  • The adapter and FFmpeg 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.
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.

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.

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.