Node.js and Electron
@alpha2-sh/node is a Node.js client for alpha2-host, the native alpha2 renderer. After reading this page you can install the host, export a project to a video file with progress and cancellation, render still frames, check what the machine can encode, and ship the host inside an Electron app.
The host renders on the GPU (or on the CPU where there is none) and uses FFmpeg 8 to decode and encode. The client starts it as a child process and talks to it over the host protocol.
Install
Section titled “Install”-
Add the package:
Terminal window npm install @alpha2-sh/nodeTerminal window pnpm add @alpha2-sh/nodeTerminal window yarn add @alpha2-sh/node -
Download
alpha2-hostfor this package’s version and platform:Terminal window npx @alpha2-sh/node install
What install does
Section titled “What install does”The package does not include alpha2-host. The install command fetches the platform’s bundled archive from the GitHub release of the same version and unpacks it into a cache that every alpha2 client shares (the Python package uses the same one):
ALPHA2_HOST_CACHE_DIRwhen it is set.- Otherwise
alpha2in$XDG_CACHE_HOMEor~/.cacheon Linux,~/Library/Cacheson macOS, and%LOCALAPPDATA%on Windows.
--force downloads again. installHost(options) does the same from code, with cacheDirectory, force, downloadUrl, onProgress and signal options; it resolves with { hostPath, directory, downloaded, verifiedBy }.
SHA-256 pinning
Section titled “SHA-256 pinning”The published package carries the SHA-256 of every host archive of its version (in dist/host-archives.json). The installer checks the download against that pin, so a mirror cannot substitute an archive, and does not fetch checksums.sha256 at all. A source checkout has no pin, so it checks against the release’s checksums.sha256 instead. The result’s verifiedBy says which: release-pin or checksums.
A failed install rejects with Alpha2HostInstallError, whose code is UNSUPPORTED_PLATFORM, DOWNLOAD_FAILED, CHECKSUM_MISMATCH, EXTRACT_FAILED or ABORTED.
Where the client finds the host
Section titled “Where the client finds the host”The client looks for alpha2-host (alpha2-host.exe on Windows) in this order:
- the
hostPathoption; - the
ALPHA2_HOST_PATHenvironment variable (a relative path is relative to thecwdoption, else the working directory); - in Electron,
process.resourcesPath/bin/, thenprocess.resourcesPath/; - the package’s own
bin/directory; - the archive of the package’s version in the cache (the
cacheDirectoryoption, else the directory above); - the directories on
PATH.
When none has it, the client throws an Alpha2HostError with code HOST_NOT_FOUND. To use a system FFmpeg 8, build the host from source (cargo install alpha2-host) and set ALPHA2_HOST_PATH.
Export a project
Section titled “Export a project”import { Alpha2NodeClient } from "@alpha2-sh/node";
const client = new Alpha2NodeClient(); // finds the host `npx @alpha2-sh/node install` put in the cacheconst output = await client.exportProject({ project, // an alpha2 project document assets: { "image-1": "/media/image.png" }, // the file of each source startTicks: 0, endTicks: 120_000, // ticks: 120,000 a second frameRate: { num: 30, den: 1 }, videoQuality: "high", output: "/exports/tutorial.mp4", onProgress: ({ phase, completed, total }) => console.log(phase, completed, total),});console.log("wrote", output);exportProject resolves with the output path once the file is complete. Each export runs in its own alpha2-host process.
- The client checks the project with
validateProjectfromalpha2/projectbefore a process starts. An invalid one is refused with anAlpha2ProjectValidationErrorlisting theproblems. - An existing
outputis refused unless the request setsoverwrite: true, which replaces it only once the export is whole. An output that is also one of the request’s inputs is always refused. - The host writes
<stem>.a2partial.<ext>beside the output and moves it into place when it is complete. A finished file that cannot be moved there is kept beside it, and the error (OUTPUT_PUBLISH_FAILED) names it inkeptPath. onResultreceives what the host reports about the export: frames, encoder, rate control, timing, which media each source read.
ProjectBuilder from alpha2/builder writes projects whose locators are file paths; its assets() returns the assets map for them. See the browser guide.
Request fields
Section titled “Request fields”ExportProjectRequest types every field; the host protocol lists the same fields with their defaults.
| Field | Default | |
|---|---|---|
project, assets |
required | The document, and a file path per source id. |
startTicks, endTicks |
required | The timeline range, widened to the frame grid. |
frameRate |
required | The output frame rate, { num, den }. |
output |
required | .mp4, .m4v, .mov, .mkv or .webm. |
overwrite |
false |
Replace an existing file once the export is whole. |
container |
the output’s extension’s | mp4, mov, mkv, webm. |
videoCodec |
the container’s (h264, or vp9 for WebM) |
h264, hevc, vp9, av1, mpeg4-part2, prores (MOV only). |
audioCodec |
the container’s (aac, or opus for WebM) |
aac, opus. |
videoQuality |
high |
very-low to very-high, or { bitrate, mode?, quantizer? }. |
audioQuality |
high |
A level, or { bitrate }. |
includeAudio |
true |
|
alpha |
false |
Keep transparency: 8-bit VP9 in WebM or Matroska, or ProRes 4444 in MOV. |
bitDepth |
10 for ProRes, else 8 | 10 needs HEVC, AV1, VP9 or ProRes. |
loudness |
none | { targetLufs, truePeakCeilingDbtp }: normalize the audio. |
fonts |
[] |
Font files for all text, in fallback order. A project that shows text fails without them. |
effects, effectAssets |
none | Runtime effect definitions, and files for effect resources such as .cube LUTs. |
proxies, media |
none, original |
Proxy files per source; media: "proxies" reads them. |
frames, resources |
none | Image sequence frames and files SVG or Lottie sources reference. See extensions. |
renderCache |
none | { directory, quotaBytes?, backlog? }: reuse frames a previous export rendered. |
memory |
host defaults | Byte budgets (inFlightBytes, decodeQueueBytes, decoderSessionBytes, …). |
strategy |
render |
prefer-copy or require-copy copies untouched source packets instead of rendering. |
inputPolicy |
auto |
gpu-required or cpu-readback: how frames reach the encoder. |
videoEncoder |
chosen by the host | An FFmpeg encoder name from the capabilities. |
videoDecode, renderer |
the client’s | Override the client’s settings for this export. |
signal, onProgress, onResult |
Cancellation and callbacks. |
Progress and cancellation
Section titled “Progress and cancellation”onProgress receives { phase, completed, total }. phase is frames while frames are encoded and finalizing while the file is flushed and closed. The export is complete only when exportProject resolves. A promise the callback returns is waited for, and a throw or rejection fails the export with PROGRESS_CALLBACK_FAILED.
signal (an AbortSignal) cancels an export:
const controller = new AbortController();process.once("SIGINT", () => controller.abort());
try { await client.exportProject({ ...request, signal: controller.signal });} catch (error) { if (error.code === "ABORTED") console.log("cancelled"); else throw error;}The client writes a cancel line to the host, which stops the export, removes its partial file and answers. Then the process ends, and exportProject rejects with Alpha2AbortError. A host that does not answer within 5 seconds is ended. The host also watches the process that started it: when that process dies, the host cancels its export and exits.
Still frames
Section titled “Still frames”client.still({ project, assets, time, output }) renders one frame to a PNG, the frame an export draws at that time, without encoding a video. client.stills({ project, assets, stills: [{ time, output }, ...] }) renders up to 256 frames with one host, decoding once. time is in ticks.
const result = await client.still({ project, assets, time: 60_000, output: "/tmp/check.png" });console.log(result.stills[0].frameTime); // the grid frame that holds 0.5 s, in ticksThe PNG is 8-bit RGBA, the picture before any video encoding. size: { width, height } renders at another canvas size, and alpha: true draws without the project background.
Check what the machine can do
Section titled “Check what the machine can do”client.getCapabilities() reports what the host can decode, render and encode:
const { capabilities } = await client.getCapabilities();const { encode } = capabilities;if (encode.h264.status !== "available") console.warn(encode.h264.reason, encode.h264.remedy);console.log(encode.containers); // which codecs each container offersAn MP4, MOV or Matroska export without videoCodec gets H.264, and a host that cannot encode H.264 refuses the export rather than write another codec. encode.h264.status sums this up: available (every export can), gpu-only (only exports the GPU-resident path takes) or unavailable, with a reason and a remedy. Where no hardware H.264 encoder exists, install Cisco’s OpenH264 with npx @alpha2-sh/node install --openh264 (see CLI).
The first getCapabilities call for a decode mode and renderer starts a host. Calls made while it runs share it, and its answer serves later calls.
client.conform({ project, assets }) checks the media without rendering and without a GPU. It resolves with { catalog, report }; report.ready is false when something blocks an export.
Renderer and hardware decode
Section titled “Renderer and hardware decode”Both are client options, and per-export overrides:
renderer:auto,gpuorcpu. Unset, the host readsALPHA2_RENDERER, else usesauto.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 it finds and fails withGPU_INIT_FAILEDwithout one.cpuopens no graphics driver. A runtime WGSL effect cannot run on the CPU renderer: such an export fails withEFFECT_ERROR.videoDecode:software,autoorhardware-required. Unset, the host decides:autoon Linux and macOS,softwareon Windows. Underauto, an export whose hardware decoder stalls runs once more decoding in software.hardware-requiredfails instead of decoding a frame on the CPU.
const client = new Alpha2NodeClient({ renderer: "auto", videoDecode: "auto", concurrency: 2 });Results and capabilities report renderer (gpu or cpu), rendererReason (why the host renders on the CPU) and, on the CPU, rendererThreads.
Concurrency
Section titled “Concurrency”concurrency (default 1) is how many host processes a client runs at once, and so how many exports share the GPU. More requests wait for a slot. The module-level exportProject(request, options), still, stills and getCapabilities(options) share one client per host binary and concurrency:
import { exportProject } from "@alpha2-sh/node";
await exportProject(request, { concurrency: 2 });Errors
Section titled “Errors”Every rejection is an Alpha2HostError with a code: the protocol’s error codes and the client’s own (HOST_NOT_FOUND, HOST_EXITED, HOST_OOM_KILLED, HOST_LOAD_FAILED, HOST_START_TIMEOUT, HOST_QUARANTINED, HOST_BLOCKED, ABORTED, and others). Its failure classifies it for a retry policy (alpha2/export-failure, alpha2/export-retry), and stderr holds the end of the host’s standard error.
| Subclass | When |
|---|---|
Alpha2ProjectValidationError |
The project is invalid (problems). |
Alpha2ConformError |
The media is not ready, so nothing rendered (report says what is missing, offline or not the recorded media). |
Alpha2GpuInitializationError |
The host could not create its GPU device (only with renderer: "gpu"). |
Alpha2AbortError |
The request’s signal aborted it. |
Electron
Section titled “Electron”Run the client in the main process. Ship the archive’s bin/ and lib/ outside the ASAR archive, for example as bin/ and lib/ in the application’s resources directory (process.resourcesPath). The client looks there in an Electron main process. Keep bin/ and lib/ side by side: the host finds the FFmpeg libraries relative to itself.
- CLI: the same host from a shell.
- Host protocol: every request field and error code.
- Editor: send editor documents to an export service built on this package.
