Skip to content

Node.js and Electron

This content is for v0.4. Switch to the latest version for up-to-date documentation.

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

  1. Add the package:

    Terminal window
    npm install @alpha2-sh/node
  2. Download alpha2-host for this package’s version and platform:

    Terminal window
    npx @alpha2-sh/node install

The package does not include alpha2-host. The install command fetches the platform’s bundled archive from the GitHub release of the same version, checks it against the release’s checksums.sha256, and unpacks it into a cache that every alpha2 client shares (the Python package uses the same one):

  • ALPHA2_HOST_CACHE_DIR when it is set.
  • Otherwise alpha2 in $XDG_CACHE_HOME or ~/.cache on Linux, ~/Library/Caches on macOS, and %LOCALAPPDATA% on Windows.

--thin fetches the archive that loads the system’s FFmpeg 8 instead (not on Windows), and --force downloads again. installHost(options) does the same from code, with cacheDirectory, kind (bundled or thin), force, downloadUrl, onProgress and signal options; it resolves with { hostPath, directory, downloaded }. ALPHA2_HOST_DOWNLOAD_URL (or downloadUrl) names a mirror laid out like https://github.com/alpha2-sh/alpha2/releases/download.

A failed install rejects with Alpha2HostInstallError, whose code is UNSUPPORTED_PLATFORM, DOWNLOAD_FAILED, CHECKSUM_MISMATCH, EXTRACT_FAILED or ABORTED.

The client looks for alpha2-host (alpha2-host.exe on Windows) in this order:

  1. the hostPath option;
  2. the ALPHA2_HOST_PATH environment variable (a relative path is relative to the cwd option, else the working directory);
  3. in Electron, process.resourcesPath/bin/, then process.resourcesPath/;
  4. the package’s own bin/ directory;
  5. the archive of the package’s version in the cache (the cacheDirectory option, else the directory above), bundled before thin;
  6. the directories on PATH.

When none has it, the client throws an Alpha2HostError with code HOST_NOT_FOUND.

import { Alpha2NodeClient } from "@alpha2-sh/node";
const client = new Alpha2NodeClient(); // finds the host `npx @alpha2-sh/node install` put in the cache
const 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 validateProject from alpha2/project before a process starts. An invalid one is refused with an Alpha2ProjectValidationError listing the problems.
  • An existing output is refused unless the request sets overwrite: 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>.alpha2-partial.<ext> beside the output and renames it into place when it is complete.
  • onResult receives 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.

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

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 host stops, removes its partial file and exits, and exportProject rejects with Alpha2AbortError once the process is gone. On Windows the client ends the host process at once, which leaves the partial file; the next export to the same output takes it over.

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 ticks

The PNG is 8-bit RGBA, the picture before any video encoding. size: { width, height } renders at another canvas size.

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 offers

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

Both are client options, and per-export overrides:

  • renderer: auto, gpu or cpu. Unset, the host reads ALPHA2_RENDERER, else uses auto. auto renders on a hardware GPU when there is one and on the CPU otherwise, never on a software GPU adapter (lavapipe, llvmpipe, WARP, SwiftShader). gpu takes whatever adapter it finds and fails with GPU_INIT_FAILED without one. cpu opens no graphics driver. A runtime WGSL effect cannot run on the CPU renderer: such an export fails with EFFECT_ERROR.
  • videoDecode: software, auto or hardware-required. Unset, the host decides: auto on Linux, software on macOS and Windows. Under auto, an export whose hardware decoder stalls runs once more decoding in software. hardware-required fails 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 (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 });

Every rejection is an Alpha2HostError with a code: the protocol’s error codes and the client’s own (HOST_NOT_FOUND, HOST_EXITED, HOST_LOAD_FAILED, 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.

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.