@alpha2-sh/node 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,814 @@
1
+ import type { AssetCatalog, ConformReport, MediaPurpose, Selection } from "alpha2/assets";
2
+ import type { CapabilityReport } from "alpha2";
3
+ import type { ExportFailure, ExportPhase } from "alpha2/export-failure";
4
+ import type { BuiltinEffectCatalog, EffectDefinition } from "alpha2/effects";
5
+ import type { FrameRate, MediaFingerprint, Project } from "alpha2/project";
6
+ import { type ProgressPhase } from "./protocol.js";
7
+ export { PROTOCOL_VERSION } from "./protocol.js";
8
+ export type { ProgressPhase } from "./protocol.js";
9
+ export type { ExportFailure } from "alpha2/export-failure";
10
+ /** Where to find `alpha2-host`; see {@link resolveHostBinary}. */
11
+ export interface ResolveHostBinaryOptions {
12
+ /** Exact binary path. Takes precedence over every discovery mechanism. */
13
+ readonly hostPath?: string;
14
+ /** Electron's `process.resourcesPath`, injectable for non-Electron callers. */
15
+ readonly resourcesPath?: string;
16
+ /** Base directory for relative explicit paths. Defaults to `process.cwd()`. */
17
+ readonly cwd?: string;
18
+ }
19
+ /** Options of an {@link Alpha2NodeClient}. */
20
+ export interface Alpha2NodeOptions extends ResolveHostBinaryOptions {
21
+ /** Maximum simultaneous sidecars. Defaults to one. */
22
+ readonly concurrency?: number;
23
+ /**
24
+ * Video decode path for every request this client makes. When it is unset the host uses its
25
+ * platform default, which is "auto" on Linux and "software" on macOS and Windows. "auto" uses the
26
+ * GPU decoder where the device and stream support it, and "hardware-required" fails instead of
27
+ * decoding a video frame on the CPU.
28
+ */
29
+ readonly videoDecode?: VideoDecodeMode;
30
+ }
31
+ /** Video decode path requested from the native host. */
32
+ export type VideoDecodeMode = "software" | "auto" | "hardware-required";
33
+ /**
34
+ * The engine's five quality levels. The engine defines what each one costs (a bitrate that depends
35
+ * on codec, output size and frame rate) and the host resolves it before the encoder opens; an app
36
+ * maps its own presets onto these rather than inventing rates.
37
+ */
38
+ export type QualityLevel = "very-low" | "low" | "medium" | "high" | "very-high";
39
+ /** Every {@link QualityLevel}, lowest first. */
40
+ export declare const QUALITY_LEVELS: readonly QualityLevel[];
41
+ /** A quality level, or an explicit rate in bits per second. */
42
+ export type VideoQuality = QualityLevel | {
43
+ readonly bitrate: number;
44
+ /** Defaults to `variable`. */
45
+ readonly mode?: "constant" | "variable";
46
+ /**
47
+ * A constant quantizer on the codec's own scale (0-51 for H.264). The encoder runs it where
48
+ * it can and falls back to `bitrate` where it cannot; `videoRateControl.applied` reports
49
+ * which ran.
50
+ */
51
+ readonly quantizer?: number;
52
+ };
53
+ /** A quality level, or an explicit stereo bitrate in bits per second. */
54
+ export type AudioQuality = QualityLevel | {
55
+ readonly bitrate: number;
56
+ };
57
+ /** The rate an export actually encoded video at, as the host resolved it. */
58
+ export interface ResolvedVideoRateControl {
59
+ readonly bitrate: number;
60
+ readonly mode: "constant" | "variable";
61
+ readonly quantizer: number | null;
62
+ /**
63
+ * What the encoder ran: `quantizer`, or `bitrate` because none was asked for or because the
64
+ * encoder refused it (`quantizerRefused` says why).
65
+ */
66
+ readonly applied: "quantizer" | "bitrate";
67
+ readonly quantizerRefused: string | null;
68
+ /** The level the request named, or null for an explicit rate. */
69
+ readonly level: QualityLevel | null;
70
+ /**
71
+ * `env-override` means `ALPHA2_EXPORT_VIDEO_BITRATE` was set in the host's environment and won
72
+ * over the request. It is test instrumentation, never a production setting.
73
+ */
74
+ readonly source: "request" | "env-override";
75
+ }
76
+ /** Whether an export may copy its source's packets; see {@link ExportProjectRequest.strategy}. */
77
+ export type ExportStrategy = "render" | "prefer-copy" | "require-copy";
78
+ /** How an export's encoder receives frames; see {@link ExportProjectRequest.inputPolicy}. */
79
+ export type InputPolicy = "auto" | "gpu-required" | "cpu-readback";
80
+ /** One progress report of an export. */
81
+ export interface ExportProgress {
82
+ /**
83
+ * `frames` while frames are encoded; `finalizing` once every frame is and the output is being
84
+ * flushed and closed. The export is complete only when `exportProject` resolves.
85
+ */
86
+ readonly phase: ProgressPhase;
87
+ /** Frames encoded so far; at most `total`. */
88
+ readonly completed: number;
89
+ /** Frames in the export. */
90
+ readonly total: number;
91
+ }
92
+ /**
93
+ * Receives an export's progress. A promise it returns is waited for before the export settles, and
94
+ * its rejection fails the export.
95
+ */
96
+ export type ExportProgressCallback = (progress: ExportProgress) => void | Promise<void>;
97
+ /** Successful metadata returned by the native host after an export. */
98
+ export interface NativeExportResult {
99
+ readonly renderMs: number;
100
+ readonly frames: number;
101
+ readonly audio: boolean;
102
+ readonly fps: number;
103
+ /**
104
+ * Which output path produced this export. Several `performance` counters are structurally absent
105
+ * on one of them rather than merely zero, so a reader comparing two exports needs this to tell
106
+ * the two kinds of zero apart.
107
+ */
108
+ readonly videoOutputPath: "readback" | "gpu-resident" | "packet-copy";
109
+ /**
110
+ * Why the export took the readback path: the host has no GPU encode device, the codec or a pinned
111
+ * encoder is not the GPU path's, `inputPolicy` is `cpu-readback`, an old validation layer,
112
+ * `ALPHA2_GPU_ENCODE=0`, an output the encoder refuses, or an in-flight budget too small for it.
113
+ * `null` on the GPU-resident path.
114
+ */
115
+ readonly gpuOutputPathUnavailable: string | null;
116
+ /** The request's {@link ExportProjectRequest.inputPolicy}. */
117
+ readonly inputPolicy: InputPolicy;
118
+ /** What the export did: rendered and encoded every frame, or copied the source's packets. */
119
+ readonly strategy: "render" | "copy";
120
+ /** The request's {@link ExportProjectRequest.strategy}. */
121
+ readonly requestedStrategy: ExportStrategy;
122
+ /** Why a `prefer-copy` export rendered instead; `null` when it copied or did not ask to. */
123
+ readonly copyIneligible: string | null;
124
+ /**
125
+ * A copy's audio: `copy` when the mix is the source's own audio, untouched, over the whole file,
126
+ * so its packets are copied; `encode` when it is mixed as a render mixes it and encoded to AAC
127
+ * beside the copied video; `null` without audio. Absent on rendered exports.
128
+ */
129
+ readonly audioStrategy?: "copy" | "encode" | null;
130
+ /** Why a copy's audio was encoded rather than copied. Absent on rendered exports. */
131
+ readonly audioCopyIneligible?: string | null;
132
+ /** Clips a copy joined back to back. Absent on rendered exports. */
133
+ readonly copiedSegments?: number;
134
+ /**
135
+ * How a GPU-resident export fed its encoder without rendering: `yuv-repack` when every frame was
136
+ * a source picture shown as it is (the same clips a copy needs, any codec or frame rate, each
137
+ * source already at the output size, upright with square pixels, tagged BT.709 limited 8-bit
138
+ * 4:2:0), so each decoded picture was copied plane for plane into the encoder's input with no
139
+ * graph and no color conversion (`performance.yuvRepacks`, with `graphPasses` and
140
+ * `sourceConversions` zero). `null` when it rendered, and on a copy.
141
+ */
142
+ readonly videoTranscode: "yuv-repack" | null;
143
+ /** Why a rendered export did not transcode; `null` when it did, and on a copy. */
144
+ readonly yuvTranscodeIneligible: string | null;
145
+ /**
146
+ * The timeline range the export covered: the requested range widened to the frame grid.
147
+ * `startTicks` is the output's zero for video and audio, and can be up to one frame before the
148
+ * requested start.
149
+ */
150
+ readonly effectiveRange: {
151
+ readonly startTicks: number;
152
+ readonly endTicks: number;
153
+ };
154
+ /** The color pipeline the export ran. */
155
+ readonly color: {
156
+ readonly workingSpace: "linear_bt709_sdr";
157
+ /**
158
+ * What each intermediate picture was stored in: half float wherever the adapter renders,
159
+ * filters, and copies it, 8-bit elsewhere; the tier does not decide it.
160
+ */
161
+ readonly intermediateFormat: "rgba16float" | "rgba8unorm";
162
+ /** The project's HDR source policy, and the peak HDR sources were taken to reach. */
163
+ readonly hdrSources: "tone_map" | "reject";
164
+ readonly hdrPeakNits: number;
165
+ readonly output: "bt709_sdr";
166
+ };
167
+ /** What the export's render cache did; `null` when it asked for none. */
168
+ readonly renderCache: RenderCacheReport | null;
169
+ /**
170
+ * What extension providers reported they could not draw as asked (a missing font or image, a
171
+ * feature left out, changed samples, a normalized delay), each once per source in the order first
172
+ * seen; `[]` when none did.
173
+ */
174
+ readonly providerDiagnostics: readonly ProviderDiagnostic[];
175
+ /** Distinct provider reports past the 256 `providerDiagnostics` keeps. */
176
+ readonly providerDiagnosticsOmitted: number;
177
+ /**
178
+ * What the export's decoders reported about damaged input, per source in source ID order: what a
179
+ * `tolerant` decode policy (`ALPHA2_DECODE_POLICY`) skipped, and silence inserted where a clean
180
+ * stream's timestamps jump; `[]` when they reported nothing.
181
+ */
182
+ readonly decodeDiagnostics: readonly DecodeDiagnostics[];
183
+ /**
184
+ * What the renderer's result cache did: repeated frames and unchanged clip pictures are found
185
+ * instead of drawn, with the same output.
186
+ */
187
+ readonly resultCache: {
188
+ readonly budgetBytes: number;
189
+ readonly hits: number;
190
+ readonly misses: number;
191
+ readonly ineligible: number;
192
+ readonly inserts: number;
193
+ readonly evictions: number;
194
+ readonly skippedNodes: number;
195
+ };
196
+ /**
197
+ * The loudness normalization an export asked for: what the mix measured before it, the gain
198
+ * applied (`applied` is the plan, `null` for a mix silent below -70 LUFS), and what the export
199
+ * put out (`output`, measured after the master limiter). `null` when the request had no
200
+ * `loudness`.
201
+ */
202
+ readonly loudness: {
203
+ readonly targetLufs: number;
204
+ readonly truePeakCeilingDbtp: number;
205
+ /** The master limiter's linear threshold the plan kept the mix under, `null` without one. */
206
+ readonly limiterThreshold: number | null;
207
+ readonly measured: {
208
+ readonly integratedLufs: number | null;
209
+ readonly loudnessRangeLu: number | null;
210
+ readonly truePeakDbtp: number | null;
211
+ readonly samplePeakDbfs: number | null;
212
+ };
213
+ readonly applied: {
214
+ readonly gainDb: number;
215
+ readonly integratedLufs: number;
216
+ readonly truePeakDbtp: number;
217
+ readonly peakLimited: boolean;
218
+ readonly limiterLimited: boolean;
219
+ } | null;
220
+ /** The exported mix, after the master limiter, before the audio encoder. */
221
+ readonly output: {
222
+ readonly integratedLufs: number | null;
223
+ readonly loudnessRangeLu: number | null;
224
+ readonly truePeakDbtp: number | null;
225
+ readonly samplePeakDbfs: number | null;
226
+ };
227
+ } | null;
228
+ /**
229
+ * The video rate control the encoder was opened with; `null` for a copy, which encodes nothing.
230
+ */
231
+ readonly videoRateControl: ResolvedVideoRateControl | null;
232
+ /** The audio bitrate, when the export has audio. */
233
+ readonly audioRateControl: {
234
+ readonly bitrate: number;
235
+ readonly level: QualityLevel | null;
236
+ } | null;
237
+ /** Timing and pipeline counters. Their names are diagnostic and may change between versions. */
238
+ readonly performance: ExportPerformance;
239
+ /** The media each source read: its original, or a proxy when the request asked for proxies. */
240
+ readonly media: Readonly<Record<string, Selection>>;
241
+ readonly [metadata: string]: unknown;
242
+ }
243
+ /**
244
+ * An export's timing and pipeline counters (see the host protocol). Most are numbers, flags or
245
+ * names; the ones that are not are declared. A copy reports a few counters of its own and none of
246
+ * the declared ones.
247
+ */
248
+ export interface ExportPerformance {
249
+ /** Per reason a source left the hardware decode path, how many times it did. */
250
+ readonly hardwareFallbackReasons?: Readonly<Record<string, number>>;
251
+ /**
252
+ * How many times the encode worker took a command and left each number of frames behind in its
253
+ * channel, indexed by that number.
254
+ */
255
+ readonly encodeChannelDepthSamples?: readonly number[];
256
+ /** What the GPU-resident path reserved from the in-flight budget; `null` on the readback path. */
257
+ readonly gpuEncodeBudget?: GpuEncodeBudget | null;
258
+ readonly [counter: string]: PerformanceValue | undefined;
259
+ }
260
+ /** A value of {@link ExportPerformance}. */
261
+ export type PerformanceValue = number | boolean | string | null | readonly number[] | Readonly<Record<string, number>> | GpuEncodeBudget;
262
+ /** The device memory the GPU-resident path reserved, in bytes and surfaces. */
263
+ export interface GpuEncodeBudget {
264
+ readonly surfaceBytes: number;
265
+ readonly planeRingBytes: number;
266
+ readonly leaseBound: number;
267
+ readonly retainedSurfaces: number;
268
+ readonly surfaceCeiling: number;
269
+ readonly reservedBytes: number;
270
+ }
271
+ /**
272
+ * One extension provider report: the source it is about, its `kind` (`missing_font`,
273
+ * `missing_image`, `unreadable_image`, `unsupported_feature`, `unreadable_item`,
274
+ * `normalized_delay`, `non_finite_samples`, `out_of_half_range`, `reduced_precision`), and that
275
+ * kind's fields.
276
+ */
277
+ export interface ProviderDiagnostic {
278
+ readonly source: string;
279
+ readonly kind: string;
280
+ readonly [field: string]: unknown;
281
+ }
282
+ /**
283
+ * What one source's decoders reported during an export. Audio samples are at the project's sample
284
+ * rate.
285
+ */
286
+ export interface DecodeDiagnostics {
287
+ readonly source: string;
288
+ /**
289
+ * Corruption observations: FFmpeg may report one damaged packet at more than one stage, so this
290
+ * is not a count of distinct packets.
291
+ */
292
+ readonly corruptPackets: number;
293
+ /** Runs of dropped video between healthy frames, an unrecovered tail included. */
294
+ readonly videoGaps: number;
295
+ /** Audio samples decoded as silence in place of damaged input. */
296
+ readonly concealedAudioSamples: number;
297
+ /** Audio samples decoded as silence where a clean stream's timestamps jump. */
298
+ readonly timestampGapSamples: number;
299
+ /** Audio samples dropped because their timestamps repeated samples already decoded. */
300
+ readonly droppedAudioSamples: number;
301
+ }
302
+ /** Where {@link ExportProjectRequest.renderCache} bakes frames. */
303
+ export interface RenderCacheOptions {
304
+ readonly directory: string;
305
+ /** The most bytes the directory holds. Default 4 GiB. */
306
+ readonly quotaBytes?: number;
307
+ /**
308
+ * A frame rendered while the host's cache writer is full: `"skip"` (default) leaves it unbaked so
309
+ * the cache never slows the export; `"wait"` holds the export until it is baked.
310
+ */
311
+ readonly backlog?: "skip" | "wait";
312
+ }
313
+ /** What an export's render cache did. */
314
+ export type RenderCacheReport = {
315
+ readonly status: "used";
316
+ readonly hits: number;
317
+ readonly writes: number;
318
+ readonly bytesRead: number;
319
+ readonly bytesWritten: number;
320
+ /** Frames a bypassed clip kept out of the cache. */
321
+ readonly bypassedFrames: number;
322
+ /** Frames that failed their checksum, deleted and rendered again. */
323
+ readonly corrupt: number;
324
+ readonly writeErrors: number;
325
+ readonly evictions: number;
326
+ /** Frames not baked because the quota had no room. */
327
+ readonly quotaSkips: number;
328
+ /** Frames not baked because the writer was full (`backlog: "skip"`). */
329
+ readonly busySkips: number;
330
+ readonly quotaBytes: number;
331
+ readonly backlog: "skip" | "wait";
332
+ /**
333
+ * The cache's writer or reader was left stuck in the directory's
334
+ * filesystem; the export rendered every frame it had not taken by
335
+ * then, the writer's counts are what it had done, and the host process
336
+ * uses the directory no more.
337
+ */
338
+ readonly abandoned: boolean;
339
+ } | {
340
+ readonly status: "unavailable";
341
+ readonly reason: string;
342
+ };
343
+ /** A loudness target for {@link ExportProjectRequest.loudness}. */
344
+ export interface LoudnessTarget {
345
+ /** Integrated loudness, `-60..=0` LUFS; streaming platforms use -14 to -16. */
346
+ readonly targetLufs: number;
347
+ /** True-peak ceiling, `-20..=0` dBTP; EBU R128 uses -1. */
348
+ readonly truePeakCeilingDbtp: number;
349
+ }
350
+ /** A proxy made from a source's original. */
351
+ export interface ProxyFile {
352
+ readonly path: string;
353
+ /** Evidence of the original it was made from, when known. */
354
+ readonly madeFrom?: MediaFingerprint;
355
+ /** The color pipeline baked into it, when it was tone-mapped. */
356
+ readonly colorPipeline?: string;
357
+ }
358
+ /** What {@link Alpha2NodeClient.exportProject} renders, and where it writes it. */
359
+ export interface ExportProjectRequest {
360
+ readonly project: Project;
361
+ /**
362
+ * A file path per source id, for the sources a render reads. The host checks them before it
363
+ * renders and refuses with an {@link Alpha2ConformError} listing what is missing, offline, or not
364
+ * the media the project recorded.
365
+ */
366
+ readonly assets: Readonly<Record<string, string>>;
367
+ /** Proxies per source id, in order of preference. */
368
+ readonly proxies?: Readonly<Record<string, readonly ProxyFile[]>>;
369
+ /**
370
+ * Each image sequence's frame files in the project's order, per source id; `null` where the
371
+ * sequence has a gap.
372
+ */
373
+ readonly frames?: Readonly<Record<string, readonly (string | null)[]>>;
374
+ /**
375
+ * Files an SVG source's `<image href>` or a Lottie source's image assets name outside the source,
376
+ * per source id and then per locator as the source writes it
377
+ * (`{ label: { "art/badge.png": "/media/badge.png" } }`): what a browser page registers with
378
+ * `registerResource`. A locator not given draws as missing and is reported as `missing_image`.
379
+ * Resources for a source that is not an SVG or Lottie source of the project are `INVALID_INPUT`.
380
+ */
381
+ readonly resources?: Readonly<Record<string, Readonly<Record<string, string>>>>;
382
+ /**
383
+ * `original` (the default) reads originals only; `proxies` reads fresh proxies where there are
384
+ * some. The result's `media` says what each source read.
385
+ */
386
+ readonly media?: "original" | "proxies";
387
+ /** A file path per effect resource id (LUT files and the like). */
388
+ readonly effectAssets?: Readonly<Record<string, string>>;
389
+ /**
390
+ * Font files caption tracks, text generators, and SVG text draw with, in fallback order. A
391
+ * project that shows text fails without them.
392
+ */
393
+ readonly fonts?: readonly string[];
394
+ /** Definitions of custom effects the project uses beyond the built-in catalog. */
395
+ readonly effects?: readonly EffectDefinition[];
396
+ /** The timeline range to export, in ticks; widened to the frame grid (see `effectiveRange`). */
397
+ readonly startTicks: number;
398
+ readonly endTicks: number;
399
+ /** The output frame rate. */
400
+ readonly frameRate: FrameRate;
401
+ /** Whether the output has an audio track. Defaults to `true`. */
402
+ readonly includeAudio?: boolean;
403
+ /** Defaults to `h264`. */
404
+ readonly videoCodec?: "h264" | "hevc" | "vp9" | "av1" | "mpeg4-part2";
405
+ /**
406
+ * Concrete FFmpeg encoder short name: one of `capabilities.encode.video`, or the GPU path's
407
+ * `capabilities.encode.gpuOutputPath.encoder`. Pinning the GPU path's encoder asks for the
408
+ * GPU-resident path when it takes GPU frames only (`gpuOutputPath.gpuInputOnly`, as `h264_vulkan`
409
+ * does); `h264_videotoolbox` is in both and runs on whichever path `inputPolicy` selects.
410
+ */
411
+ readonly videoEncoder?: string;
412
+ /**
413
+ * How the encoder receives frames: `auto` (default) takes the GPU-resident path where the host,
414
+ * codec, size, and budget allow it and the readback path otherwise; `gpu-required` fails with
415
+ * `GPU_OUTPUT_UNAVAILABLE` instead of falling back; `cpu-readback` always reads back. The
416
+ * result's `videoOutputPath` says which ran.
417
+ */
418
+ readonly inputPolicy?: InputPolicy;
419
+ /**
420
+ * `render` (default) renders and encodes every frame.
421
+ *
422
+ * `prefer-copy` copies the sources' compressed packets instead when both hold:
423
+ *
424
+ * - the range is its sources shown untouched: clips that tile it one after another, each with an
425
+ * identity transform and no composition, mask, matte, caption, transition, or retime, none
426
+ * another clip's matte, cut at IDR pictures and encoded alike;
427
+ * - its packets under the range decode on their own into exactly those frames: an MP4/MOV H.264
428
+ * or HEVC source already at the output size, tagged BT.709 limited 8-bit 4:2:0, neither
429
+ * mirrored nor shown with non-square pixels (the container's declared pixel aspect ratio, else
430
+ * the codec's) nor declaring non-square pixels in the codec, on the export's frame grid,
431
+ * starting at an IDR picture.
432
+ *
433
+ * A copy's audio, if any, is copied when it is the only sound the render hears and the source's
434
+ * own, untouched, over the whole file; otherwise it is mixed as a render mixes it (silence
435
+ * included) and encoded to AAC beside the copied video. `videoQuality` does not apply to a copy,
436
+ * which keeps the source's encoding. When the range does not qualify, `prefer-copy` renders and
437
+ * says why in `copyIneligible`; `require-copy` fails with `COPY_INELIGIBLE` instead.
438
+ */
439
+ readonly strategy?: ExportStrategy;
440
+ /** Output video quality. Defaults to `high`. */
441
+ readonly videoQuality?: VideoQuality;
442
+ /** Defaults to `aac`. */
443
+ readonly audioCodec?: "aac";
444
+ /** Output audio quality. Defaults to `high`. */
445
+ readonly audioQuality?: AudioQuality;
446
+ /**
447
+ * Normalizes the export's audio to an integrated loudness (ITU-R BS.1770-4, EBU R128), keeping
448
+ * its true peak at or below a ceiling. Needs `includeAudio` and a project that uses an audible
449
+ * source; the host refuses it otherwise with `INVALID_INPUT`. The host measures the mix in a
450
+ * first pass.
451
+ */
452
+ readonly loudness?: LoudnessTarget;
453
+ /**
454
+ * A directory to bake the export's frames into and find them in: a later export of the same
455
+ * snapshot (the same host build, GPU, project, files, and effects) takes each baked frame instead
456
+ * of rendering it, and writes the same file. `quotaBytes` (default 4 GiB) bounds the directory;
457
+ * the least recently used frames go first, pinned clips' frames last. Frames are baked on the
458
+ * readback path only, on a writer thread beside the export; `backlog` says whether a frame the
459
+ * writer has no room for is skipped or waited for. The result's `renderCache` says what happened.
460
+ */
461
+ readonly renderCache?: RenderCacheOptions;
462
+ /**
463
+ * Byte budgets for this export, replacing the host's environment settings
464
+ * (`ALPHA2_EXPORT_IN_FLIGHT_BYTES` and the rest) and defaults field by field. Each bounds memory
465
+ * the host reserves before it allocates; an export whose least working set does not fit fails
466
+ * with `RESOURCE_LIMIT` before it renders. The result's `memoryPolicy` reports what applied and
467
+ * where each value came from.
468
+ */
469
+ readonly memory?: ExportMemoryBudget;
470
+ /** Overrides the client's decode mode for this export. */
471
+ readonly videoDecode?: VideoDecodeMode;
472
+ /** The MP4 file to write. */
473
+ readonly output: string;
474
+ /**
475
+ * Cancels the export: the host removes its partial file and exits, and the promise rejects with
476
+ * `Alpha2AbortError` once it has.
477
+ */
478
+ readonly signal?: AbortSignal;
479
+ /**
480
+ * Receives each progress report. A throw, or a rejection of the promise it returns, fails the
481
+ * export with `PROGRESS_CALLBACK_FAILED` and ends its host; the export settles only after every
482
+ * promise it returned has.
483
+ */
484
+ readonly onProgress?: ExportProgressCallback;
485
+ /**
486
+ * Receives native timing and pipeline metrics after a successful export. The export resolves
487
+ * after the promise it returns does; a throw or a rejection rejects the export with that error.
488
+ */
489
+ readonly onResult?: (result: NativeExportResult) => void | Promise<void>;
490
+ }
491
+ /** The byte budgets an export runs within. See `memoryPolicy` in the host protocol. */
492
+ export interface ExportMemoryBudget {
493
+ /** Rendered frames between the GPU and the encoder. Default 256 MiB. */
494
+ readonly inFlightBytes?: number;
495
+ /** Decoded source frames waiting to be rendered. Default 256 MiB. */
496
+ readonly decodeQueueBytes?: number;
497
+ /** Frames a reversed clip keeps decoded. Default 256 MiB. */
498
+ readonly reverseCacheBytes?: number;
499
+ /** Decoded audio kept around the mix window. Default 256 MiB. */
500
+ readonly audioWindowBytes?: number;
501
+ /**
502
+ * What the open extension-provider sources (SVG, animated images, Lottie, OpenEXR, image
503
+ * sequences) hold together; past it the host closes the ones the last two frames did not show and
504
+ * opens them again when shown. Default 512 MiB.
505
+ */
506
+ readonly providerSourceBytes?: number;
507
+ /**
508
+ * A fraction in (0, 1] every budget is scaled by once resolved: how a retry asks for less in
509
+ * flight.
510
+ */
511
+ readonly scale?: number;
512
+ }
513
+ /** One encoder a native host can feed. */
514
+ export interface EncodeCapability {
515
+ /** The codec id, such as `h264` or `aac`. */
516
+ readonly id: string;
517
+ /** The FFmpeg encoder short name, as `videoEncoder` takes it. */
518
+ readonly encoder: string;
519
+ /**
520
+ * How the linked runtime declares this encoder executes, corrected for the encoders the pinned
521
+ * runtime misdeclares (`h264_videotoolbox` is `hardware`). `hybrid` is a MediaFoundation
522
+ * encoder, which is a software transform unless the host is configured to ask for a hardware one.
523
+ */
524
+ readonly encoderClass?: "software" | "hardware" | "hybrid";
525
+ readonly hardwareAccelerated?: boolean;
526
+ readonly preferred?: boolean;
527
+ /** Smallest output the encoder accepts, in pixels; `null` when it reports none. */
528
+ readonly minimumWidth?: number | null;
529
+ readonly minimumHeight?: number | null;
530
+ }
531
+ /**
532
+ * The GPU-resident output path, which the encoder list cannot describe.
533
+ *
534
+ * `capabilities.encode.video` lists the encoders the host's software sinks can feed. On Linux the
535
+ * GPU path's encoder (`h264_vulkan`) takes hardware frames and nothing else, so it is not in that
536
+ * list; on macOS it is `h264_videotoolbox`, which is. This says whether the path exists on this
537
+ * host and what it accepts, so a caller can tell before exporting whether its output will be
538
+ * GPU-resident: the codec must be the one named here, no other encoder may be pinned (pinning a
539
+ * `gpuInputOnly` encoder asks for this path), and the output size must be even and within the
540
+ * reported extents. Under `inputPolicy: "auto"` an export outside them still succeeds on the
541
+ * readback path; under `gpu-required` it fails with `GPU_OUTPUT_UNAVAILABLE`. Every export reports
542
+ * the path it took as `videoOutputPath`.
543
+ */
544
+ export interface GpuOutputPathCapability {
545
+ readonly available: boolean;
546
+ /** Why the path is unavailable, or null when it is available. */
547
+ readonly reason: string | null;
548
+ /** The native API the encoder runs on: `vulkan` or `videotoolbox`. */
549
+ readonly backend: string | null;
550
+ readonly encoder: string | null;
551
+ readonly videoCodec: string | null;
552
+ /** The surface layout the encoder takes: `nv12`. */
553
+ readonly inputLayout: string | null;
554
+ /**
555
+ * How a packed frame reaches the encoder: `gpu-copy` renders into the backend's planes and copies
556
+ * them on the GPU; `direct-target` renders into the encoder's own surface.
557
+ */
558
+ readonly inputHandoff: string | null;
559
+ /** Whether the encoder takes GPU frames only, so that pinning it asks for this path. */
560
+ readonly gpuInputOnly: boolean | null;
561
+ /** Whether the encoder runs on the renderer's own device. */
562
+ readonly sharesRendererDevice: boolean | null;
563
+ /** Smallest output the device's encoder accepts, in pixels. */
564
+ readonly minimumWidth: number | null;
565
+ readonly minimumHeight: number | null;
566
+ readonly maximumWidth: number | null;
567
+ readonly maximumHeight: number | null;
568
+ /**
569
+ * Granularity the output is aligned up to before the extents apply. A 158x62 output is accepted
570
+ * where 64x48 is not, because the first aligns up to the minimum coded extent and the second does
571
+ * not.
572
+ */
573
+ readonly widthAlignment: number | null;
574
+ readonly heightAlignment: number | null;
575
+ /**
576
+ * The device's H.264 encode reference limits. B pictures need a non-zero
577
+ * `maxBPictureL0References` or `maxL1References`; absent when the path is unavailable, and null
578
+ * when the encoder does not report them (VideoToolbox).
579
+ */
580
+ readonly maxPPictureL0References?: number | null;
581
+ readonly maxBPictureL0References?: number | null;
582
+ readonly maxL1References?: number | null;
583
+ /** Rate-control modes the device reports: `cbr`, `vbr`, `disabled` (constant QP). */
584
+ readonly rateControlModes?: readonly ("cbr" | "vbr" | "disabled")[];
585
+ /**
586
+ * Plane-texture pairs the renderer packs into besides the encoder's surfaces; 0 for
587
+ * `direct-target`.
588
+ */
589
+ readonly planeRingSlots: number | null;
590
+ /**
591
+ * Surfaces the handoff and the encoder still hold after the host lets go; the in-flight byte
592
+ * budget pays for them too.
593
+ */
594
+ readonly retainedSurfaces: number | null;
595
+ readonly requiresEvenDimensions: boolean;
596
+ /**
597
+ * Every codec the path takes on this device, each with its own limits; the fields above describe
598
+ * the first. One entry on Linux and macOS; on Windows each of H.264, HEVC and AV1 the device
599
+ * encodes. Empty when the path is unavailable.
600
+ */
601
+ readonly codecs?: readonly GpuOutputCodec[];
602
+ }
603
+ /** What the GPU-resident path's encoder takes for one codec. */
604
+ export interface GpuOutputCodec {
605
+ readonly videoCodec: string;
606
+ readonly encoder: string;
607
+ readonly minimumWidth: number;
608
+ readonly minimumHeight: number;
609
+ readonly maximumWidth: number;
610
+ readonly maximumHeight: number;
611
+ readonly widthAlignment: number;
612
+ readonly heightAlignment: number;
613
+ readonly maxPPictureL0References: number | null;
614
+ readonly maxBPictureL0References: number | null;
615
+ readonly maxL1References: number | null;
616
+ readonly rateControlModes: readonly string[];
617
+ }
618
+ /** One codec's hardware decode registration in the linked FFmpeg build. */
619
+ export interface DecodeCodecCapability {
620
+ readonly id: string;
621
+ readonly decoder: string | null;
622
+ /**
623
+ * Whether the decoder advertises this platform's hardware configuration. Registration only: a
624
+ * device, profile, and surface format are still decided per stream, and every export reports the
625
+ * path its frames took.
626
+ */
627
+ readonly hardwareConfig: boolean;
628
+ /**
629
+ * The decoder a software session opens for the codec, or `null` when only a hardware session
630
+ * decodes it (AV1 on a runtime built without libdav1d; the pinned FFmpeg SDK decodes it with
631
+ * `libdav1d`). A source with such a stream is `unsupported` on a host that cannot decode it in
632
+ * hardware.
633
+ */
634
+ readonly softwareDecoder: string | null;
635
+ }
636
+ /** What a native host encodes. */
637
+ export interface HostEncodeCapabilities {
638
+ readonly container: "mp4";
639
+ /** Encoders the host's software sinks can feed. */
640
+ readonly video: readonly EncodeCapability[];
641
+ readonly audio: readonly EncodeCapability[];
642
+ readonly gpuOutputPath: GpuOutputPathCapability;
643
+ }
644
+ /** How a native host decodes video. */
645
+ export interface HostDecodeCapabilities {
646
+ readonly mode: VideoDecodeMode;
647
+ readonly backend: "vulkan" | "videotoolbox" | "d3d12va" | "none";
648
+ /** Whether a shared decode context was created for this host process. */
649
+ readonly deviceReady: boolean;
650
+ /**
651
+ * Why `auto` could not set up hardware decode at the host's start and decodes in software;
652
+ * `null` otherwise.
653
+ */
654
+ readonly hardwareUnavailable: string | null;
655
+ /** Whether the device can present 10-bit decoded planes. */
656
+ readonly tenBit: boolean;
657
+ /** Codecs the decode device itself enabled, where the backend reports them. */
658
+ readonly deviceCodecs: readonly string[] | null;
659
+ readonly codecs: readonly DecodeCodecCapability[];
660
+ }
661
+ /** What {@link Alpha2NodeClient.getCapabilities} reports about the host. */
662
+ export interface HostCapabilities {
663
+ readonly status: "ready";
664
+ /** What the host can do, in the report every alpha2 runtime gives. */
665
+ readonly capabilities: CapabilityReport<HostDecodeCapabilities, HostEncodeCapabilities>;
666
+ readonly effectCatalog: BuiltinEffectCatalog;
667
+ readonly [diagnostic: string]: unknown;
668
+ }
669
+ /** What a host error carries besides its code and message. */
670
+ export interface Alpha2HostErrorOptions extends ErrorOptions {
671
+ /**
672
+ * The host's report of a failed export. Without one, the error's code classifies it. See
673
+ * `alpha2/export-failure`.
674
+ */
675
+ readonly failure?: ExportFailure;
676
+ /** The export step the error came from, for a failure classified here. Default `prepare`. */
677
+ readonly phase?: ExportPhase;
678
+ /** The last part of the host's standard error, at most {@link STDERR_TAIL_BYTES}. */
679
+ readonly stderr?: string;
680
+ /** How the host process ended, when it ended before answering. */
681
+ readonly exitCode?: number | null;
682
+ readonly signal?: string | null;
683
+ }
684
+ /** Bytes of the host's standard error an error keeps. */
685
+ export declare const STDERR_TAIL_BYTES = 16384;
686
+ /**
687
+ * Every error this client rejects with. Its `failure` classifies it for a retry policy
688
+ * (`alpha2/export-retry`): the host's own report when the host sent one, and otherwise the class
689
+ * its code is evidence of (`failureForCode`), as the host would have classified the same code, so a
690
+ * refusal made here or before an export started is as final as the host's.
691
+ */
692
+ export declare class Alpha2HostError extends Error {
693
+ /** A machine-readable code, such as `HOST_NOT_FOUND`, `INVALID_INPUT` or `DECODE_STALLED`. */
694
+ readonly code: string;
695
+ readonly failure: ExportFailure;
696
+ readonly stderr?: string;
697
+ readonly exitCode?: number | null;
698
+ readonly signal?: string | null;
699
+ constructor(code: string, message: string, options?: Alpha2HostErrorOptions);
700
+ }
701
+ /** An export refused before it rendered: the project's media is not ready. */
702
+ export declare class Alpha2ConformError extends Alpha2HostError {
703
+ /** What each source would read and what blocks it. */
704
+ readonly report: ConformReport;
705
+ constructor(message: string, report: ConformReport, options?: Alpha2HostErrorOptions);
706
+ }
707
+ /** What {@link Alpha2NodeClient.getCapabilities} takes. */
708
+ export interface CapabilitiesOptions {
709
+ /** Withdraws this call; see {@link Alpha2NodeClient.getCapabilities}. */
710
+ readonly signal?: AbortSignal;
711
+ /** The decode mode to report for, in place of the client's. */
712
+ readonly videoDecode?: VideoDecodeMode;
713
+ }
714
+ /** What {@link Alpha2NodeClient.conform} asks. */
715
+ export interface ConformRequest {
716
+ readonly project: Project;
717
+ /** As {@link ExportProjectRequest.assets}, {@link ExportProjectRequest.proxies} and `frames`. */
718
+ readonly assets?: Readonly<Record<string, string>>;
719
+ readonly proxies?: Readonly<Record<string, readonly ProxyFile[]>>;
720
+ readonly frames?: Readonly<Record<string, readonly (string | null)[]>>;
721
+ /** Defaults to `export_original`. */
722
+ readonly purpose?: MediaPurpose;
723
+ readonly signal?: AbortSignal;
724
+ }
725
+ /** What the host found, and what the purpose would read. */
726
+ export interface ConformResult {
727
+ readonly catalog: AssetCatalog;
728
+ readonly report: ConformReport;
729
+ }
730
+ /** The host could not create its GPU device (`GPU_INIT_FAILED`). */
731
+ export declare class Alpha2GpuInitializationError extends Alpha2HostError {
732
+ constructor(message: string, options?: Alpha2HostErrorOptions);
733
+ }
734
+ /** The project failed validation before a host started (`INVALID_INPUT`); `problems` lists why. */
735
+ export declare class Alpha2ProjectValidationError extends Alpha2HostError {
736
+ readonly problems: readonly string[];
737
+ constructor(problems: readonly string[]);
738
+ }
739
+ /** The request's `signal` aborted it (`ABORTED`). */
740
+ export declare class Alpha2AbortError extends Alpha2HostError {
741
+ constructor();
742
+ }
743
+ /**
744
+ * The file name `alpha2-host` has on `platform`: Windows executables carry `.exe`, and nothing
745
+ * there adds it to a path that lacks it.
746
+ */
747
+ export declare function hostExecutableName(platform?: NodeJS.Platform): string;
748
+ /**
749
+ * Locates `alpha2-host` without loading Electron. The package does not ship the binary: it comes
750
+ * in the platform's native archive on https://github.com/alpha2-sh/alpha2/releases, as
751
+ * `bin/alpha2-host` beside the `lib/` it loads. Native archives and Electron applications should
752
+ * keep `bin/` and `lib/` together and unpack both from ASAR. Resolution order is explicit path,
753
+ * `ALPHA2_HOST_PATH`, Electron resources (`bin/`, then the directory itself), this package's own
754
+ * `bin/`, then `PATH`.
755
+ *
756
+ * Throws an {@link Alpha2HostError}: `INVALID_INPUT` for an empty `hostPath`, `HOST_NOT_FOUND` when
757
+ * an explicit path is not a file or no candidate exists; the latter message names the native
758
+ * archive. An empty `ALPHA2_HOST_PATH` is unset.
759
+ */
760
+ export declare function resolveHostBinary(options?: ResolveHostBinaryOptions): string;
761
+ /**
762
+ * Runs requests on `alpha2-host` sidecar processes, one process per request, at most
763
+ * `concurrency` at a time; further requests wait for a slot. Every rejection is an
764
+ * {@link Alpha2HostError}.
765
+ */
766
+ export declare class Alpha2NodeClient {
767
+ #private;
768
+ /**
769
+ * Resolves the host binary now. Throws a `RangeError` for a `concurrency` that is not a positive
770
+ * integer, and an {@link Alpha2HostError} when `videoDecode` is not a mode (`INVALID_INPUT`) or
771
+ * the binary cannot be found.
772
+ */
773
+ constructor(options?: Alpha2NodeOptions);
774
+ /**
775
+ * What the host can decode, render and encode, for the client's decode mode or
776
+ * `options.videoDecode`. The first call for a mode starts a host, in a slot like any request;
777
+ * calls made while it runs share it, and its report answers every later call for that mode on
778
+ * this client, since the same binary and mode report the same. A probe that fails is not kept:
779
+ * the next call starts another.
780
+ *
781
+ * `options.signal` withdraws this call (`Alpha2AbortError`); the probe goes on while another call
782
+ * still waits for it, and is withdrawn, from its slot or its host, when none does.
783
+ */
784
+ getCapabilities(options?: CapabilitiesOptions): Promise<HostCapabilities>;
785
+ /**
786
+ * Which media a purpose would read for each source, and what blocks it, without rendering. The
787
+ * host hashes an original only when the project recorded a hash to compare. Rejects with an
788
+ * {@link Alpha2ProjectValidationError} for an invalid project.
789
+ */
790
+ conform(request: ConformRequest): Promise<ConformResult>;
791
+ /**
792
+ * Renders and encodes `request.project` to `request.output` and resolves with that path.
793
+ *
794
+ * Rejects with an {@link Alpha2ProjectValidationError} or an `INVALID_INPUT` error before a host
795
+ * starts when the project or request is invalid, an {@link Alpha2ConformError} when the media is
796
+ * not ready, an {@link Alpha2AbortError} when `signal` aborts, and an {@link Alpha2HostError}
797
+ * otherwise. Under the `auto` decode mode, an export whose hardware decoder stalls
798
+ * (`DECODE_STALLED`) runs once more decoding in software; its progress starts over. When that
799
+ * run fails too, its error is the export's, with the stall as its `cause` and in
800
+ * `failure.diagnostics.decodeStall`.
801
+ */
802
+ exportProject(request: ExportProjectRequest): Promise<string>;
803
+ }
804
+ /**
805
+ * {@link Alpha2NodeClient.getCapabilities} on a client shared by every module-level call with the
806
+ * same `concurrency` and resolved binary, for `options.videoDecode`.
807
+ */
808
+ export declare function getCapabilities(options?: Alpha2NodeOptions & CapabilitiesOptions): Promise<HostCapabilities>;
809
+ /**
810
+ * {@link Alpha2NodeClient.exportProject} on a client shared by every module-level call with the
811
+ * same `concurrency` and resolved binary, so `concurrency` holds across calls whatever decode mode
812
+ * each asks for. `options.videoDecode` applies where the request names none.
813
+ */
814
+ export declare function exportProject(request: ExportProjectRequest, options?: Alpha2NodeOptions): Promise<string>;