@camstack/addon-pipeline 1.1.28 → 1.1.30

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.
Files changed (39) hide show
  1. package/dist/audio-analyzer/index.js +1 -1
  2. package/dist/audio-analyzer/index.mjs +1 -1
  3. package/dist/audio-codec-ffmpeg/index.js +1 -5
  4. package/dist/audio-codec-ffmpeg/index.mjs +1 -5
  5. package/dist/decoder-ffmpeg/index.js +102 -352
  6. package/dist/decoder-ffmpeg/index.mjs +82 -332
  7. package/dist/decoder-nodeav/index.js +6 -4
  8. package/dist/decoder-nodeav/index.mjs +6 -4
  9. package/dist/detection-pipeline/index.js +254 -186
  10. package/dist/detection-pipeline/index.mjs +254 -186
  11. package/dist/{dist-Biq62zt4.js → dist-Cwc0TUQr.js} +7 -42
  12. package/dist/{dist-C6_wgXqF.mjs → dist-DjuGmyG9.mjs} +7 -42
  13. package/dist/ffmpeg-args-C5GPp8Cw.mjs +323 -0
  14. package/dist/ffmpeg-args-D6h1edXK.js +418 -0
  15. package/dist/frame-dropper-AjheBGMG.mjs +22 -0
  16. package/dist/frame-dropper-DKLM6pMz.js +27 -0
  17. package/dist/{frame-handle-plane-BIoY6nRV.mjs → frame-handle-plane-Bkxz-TTD.mjs} +1 -1
  18. package/dist/{frame-handle-plane-D6BzyEgy.js → frame-handle-plane-DQNCTrpC.js} +1 -1
  19. package/dist/{frame-ring-sink-9J0wCdLF.js → frame-ring-sink-8LLV-cvH.js} +1 -1
  20. package/dist/{frame-ring-sink-Cs9vby6v.mjs → frame-ring-sink-ClEWjiRU.mjs} +1 -1
  21. package/dist/motion-wasm/index.js +1 -1
  22. package/dist/motion-wasm/index.mjs +1 -1
  23. package/dist/pipeline-runner/index.js +2 -2
  24. package/dist/pipeline-runner/index.mjs +2 -2
  25. package/dist/recorder/index.js +1 -1
  26. package/dist/recorder/index.mjs +1 -1
  27. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DoReAb4y.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-D567z31g.mjs} +3 -3
  28. package/dist/stream-broker/{hostInit-C0SuwQhL.mjs → hostInit-B_b3PIZB.mjs} +3 -3
  29. package/dist/stream-broker/index.js +168 -121
  30. package/dist/stream-broker/index.mjs +159 -112
  31. package/dist/stream-broker/remoteEntry.js +1 -1
  32. package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-Br2yi8ah.js → MaskShapeCanvas-DI4BY7W2-x-DZOuQL.js} +1 -1
  33. package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-BSCcPdPf.js → MotionZonesSettings-NcxxQN8r-DTeuDGCU.js} +1 -1
  34. package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-ztxvxSjS.js → PrivacyMaskSettings-APgPLF7p-CirtxO3e.js} +1 -1
  35. package/embed-dist/assets/{index-CeZV1B-2.js → index-CpUy8OIE.js} +9 -9
  36. package/embed-dist/index.html +1 -1
  37. package/package.json +1 -1
  38. package/dist/frame-dropper-7RTo_YyG.js +0 -68
  39. package/dist/frame-dropper-CwkBTPGV.mjs +0 -51
@@ -16403,7 +16403,10 @@ var AgentLoadSummarySchema = object({
16403
16403
  online: boolean(),
16404
16404
  load: RunnerLocalLoadSchema,
16405
16405
  /** Computed score used by the L2 capacity balancer (lower = less loaded). */
16406
- score: number()
16406
+ score: number(),
16407
+ /** This node's decode hwaccel backend (per-node `probedBestHwaccel`), or null
16408
+ * when not yet probed — for the cluster Pipeline table UI (P0.2). */
16409
+ decodeHwaccel: string().nullable()
16407
16410
  });
16408
16411
  /**
16409
16412
  * Aggregate metrics across the whole detection cluster. Replaces the legacy
@@ -19034,7 +19037,7 @@ var HardwareEncoderIdSchema = _enum([
19034
19037
  "libx264",
19035
19038
  "libx265"
19036
19039
  ]);
19037
- var HardwareEncodersSchema = object({
19040
+ object({
19038
19041
  encoders: array(object({
19039
19042
  encoder: HardwareEncoderIdSchema,
19040
19043
  codec: _enum(["H264", "H265"]),
@@ -19053,15 +19056,7 @@ var HardwareEncodersSchema = object({
19053
19056
  defaultH265: HardwareEncoderIdSchema,
19054
19057
  probedAt: number()
19055
19058
  });
19056
- /**
19057
- * Decode-side companion to {@link HardwareEncodersSchema}: the `-hwaccel`
19058
- * methods the configured ffmpeg binary actually supports (parsed from
19059
- * `ffmpeg -hwaccels`). Used to gate decode-hwaccel attempts so the transcode
19060
- * egress never spends a spawn on a backend this build cannot offer. Per-stream
19061
- * decode fragility (e.g. VideoToolbox HEVC) is still handled by the egress's
19062
- * software fallback — this only filters out wholly-unsupported backends.
19063
- */
19064
- var HardwareDecodeAccelsSchema = object({
19059
+ object({
19065
19060
  methods: array(string()).readonly(),
19066
19061
  probedAt: number()
19067
19062
  });
@@ -19124,13 +19119,7 @@ var ResolvedInferenceConfigSchema = object({
19124
19119
  format: ModelFormatSchema,
19125
19120
  reason: string()
19126
19121
  });
19127
- method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema), method(object({ requirements: array(ModelRequirementSchema).readonly() }), ResolvedInferenceConfigSchema), method(object({ prefer: HwAccelBackendInputSchema }), HwAccelResolutionSchema), method(_void(), HardwareEncodersSchema), method(_void(), HardwareEncodersSchema, {
19128
- kind: "mutation",
19129
- auth: "admin"
19130
- }), method(_void(), HardwareDecodeAccelsSchema), method(_void(), HardwareDecodeAccelsSchema, {
19131
- kind: "mutation",
19132
- auth: "admin"
19133
- });
19122
+ method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema), method(object({ requirements: array(ModelRequirementSchema).readonly() }), ResolvedInferenceConfigSchema), method(object({ prefer: HwAccelBackendInputSchema }), HwAccelResolutionSchema);
19134
19123
  var PtzPresetSchema = object({
19135
19124
  id: string(),
19136
19125
  name: string()
@@ -22788,30 +22777,6 @@ Object.freeze({
22788
22777
  addonId: null,
22789
22778
  access: "view"
22790
22779
  },
22791
- "platformProbe.getHardwareDecodeAccels": {
22792
- capName: "platform-probe",
22793
- capScope: "system",
22794
- addonId: null,
22795
- access: "view"
22796
- },
22797
- "platformProbe.getHardwareEncoders": {
22798
- capName: "platform-probe",
22799
- capScope: "system",
22800
- addonId: null,
22801
- access: "view"
22802
- },
22803
- "platformProbe.refreshHardwareDecodeAccels": {
22804
- capName: "platform-probe",
22805
- capScope: "system",
22806
- addonId: null,
22807
- access: "create"
22808
- },
22809
- "platformProbe.refreshHardwareEncoders": {
22810
- capName: "platform-probe",
22811
- capScope: "system",
22812
- addonId: null,
22813
- access: "create"
22814
- },
22815
22780
  "platformProbe.resolveHwAccel": {
22816
22781
  capName: "platform-probe",
22817
22782
  capScope: "system",
@@ -16403,7 +16403,10 @@ var AgentLoadSummarySchema = object({
16403
16403
  online: boolean(),
16404
16404
  load: RunnerLocalLoadSchema,
16405
16405
  /** Computed score used by the L2 capacity balancer (lower = less loaded). */
16406
- score: number()
16406
+ score: number(),
16407
+ /** This node's decode hwaccel backend (per-node `probedBestHwaccel`), or null
16408
+ * when not yet probed — for the cluster Pipeline table UI (P0.2). */
16409
+ decodeHwaccel: string().nullable()
16407
16410
  });
16408
16411
  /**
16409
16412
  * Aggregate metrics across the whole detection cluster. Replaces the legacy
@@ -19034,7 +19037,7 @@ var HardwareEncoderIdSchema = _enum([
19034
19037
  "libx264",
19035
19038
  "libx265"
19036
19039
  ]);
19037
- var HardwareEncodersSchema = object({
19040
+ object({
19038
19041
  encoders: array(object({
19039
19042
  encoder: HardwareEncoderIdSchema,
19040
19043
  codec: _enum(["H264", "H265"]),
@@ -19053,15 +19056,7 @@ var HardwareEncodersSchema = object({
19053
19056
  defaultH265: HardwareEncoderIdSchema,
19054
19057
  probedAt: number()
19055
19058
  });
19056
- /**
19057
- * Decode-side companion to {@link HardwareEncodersSchema}: the `-hwaccel`
19058
- * methods the configured ffmpeg binary actually supports (parsed from
19059
- * `ffmpeg -hwaccels`). Used to gate decode-hwaccel attempts so the transcode
19060
- * egress never spends a spawn on a backend this build cannot offer. Per-stream
19061
- * decode fragility (e.g. VideoToolbox HEVC) is still handled by the egress's
19062
- * software fallback — this only filters out wholly-unsupported backends.
19063
- */
19064
- var HardwareDecodeAccelsSchema = object({
19059
+ object({
19065
19060
  methods: array(string()).readonly(),
19066
19061
  probedAt: number()
19067
19062
  });
@@ -19124,13 +19119,7 @@ var ResolvedInferenceConfigSchema = object({
19124
19119
  format: ModelFormatSchema,
19125
19120
  reason: string()
19126
19121
  });
19127
- method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema), method(object({ requirements: array(ModelRequirementSchema).readonly() }), ResolvedInferenceConfigSchema), method(object({ prefer: HwAccelBackendInputSchema }), HwAccelResolutionSchema), method(_void(), HardwareEncodersSchema), method(_void(), HardwareEncodersSchema, {
19128
- kind: "mutation",
19129
- auth: "admin"
19130
- }), method(_void(), HardwareDecodeAccelsSchema), method(_void(), HardwareDecodeAccelsSchema, {
19131
- kind: "mutation",
19132
- auth: "admin"
19133
- });
19122
+ method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema), method(object({ requirements: array(ModelRequirementSchema).readonly() }), ResolvedInferenceConfigSchema), method(object({ prefer: HwAccelBackendInputSchema }), HwAccelResolutionSchema);
19134
19123
  var PtzPresetSchema = object({
19135
19124
  id: string(),
19136
19125
  name: string()
@@ -22788,30 +22777,6 @@ Object.freeze({
22788
22777
  addonId: null,
22789
22778
  access: "view"
22790
22779
  },
22791
- "platformProbe.getHardwareDecodeAccels": {
22792
- capName: "platform-probe",
22793
- capScope: "system",
22794
- addonId: null,
22795
- access: "view"
22796
- },
22797
- "platformProbe.getHardwareEncoders": {
22798
- capName: "platform-probe",
22799
- capScope: "system",
22800
- addonId: null,
22801
- access: "view"
22802
- },
22803
- "platformProbe.refreshHardwareDecodeAccels": {
22804
- capName: "platform-probe",
22805
- capScope: "system",
22806
- addonId: null,
22807
- access: "create"
22808
- },
22809
- "platformProbe.refreshHardwareEncoders": {
22810
- capName: "platform-probe",
22811
- capScope: "system",
22812
- addonId: null,
22813
- access: "create"
22814
- },
22815
22780
  "platformProbe.resolveHwAccel": {
22816
22781
  capName: "platform-probe",
22817
22782
  capScope: "system",
@@ -0,0 +1,323 @@
1
+ //#region src/stream-broker/stream-broker/ffmpeg-args-common.ts
2
+ var AUDIO_ENCODER_BY_CODEC = {
3
+ opus: "libopus",
4
+ aac: "aac",
5
+ pcmu: "pcm_mulaw",
6
+ pcma: "pcm_alaw",
7
+ copy: "copy"
8
+ };
9
+ /**
10
+ * Build the audio-encode args `-c:a <enc> [-b:a Nk] [-ar N] [-ac N]`. Each knob
11
+ * is emitted only when provided, in this fixed order — matching what every
12
+ * broker site already emitted by hand.
13
+ */
14
+ function audioEncoderArgs(codec, opts = {}) {
15
+ const args = ["-c:a", AUDIO_ENCODER_BY_CODEC[codec]];
16
+ if (opts.bitrateKbps !== void 0) args.push("-b:a", `${opts.bitrateKbps}k`);
17
+ if (opts.sampleRateHz !== void 0) args.push("-ar", String(opts.sampleRateHz));
18
+ if (opts.channels !== void 0) args.push("-ac", String(opts.channels));
19
+ return args;
20
+ }
21
+ /** The `-hide_banner -loglevel <level>` preamble every broker ffmpeg site opens with. */
22
+ function logBannerArgs(level) {
23
+ return [
24
+ "-hide_banner",
25
+ "-loglevel",
26
+ level
27
+ ];
28
+ }
29
+ //#endregion
30
+ //#region src/decoder-ffmpeg/ffmpeg-args.ts
31
+ /**
32
+ * Pure, side-effect-free ffmpeg argument construction for the
33
+ * `decoder-ffmpeg` addon.
34
+ *
35
+ * The addon decodes video in an **ffmpeg subprocess** (unlike `decoder-nodeav`
36
+ * which decodes in-process via node-av's native bindings). On the Intel hub,
37
+ * node-av's VA-API decode path SIGBUSes (uncatchable) on 8MP h264 and takes the
38
+ * whole decoder runner down; ffmpeg-as-subprocess decodes the same hardware
39
+ * cleanly because a crash is isolated to the child process.
40
+ *
41
+ * These helpers are extracted from the session so the argument shape is
42
+ * unit-testable without spawning a real ffmpeg binary.
43
+ *
44
+ * Validated working command on the Intel hub (renderD128, 8MP h264, ~50fps,
45
+ * exit 0):
46
+ *
47
+ * ffmpeg -hwaccel vaapi -hwaccel_device /dev/dri/renderD128 \
48
+ * -hwaccel_output_format vaapi -f h264 -i <in> \
49
+ * -vf hwdownload,format=nv12,scale=640:360,format=gray \
50
+ * -f rawvideo -y /dev/null
51
+ */
52
+ /** Default DRM render node used for the VA-API / QSV hardware decode path. */
53
+ var DEFAULT_VAAPI_RENDER_DEVICE = "/dev/dri/renderD128";
54
+ /** Whether a resolved hwaccel value means "decode in software" (no `-hwaccel`). */
55
+ function isSoftwareHwAccel(hwaccel) {
56
+ return hwaccel === null || hwaccel === "" || hwaccel === "none";
57
+ }
58
+ /** Backends whose decoded frames stay on the GPU and need an explicit
59
+ * `-hwaccel_output_format <m>` + `hwdownload,format=nv12` chain. */
60
+ function needsHwDownload(hwaccel) {
61
+ return hwaccel === "vaapi" || hwaccel === "qsv";
62
+ }
63
+ /** Decode backend → its GPU scaler filter. */
64
+ var GPU_SCALE_FILTER_BY_BACKEND = {
65
+ vaapi: "scale_vaapi",
66
+ qsv: "scale_qsv",
67
+ videotoolbox: "scale_vt"
68
+ };
69
+ /** Every GPU scaler this addon knows how to drive — used to scan `-filters`. */
70
+ var KNOWN_GPU_SCALE_FILTERS = [
71
+ "scale_vaapi",
72
+ "scale_qsv",
73
+ "scale_vt"
74
+ ];
75
+ /**
76
+ * The GPU scaler that pairs with a decode backend, ignoring build support — a
77
+ * static hwaccel → filter map. Returns null for software / a backend with no
78
+ * GPU scaler (e.g. `cuda`, `drm`). Case-insensitive.
79
+ */
80
+ function gpuScaleFilterForBackend(hwaccel) {
81
+ if (hwaccel === null) return null;
82
+ return GPU_SCALE_FILTER_BY_BACKEND[hwaccel.toLowerCase()] ?? null;
83
+ }
84
+ /**
85
+ * Resolve the GPU scaler to use for a decode backend GIVEN the filters the
86
+ * configured ffmpeg build actually offers ({@link parseAvailableGpuScaleFilters}).
87
+ * Returns the filter when the backend maps to one AND the build has it; null
88
+ * otherwise — the caller then emits the CPU-scale chain (graceful fallback).
89
+ */
90
+ function resolveGpuScaleFilter(hwaccel, availableFilters) {
91
+ const filter = gpuScaleFilterForBackend(hwaccel);
92
+ if (filter === null) return null;
93
+ return availableFilters.has(filter) ? filter : null;
94
+ }
95
+ /**
96
+ * Parse `ffmpeg -filters` stdout into the set of {@link KNOWN_GPU_SCALE_FILTERS}
97
+ * the build offers. Matches whole filter tokens (word-boundary) so the plain
98
+ * `scale` filter is never mistaken for a GPU scaler. PURE — the spawn lives in
99
+ * the addon; only the parse is here so it is unit-tested.
100
+ */
101
+ function parseAvailableGpuScaleFilters(filtersStdout) {
102
+ const present = /* @__PURE__ */ new Set();
103
+ for (const filter of KNOWN_GPU_SCALE_FILTERS) if (new RegExp(`\\b${filter}\\b`).test(filtersStdout)) present.add(filter);
104
+ return present;
105
+ }
106
+ /**
107
+ * Whether the decode keeps frames on the GPU — needs `-hwaccel_output_format`
108
+ * and an `hwdownload` in the filtergraph. VA-API / QSV always do (their decoded
109
+ * surfaces live on the GPU); another backend only when a GPU scaler will run on
110
+ * those surfaces (e.g. `videotoolbox` + `scale_vt`).
111
+ */
112
+ function keepFramesOnGpu(hwaccel, gpuScaleFilter) {
113
+ return needsHwDownload(hwaccel) || gpuScaleFilter !== null;
114
+ }
115
+ /** Map a cap codec string to the ffmpeg `-f` input demuxer/format. */
116
+ function codecToInputFormat(codec) {
117
+ switch (codec.toLowerCase()) {
118
+ case "h265":
119
+ case "hevc": return "hevc";
120
+ default: return "h264";
121
+ }
122
+ }
123
+ /** Map the cap-level output format to a raw ffmpeg pixel format. */
124
+ function rawPixelForFormat(outputFormat) {
125
+ return outputFormat === "gray" ? "gray" : "rgb24";
126
+ }
127
+ /** Packed bytes-per-pixel for a raw output format (rgb24 = 3, gray = 1). */
128
+ function channelsForPixel(pixel) {
129
+ return pixel === "gray" ? 1 : 3;
130
+ }
131
+ /**
132
+ * The maximum output width for a given `scale` divisor — matches the node-av
133
+ * scaler's `maxW = floor(640 / scale)` rule so both decoders produce
134
+ * comparably-sized frames for the motion / detection pipeline.
135
+ */
136
+ function maxDecodeWidth(scale) {
137
+ return Math.max(2, Math.floor(640 / (scale > 1 ? scale : 1)));
138
+ }
139
+ /**
140
+ * Compute exact output geometry from a known source resolution — the node-av
141
+ * rule (`outWidth = min(srcW, floor(640/scale))`, `outHeight =
142
+ * round(outWidth*srcH/srcW)`). Provided for callers that DO know the source
143
+ * dimensions; the live broker path leaves them to ffmpeg (`-2` height).
144
+ */
145
+ function computeOutputGeometry(srcWidth, srcHeight, scale) {
146
+ const width = Math.min(srcWidth, maxDecodeWidth(scale));
147
+ return {
148
+ width,
149
+ height: Math.round(width * srcHeight / srcWidth)
150
+ };
151
+ }
152
+ /** Build the `-vf` filtergraph string for the requested hwaccel + pixel format. */
153
+ function buildVideoFilter(hwaccel, pixel, outWidth, outHeight, maxW, gpuScaleFilter) {
154
+ const exact = outWidth !== void 0 && outWidth > 0 && outHeight !== void 0 && outHeight > 0;
155
+ if (gpuScaleFilter !== null) return `${gpuScaleFilter}=${exact ? `w=${outWidth}:h=${outHeight}` : `w=min(iw\\,${maxW}):h=-2`},hwdownload,format=nv12,format=${pixel}`;
156
+ const scaleAndFormat = `scale=${exact ? `${outWidth}:${outHeight}` : `min(iw\\,${maxW}):-2`},format=${pixel}`;
157
+ return needsHwDownload(hwaccel) ? `hwdownload,format=nv12,${scaleAndFormat}` : scaleAndFormat;
158
+ }
159
+ /**
160
+ * Build the full ffmpeg argv for a decode session. PURE — no spawn, no env
161
+ * access — so the argument shape is asserted in unit tests.
162
+ *
163
+ * Notes:
164
+ * - `-loglevel info -nostats`: the session parses the muxer's `Output #0`
165
+ * stream line from stderr to learn the exact output W×H (the broker does not
166
+ * pass source geometry for the shm path). `-nostats` suppresses the periodic
167
+ * `frame= …` counter while keeping the one-shot Output block.
168
+ * - The input is `pipe:0` (Annex-B packets written to stdin) in push mode, or
169
+ * `options.inputUrl` (the owner's restream, dialed by ffmpeg itself —
170
+ * `-rtsp_transport tcp` for rtsp URLs) in pull mode; the output is raw video
171
+ * on `pipe:1` either way.
172
+ */
173
+ function buildFfmpegDecodeArgs(config, options) {
174
+ const inputFormat = codecToInputFormat(config.codec);
175
+ const pixel = rawPixelForFormat(config.outputFormat);
176
+ const maxW = maxDecodeWidth(config.scale);
177
+ const { hwaccel } = options;
178
+ const gpuScaleFilter = options.gpuScaleFilter ?? null;
179
+ const vf = buildVideoFilter(hwaccel, pixel, options.outWidth, options.outHeight, maxW, gpuScaleFilter);
180
+ let hwaccelArgs;
181
+ if (isSoftwareHwAccel(hwaccel) || hwaccel === null) hwaccelArgs = [];
182
+ else {
183
+ hwaccelArgs = ["-hwaccel", hwaccel];
184
+ if (needsHwDownload(hwaccel)) hwaccelArgs.push("-hwaccel_device", options.renderDevice ?? "/dev/dri/renderD128");
185
+ if (keepFramesOnGpu(hwaccel, gpuScaleFilter)) hwaccelArgs.push("-hwaccel_output_format", hwaccel);
186
+ }
187
+ const inputArgs = options.inputUrl !== void 0 ? [
188
+ ...options.inputUrl.startsWith("rtsp") ? ["-rtsp_transport", "tcp"] : [],
189
+ "-i",
190
+ options.inputUrl
191
+ ] : [
192
+ "-f",
193
+ inputFormat,
194
+ "-i",
195
+ "pipe:0"
196
+ ];
197
+ return [
198
+ ...logBannerArgs("info"),
199
+ "-nostats",
200
+ "-fflags",
201
+ "+nobuffer+flush_packets",
202
+ "-flags",
203
+ "low_delay",
204
+ "-probesize",
205
+ "1M",
206
+ "-analyzeduration",
207
+ "0",
208
+ ...hwaccelArgs,
209
+ ...inputArgs,
210
+ "-vf",
211
+ vf,
212
+ "-f",
213
+ "rawvideo",
214
+ "-pix_fmt",
215
+ pixel,
216
+ "pipe:1"
217
+ ];
218
+ }
219
+ /**
220
+ * Parse the exact output geometry from ffmpeg's stderr. The muxer prints an
221
+ * `Output #0, rawvideo …` block whose `Stream #0:0: Video: rawvideo …, <fmt>,
222
+ * WIDTHxHEIGHT …` line carries the scaled dimensions. Gating on the `Output #`
223
+ * marker avoids picking up the INPUT stream's (source) resolution, which is
224
+ * printed earlier.
225
+ *
226
+ * Returns `null` until the Output block with a `WxH` token has been seen.
227
+ */
228
+ function parseFfmpegOutputDims(stderr) {
229
+ const outputIdx = stderr.indexOf("Output #");
230
+ if (outputIdx < 0) return null;
231
+ const match = stderr.slice(outputIdx).match(/(\d{2,5})x(\d{2,5})/);
232
+ if (!match) return null;
233
+ const width = Number(match[1]);
234
+ const height = Number(match[2]);
235
+ if (!Number.isInteger(width) || !Number.isInteger(height) || width <= 0 || height <= 0) return null;
236
+ return {
237
+ width,
238
+ height
239
+ };
240
+ }
241
+ /**
242
+ * Pick the first kernel-preferred `-hwaccel` method the configured ffmpeg build
243
+ * actually supports, or `null` (software) when it supports none. Mirrors the
244
+ * broker's `pickDecodeHwAccel` (`ffmpeg-invocation.ts`) so the decoder gates
245
+ * decode-hwaccel against the same probed evidence:
246
+ * - empty `preferred` → `null` (software);
247
+ * - empty `supportedMethods` (probe miss) → keep the top preference and let the
248
+ * session's per-child software fallback cover a genuine failure;
249
+ * - otherwise the first preference the build supports, or `null`.
250
+ */
251
+ function pickDecodeHwAccel(preferred, supportedMethods) {
252
+ if (preferred.length === 0) return null;
253
+ if (supportedMethods.length === 0) return preferred[0] ?? null;
254
+ const supported = new Set(supportedMethods.map((m) => m.toLowerCase()));
255
+ return preferred.find((name) => supported.has(name.toLowerCase())) ?? null;
256
+ }
257
+ /**
258
+ * Canonical decode-hwaccel preference order for the ffmpeg-subprocess decoder.
259
+ *
260
+ * `vaapi` is ranked ABOVE `qsv` deliberately: on the Intel hub the `qsv` decode
261
+ * child exits early (`code=171`, no frames) while the `vaapi` path decodes 8MP
262
+ * h264 cleanly at ~50fps. The kernel probe (`resolveHwAccel`) tends to surface
263
+ * `qsv` first on Intel, so we re-rank its result through this table before
264
+ * building the attempt chain. `videotoolbox` leads for macOS; `cuda` / `nvdec`
265
+ * trail for NVIDIA. A method not listed here keeps its incoming relative order,
266
+ * placed AFTER every ranked one.
267
+ */
268
+ var DECODE_HWACCEL_RANK = [
269
+ "videotoolbox",
270
+ "vaapi",
271
+ "qsv",
272
+ "cuda",
273
+ "nvdec",
274
+ "d3d11va",
275
+ "dxva2",
276
+ "amf",
277
+ "vdpau",
278
+ "drm"
279
+ ];
280
+ /** Rank index for {@link DECODE_HWACCEL_RANK} — unranked methods sort last. */
281
+ function decodeHwAccelRankIndex(method) {
282
+ const idx = DECODE_HWACCEL_RANK.indexOf(method.toLowerCase());
283
+ return idx < 0 ? DECODE_HWACCEL_RANK.length : idx;
284
+ }
285
+ /**
286
+ * Build the ORDERED list of hardware `-hwaccel` methods to attempt for an
287
+ * `'auto'` decode session, best-first. The session walks this chain on an
288
+ * early-exit-with-no-frames and only falls to software once it is exhausted.
289
+ *
290
+ * Strategy:
291
+ * - Seed candidates from the kernel-preferred order UNION the canonical rank
292
+ * UNION the build's supported list, so a method the kernel omitted but the
293
+ * build supports (the Intel `vaapi` case — kernel surfaces only `qsv`) is
294
+ * still attempted.
295
+ * - When the build's supported-method list is known (non-empty), keep only
296
+ * methods it actually offers; on a probe miss (empty) keep just the kernel
297
+ * preferences (avoid blindly spawning every backend).
298
+ * - Re-rank the survivors by {@link DECODE_HWACCEL_RANK} so `vaapi` precedes
299
+ * `qsv`. A supported method not in the rank table keeps its position AFTER
300
+ * every ranked one (stable sort). Returns lowercased method names; empty ⇒
301
+ * pure software.
302
+ */
303
+ function rankDecodeHwAccels(preferred, supportedMethods) {
304
+ const supported = new Set(supportedMethods.map((m) => m.toLowerCase()));
305
+ const hasSupportList = supported.size > 0;
306
+ const pool = hasSupportList ? [
307
+ ...preferred,
308
+ ...DECODE_HWACCEL_RANK,
309
+ ...supportedMethods
310
+ ] : [...preferred];
311
+ const seen = /* @__PURE__ */ new Set();
312
+ const candidates = [];
313
+ for (const raw of pool) {
314
+ const method = raw.toLowerCase();
315
+ if (seen.has(method)) continue;
316
+ seen.add(method);
317
+ if (hasSupportList && !supported.has(method)) continue;
318
+ candidates.push(method);
319
+ }
320
+ return candidates.sort((a, b) => decodeHwAccelRankIndex(a) - decodeHwAccelRankIndex(b));
321
+ }
322
+ //#endregion
323
+ export { codecToInputFormat as a, maxDecodeWidth as c, pickDecodeHwAccel as d, rankDecodeHwAccels as f, logBannerArgs as g, audioEncoderArgs as h, channelsForPixel as i, parseAvailableGpuScaleFilters as l, resolveGpuScaleFilter as m, DEFAULT_VAAPI_RENDER_DEVICE as n, computeOutputGeometry as o, rawPixelForFormat as p, buildFfmpegDecodeArgs as r, isSoftwareHwAccel as s, DECODE_HWACCEL_RANK as t, parseFfmpegOutputDims as u };