node-av 3.1.3 → 5.0.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.
- package/README.md +88 -52
- package/binding.gyp +23 -11
- package/dist/api/audio-frame-buffer.d.ts +201 -0
- package/dist/api/audio-frame-buffer.js +275 -0
- package/dist/api/audio-frame-buffer.js.map +1 -0
- package/dist/api/bitstream-filter.d.ts +320 -78
- package/dist/api/bitstream-filter.js +684 -151
- package/dist/api/bitstream-filter.js.map +1 -1
- package/dist/api/constants.d.ts +44 -0
- package/dist/api/constants.js +45 -0
- package/dist/api/constants.js.map +1 -0
- package/dist/api/data/test_av1.ivf +0 -0
- package/dist/api/data/test_mjpeg.mjpeg +0 -0
- package/dist/api/data/test_vp8.ivf +0 -0
- package/dist/api/data/test_vp9.ivf +0 -0
- package/dist/api/decoder.d.ts +454 -77
- package/dist/api/decoder.js +1081 -271
- package/dist/api/decoder.js.map +1 -1
- package/dist/api/{media-input.d.ts → demuxer.d.ts} +295 -45
- package/dist/api/demuxer.js +1965 -0
- package/dist/api/demuxer.js.map +1 -0
- package/dist/api/encoder.d.ts +423 -132
- package/dist/api/encoder.js +1089 -240
- package/dist/api/encoder.js.map +1 -1
- package/dist/api/filter-complex.d.ts +769 -0
- package/dist/api/filter-complex.js +1596 -0
- package/dist/api/filter-complex.js.map +1 -0
- package/dist/api/filter-presets.d.ts +80 -5
- package/dist/api/filter-presets.js +117 -7
- package/dist/api/filter-presets.js.map +1 -1
- package/dist/api/filter.d.ts +561 -125
- package/dist/api/filter.js +1083 -274
- package/dist/api/filter.js.map +1 -1
- package/dist/api/{fmp4.d.ts → fmp4-stream.d.ts} +141 -140
- package/dist/api/fmp4-stream.js +539 -0
- package/dist/api/fmp4-stream.js.map +1 -0
- package/dist/api/hardware.d.ts +58 -6
- package/dist/api/hardware.js +127 -11
- package/dist/api/hardware.js.map +1 -1
- package/dist/api/index.d.ts +8 -4
- package/dist/api/index.js +17 -8
- package/dist/api/index.js.map +1 -1
- package/dist/api/io-stream.d.ts +6 -6
- package/dist/api/io-stream.js +5 -4
- package/dist/api/io-stream.js.map +1 -1
- package/dist/api/{media-output.d.ts → muxer.d.ts} +280 -66
- package/dist/api/muxer.js +1934 -0
- package/dist/api/muxer.js.map +1 -0
- package/dist/api/pipeline.d.ts +77 -29
- package/dist/api/pipeline.js +449 -439
- package/dist/api/pipeline.js.map +1 -1
- package/dist/api/rtp-stream.d.ts +312 -0
- package/dist/api/rtp-stream.js +630 -0
- package/dist/api/rtp-stream.js.map +1 -0
- package/dist/api/types.d.ts +533 -56
- package/dist/api/utilities/async-queue.d.ts +91 -0
- package/dist/api/utilities/async-queue.js +162 -0
- package/dist/api/utilities/async-queue.js.map +1 -0
- package/dist/api/utilities/audio-sample.d.ts +11 -1
- package/dist/api/utilities/audio-sample.js +10 -0
- package/dist/api/utilities/audio-sample.js.map +1 -1
- package/dist/api/utilities/channel-layout.d.ts +1 -0
- package/dist/api/utilities/channel-layout.js +1 -0
- package/dist/api/utilities/channel-layout.js.map +1 -1
- package/dist/api/utilities/image.d.ts +39 -1
- package/dist/api/utilities/image.js +38 -0
- package/dist/api/utilities/image.js.map +1 -1
- package/dist/api/utilities/index.d.ts +3 -0
- package/dist/api/utilities/index.js +6 -0
- package/dist/api/utilities/index.js.map +1 -1
- package/dist/api/utilities/media-type.d.ts +2 -1
- package/dist/api/utilities/media-type.js +1 -0
- package/dist/api/utilities/media-type.js.map +1 -1
- package/dist/api/utilities/pixel-format.d.ts +4 -1
- package/dist/api/utilities/pixel-format.js +3 -0
- package/dist/api/utilities/pixel-format.js.map +1 -1
- package/dist/api/utilities/sample-format.d.ts +6 -1
- package/dist/api/utilities/sample-format.js +5 -0
- package/dist/api/utilities/sample-format.js.map +1 -1
- package/dist/api/utilities/scheduler.d.ts +138 -0
- package/dist/api/utilities/scheduler.js +98 -0
- package/dist/api/utilities/scheduler.js.map +1 -0
- package/dist/api/utilities/streaming.d.ts +105 -15
- package/dist/api/utilities/streaming.js +201 -12
- package/dist/api/utilities/streaming.js.map +1 -1
- package/dist/api/utilities/timestamp.d.ts +15 -1
- package/dist/api/utilities/timestamp.js +14 -0
- package/dist/api/utilities/timestamp.js.map +1 -1
- package/dist/api/utilities/whisper-model.d.ts +310 -0
- package/dist/api/utilities/whisper-model.js +528 -0
- package/dist/api/utilities/whisper-model.js.map +1 -0
- package/dist/api/webrtc-stream.d.ts +288 -0
- package/dist/api/webrtc-stream.js +440 -0
- package/dist/api/webrtc-stream.js.map +1 -0
- package/dist/api/whisper.d.ts +324 -0
- package/dist/api/whisper.js +362 -0
- package/dist/api/whisper.js.map +1 -0
- package/dist/constants/constants.d.ts +54 -2
- package/dist/constants/constants.js +48 -1
- package/dist/constants/constants.js.map +1 -1
- package/dist/constants/encoders.d.ts +2 -1
- package/dist/constants/encoders.js +4 -3
- package/dist/constants/encoders.js.map +1 -1
- package/dist/constants/hardware.d.ts +26 -0
- package/dist/constants/hardware.js +27 -0
- package/dist/constants/hardware.js.map +1 -0
- package/dist/constants/index.d.ts +1 -0
- package/dist/constants/index.js +1 -0
- package/dist/constants/index.js.map +1 -1
- package/dist/ffmpeg/index.d.ts +3 -3
- package/dist/ffmpeg/index.js +3 -3
- package/dist/ffmpeg/utils.d.ts +27 -0
- package/dist/ffmpeg/utils.js +28 -16
- package/dist/ffmpeg/utils.js.map +1 -1
- package/dist/lib/binding.d.ts +22 -11
- package/dist/lib/binding.js.map +1 -1
- package/dist/lib/codec-context.d.ts +87 -0
- package/dist/lib/codec-context.js +125 -4
- package/dist/lib/codec-context.js.map +1 -1
- package/dist/lib/codec-parameters.d.ts +229 -1
- package/dist/lib/codec-parameters.js +264 -0
- package/dist/lib/codec-parameters.js.map +1 -1
- package/dist/lib/codec-parser.d.ts +23 -0
- package/dist/lib/codec-parser.js +25 -0
- package/dist/lib/codec-parser.js.map +1 -1
- package/dist/lib/codec.d.ts +26 -4
- package/dist/lib/codec.js +35 -0
- package/dist/lib/codec.js.map +1 -1
- package/dist/lib/dictionary.js +1 -0
- package/dist/lib/dictionary.js.map +1 -1
- package/dist/lib/error.js +1 -1
- package/dist/lib/error.js.map +1 -1
- package/dist/lib/fifo.d.ts +416 -0
- package/dist/lib/fifo.js +453 -0
- package/dist/lib/fifo.js.map +1 -0
- package/dist/lib/filter-context.d.ts +52 -11
- package/dist/lib/filter-context.js +56 -12
- package/dist/lib/filter-context.js.map +1 -1
- package/dist/lib/filter-graph.d.ts +9 -0
- package/dist/lib/filter-graph.js +13 -0
- package/dist/lib/filter-graph.js.map +1 -1
- package/dist/lib/filter.d.ts +21 -0
- package/dist/lib/filter.js +28 -0
- package/dist/lib/filter.js.map +1 -1
- package/dist/lib/format-context.d.ts +48 -14
- package/dist/lib/format-context.js +76 -7
- package/dist/lib/format-context.js.map +1 -1
- package/dist/lib/frame.d.ts +264 -1
- package/dist/lib/frame.js +351 -1
- package/dist/lib/frame.js.map +1 -1
- package/dist/lib/hardware-device-context.d.ts +3 -2
- package/dist/lib/hardware-device-context.js.map +1 -1
- package/dist/lib/index.d.ts +2 -0
- package/dist/lib/index.js +4 -0
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/input-format.d.ts +21 -0
- package/dist/lib/input-format.js +42 -2
- package/dist/lib/input-format.js.map +1 -1
- package/dist/lib/native-types.d.ts +76 -27
- package/dist/lib/option.d.ts +25 -13
- package/dist/lib/option.js +28 -0
- package/dist/lib/option.js.map +1 -1
- package/dist/lib/output-format.d.ts +22 -1
- package/dist/lib/output-format.js +28 -0
- package/dist/lib/output-format.js.map +1 -1
- package/dist/lib/packet.d.ts +35 -0
- package/dist/lib/packet.js +52 -2
- package/dist/lib/packet.js.map +1 -1
- package/dist/lib/rational.d.ts +18 -0
- package/dist/lib/rational.js +19 -0
- package/dist/lib/rational.js.map +1 -1
- package/dist/lib/stream.d.ts +126 -0
- package/dist/lib/stream.js +188 -5
- package/dist/lib/stream.js.map +1 -1
- package/dist/lib/sync-queue.d.ts +179 -0
- package/dist/lib/sync-queue.js +197 -0
- package/dist/lib/sync-queue.js.map +1 -0
- package/dist/lib/types.d.ts +49 -1
- package/dist/lib/utilities.d.ts +281 -53
- package/dist/lib/utilities.js +298 -55
- package/dist/lib/utilities.js.map +1 -1
- package/install/check.js +2 -2
- package/package.json +37 -26
- package/dist/api/fmp4.js +0 -710
- package/dist/api/fmp4.js.map +0 -1
- package/dist/api/media-input.js +0 -1075
- package/dist/api/media-input.js.map +0 -1
- package/dist/api/media-output.js +0 -1040
- package/dist/api/media-output.js.map +0 -1
- package/dist/api/webrtc.d.ts +0 -664
- package/dist/api/webrtc.js +0 -1132
- package/dist/api/webrtc.js.map +0 -1
package/dist/api/pipeline.js
CHANGED
|
@@ -1,8 +1,55 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
2
|
+
if (value !== null && value !== void 0) {
|
|
3
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
4
|
+
var dispose, inner;
|
|
5
|
+
if (async) {
|
|
6
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
7
|
+
dispose = value[Symbol.asyncDispose];
|
|
8
|
+
}
|
|
9
|
+
if (dispose === void 0) {
|
|
10
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
11
|
+
dispose = value[Symbol.dispose];
|
|
12
|
+
if (async) inner = dispose;
|
|
13
|
+
}
|
|
14
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
15
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
16
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
17
|
+
}
|
|
18
|
+
else if (async) {
|
|
19
|
+
env.stack.push({ async: true });
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
};
|
|
23
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
24
|
+
return function (env) {
|
|
25
|
+
function fail(e) {
|
|
26
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
27
|
+
env.hasError = true;
|
|
28
|
+
}
|
|
29
|
+
var r, s = 0;
|
|
30
|
+
function next() {
|
|
31
|
+
while (r = env.stack.pop()) {
|
|
32
|
+
try {
|
|
33
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
34
|
+
if (r.dispose) {
|
|
35
|
+
var result = r.dispose.call(r.value);
|
|
36
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
37
|
+
}
|
|
38
|
+
else s |= 1;
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
fail(e);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
45
|
+
if (env.hasError) throw env.error;
|
|
46
|
+
}
|
|
47
|
+
return next();
|
|
48
|
+
};
|
|
49
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
50
|
+
var e = new Error(message);
|
|
51
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
|
+
});
|
|
6
53
|
// ============================================================================
|
|
7
54
|
// Implementation
|
|
8
55
|
// ============================================================================
|
|
@@ -43,6 +90,26 @@
|
|
|
43
90
|
export function pipeline(...args) {
|
|
44
91
|
// Detect pipeline type based on first argument
|
|
45
92
|
const firstArg = args[0];
|
|
93
|
+
const secondArg = args[1];
|
|
94
|
+
// Check for shared Demuxer + NamedStages pattern
|
|
95
|
+
if (isDemuxer(firstArg) && isNamedStages(secondArg)) {
|
|
96
|
+
// Convert shared input to NamedInputs based on stages keys
|
|
97
|
+
const sharedInput = firstArg;
|
|
98
|
+
const stages = secondArg;
|
|
99
|
+
const namedInputs = {};
|
|
100
|
+
// Create NamedInputs with shared input for all streams in stages
|
|
101
|
+
for (const streamName of Object.keys(stages)) {
|
|
102
|
+
namedInputs[streamName] = sharedInput;
|
|
103
|
+
}
|
|
104
|
+
if (args.length === 3) {
|
|
105
|
+
// Full named pipeline with output(s)
|
|
106
|
+
return runNamedPipeline(namedInputs, stages, args[2]);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
// Partial named pipeline
|
|
110
|
+
return runNamedPartialPipeline(namedInputs, stages);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
46
113
|
if (isNamedInputs(firstArg)) {
|
|
47
114
|
// Named pipeline (2 or 3 arguments)
|
|
48
115
|
if (args.length === 2) {
|
|
@@ -54,14 +121,14 @@ export function pipeline(...args) {
|
|
|
54
121
|
return runNamedPipeline(args[0], args[1], args[2]);
|
|
55
122
|
}
|
|
56
123
|
}
|
|
57
|
-
else if (
|
|
58
|
-
// Check if this is a stream copy (
|
|
59
|
-
if (args.length === 2 &&
|
|
124
|
+
else if (isDemuxer(firstArg)) {
|
|
125
|
+
// Check if this is a stream copy (Demuxer → Muxer)
|
|
126
|
+
if (args.length === 2 && isMuxer(args[1])) {
|
|
60
127
|
// Stream copy all streams
|
|
61
|
-
return
|
|
128
|
+
return runDemuxerPipeline(args[0], args[1]);
|
|
62
129
|
}
|
|
63
130
|
else {
|
|
64
|
-
// Simple pipeline starting with
|
|
131
|
+
// Simple pipeline starting with Demuxer
|
|
65
132
|
return runSimplePipeline(args);
|
|
66
133
|
}
|
|
67
134
|
}
|
|
@@ -128,10 +195,10 @@ class PipelineControlImpl {
|
|
|
128
195
|
}
|
|
129
196
|
}
|
|
130
197
|
// ============================================================================
|
|
131
|
-
//
|
|
198
|
+
// Demuxer Pipeline Implementation
|
|
132
199
|
// ============================================================================
|
|
133
200
|
/**
|
|
134
|
-
* Run a
|
|
201
|
+
* Run a demuxer pipeline for stream copy.
|
|
135
202
|
*
|
|
136
203
|
* @param input - Media input source
|
|
137
204
|
*
|
|
@@ -141,14 +208,14 @@ class PipelineControlImpl {
|
|
|
141
208
|
*
|
|
142
209
|
* @internal
|
|
143
210
|
*/
|
|
144
|
-
function
|
|
211
|
+
function runDemuxerPipeline(input, output) {
|
|
145
212
|
let control;
|
|
146
213
|
// eslint-disable-next-line prefer-const
|
|
147
|
-
control = new PipelineControlImpl(
|
|
214
|
+
control = new PipelineControlImpl(runDemuxerPipelineAsync(input, output, () => control?.isStopped() ?? false));
|
|
148
215
|
return control;
|
|
149
216
|
}
|
|
150
217
|
/**
|
|
151
|
-
* Run
|
|
218
|
+
* Run demuxer pipeline asynchronously.
|
|
152
219
|
*
|
|
153
220
|
* @param input - Media input source
|
|
154
221
|
*
|
|
@@ -158,7 +225,7 @@ function runMediaInputPipeline(input, output) {
|
|
|
158
225
|
*
|
|
159
226
|
* @internal
|
|
160
227
|
*/
|
|
161
|
-
async function
|
|
228
|
+
async function runDemuxerPipelineAsync(input, output, shouldStop) {
|
|
162
229
|
// Get all streams from input
|
|
163
230
|
const videoStream = input.video();
|
|
164
231
|
const audioStream = input.audio();
|
|
@@ -183,18 +250,35 @@ async function runMediaInputPipelineAsync(input, output, shouldStop) {
|
|
|
183
250
|
}
|
|
184
251
|
}
|
|
185
252
|
// Copy all packets
|
|
186
|
-
for await (const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
packet
|
|
190
|
-
|
|
253
|
+
for await (const packet_1 of input.packets()) {
|
|
254
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
255
|
+
try {
|
|
256
|
+
const packet = __addDisposableResource(env_1, packet_1, false);
|
|
257
|
+
// Check if we should stop
|
|
258
|
+
if (shouldStop()) {
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
// Handle EOF signal (null packet from input means all streams are done)
|
|
262
|
+
if (packet === null) {
|
|
263
|
+
// Signal EOF for all streams
|
|
264
|
+
for (const mapping of streams) {
|
|
265
|
+
await output.writePacket(null, mapping.index);
|
|
266
|
+
}
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
// Find the corresponding output stream index
|
|
270
|
+
const mapping = streams.find((s) => s.stream.index === packet.streamIndex);
|
|
271
|
+
if (mapping) {
|
|
272
|
+
await output.writePacket(packet, mapping.index);
|
|
273
|
+
}
|
|
191
274
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
275
|
+
catch (e_1) {
|
|
276
|
+
env_1.error = e_1;
|
|
277
|
+
env_1.hasError = true;
|
|
278
|
+
}
|
|
279
|
+
finally {
|
|
280
|
+
__disposeResources(env_1);
|
|
196
281
|
}
|
|
197
|
-
packet.free(); // Free packet after processing
|
|
198
282
|
}
|
|
199
283
|
await output.close();
|
|
200
284
|
}
|
|
@@ -212,14 +296,14 @@ async function runMediaInputPipelineAsync(input, output, shouldStop) {
|
|
|
212
296
|
*/
|
|
213
297
|
function runSimplePipeline(args) {
|
|
214
298
|
const [source, ...stages] = args;
|
|
215
|
-
// Check if last stage is
|
|
299
|
+
// Check if last stage is Muxer (consumes stream)
|
|
216
300
|
const lastStage = stages[stages.length - 1];
|
|
217
|
-
const isOutput =
|
|
301
|
+
const isOutput = isMuxer(lastStage);
|
|
218
302
|
// Track metadata through pipeline
|
|
219
303
|
const metadata = {};
|
|
220
|
-
// Store
|
|
221
|
-
if (
|
|
222
|
-
metadata.
|
|
304
|
+
// Store Demuxer reference if we have one
|
|
305
|
+
if (isDemuxer(source)) {
|
|
306
|
+
metadata.demuxer = source;
|
|
223
307
|
}
|
|
224
308
|
// Build the pipeline generator
|
|
225
309
|
// If output is present, exclude it from stages for processing
|
|
@@ -236,10 +320,10 @@ function runSimplePipeline(args) {
|
|
|
236
320
|
metadata.bitStreamFilter = stage;
|
|
237
321
|
}
|
|
238
322
|
}
|
|
239
|
-
// Convert
|
|
323
|
+
// Convert Demuxer to packet stream if needed
|
|
240
324
|
// If we have a decoder or BSF, filter packets by stream index
|
|
241
325
|
let actualSource;
|
|
242
|
-
if (
|
|
326
|
+
if (isDemuxer(source)) {
|
|
243
327
|
if (metadata.decoder) {
|
|
244
328
|
// Filter packets for the decoder's stream
|
|
245
329
|
const streamIndex = metadata.decoder.getStream().index;
|
|
@@ -263,7 +347,7 @@ function runSimplePipeline(args) {
|
|
|
263
347
|
if (isOutput) {
|
|
264
348
|
let control;
|
|
265
349
|
// eslint-disable-next-line prefer-const
|
|
266
|
-
control = new PipelineControlImpl(consumeSimplePipeline(generator, lastStage, metadata, () => control
|
|
350
|
+
control = new PipelineControlImpl(consumeSimplePipeline(generator, lastStage, metadata, () => control?.isStopped() ?? false));
|
|
267
351
|
return control;
|
|
268
352
|
}
|
|
269
353
|
// Otherwise return the generator for further processing
|
|
@@ -284,25 +368,25 @@ async function* buildSimplePipeline(source, stages) {
|
|
|
284
368
|
let stream = source;
|
|
285
369
|
for (const stage of stages) {
|
|
286
370
|
if (isDecoder(stage)) {
|
|
287
|
-
stream =
|
|
371
|
+
stream = stage.frames(stream);
|
|
288
372
|
}
|
|
289
373
|
else if (isEncoder(stage)) {
|
|
290
|
-
stream =
|
|
374
|
+
stream = stage.packets(stream);
|
|
291
375
|
}
|
|
292
376
|
else if (isFilterAPI(stage)) {
|
|
293
|
-
stream =
|
|
377
|
+
stream = stage.frames(stream);
|
|
294
378
|
}
|
|
295
379
|
else if (isBitStreamFilterAPI(stage)) {
|
|
296
|
-
stream =
|
|
380
|
+
stream = stage.packets(stream);
|
|
297
381
|
}
|
|
298
382
|
else if (Array.isArray(stage)) {
|
|
299
383
|
// Chain multiple filters or BSFs
|
|
300
384
|
for (const filter of stage) {
|
|
301
385
|
if (isFilterAPI(filter)) {
|
|
302
|
-
stream =
|
|
386
|
+
stream = filter.frames(stream);
|
|
303
387
|
}
|
|
304
388
|
else if (isBitStreamFilterAPI(filter)) {
|
|
305
|
-
stream =
|
|
389
|
+
stream = filter.packets(stream);
|
|
306
390
|
}
|
|
307
391
|
}
|
|
308
392
|
}
|
|
@@ -326,7 +410,16 @@ async function consumeSimplePipeline(stream, output, metadata, shouldStop) {
|
|
|
326
410
|
// Add stream to output if we have encoder or decoder info
|
|
327
411
|
let streamIndex = 0;
|
|
328
412
|
if (metadata.encoder) {
|
|
329
|
-
|
|
413
|
+
// Encoding path
|
|
414
|
+
if (metadata.decoder) {
|
|
415
|
+
// Have decoder - use its stream for metadata/properties
|
|
416
|
+
const originalStream = metadata.decoder.getStream();
|
|
417
|
+
streamIndex = output.addStream(originalStream, { encoder: metadata.encoder });
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
// Encoder-only mode (e.g., frame generator) - no input stream
|
|
421
|
+
streamIndex = output.addStream(metadata.encoder);
|
|
422
|
+
}
|
|
330
423
|
}
|
|
331
424
|
else if (metadata.decoder) {
|
|
332
425
|
// Stream copy - use decoder's original stream
|
|
@@ -339,29 +432,37 @@ async function consumeSimplePipeline(stream, output, metadata, shouldStop) {
|
|
|
339
432
|
streamIndex = output.addStream(originalStream);
|
|
340
433
|
}
|
|
341
434
|
else {
|
|
342
|
-
// For direct
|
|
435
|
+
// For direct Demuxer → Muxer, we redirect to runDemuxerPipeline
|
|
343
436
|
// This case shouldn't happen in simple pipeline
|
|
344
437
|
throw new Error('Cannot determine stream configuration. This is likely a bug in the pipeline.');
|
|
345
438
|
}
|
|
346
439
|
// Process stream
|
|
347
|
-
for await (const
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
440
|
+
for await (const item_1 of stream) {
|
|
441
|
+
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
442
|
+
try {
|
|
443
|
+
const item = __addDisposableResource(env_2, item_1, false);
|
|
444
|
+
// Check if we should stop
|
|
445
|
+
if (shouldStop()) {
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
// Handle EOF signal
|
|
449
|
+
if (item === null) {
|
|
450
|
+
await output.writePacket(null, streamIndex);
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
if (isPacket(item) || item === null) {
|
|
454
|
+
await output.writePacket(item, streamIndex);
|
|
352
455
|
}
|
|
353
456
|
else {
|
|
354
|
-
|
|
457
|
+
throw new Error('Cannot write frames directly to Muxer. Use an encoder first.');
|
|
355
458
|
}
|
|
356
|
-
break;
|
|
357
459
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
item.free();
|
|
460
|
+
catch (e_2) {
|
|
461
|
+
env_2.error = e_2;
|
|
462
|
+
env_2.hasError = true;
|
|
362
463
|
}
|
|
363
|
-
|
|
364
|
-
|
|
464
|
+
finally {
|
|
465
|
+
__disposeResources(env_2);
|
|
365
466
|
}
|
|
366
467
|
}
|
|
367
468
|
await output.close();
|
|
@@ -403,7 +504,19 @@ function runNamedPartialPipeline(inputs, stages) {
|
|
|
403
504
|
if (!stream) {
|
|
404
505
|
throw new Error(`No ${streamName} stream found in input.`);
|
|
405
506
|
}
|
|
406
|
-
if
|
|
507
|
+
// Normalize stages: if array contains only undefined, treat as passthrough
|
|
508
|
+
// Also filter out undefined entries from the array
|
|
509
|
+
let normalizedStages = streamStages;
|
|
510
|
+
if (Array.isArray(streamStages)) {
|
|
511
|
+
const definedStages = streamStages.filter((stage) => stage !== undefined);
|
|
512
|
+
if (definedStages.length === 0) {
|
|
513
|
+
normalizedStages = 'passthrough';
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
normalizedStages = definedStages;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
if (normalizedStages === 'passthrough') {
|
|
407
520
|
// Direct passthrough - return input packets for this specific stream
|
|
408
521
|
result[streamName] = (async function* () {
|
|
409
522
|
for await (const packet of input.packets(stream.index)) {
|
|
@@ -415,7 +528,8 @@ function runNamedPartialPipeline(inputs, stages) {
|
|
|
415
528
|
// Process the stream - pass packets for this specific stream only
|
|
416
529
|
// Build pipeline for this stream (can return frames or packets)
|
|
417
530
|
const metadata = {};
|
|
418
|
-
|
|
531
|
+
const stages = normalizedStages;
|
|
532
|
+
result[streamName] = buildFlexibleNamedStreamPipeline(input.packets(stream.index), stages, metadata);
|
|
419
533
|
}
|
|
420
534
|
}
|
|
421
535
|
return result;
|
|
@@ -436,7 +550,7 @@ function runNamedPartialPipeline(inputs, stages) {
|
|
|
436
550
|
function runNamedPipeline(inputs, stages, output) {
|
|
437
551
|
let control;
|
|
438
552
|
// eslint-disable-next-line prefer-const
|
|
439
|
-
control = new PipelineControlImpl(runNamedPipelineAsync(inputs, stages, output, () => control
|
|
553
|
+
control = new PipelineControlImpl(runNamedPipelineAsync(inputs, stages, output, () => control?.isStopped() ?? false));
|
|
440
554
|
return control;
|
|
441
555
|
}
|
|
442
556
|
/**
|
|
@@ -453,84 +567,210 @@ function runNamedPipeline(inputs, stages, output) {
|
|
|
453
567
|
* @internal
|
|
454
568
|
*/
|
|
455
569
|
async function runNamedPipelineAsync(inputs, stages, output, shouldStop) {
|
|
570
|
+
// Check if all inputs reference the same Demuxer instance
|
|
571
|
+
const inputValues = Object.values(inputs);
|
|
572
|
+
const allSameInput = inputValues.length > 1 && inputValues.every((input) => input === inputValues[0]);
|
|
456
573
|
// Track metadata for each stream
|
|
457
574
|
const streamMetadata = {};
|
|
458
575
|
// Process each named stream into generators
|
|
459
576
|
const processedStreams = {};
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
metadata.type = 'audio';
|
|
478
|
-
break;
|
|
479
|
-
}
|
|
480
|
-
if (!stream) {
|
|
481
|
-
throw new Error(`No ${streamName} stream found in input for passthrough.`);
|
|
577
|
+
// If all inputs are the same instance, use Demuxer's built-in parallel packet generators
|
|
578
|
+
if (allSameInput) {
|
|
579
|
+
const sharedInput = inputValues[0];
|
|
580
|
+
// Single pass: collect metadata and build pipelines directly using input.packets(streamIndex)
|
|
581
|
+
for (const [streamName, streamStages] of Object.entries(stages)) {
|
|
582
|
+
const metadata = {};
|
|
583
|
+
streamMetadata[streamName] = metadata;
|
|
584
|
+
// Normalize stages
|
|
585
|
+
let normalizedStages = streamStages;
|
|
586
|
+
if (Array.isArray(streamStages)) {
|
|
587
|
+
const definedStages = streamStages.filter((stage) => stage !== undefined);
|
|
588
|
+
if (definedStages.length === 0) {
|
|
589
|
+
normalizedStages = 'passthrough';
|
|
590
|
+
}
|
|
591
|
+
else {
|
|
592
|
+
normalizedStages = definedStages;
|
|
593
|
+
}
|
|
482
594
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
595
|
+
// Determine stream index and build pipeline
|
|
596
|
+
let streamIndex;
|
|
597
|
+
if (normalizedStages !== 'passthrough') {
|
|
598
|
+
const stages = normalizedStages;
|
|
599
|
+
// Set stream type
|
|
600
|
+
metadata.type = streamName;
|
|
601
|
+
// Populate metadata by walking through ALL stages
|
|
602
|
+
for (const stage of stages) {
|
|
603
|
+
if (isDecoder(stage)) {
|
|
604
|
+
metadata.decoder = stage;
|
|
605
|
+
streamIndex ??= stage.getStream().index;
|
|
606
|
+
}
|
|
607
|
+
else if (isBitStreamFilterAPI(stage)) {
|
|
608
|
+
metadata.bitStreamFilter = stage;
|
|
609
|
+
streamIndex ??= stage.getStream().index;
|
|
610
|
+
}
|
|
611
|
+
else if (isEncoder(stage)) {
|
|
612
|
+
metadata.encoder = stage;
|
|
613
|
+
}
|
|
492
614
|
}
|
|
493
|
-
|
|
494
|
-
|
|
615
|
+
// If no decoder/BSF, use stream name to determine index
|
|
616
|
+
if (streamIndex === undefined) {
|
|
617
|
+
const stream = streamName === 'video' ? sharedInput.video() : sharedInput.audio();
|
|
618
|
+
if (!stream) {
|
|
619
|
+
throw new Error(`No ${streamName} stream found in input.`);
|
|
620
|
+
}
|
|
621
|
+
streamIndex = stream.index;
|
|
495
622
|
}
|
|
496
|
-
|
|
497
|
-
|
|
623
|
+
// Build pipeline with packets from this specific stream
|
|
624
|
+
processedStreams[streamName] = buildNamedStreamPipeline(sharedInput.packets(streamIndex), stages, metadata);
|
|
625
|
+
}
|
|
626
|
+
else {
|
|
627
|
+
// Passthrough - use Demuxer's built-in stream filtering
|
|
628
|
+
metadata.type = streamName;
|
|
629
|
+
metadata.demuxer = sharedInput;
|
|
630
|
+
const stream = streamName === 'video' ? sharedInput.video() : sharedInput.audio();
|
|
631
|
+
if (!stream) {
|
|
632
|
+
throw new Error(`No ${streamName} stream found in input for passthrough.`);
|
|
498
633
|
}
|
|
634
|
+
streamIndex = stream.index;
|
|
635
|
+
// Direct passthrough using input.packets(streamIndex)
|
|
636
|
+
processedStreams[streamName] = sharedInput.packets(streamIndex);
|
|
499
637
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
else {
|
|
641
|
+
// Original logic: separate inputs or single input
|
|
642
|
+
for (const [streamName, streamStages] of Object.entries(stages)) {
|
|
643
|
+
const metadata = {};
|
|
644
|
+
streamMetadata[streamName] = metadata;
|
|
645
|
+
const input = inputs[streamName];
|
|
646
|
+
if (!input) {
|
|
647
|
+
throw new Error(`No input found for stream: ${streamName}`);
|
|
505
648
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
649
|
+
// Normalize stages: if array contains only undefined, treat as passthrough
|
|
650
|
+
// Also filter out undefined entries from the array
|
|
651
|
+
let normalizedStages = streamStages;
|
|
652
|
+
if (Array.isArray(streamStages)) {
|
|
653
|
+
const definedStages = streamStages.filter((stage) => stage !== undefined);
|
|
654
|
+
if (definedStages.length === 0) {
|
|
655
|
+
normalizedStages = 'passthrough';
|
|
656
|
+
}
|
|
657
|
+
else {
|
|
658
|
+
normalizedStages = definedStages;
|
|
659
|
+
}
|
|
509
660
|
}
|
|
510
|
-
|
|
511
|
-
//
|
|
661
|
+
if (normalizedStages === 'passthrough') {
|
|
662
|
+
// Direct passthrough - no processing
|
|
512
663
|
let stream = null;
|
|
513
664
|
switch (streamName) {
|
|
514
665
|
case 'video':
|
|
515
666
|
stream = input.video() ?? null;
|
|
667
|
+
metadata.type = 'video';
|
|
516
668
|
break;
|
|
517
669
|
case 'audio':
|
|
518
670
|
stream = input.audio() ?? null;
|
|
671
|
+
metadata.type = 'audio';
|
|
519
672
|
break;
|
|
520
673
|
}
|
|
521
674
|
if (!stream) {
|
|
522
|
-
throw new Error(`No ${streamName} stream found in input.`);
|
|
675
|
+
throw new Error(`No ${streamName} stream found in input for passthrough.`);
|
|
523
676
|
}
|
|
524
|
-
|
|
677
|
+
processedStreams[streamName] = input.packets(stream.index);
|
|
678
|
+
metadata.demuxer = input; // Track Demuxer for passthrough
|
|
679
|
+
}
|
|
680
|
+
else {
|
|
681
|
+
// Process the stream - normalizedStages is guaranteed to be an array here
|
|
682
|
+
const stages = normalizedStages;
|
|
683
|
+
// Pre-populate metadata by walking through stages
|
|
684
|
+
for (const stage of stages) {
|
|
685
|
+
if (isDecoder(stage)) {
|
|
686
|
+
metadata.decoder = stage;
|
|
687
|
+
}
|
|
688
|
+
else if (isEncoder(stage)) {
|
|
689
|
+
metadata.encoder = stage;
|
|
690
|
+
}
|
|
691
|
+
else if (isBitStreamFilterAPI(stage)) {
|
|
692
|
+
metadata.bitStreamFilter = stage;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
// Get packets - filter by stream index based on decoder, BSF, or stream type
|
|
696
|
+
let packets;
|
|
697
|
+
if (metadata.decoder) {
|
|
698
|
+
const streamIndex = metadata.decoder.getStream().index;
|
|
699
|
+
packets = input.packets(streamIndex);
|
|
700
|
+
}
|
|
701
|
+
else if (metadata.bitStreamFilter) {
|
|
702
|
+
const streamIndex = metadata.bitStreamFilter.getStream().index;
|
|
703
|
+
packets = input.packets(streamIndex);
|
|
704
|
+
}
|
|
705
|
+
else {
|
|
706
|
+
// No decoder or BSF - determine stream by name
|
|
707
|
+
let stream = null;
|
|
708
|
+
switch (streamName) {
|
|
709
|
+
case 'video':
|
|
710
|
+
stream = input.video() ?? null;
|
|
711
|
+
break;
|
|
712
|
+
case 'audio':
|
|
713
|
+
stream = input.audio() ?? null;
|
|
714
|
+
break;
|
|
715
|
+
}
|
|
716
|
+
if (!stream) {
|
|
717
|
+
throw new Error(`No ${streamName} stream found in input.`);
|
|
718
|
+
}
|
|
719
|
+
packets = input.packets(stream.index);
|
|
720
|
+
}
|
|
721
|
+
// Build pipeline for this stream
|
|
722
|
+
processedStreams[streamName] = buildNamedStreamPipeline(packets, stages, metadata);
|
|
525
723
|
}
|
|
526
|
-
// Build pipeline for this stream
|
|
527
|
-
processedStreams[streamName] = buildNamedStreamPipeline(packets, streamStages, metadata);
|
|
528
724
|
}
|
|
529
725
|
}
|
|
530
726
|
// Write to output(s)
|
|
531
|
-
if (
|
|
532
|
-
//
|
|
533
|
-
|
|
727
|
+
if (isMuxer(output)) {
|
|
728
|
+
// Always write streams in parallel - Muxer's SyncQueue handles interleaving internally
|
|
729
|
+
const streamIndices = {};
|
|
730
|
+
// Add all streams to output first
|
|
731
|
+
for (const [name, meta] of Object.entries(streamMetadata)) {
|
|
732
|
+
if (meta.encoder) {
|
|
733
|
+
// Encoding path
|
|
734
|
+
if (meta.decoder) {
|
|
735
|
+
// Have decoder - use its stream for metadata/properties
|
|
736
|
+
const originalStream = meta.decoder.getStream();
|
|
737
|
+
streamIndices[name] = output.addStream(originalStream, { encoder: meta.encoder });
|
|
738
|
+
}
|
|
739
|
+
else {
|
|
740
|
+
// Encoder-only mode (e.g., frame generator) - no input stream
|
|
741
|
+
streamIndices[name] = output.addStream(meta.encoder);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
else if (meta.decoder) {
|
|
745
|
+
// Stream copy - use decoder's original stream
|
|
746
|
+
const originalStream = meta.decoder.getStream();
|
|
747
|
+
streamIndices[name] = output.addStream(originalStream);
|
|
748
|
+
}
|
|
749
|
+
else if (meta.bitStreamFilter) {
|
|
750
|
+
// BSF - use BSF's original stream
|
|
751
|
+
const originalStream = meta.bitStreamFilter.getStream();
|
|
752
|
+
streamIndices[name] = output.addStream(originalStream);
|
|
753
|
+
}
|
|
754
|
+
else if (meta.demuxer) {
|
|
755
|
+
// Passthrough from Demuxer
|
|
756
|
+
const stream = name.includes('video') ? meta.demuxer.video() : meta.demuxer.audio();
|
|
757
|
+
if (!stream) {
|
|
758
|
+
throw new Error(`No matching stream found in Demuxer for ${name}`);
|
|
759
|
+
}
|
|
760
|
+
streamIndices[name] = output.addStream(stream);
|
|
761
|
+
}
|
|
762
|
+
else {
|
|
763
|
+
throw new Error(`Cannot determine stream configuration for ${name}. This is likely a bug in the pipeline.`);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
// Write all streams in parallel - Muxer's SyncQueue handles interleaving
|
|
767
|
+
const promises = [];
|
|
768
|
+
for (const [name, stream] of Object.entries(processedStreams)) {
|
|
769
|
+
const streamIndex = streamIndices[name];
|
|
770
|
+
promises.push(consumeStreamInParallel(stream, output, streamIndex, shouldStop));
|
|
771
|
+
}
|
|
772
|
+
await Promise.all(promises);
|
|
773
|
+
await output.close();
|
|
534
774
|
}
|
|
535
775
|
else {
|
|
536
776
|
// Multiple outputs - write each stream to its output
|
|
@@ -564,27 +804,27 @@ async function* buildFlexibleNamedStreamPipeline(source, stages, metadata) {
|
|
|
564
804
|
for (const stage of stages) {
|
|
565
805
|
if (isDecoder(stage)) {
|
|
566
806
|
metadata.decoder = stage;
|
|
567
|
-
stream =
|
|
807
|
+
stream = stage.frames(stream);
|
|
568
808
|
}
|
|
569
809
|
else if (isEncoder(stage)) {
|
|
570
810
|
metadata.encoder = stage;
|
|
571
|
-
stream =
|
|
811
|
+
stream = stage.packets(stream);
|
|
572
812
|
}
|
|
573
813
|
else if (isFilterAPI(stage)) {
|
|
574
|
-
stream =
|
|
814
|
+
stream = stage.frames(stream);
|
|
575
815
|
}
|
|
576
816
|
else if (isBitStreamFilterAPI(stage)) {
|
|
577
817
|
metadata.bitStreamFilter = stage;
|
|
578
|
-
stream =
|
|
818
|
+
stream = stage.packets(stream);
|
|
579
819
|
}
|
|
580
820
|
else if (Array.isArray(stage)) {
|
|
581
821
|
// Chain multiple filters or BSFs
|
|
582
822
|
for (const filter of stage) {
|
|
583
823
|
if (isFilterAPI(filter)) {
|
|
584
|
-
stream =
|
|
824
|
+
stream = filter.frames(stream);
|
|
585
825
|
}
|
|
586
826
|
else if (isBitStreamFilterAPI(filter)) {
|
|
587
|
-
stream =
|
|
827
|
+
stream = filter.packets(stream);
|
|
588
828
|
}
|
|
589
829
|
}
|
|
590
830
|
}
|
|
@@ -610,34 +850,34 @@ async function* buildNamedStreamPipeline(source, stages, metadata) {
|
|
|
610
850
|
for (const stage of stages) {
|
|
611
851
|
if (isDecoder(stage)) {
|
|
612
852
|
metadata.decoder = stage;
|
|
613
|
-
stream =
|
|
853
|
+
stream = stage.frames(stream);
|
|
614
854
|
}
|
|
615
855
|
else if (isEncoder(stage)) {
|
|
616
856
|
metadata.encoder = stage;
|
|
617
|
-
stream =
|
|
857
|
+
stream = stage.packets(stream);
|
|
618
858
|
}
|
|
619
859
|
else if (isFilterAPI(stage)) {
|
|
620
|
-
stream =
|
|
860
|
+
stream = stage.frames(stream);
|
|
621
861
|
}
|
|
622
862
|
else if (isBitStreamFilterAPI(stage)) {
|
|
623
863
|
metadata.bitStreamFilter = stage;
|
|
624
|
-
stream =
|
|
864
|
+
stream = stage.packets(stream);
|
|
625
865
|
}
|
|
626
866
|
else if (Array.isArray(stage)) {
|
|
627
867
|
// Chain multiple filters or BSFs
|
|
628
868
|
for (const filter of stage) {
|
|
629
869
|
if (isFilterAPI(filter)) {
|
|
630
|
-
stream =
|
|
870
|
+
stream = filter.frames(stream);
|
|
631
871
|
}
|
|
632
872
|
else if (isBitStreamFilterAPI(filter)) {
|
|
633
|
-
stream =
|
|
873
|
+
stream = filter.packets(stream);
|
|
634
874
|
}
|
|
635
875
|
}
|
|
636
876
|
}
|
|
637
877
|
}
|
|
638
878
|
// Ensure we're yielding packets
|
|
639
879
|
for await (const item of stream) {
|
|
640
|
-
if (isPacket(item)) {
|
|
880
|
+
if (isPacket(item) || item === null) {
|
|
641
881
|
yield item;
|
|
642
882
|
}
|
|
643
883
|
else {
|
|
@@ -645,6 +885,42 @@ async function* buildNamedStreamPipeline(source, stages, metadata) {
|
|
|
645
885
|
}
|
|
646
886
|
}
|
|
647
887
|
}
|
|
888
|
+
/**
|
|
889
|
+
* Consume a stream in parallel (for passthrough pipelines).
|
|
890
|
+
* Stream index is already added to output.
|
|
891
|
+
*
|
|
892
|
+
* @param stream - Stream of packets
|
|
893
|
+
*
|
|
894
|
+
* @param output - Media output destination
|
|
895
|
+
*
|
|
896
|
+
* @param streamIndex - Pre-allocated stream index in output
|
|
897
|
+
*
|
|
898
|
+
* @param shouldStop - Function to check if pipeline should stop
|
|
899
|
+
*
|
|
900
|
+
* @internal
|
|
901
|
+
*/
|
|
902
|
+
async function consumeStreamInParallel(stream, output, streamIndex, shouldStop) {
|
|
903
|
+
// Write all packets (including EOF null)
|
|
904
|
+
for await (const packet_2 of stream) {
|
|
905
|
+
const env_3 = { stack: [], error: void 0, hasError: false };
|
|
906
|
+
try {
|
|
907
|
+
const packet = __addDisposableResource(env_3, packet_2, false);
|
|
908
|
+
// Check if we should stop
|
|
909
|
+
if (shouldStop()) {
|
|
910
|
+
break;
|
|
911
|
+
}
|
|
912
|
+
await output.writePacket(packet, streamIndex);
|
|
913
|
+
}
|
|
914
|
+
catch (e_3) {
|
|
915
|
+
env_3.error = e_3;
|
|
916
|
+
env_3.hasError = true;
|
|
917
|
+
}
|
|
918
|
+
finally {
|
|
919
|
+
__disposeResources(env_3);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
// Note: Don't close output here - it will be closed by the caller after all streams finish
|
|
923
|
+
}
|
|
648
924
|
/**
|
|
649
925
|
* Consume a named stream and write to output.
|
|
650
926
|
*
|
|
@@ -662,7 +938,16 @@ async function consumeNamedStream(stream, output, metadata, shouldStop) {
|
|
|
662
938
|
// Add stream to output
|
|
663
939
|
let streamIndex = 0;
|
|
664
940
|
if (metadata.encoder) {
|
|
665
|
-
|
|
941
|
+
// Encoding path
|
|
942
|
+
if (metadata.decoder) {
|
|
943
|
+
// Have decoder - use its stream for metadata/properties
|
|
944
|
+
const originalStream = metadata.decoder.getStream();
|
|
945
|
+
streamIndex = output.addStream(originalStream, { encoder: metadata.encoder });
|
|
946
|
+
}
|
|
947
|
+
else {
|
|
948
|
+
// Encoder-only mode (e.g., frame generator) - no input stream
|
|
949
|
+
streamIndex = output.addStream(metadata.encoder);
|
|
950
|
+
}
|
|
666
951
|
}
|
|
667
952
|
else if (metadata.decoder) {
|
|
668
953
|
// Stream copy - use decoder's original stream
|
|
@@ -674,11 +959,11 @@ async function consumeNamedStream(stream, output, metadata, shouldStop) {
|
|
|
674
959
|
const originalStream = metadata.bitStreamFilter.getStream();
|
|
675
960
|
streamIndex = output.addStream(originalStream);
|
|
676
961
|
}
|
|
677
|
-
else if (metadata.
|
|
678
|
-
// Passthrough from
|
|
679
|
-
const inputStream = metadata.type === 'video' ? metadata.
|
|
962
|
+
else if (metadata.demuxer) {
|
|
963
|
+
// Passthrough from Demuxer - use type hint from metadata
|
|
964
|
+
const inputStream = metadata.type === 'video' ? metadata.demuxer.video() : metadata.demuxer.audio();
|
|
680
965
|
if (!inputStream) {
|
|
681
|
-
throw new Error(`No ${metadata.type} stream found in
|
|
966
|
+
throw new Error(`No ${metadata.type} stream found in Demuxer`);
|
|
682
967
|
}
|
|
683
968
|
streamIndex = output.addStream(inputStream);
|
|
684
969
|
}
|
|
@@ -688,333 +973,58 @@ async function consumeNamedStream(stream, output, metadata, shouldStop) {
|
|
|
688
973
|
}
|
|
689
974
|
// Store for later use
|
|
690
975
|
metadata.streamIndex = streamIndex;
|
|
691
|
-
// Write all packets
|
|
692
|
-
for await (const
|
|
693
|
-
|
|
694
|
-
if (shouldStop()) {
|
|
695
|
-
packet.free();
|
|
696
|
-
break;
|
|
697
|
-
}
|
|
976
|
+
// Write all packets (including EOF null)
|
|
977
|
+
for await (const packet_3 of stream) {
|
|
978
|
+
const env_4 = { stack: [], error: void 0, hasError: false };
|
|
698
979
|
try {
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
// Note: Trailer will be written by interleaveToOutput
|
|
706
|
-
}
|
|
707
|
-
/**
|
|
708
|
-
* Interleave multiple streams to a single output.
|
|
709
|
-
*
|
|
710
|
-
* @param streams - Record of packet streams
|
|
711
|
-
*
|
|
712
|
-
* @param output - Media output destination
|
|
713
|
-
*
|
|
714
|
-
* @param metadata - Stream metadata for each stream
|
|
715
|
-
*
|
|
716
|
-
* @param shouldStop - Function to check if pipeline should stop
|
|
717
|
-
*
|
|
718
|
-
* @internal
|
|
719
|
-
*/
|
|
720
|
-
async function interleaveToOutput(streams, output, metadata, shouldStop) {
|
|
721
|
-
// Add all streams to output first
|
|
722
|
-
const streamIndices = {};
|
|
723
|
-
for (const [name, meta] of Object.entries(metadata)) {
|
|
724
|
-
if (meta.encoder) {
|
|
725
|
-
streamIndices[name] = output.addStream(meta.encoder);
|
|
726
|
-
}
|
|
727
|
-
else if (meta.decoder) {
|
|
728
|
-
// Stream copy - use decoder's original stream
|
|
729
|
-
const originalStream = meta.decoder.getStream();
|
|
730
|
-
streamIndices[name] = output.addStream(originalStream);
|
|
731
|
-
}
|
|
732
|
-
else if (meta.bitStreamFilter) {
|
|
733
|
-
// BSF - use BSF's original stream
|
|
734
|
-
const originalStream = meta.bitStreamFilter.getStream();
|
|
735
|
-
streamIndices[name] = output.addStream(originalStream);
|
|
736
|
-
}
|
|
737
|
-
else if (meta.mediaInput) {
|
|
738
|
-
// Passthrough from MediaInput - use stream name to determine which stream
|
|
739
|
-
const stream = name.includes('video') ? meta.mediaInput.video() : meta.mediaInput.audio();
|
|
740
|
-
if (!stream) {
|
|
741
|
-
throw new Error(`No matching stream found in MediaInput for ${name}`);
|
|
742
|
-
}
|
|
743
|
-
streamIndices[name] = output.addStream(stream);
|
|
744
|
-
}
|
|
745
|
-
else {
|
|
746
|
-
// This should not happen
|
|
747
|
-
throw new Error(`Cannot determine stream configuration for ${name}. This is likely a bug in the pipeline.`);
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
const queues = new Map();
|
|
751
|
-
const iterators = new Map();
|
|
752
|
-
const done = new Set();
|
|
753
|
-
// Initialize iterators
|
|
754
|
-
for (const [name, stream] of Object.entries(streams)) {
|
|
755
|
-
queues.set(name, []);
|
|
756
|
-
iterators.set(name, stream[Symbol.asyncIterator]());
|
|
757
|
-
}
|
|
758
|
-
// Read initial packet from each stream
|
|
759
|
-
for (const [name, iterator] of iterators) {
|
|
760
|
-
const result = await iterator.next();
|
|
761
|
-
if (!result.done && result.value) {
|
|
762
|
-
const packet = result.value;
|
|
763
|
-
packet._streamName = name;
|
|
764
|
-
queues.get(name).push(packet);
|
|
765
|
-
}
|
|
766
|
-
else {
|
|
767
|
-
done.add(name);
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
// Interleave packets based on DTS/PTS
|
|
771
|
-
while (done.size < Object.keys(streams).length && !shouldStop()) {
|
|
772
|
-
// Find packet with smallest DTS/PTS
|
|
773
|
-
let minPacket = null;
|
|
774
|
-
let minStreamName = null;
|
|
775
|
-
let minTime = BigInt(Number.MAX_SAFE_INTEGER);
|
|
776
|
-
for (const [name, queue] of queues) {
|
|
777
|
-
if (queue.length > 0) {
|
|
778
|
-
const packet = queue[0];
|
|
779
|
-
const time = packet.dts ?? packet.pts ?? BigInt(0);
|
|
780
|
-
if (time < minTime) {
|
|
781
|
-
minTime = time;
|
|
782
|
-
minPacket = packet;
|
|
783
|
-
minStreamName = name;
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
if (!minPacket || !minStreamName) {
|
|
788
|
-
// All queues empty, read more
|
|
789
|
-
for (const [name, iterator] of iterators) {
|
|
790
|
-
if (!done.has(name)) {
|
|
791
|
-
const result = await iterator.next();
|
|
792
|
-
if (!result.done && result.value) {
|
|
793
|
-
const packet = result.value;
|
|
794
|
-
packet._streamName = name;
|
|
795
|
-
queues.get(name).push(packet);
|
|
796
|
-
}
|
|
797
|
-
else {
|
|
798
|
-
done.add(name);
|
|
799
|
-
}
|
|
800
|
-
}
|
|
980
|
+
const packet = __addDisposableResource(env_4, packet_3, false);
|
|
981
|
+
// Check if we should stop
|
|
982
|
+
if (shouldStop()) {
|
|
983
|
+
break;
|
|
801
984
|
}
|
|
802
|
-
|
|
803
|
-
}
|
|
804
|
-
// Write the packet with smallest timestamp
|
|
805
|
-
const streamIndex = streamIndices[minStreamName];
|
|
806
|
-
await output.writePacket(minPacket, streamIndex);
|
|
807
|
-
// Free the packet after writing
|
|
808
|
-
minPacket.free();
|
|
809
|
-
// Remove from queue
|
|
810
|
-
queues.get(minStreamName).shift();
|
|
811
|
-
// Read next packet from that stream
|
|
812
|
-
const iterator = iterators.get(minStreamName);
|
|
813
|
-
const result = await iterator.next();
|
|
814
|
-
if (!result.done && result.value) {
|
|
815
|
-
const packet = result.value;
|
|
816
|
-
packet._streamName = minStreamName;
|
|
817
|
-
queues.get(minStreamName).push(packet);
|
|
818
|
-
}
|
|
819
|
-
else {
|
|
820
|
-
done.add(minStreamName);
|
|
985
|
+
await output.writePacket(packet, streamIndex);
|
|
821
986
|
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
for (const [, queue] of queues) {
|
|
826
|
-
for (const packet of queue) {
|
|
827
|
-
packet.free();
|
|
828
|
-
}
|
|
987
|
+
catch (e_4) {
|
|
988
|
+
env_4.error = e_4;
|
|
989
|
+
env_4.hasError = true;
|
|
829
990
|
}
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
// Write any remaining packets
|
|
833
|
-
for (const [name, queue] of queues) {
|
|
834
|
-
const streamIndex = streamIndices[name];
|
|
835
|
-
for (const packet of queue) {
|
|
836
|
-
await output.writePacket(packet, streamIndex);
|
|
837
|
-
packet.free(); // Free packet after writing
|
|
838
|
-
}
|
|
991
|
+
finally {
|
|
992
|
+
__disposeResources(env_4);
|
|
839
993
|
}
|
|
840
994
|
}
|
|
841
|
-
|
|
995
|
+
// Note: Output is closed by the caller after all streams finish
|
|
842
996
|
}
|
|
843
997
|
// ============================================================================
|
|
844
|
-
//
|
|
998
|
+
// Type Guards
|
|
845
999
|
// ============================================================================
|
|
846
1000
|
/**
|
|
847
|
-
*
|
|
848
|
-
*
|
|
849
|
-
* @param packets - Input packets
|
|
850
|
-
*
|
|
851
|
-
* @param decoder - Decoder instance
|
|
852
|
-
*
|
|
853
|
-
* @yields {Frame} Decoded frames
|
|
854
|
-
*
|
|
855
|
-
* @internal
|
|
856
|
-
*/
|
|
857
|
-
async function* decodeStream(packets, decoder) {
|
|
858
|
-
// Process all packets
|
|
859
|
-
for await (const packet of packets) {
|
|
860
|
-
try {
|
|
861
|
-
const frame = await decoder.decode(packet);
|
|
862
|
-
if (frame) {
|
|
863
|
-
yield frame;
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
finally {
|
|
867
|
-
// Free packet after decoding
|
|
868
|
-
packet.free();
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
// Flush decoder
|
|
872
|
-
if ('flushFrames' in decoder && typeof decoder.flushFrames === 'function') {
|
|
873
|
-
// Use generator method if available
|
|
874
|
-
for await (const frame of decoder.flushFrames()) {
|
|
875
|
-
yield frame;
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
else {
|
|
879
|
-
// Fallback to manual flush + receive
|
|
880
|
-
await decoder.flush();
|
|
881
|
-
let packet;
|
|
882
|
-
while ((packet = await decoder.receive()) !== null) {
|
|
883
|
-
yield packet;
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
/**
|
|
888
|
-
* Encode a stream of frames to packets.
|
|
889
|
-
*
|
|
890
|
-
* @param frames - Input frames
|
|
891
|
-
*
|
|
892
|
-
* @param encoder - Encoder instance
|
|
893
|
-
*
|
|
894
|
-
* @yields {Packet} Encoded packets
|
|
895
|
-
*
|
|
896
|
-
* @internal
|
|
897
|
-
*/
|
|
898
|
-
async function* encodeStream(frames, encoder) {
|
|
899
|
-
// Process all frames
|
|
900
|
-
for await (const frame of frames) {
|
|
901
|
-
try {
|
|
902
|
-
const packet = await encoder.encode(frame);
|
|
903
|
-
if (packet) {
|
|
904
|
-
yield packet;
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
finally {
|
|
908
|
-
// Free the input frame after encoding
|
|
909
|
-
frame.free();
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
// Flush encoder
|
|
913
|
-
if ('flushPackets' in encoder && typeof encoder.flushPackets === 'function') {
|
|
914
|
-
// Use generator method if available
|
|
915
|
-
for await (const packet of encoder.flushPackets()) {
|
|
916
|
-
yield packet;
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
else {
|
|
920
|
-
// Fallback to manual flush + receive
|
|
921
|
-
await encoder.flush();
|
|
922
|
-
let packet;
|
|
923
|
-
while ((packet = await encoder.receive()) !== null) {
|
|
924
|
-
yield packet;
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
/**
|
|
929
|
-
* Filter a stream of frames.
|
|
930
|
-
*
|
|
931
|
-
* @param frames - Input frames
|
|
932
|
-
*
|
|
933
|
-
* @param filter - Filter instance
|
|
934
|
-
*
|
|
935
|
-
* @yields {Frame} Filtered frames
|
|
936
|
-
*
|
|
937
|
-
* @internal
|
|
938
|
-
*/
|
|
939
|
-
async function* filterStream(frames, filter) {
|
|
940
|
-
// Process all frames
|
|
941
|
-
for await (const frame of frames) {
|
|
942
|
-
try {
|
|
943
|
-
const filtered = await filter.process(frame);
|
|
944
|
-
if (filtered) {
|
|
945
|
-
yield filtered;
|
|
946
|
-
}
|
|
947
|
-
// Check for buffered frames
|
|
948
|
-
let buffered;
|
|
949
|
-
while ((buffered = await filter.receive()) !== null) {
|
|
950
|
-
yield buffered;
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
finally {
|
|
954
|
-
// Free the input frame after filtering
|
|
955
|
-
frame.free();
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
// Flush filter
|
|
959
|
-
if ('flushFrames' in filter && typeof filter.flushFrames === 'function') {
|
|
960
|
-
// Use generator method if available
|
|
961
|
-
for await (const frame of filter.flushFrames()) {
|
|
962
|
-
yield frame;
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
else {
|
|
966
|
-
// Fallback to manual flush + receive
|
|
967
|
-
await filter.flush();
|
|
968
|
-
let frame;
|
|
969
|
-
while ((frame = await filter.receive()) !== null) {
|
|
970
|
-
yield frame;
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
/**
|
|
975
|
-
* Process packets through a bitstream filter.
|
|
976
|
-
*
|
|
977
|
-
* @param packets - Input packets
|
|
1001
|
+
* Check if object is named inputs.
|
|
978
1002
|
*
|
|
979
|
-
* @param
|
|
1003
|
+
* @param obj - Object to check
|
|
980
1004
|
*
|
|
981
|
-
* @
|
|
1005
|
+
* @returns True if object is NamedInputs
|
|
982
1006
|
*
|
|
983
1007
|
* @internal
|
|
984
1008
|
*/
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
for await (const packet of packets) {
|
|
988
|
-
try {
|
|
989
|
-
const filtered = await bsf.process(packet);
|
|
990
|
-
for (const outPacket of filtered) {
|
|
991
|
-
yield outPacket;
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
finally {
|
|
995
|
-
// Free the input packet after filtering
|
|
996
|
-
packet.free();
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
// Flush bitstream filter
|
|
1000
|
-
for await (const packet of bsf.flushPackets()) {
|
|
1001
|
-
yield packet;
|
|
1002
|
-
}
|
|
1009
|
+
function isNamedInputs(obj) {
|
|
1010
|
+
return obj && typeof obj === 'object' && !Array.isArray(obj) && !isAsyncIterable(obj) && !isDemuxer(obj);
|
|
1003
1011
|
}
|
|
1004
|
-
// ============================================================================
|
|
1005
|
-
// Type Guards
|
|
1006
|
-
// ============================================================================
|
|
1007
1012
|
/**
|
|
1008
|
-
* Check if object is named
|
|
1013
|
+
* Check if object is named stages.
|
|
1009
1014
|
*
|
|
1010
1015
|
* @param obj - Object to check
|
|
1011
1016
|
*
|
|
1012
|
-
* @returns True if object is
|
|
1017
|
+
* @returns True if object is NamedStages
|
|
1013
1018
|
*
|
|
1014
1019
|
* @internal
|
|
1015
1020
|
*/
|
|
1016
|
-
function
|
|
1017
|
-
|
|
1021
|
+
function isNamedStages(obj) {
|
|
1022
|
+
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
|
|
1023
|
+
return false;
|
|
1024
|
+
}
|
|
1025
|
+
// Check if object has at least one stream name key (video or audio)
|
|
1026
|
+
const keys = Object.keys(obj);
|
|
1027
|
+
return keys.length > 0 && keys.every((key) => key === 'video' || key === 'audio');
|
|
1018
1028
|
}
|
|
1019
1029
|
/**
|
|
1020
1030
|
* Check if object is async iterable.
|
|
@@ -1029,15 +1039,15 @@ function isAsyncIterable(obj) {
|
|
|
1029
1039
|
return obj && typeof obj[Symbol.asyncIterator] === 'function';
|
|
1030
1040
|
}
|
|
1031
1041
|
/**
|
|
1032
|
-
* Check if object is
|
|
1042
|
+
* Check if object is Demuxer.
|
|
1033
1043
|
*
|
|
1034
1044
|
* @param obj - Object to check
|
|
1035
1045
|
*
|
|
1036
|
-
* @returns True if object is
|
|
1046
|
+
* @returns True if object is Demuxer
|
|
1037
1047
|
*
|
|
1038
1048
|
* @internal
|
|
1039
1049
|
*/
|
|
1040
|
-
function
|
|
1050
|
+
function isDemuxer(obj) {
|
|
1041
1051
|
return obj && typeof obj.packets === 'function' && typeof obj.video === 'function' && typeof obj.audio === 'function';
|
|
1042
1052
|
}
|
|
1043
1053
|
/**
|
|
@@ -1086,18 +1096,18 @@ function isFilterAPI(obj) {
|
|
|
1086
1096
|
* @internal
|
|
1087
1097
|
*/
|
|
1088
1098
|
function isBitStreamFilterAPI(obj) {
|
|
1089
|
-
return obj && typeof obj.
|
|
1099
|
+
return obj && typeof obj.filter === 'function' && typeof obj.flushPackets === 'function' && typeof obj.reset === 'function';
|
|
1090
1100
|
}
|
|
1091
1101
|
/**
|
|
1092
|
-
* Check if object is
|
|
1102
|
+
* Check if object is Muxer.
|
|
1093
1103
|
*
|
|
1094
1104
|
* @param obj - Object to check
|
|
1095
1105
|
*
|
|
1096
|
-
* @returns True if object is
|
|
1106
|
+
* @returns True if object is Muxer
|
|
1097
1107
|
*
|
|
1098
1108
|
* @internal
|
|
1099
1109
|
*/
|
|
1100
|
-
function
|
|
1110
|
+
function isMuxer(obj) {
|
|
1101
1111
|
return obj && typeof obj.writePacket === 'function' && typeof obj.addStream === 'function';
|
|
1102
1112
|
}
|
|
1103
1113
|
/**
|