node-av 0.0.1

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 (175) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/LICENSE.md +22 -0
  3. package/README.md +377 -0
  4. package/binding.gyp +78 -0
  5. package/dist/api/bitstream-filter.d.ts +246 -0
  6. package/dist/api/bitstream-filter.js +369 -0
  7. package/dist/api/bitstream-filter.js.map +1 -0
  8. package/dist/api/decoder.d.ts +257 -0
  9. package/dist/api/decoder.js +424 -0
  10. package/dist/api/decoder.js.map +1 -0
  11. package/dist/api/encoder.d.ts +298 -0
  12. package/dist/api/encoder.js +574 -0
  13. package/dist/api/encoder.js.map +1 -0
  14. package/dist/api/filter.d.ts +457 -0
  15. package/dist/api/filter.js +876 -0
  16. package/dist/api/filter.js.map +1 -0
  17. package/dist/api/hardware.d.ts +318 -0
  18. package/dist/api/hardware.js +558 -0
  19. package/dist/api/hardware.js.map +1 -0
  20. package/dist/api/index.d.ts +12 -0
  21. package/dist/api/index.js +20 -0
  22. package/dist/api/index.js.map +1 -0
  23. package/dist/api/io-stream.d.ts +109 -0
  24. package/dist/api/io-stream.js +124 -0
  25. package/dist/api/io-stream.js.map +1 -0
  26. package/dist/api/media-input.d.ts +295 -0
  27. package/dist/api/media-input.js +456 -0
  28. package/dist/api/media-input.js.map +1 -0
  29. package/dist/api/media-output.d.ts +274 -0
  30. package/dist/api/media-output.js +486 -0
  31. package/dist/api/media-output.js.map +1 -0
  32. package/dist/api/pipeline.d.ts +117 -0
  33. package/dist/api/pipeline.js +836 -0
  34. package/dist/api/pipeline.js.map +1 -0
  35. package/dist/api/types.d.ts +440 -0
  36. package/dist/api/types.js +2 -0
  37. package/dist/api/types.js.map +1 -0
  38. package/dist/api/utilities/audio-sample.d.ts +115 -0
  39. package/dist/api/utilities/audio-sample.js +110 -0
  40. package/dist/api/utilities/audio-sample.js.map +1 -0
  41. package/dist/api/utilities/channel-layout.d.ts +83 -0
  42. package/dist/api/utilities/channel-layout.js +87 -0
  43. package/dist/api/utilities/channel-layout.js.map +1 -0
  44. package/dist/api/utilities/image.d.ts +177 -0
  45. package/dist/api/utilities/image.js +183 -0
  46. package/dist/api/utilities/image.js.map +1 -0
  47. package/dist/api/utilities/index.d.ts +8 -0
  48. package/dist/api/utilities/index.js +17 -0
  49. package/dist/api/utilities/index.js.map +1 -0
  50. package/dist/api/utilities/media-type.d.ts +56 -0
  51. package/dist/api/utilities/media-type.js +60 -0
  52. package/dist/api/utilities/media-type.js.map +1 -0
  53. package/dist/api/utilities/pixel-format.d.ts +94 -0
  54. package/dist/api/utilities/pixel-format.js +102 -0
  55. package/dist/api/utilities/pixel-format.js.map +1 -0
  56. package/dist/api/utilities/sample-format.d.ts +132 -0
  57. package/dist/api/utilities/sample-format.js +144 -0
  58. package/dist/api/utilities/sample-format.js.map +1 -0
  59. package/dist/api/utilities/streaming.d.ts +104 -0
  60. package/dist/api/utilities/streaming.js +137 -0
  61. package/dist/api/utilities/streaming.js.map +1 -0
  62. package/dist/api/utilities/timestamp.d.ts +187 -0
  63. package/dist/api/utilities/timestamp.js +200 -0
  64. package/dist/api/utilities/timestamp.js.map +1 -0
  65. package/dist/api/utils.d.ts +61 -0
  66. package/dist/api/utils.js +330 -0
  67. package/dist/api/utils.js.map +1 -0
  68. package/dist/index.d.ts +2 -0
  69. package/dist/index.js +5 -0
  70. package/dist/index.js.map +1 -0
  71. package/dist/lib/audio-fifo.d.ts +339 -0
  72. package/dist/lib/audio-fifo.js +365 -0
  73. package/dist/lib/audio-fifo.js.map +1 -0
  74. package/dist/lib/binding.d.ts +192 -0
  75. package/dist/lib/binding.js +70 -0
  76. package/dist/lib/binding.js.map +1 -0
  77. package/dist/lib/bitstream-filter-context.d.ts +345 -0
  78. package/dist/lib/bitstream-filter-context.js +407 -0
  79. package/dist/lib/bitstream-filter-context.js.map +1 -0
  80. package/dist/lib/bitstream-filter.d.ts +124 -0
  81. package/dist/lib/bitstream-filter.js +138 -0
  82. package/dist/lib/bitstream-filter.js.map +1 -0
  83. package/dist/lib/channel-layouts.d.ts +51 -0
  84. package/dist/lib/channel-layouts.js +55 -0
  85. package/dist/lib/channel-layouts.js.map +1 -0
  86. package/dist/lib/codec-context.d.ts +763 -0
  87. package/dist/lib/codec-context.js +974 -0
  88. package/dist/lib/codec-context.js.map +1 -0
  89. package/dist/lib/codec-parameters.d.ts +362 -0
  90. package/dist/lib/codec-parameters.js +460 -0
  91. package/dist/lib/codec-parameters.js.map +1 -0
  92. package/dist/lib/codec-parser.d.ts +185 -0
  93. package/dist/lib/codec-parser.js +193 -0
  94. package/dist/lib/codec-parser.js.map +1 -0
  95. package/dist/lib/codec.d.ts +432 -0
  96. package/dist/lib/codec.js +492 -0
  97. package/dist/lib/codec.js.map +1 -0
  98. package/dist/lib/constants.d.ts +2037 -0
  99. package/dist/lib/constants.js +1659 -0
  100. package/dist/lib/constants.js.map +1 -0
  101. package/dist/lib/dictionary.d.ts +371 -0
  102. package/dist/lib/dictionary.js +406 -0
  103. package/dist/lib/dictionary.js.map +1 -0
  104. package/dist/lib/error.d.ts +216 -0
  105. package/dist/lib/error.js +254 -0
  106. package/dist/lib/error.js.map +1 -0
  107. package/dist/lib/filter-context.d.ts +445 -0
  108. package/dist/lib/filter-context.js +505 -0
  109. package/dist/lib/filter-context.js.map +1 -0
  110. package/dist/lib/filter-graph.d.ts +556 -0
  111. package/dist/lib/filter-graph.js +608 -0
  112. package/dist/lib/filter-graph.js.map +1 -0
  113. package/dist/lib/filter-inout.d.ts +205 -0
  114. package/dist/lib/filter-inout.js +264 -0
  115. package/dist/lib/filter-inout.js.map +1 -0
  116. package/dist/lib/filter.d.ts +231 -0
  117. package/dist/lib/filter.js +260 -0
  118. package/dist/lib/filter.js.map +1 -0
  119. package/dist/lib/format-context.d.ts +798 -0
  120. package/dist/lib/format-context.js +845 -0
  121. package/dist/lib/format-context.js.map +1 -0
  122. package/dist/lib/frame.d.ts +784 -0
  123. package/dist/lib/frame.js +933 -0
  124. package/dist/lib/frame.js.map +1 -0
  125. package/dist/lib/hardware-device-context.d.ts +407 -0
  126. package/dist/lib/hardware-device-context.js +429 -0
  127. package/dist/lib/hardware-device-context.js.map +1 -0
  128. package/dist/lib/hardware-frames-context.d.ts +374 -0
  129. package/dist/lib/hardware-frames-context.js +430 -0
  130. package/dist/lib/hardware-frames-context.js.map +1 -0
  131. package/dist/lib/index.d.ts +31 -0
  132. package/dist/lib/index.js +54 -0
  133. package/dist/lib/index.js.map +1 -0
  134. package/dist/lib/input-format.d.ts +216 -0
  135. package/dist/lib/input-format.js +246 -0
  136. package/dist/lib/input-format.js.map +1 -0
  137. package/dist/lib/io-context.d.ts +495 -0
  138. package/dist/lib/io-context.js +550 -0
  139. package/dist/lib/io-context.js.map +1 -0
  140. package/dist/lib/log.d.ts +201 -0
  141. package/dist/lib/log.js +219 -0
  142. package/dist/lib/log.js.map +1 -0
  143. package/dist/lib/native-types.d.ts +719 -0
  144. package/dist/lib/native-types.js +2 -0
  145. package/dist/lib/native-types.js.map +1 -0
  146. package/dist/lib/option.d.ts +589 -0
  147. package/dist/lib/option.js +853 -0
  148. package/dist/lib/option.js.map +1 -0
  149. package/dist/lib/output-format.d.ts +179 -0
  150. package/dist/lib/output-format.js +205 -0
  151. package/dist/lib/output-format.js.map +1 -0
  152. package/dist/lib/packet.d.ts +487 -0
  153. package/dist/lib/packet.js +558 -0
  154. package/dist/lib/packet.js.map +1 -0
  155. package/dist/lib/rational.d.ts +210 -0
  156. package/dist/lib/rational.js +233 -0
  157. package/dist/lib/rational.js.map +1 -0
  158. package/dist/lib/software-resample-context.d.ts +572 -0
  159. package/dist/lib/software-resample-context.js +610 -0
  160. package/dist/lib/software-resample-context.js.map +1 -0
  161. package/dist/lib/software-scale-context.d.ts +290 -0
  162. package/dist/lib/software-scale-context.js +308 -0
  163. package/dist/lib/software-scale-context.js.map +1 -0
  164. package/dist/lib/stream.d.ts +322 -0
  165. package/dist/lib/stream.js +408 -0
  166. package/dist/lib/stream.js.map +1 -0
  167. package/dist/lib/types.d.ts +59 -0
  168. package/dist/lib/types.js +8 -0
  169. package/dist/lib/types.js.map +1 -0
  170. package/dist/lib/utilities.d.ts +346 -0
  171. package/dist/lib/utilities.js +424 -0
  172. package/dist/lib/utilities.js.map +1 -0
  173. package/install/check.js +113 -0
  174. package/install/ffmpeg.js +163 -0
  175. package/package.json +107 -0
@@ -0,0 +1,836 @@
1
+ /**
2
+ * Pipeline - Stream-based media processing pipeline
3
+ *
4
+ * Provides a Node.js-style pipeline function for chaining media processing components.
5
+ * Automatically handles type conversions, buffering, and flushing.
6
+ *
7
+ * Supports two modes:
8
+ * 1. Simple: Single stream with variable parameters
9
+ * 2. Named: Multiple streams with named routing with variable parameters
10
+ *
11
+ * @module api/pipeline
12
+ */
13
+ // ============================================================================
14
+ // Implementation
15
+ // ============================================================================
16
+ /**
17
+ * Pipeline implementation
18
+ *
19
+ * Creates a processing pipeline from media components.
20
+ * Automatically handles type conversions and proper flushing order.
21
+ *
22
+ * @param args - Variable arguments depending on pipeline type
23
+ * @returns Promise<void> if output is present, AsyncGenerator otherwise
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * // Simple pipeline
28
+ * await pipeline(
29
+ * input,
30
+ * decoder,
31
+ * filter,
32
+ * encoder,
33
+ * output
34
+ * );
35
+ *
36
+ * // Named pipeline for muxing
37
+ * await pipeline(
38
+ * { video: videoInput, audio: audioInput },
39
+ * {
40
+ * video: [videoDecoder, scaleFilter, videoEncoder],
41
+ * audio: [audioDecoder, volumeFilter, audioEncoder]
42
+ * },
43
+ * output
44
+ * );
45
+ * ```
46
+ */
47
+ export function pipeline(...args) {
48
+ // Detect pipeline type based on first argument
49
+ const firstArg = args[0];
50
+ if (isNamedInputs(firstArg)) {
51
+ // Named pipeline (2 or 3 arguments)
52
+ if (args.length === 2) {
53
+ // Partial named pipeline - return generators
54
+ return runNamedPartialPipeline(args[0], args[1]);
55
+ }
56
+ else {
57
+ // Full named pipeline with output
58
+ return runNamedPipeline(args[0], args[1], args[2]);
59
+ }
60
+ }
61
+ else if (isMediaInput(firstArg)) {
62
+ // Check if this is a stream copy (MediaInput → MediaOutput)
63
+ if (args.length === 2 && isMediaOutput(args[1])) {
64
+ // Stream copy all streams
65
+ return runMediaInputPipeline(args[0], args[1]);
66
+ }
67
+ else {
68
+ // Simple pipeline starting with MediaInput
69
+ return runSimplePipeline(args);
70
+ }
71
+ }
72
+ else {
73
+ // Simple pipeline (variable arguments)
74
+ return runSimplePipeline(args);
75
+ }
76
+ }
77
+ // ============================================================================
78
+ // PipelineControl Implementation
79
+ // ============================================================================
80
+ class PipelineControlImpl {
81
+ _stopped = false;
82
+ _completion;
83
+ constructor(executionPromise) {
84
+ // Don't resolve immediately on stop, wait for the actual pipeline to finish
85
+ this._completion = executionPromise;
86
+ }
87
+ stop() {
88
+ this._stopped = true;
89
+ }
90
+ isStopped() {
91
+ return this._stopped;
92
+ }
93
+ get completion() {
94
+ return this._completion;
95
+ }
96
+ }
97
+ // ============================================================================
98
+ // MediaInput Pipeline Implementation
99
+ // ============================================================================
100
+ function runMediaInputPipeline(input, output) {
101
+ let control;
102
+ // eslint-disable-next-line prefer-const
103
+ control = new PipelineControlImpl(runMediaInputPipelineAsync(input, output, () => control.isStopped()));
104
+ return control;
105
+ }
106
+ async function runMediaInputPipelineAsync(input, output, shouldStop) {
107
+ // Get all streams from input
108
+ const videoStream = input.video();
109
+ const audioStream = input.audio();
110
+ const streams = [];
111
+ // Add video stream if present
112
+ if (videoStream) {
113
+ const outputIndex = output.addStream(videoStream);
114
+ streams.push({ stream: videoStream, index: outputIndex });
115
+ }
116
+ // Add audio stream if present
117
+ if (audioStream) {
118
+ const outputIndex = output.addStream(audioStream);
119
+ streams.push({ stream: audioStream, index: outputIndex });
120
+ }
121
+ // Add any other streams
122
+ const allStreams = input.streams;
123
+ for (const stream of allStreams) {
124
+ // Skip if already added
125
+ if (stream !== videoStream && stream !== audioStream) {
126
+ const outputIndex = output.addStream(stream);
127
+ streams.push({ stream, index: outputIndex });
128
+ }
129
+ }
130
+ // Write header
131
+ if (!output.isHeaderWritten()) {
132
+ await output.writeHeader();
133
+ }
134
+ // Copy all packets
135
+ for await (const packet of input.packets()) {
136
+ // Check if we should stop
137
+ if (shouldStop()) {
138
+ packet.free();
139
+ break;
140
+ }
141
+ // Find the corresponding output stream index
142
+ const mapping = streams.find((s) => s.stream.index === packet.streamIndex);
143
+ if (mapping) {
144
+ await output.writePacket(packet, mapping.index);
145
+ }
146
+ packet.free(); // Free packet after processing
147
+ }
148
+ // Write trailer
149
+ if (!output.isTrailerWritten()) {
150
+ await output.writeTrailer();
151
+ }
152
+ }
153
+ // ============================================================================
154
+ // Simple Pipeline Implementation
155
+ // ============================================================================
156
+ function runSimplePipeline(args) {
157
+ const [source, ...stages] = args;
158
+ // Check if last stage is MediaOutput (consumes stream)
159
+ const lastStage = stages[stages.length - 1];
160
+ const isOutput = isMediaOutput(lastStage);
161
+ // Track metadata through pipeline
162
+ const metadata = {};
163
+ // Store MediaInput reference if we have one
164
+ if (isMediaInput(source)) {
165
+ metadata.mediaInput = source;
166
+ }
167
+ // Build the pipeline generator
168
+ // If output is present, exclude it from stages for processing
169
+ const processStages = isOutput ? stages.slice(0, -1) : stages;
170
+ // Process metadata first by walking through stages
171
+ for (const stage of processStages) {
172
+ if (isDecoder(stage)) {
173
+ metadata.decoder = stage;
174
+ }
175
+ else if (isEncoder(stage)) {
176
+ metadata.encoder = stage;
177
+ }
178
+ else if (isBitStreamFilterAPI(stage)) {
179
+ metadata.bitStreamFilter = stage;
180
+ }
181
+ }
182
+ // Convert MediaInput to packet stream if needed
183
+ // If we have a decoder or BSF, filter packets by stream index
184
+ let actualSource;
185
+ if (isMediaInput(source)) {
186
+ if (metadata.decoder) {
187
+ // Filter packets for the decoder's stream
188
+ const streamIndex = metadata.decoder.getStream().index;
189
+ actualSource = source.packets(streamIndex);
190
+ }
191
+ else if (metadata.bitStreamFilter) {
192
+ // Filter packets for the BSF's stream
193
+ const streamIndex = metadata.bitStreamFilter.getStream().index;
194
+ actualSource = source.packets(streamIndex);
195
+ }
196
+ else {
197
+ // No decoder or BSF, pass all packets
198
+ actualSource = source.packets();
199
+ }
200
+ }
201
+ else {
202
+ actualSource = source;
203
+ }
204
+ const generator = buildSimplePipeline(actualSource, processStages);
205
+ // If output, consume the generator
206
+ if (isOutput) {
207
+ let control;
208
+ // eslint-disable-next-line prefer-const
209
+ control = new PipelineControlImpl(consumeSimplePipeline(generator, lastStage, metadata, () => control.isStopped()));
210
+ return control;
211
+ }
212
+ // Otherwise return the generator for further processing
213
+ return generator;
214
+ }
215
+ async function* buildSimplePipeline(source, stages) {
216
+ let stream = source;
217
+ for (const stage of stages) {
218
+ if (isDecoder(stage)) {
219
+ stream = decodeStream(stream, stage);
220
+ }
221
+ else if (isEncoder(stage)) {
222
+ stream = encodeStream(stream, stage);
223
+ }
224
+ else if (isFilterAPI(stage)) {
225
+ stream = filterStream(stream, stage);
226
+ }
227
+ else if (isBitStreamFilterAPI(stage)) {
228
+ stream = bitStreamFilterStream(stream, stage);
229
+ }
230
+ else if (Array.isArray(stage)) {
231
+ // Chain multiple filters or BSFs
232
+ for (const filter of stage) {
233
+ if (isFilterAPI(filter)) {
234
+ stream = filterStream(stream, filter);
235
+ }
236
+ else if (isBitStreamFilterAPI(filter)) {
237
+ stream = bitStreamFilterStream(stream, filter);
238
+ }
239
+ }
240
+ }
241
+ }
242
+ yield* stream;
243
+ }
244
+ async function consumeSimplePipeline(stream, output, metadata, shouldStop) {
245
+ // Add stream to output if we have encoder or decoder info
246
+ let streamIndex = 0;
247
+ if (metadata.encoder) {
248
+ streamIndex = output.addStream(metadata.encoder);
249
+ }
250
+ else if (metadata.decoder) {
251
+ // Stream copy - use decoder's original stream
252
+ const originalStream = metadata.decoder.getStream();
253
+ streamIndex = output.addStream(originalStream);
254
+ }
255
+ else if (metadata.bitStreamFilter) {
256
+ // BSF without encoder/decoder - use BSF's original stream
257
+ const originalStream = metadata.bitStreamFilter.getStream();
258
+ streamIndex = output.addStream(originalStream);
259
+ }
260
+ else {
261
+ // For direct MediaInput → MediaOutput, we redirect to runMediaInputPipeline
262
+ // This case shouldn't happen in simple pipeline
263
+ throw new Error('Cannot determine stream configuration. This is likely a bug in the pipeline.');
264
+ }
265
+ // Write header if needed
266
+ if (!output.isHeaderWritten()) {
267
+ await output.writeHeader();
268
+ }
269
+ // Process stream
270
+ for await (const item of stream) {
271
+ // Check if we should stop
272
+ if (shouldStop()) {
273
+ if (isPacket(item)) {
274
+ item.free();
275
+ }
276
+ else {
277
+ item.free();
278
+ }
279
+ break;
280
+ }
281
+ if (isPacket(item)) {
282
+ await output.writePacket(item, streamIndex);
283
+ // Free the packet after writing
284
+ item.free();
285
+ }
286
+ else {
287
+ throw new Error('Cannot write frames directly to MediaOutput. Use an encoder first.');
288
+ }
289
+ }
290
+ // Write trailer if needed
291
+ if (!output.isTrailerWritten()) {
292
+ await output.writeTrailer();
293
+ }
294
+ }
295
+ // ============================================================================
296
+ // Named Pipeline Implementation
297
+ // ============================================================================
298
+ function runNamedPartialPipeline(inputs, stages) {
299
+ const result = {};
300
+ for (const [streamName, streamStages] of Object.entries(stages)) {
301
+ const input = inputs[streamName];
302
+ if (!input) {
303
+ throw new Error(`No input found for stream: ${streamName}`);
304
+ }
305
+ // Get the appropriate stream based on the stream name
306
+ let stream = null;
307
+ switch (streamName) {
308
+ case 'video':
309
+ stream = input.video() ?? null;
310
+ break;
311
+ case 'audio':
312
+ stream = input.audio() ?? null;
313
+ break;
314
+ default:
315
+ // This should never happen
316
+ throw new Error(`Invalid stream name: ${streamName}. Must be 'video' or 'audio'.`);
317
+ }
318
+ if (!stream) {
319
+ throw new Error(`No ${streamName} stream found in input.`);
320
+ }
321
+ if (streamStages === 'passthrough') {
322
+ // Direct passthrough - return input packets for this specific stream
323
+ result[streamName] = (async function* () {
324
+ for await (const packet of input.packets(stream.index)) {
325
+ yield packet;
326
+ }
327
+ })();
328
+ }
329
+ else {
330
+ // Process the stream - pass packets for this specific stream only
331
+ // Build pipeline for this stream (can return frames or packets)
332
+ const metadata = {};
333
+ result[streamName] = buildFlexibleNamedStreamPipeline(input.packets(stream.index), streamStages, metadata);
334
+ }
335
+ }
336
+ return result;
337
+ }
338
+ function runNamedPipeline(inputs, stages, output) {
339
+ let control;
340
+ // eslint-disable-next-line prefer-const
341
+ control = new PipelineControlImpl(runNamedPipelineAsync(inputs, stages, output, () => control.isStopped()));
342
+ return control;
343
+ }
344
+ async function runNamedPipelineAsync(inputs, stages, output, shouldStop) {
345
+ // Track metadata for each stream
346
+ const streamMetadata = {};
347
+ // Process each named stream into generators
348
+ const processedStreams = {};
349
+ for (const [streamName, streamStages] of Object.entries(stages)) {
350
+ const metadata = {};
351
+ streamMetadata[streamName] = metadata;
352
+ const input = inputs[streamName];
353
+ if (!input) {
354
+ throw new Error(`No input found for stream: ${streamName}`);
355
+ }
356
+ if (streamStages === 'passthrough') {
357
+ // Direct passthrough - no processing
358
+ let stream = null;
359
+ switch (streamName) {
360
+ case 'video':
361
+ stream = input.video() ?? null;
362
+ metadata.type = 'video';
363
+ break;
364
+ case 'audio':
365
+ stream = input.audio() ?? null;
366
+ metadata.type = 'audio';
367
+ break;
368
+ }
369
+ if (!stream) {
370
+ throw new Error(`No ${streamName} stream found in input for passthrough.`);
371
+ }
372
+ processedStreams[streamName] = input.packets(stream.index);
373
+ metadata.mediaInput = input; // Track MediaInput for passthrough
374
+ }
375
+ else {
376
+ // Process the stream
377
+ // Pre-populate metadata by walking through stages
378
+ for (const stage of streamStages) {
379
+ if (isDecoder(stage)) {
380
+ metadata.decoder = stage;
381
+ }
382
+ else if (isEncoder(stage)) {
383
+ metadata.encoder = stage;
384
+ }
385
+ else if (isBitStreamFilterAPI(stage)) {
386
+ metadata.bitStreamFilter = stage;
387
+ }
388
+ }
389
+ // Get packets - filter by stream index based on decoder, BSF, or stream type
390
+ let packets;
391
+ if (metadata.decoder) {
392
+ const streamIndex = metadata.decoder.getStream().index;
393
+ packets = input.packets(streamIndex);
394
+ }
395
+ else if (metadata.bitStreamFilter) {
396
+ const streamIndex = metadata.bitStreamFilter.getStream().index;
397
+ packets = input.packets(streamIndex);
398
+ }
399
+ else {
400
+ // No decoder or BSF - determine stream by name
401
+ let stream = null;
402
+ switch (streamName) {
403
+ case 'video':
404
+ stream = input.video() ?? null;
405
+ break;
406
+ case 'audio':
407
+ stream = input.audio() ?? null;
408
+ break;
409
+ }
410
+ if (!stream) {
411
+ throw new Error(`No ${streamName} stream found in input.`);
412
+ }
413
+ packets = input.packets(stream.index);
414
+ }
415
+ // Build pipeline for this stream
416
+ processedStreams[streamName] = buildNamedStreamPipeline(packets, streamStages, metadata);
417
+ }
418
+ }
419
+ // Write to output(s)
420
+ if (isMediaOutput(output)) {
421
+ // Single output - properly interleave all streams
422
+ await interleaveToOutput(processedStreams, output, streamMetadata, shouldStop);
423
+ }
424
+ else {
425
+ // Multiple outputs - write each stream to its output
426
+ const outputs = output;
427
+ const promises = [];
428
+ for (const [streamName, stream] of Object.entries(processedStreams)) {
429
+ const streamOutput = outputs[streamName];
430
+ if (streamOutput) {
431
+ const metadata = streamMetadata[streamName];
432
+ promises.push(consumeNamedStream(stream, streamOutput, metadata, shouldStop));
433
+ }
434
+ }
435
+ await Promise.all(promises);
436
+ }
437
+ }
438
+ async function* buildFlexibleNamedStreamPipeline(source, stages, metadata) {
439
+ let stream = source;
440
+ for (const stage of stages) {
441
+ if (isDecoder(stage)) {
442
+ metadata.decoder = stage;
443
+ stream = decodeStream(stream, stage);
444
+ }
445
+ else if (isEncoder(stage)) {
446
+ metadata.encoder = stage;
447
+ stream = encodeStream(stream, stage);
448
+ }
449
+ else if (isFilterAPI(stage)) {
450
+ stream = filterStream(stream, stage);
451
+ }
452
+ else if (isBitStreamFilterAPI(stage)) {
453
+ metadata.bitStreamFilter = stage;
454
+ stream = bitStreamFilterStream(stream, stage);
455
+ }
456
+ else if (Array.isArray(stage)) {
457
+ // Chain multiple filters or BSFs
458
+ for (const filter of stage) {
459
+ if (isFilterAPI(filter)) {
460
+ stream = filterStream(stream, filter);
461
+ }
462
+ else if (isBitStreamFilterAPI(filter)) {
463
+ stream = bitStreamFilterStream(stream, filter);
464
+ }
465
+ }
466
+ }
467
+ }
468
+ // Yield whatever the pipeline produces (frames or packets)
469
+ yield* stream;
470
+ }
471
+ async function* buildNamedStreamPipeline(source, stages, metadata) {
472
+ let stream = source;
473
+ for (const stage of stages) {
474
+ if (isDecoder(stage)) {
475
+ metadata.decoder = stage;
476
+ stream = decodeStream(stream, stage);
477
+ }
478
+ else if (isEncoder(stage)) {
479
+ metadata.encoder = stage;
480
+ stream = encodeStream(stream, stage);
481
+ }
482
+ else if (isFilterAPI(stage)) {
483
+ stream = filterStream(stream, stage);
484
+ }
485
+ else if (isBitStreamFilterAPI(stage)) {
486
+ metadata.bitStreamFilter = stage;
487
+ stream = bitStreamFilterStream(stream, stage);
488
+ }
489
+ else if (Array.isArray(stage)) {
490
+ // Chain multiple filters or BSFs
491
+ for (const filter of stage) {
492
+ if (isFilterAPI(filter)) {
493
+ stream = filterStream(stream, filter);
494
+ }
495
+ else if (isBitStreamFilterAPI(filter)) {
496
+ stream = bitStreamFilterStream(stream, filter);
497
+ }
498
+ }
499
+ }
500
+ }
501
+ // Ensure we're yielding packets
502
+ for await (const item of stream) {
503
+ if (isPacket(item)) {
504
+ yield item;
505
+ }
506
+ else {
507
+ throw new Error('Named pipeline must end with packets (use encoder after filters)');
508
+ }
509
+ }
510
+ }
511
+ async function consumeNamedStream(stream, output, metadata, shouldStop) {
512
+ // Add stream to output
513
+ let streamIndex = 0;
514
+ if (metadata.encoder) {
515
+ streamIndex = output.addStream(metadata.encoder);
516
+ }
517
+ else if (metadata.decoder) {
518
+ // Stream copy - use decoder's original stream
519
+ const originalStream = metadata.decoder.getStream();
520
+ streamIndex = output.addStream(originalStream);
521
+ }
522
+ else if (metadata.bitStreamFilter) {
523
+ // BSF - use BSF's original stream
524
+ const originalStream = metadata.bitStreamFilter.getStream();
525
+ streamIndex = output.addStream(originalStream);
526
+ }
527
+ else if (metadata.mediaInput) {
528
+ // Passthrough from MediaInput - use type hint from metadata
529
+ const inputStream = metadata.type === 'video' ? metadata.mediaInput.video() : metadata.mediaInput.audio();
530
+ if (!inputStream) {
531
+ throw new Error(`No ${metadata.type} stream found in MediaInput`);
532
+ }
533
+ streamIndex = output.addStream(inputStream);
534
+ }
535
+ else {
536
+ // This should not happen with the new API
537
+ throw new Error('Cannot determine stream configuration. This is likely a bug in the pipeline.');
538
+ }
539
+ // Store for later use
540
+ metadata.streamIndex = streamIndex;
541
+ // Write header if needed
542
+ if (!output.isHeaderWritten()) {
543
+ await output.writeHeader();
544
+ }
545
+ // Write all packets
546
+ for await (const packet of stream) {
547
+ // Check if we should stop
548
+ if (shouldStop()) {
549
+ packet.free();
550
+ break;
551
+ }
552
+ try {
553
+ await output.writePacket(packet, streamIndex);
554
+ }
555
+ finally {
556
+ packet.free(); // Free packet after writing
557
+ }
558
+ }
559
+ // Note: Trailer will be written by interleaveToOutput
560
+ }
561
+ async function interleaveToOutput(streams, output, metadata, shouldStop) {
562
+ // Add all streams to output first
563
+ const streamIndices = {};
564
+ for (const [name, meta] of Object.entries(metadata)) {
565
+ if (meta.encoder) {
566
+ streamIndices[name] = output.addStream(meta.encoder);
567
+ }
568
+ else if (meta.decoder) {
569
+ // Stream copy - use decoder's original stream
570
+ const originalStream = meta.decoder.getStream();
571
+ streamIndices[name] = output.addStream(originalStream);
572
+ }
573
+ else if (meta.bitStreamFilter) {
574
+ // BSF - use BSF's original stream
575
+ const originalStream = meta.bitStreamFilter.getStream();
576
+ streamIndices[name] = output.addStream(originalStream);
577
+ }
578
+ else if (meta.mediaInput) {
579
+ // Passthrough from MediaInput - use stream name to determine which stream
580
+ const stream = name.includes('video') ? meta.mediaInput.video() : meta.mediaInput.audio();
581
+ if (!stream) {
582
+ throw new Error(`No matching stream found in MediaInput for ${name}`);
583
+ }
584
+ streamIndices[name] = output.addStream(stream);
585
+ }
586
+ else {
587
+ // This should not happen
588
+ throw new Error(`Cannot determine stream configuration for ${name}. This is likely a bug in the pipeline.`);
589
+ }
590
+ }
591
+ // Write header
592
+ if (!output.isHeaderWritten()) {
593
+ await output.writeHeader();
594
+ }
595
+ const queues = new Map();
596
+ const iterators = new Map();
597
+ const done = new Set();
598
+ // Initialize iterators
599
+ for (const [name, stream] of Object.entries(streams)) {
600
+ queues.set(name, []);
601
+ iterators.set(name, stream[Symbol.asyncIterator]());
602
+ }
603
+ // Read initial packet from each stream
604
+ for (const [name, iterator] of iterators) {
605
+ const result = await iterator.next();
606
+ if (!result.done && result.value) {
607
+ const packet = result.value;
608
+ packet._streamName = name;
609
+ queues.get(name).push(packet);
610
+ }
611
+ else {
612
+ done.add(name);
613
+ }
614
+ }
615
+ // Interleave packets based on DTS/PTS
616
+ while (done.size < Object.keys(streams).length && !shouldStop()) {
617
+ // Find packet with smallest DTS/PTS
618
+ let minPacket = null;
619
+ let minStreamName = null;
620
+ let minTime = BigInt(Number.MAX_SAFE_INTEGER);
621
+ for (const [name, queue] of queues) {
622
+ if (queue.length > 0) {
623
+ const packet = queue[0];
624
+ const time = packet.dts ?? packet.pts ?? BigInt(0);
625
+ if (time < minTime) {
626
+ minTime = time;
627
+ minPacket = packet;
628
+ minStreamName = name;
629
+ }
630
+ }
631
+ }
632
+ if (!minPacket || !minStreamName) {
633
+ // All queues empty, read more
634
+ for (const [name, iterator] of iterators) {
635
+ if (!done.has(name)) {
636
+ const result = await iterator.next();
637
+ if (!result.done && result.value) {
638
+ const packet = result.value;
639
+ packet._streamName = name;
640
+ queues.get(name).push(packet);
641
+ }
642
+ else {
643
+ done.add(name);
644
+ }
645
+ }
646
+ }
647
+ continue;
648
+ }
649
+ // Write the packet with smallest timestamp
650
+ const streamIndex = streamIndices[minStreamName];
651
+ await output.writePacket(minPacket, streamIndex);
652
+ // Free the packet after writing
653
+ minPacket.free();
654
+ // Remove from queue
655
+ queues.get(minStreamName).shift();
656
+ // Read next packet from that stream
657
+ const iterator = iterators.get(minStreamName);
658
+ const result = await iterator.next();
659
+ if (!result.done && result.value) {
660
+ const packet = result.value;
661
+ packet._streamName = minStreamName;
662
+ queues.get(minStreamName).push(packet);
663
+ }
664
+ else {
665
+ done.add(minStreamName);
666
+ }
667
+ }
668
+ // If stopped, free all remaining packets in queues
669
+ if (shouldStop()) {
670
+ for (const [, queue] of queues) {
671
+ for (const packet of queue) {
672
+ packet.free();
673
+ }
674
+ }
675
+ }
676
+ else {
677
+ // Write any remaining packets
678
+ for (const [name, queue] of queues) {
679
+ const streamIndex = streamIndices[name];
680
+ for (const packet of queue) {
681
+ await output.writePacket(packet, streamIndex);
682
+ packet.free(); // Free packet after writing
683
+ }
684
+ }
685
+ }
686
+ // Write trailer
687
+ if (!output.isTrailerWritten()) {
688
+ await output.writeTrailer();
689
+ }
690
+ }
691
+ // ============================================================================
692
+ // Stream Processing Functions
693
+ // ============================================================================
694
+ async function* decodeStream(packets, decoder) {
695
+ // Process all packets
696
+ for await (const packet of packets) {
697
+ try {
698
+ const frame = await decoder.decode(packet);
699
+ if (frame) {
700
+ yield frame;
701
+ }
702
+ }
703
+ finally {
704
+ // Free packet after decoding
705
+ packet.free();
706
+ }
707
+ }
708
+ // Flush decoder
709
+ if ('flushFrames' in decoder && typeof decoder.flushFrames === 'function') {
710
+ // Use generator method if available
711
+ for await (const frame of decoder.flushFrames()) {
712
+ yield frame;
713
+ }
714
+ }
715
+ else {
716
+ // Fallback to loop
717
+ let frame;
718
+ while ((frame = await decoder.flush()) !== null) {
719
+ yield frame;
720
+ }
721
+ }
722
+ }
723
+ async function* encodeStream(frames, encoder) {
724
+ // Process all frames
725
+ for await (const frame of frames) {
726
+ try {
727
+ const packet = await encoder.encode(frame);
728
+ if (packet) {
729
+ yield packet;
730
+ }
731
+ }
732
+ finally {
733
+ // Free the input frame after encoding
734
+ frame.free();
735
+ }
736
+ }
737
+ // Flush encoder
738
+ if ('flushPackets' in encoder && typeof encoder.flushPackets === 'function') {
739
+ // Use generator method if available
740
+ for await (const packet of encoder.flushPackets()) {
741
+ yield packet;
742
+ }
743
+ }
744
+ else {
745
+ // Fallback to loop
746
+ let packet;
747
+ while ((packet = await encoder.flush()) !== null) {
748
+ yield packet;
749
+ }
750
+ }
751
+ }
752
+ async function* filterStream(frames, filter) {
753
+ // Process all frames
754
+ for await (const frame of frames) {
755
+ try {
756
+ const filtered = await filter.process(frame);
757
+ if (filtered) {
758
+ yield filtered;
759
+ }
760
+ // Check for buffered frames
761
+ let buffered;
762
+ while ((buffered = await filter.receive()) !== null) {
763
+ yield buffered;
764
+ }
765
+ }
766
+ finally {
767
+ // Free the input frame after filtering
768
+ frame.free();
769
+ }
770
+ }
771
+ // Flush filter
772
+ if ('flushFrames' in filter && typeof filter.flushFrames === 'function') {
773
+ // Use generator method if available
774
+ for await (const frame of filter.flushFrames()) {
775
+ yield frame;
776
+ }
777
+ }
778
+ else {
779
+ // Fallback to manual flush + receive
780
+ await filter.flush();
781
+ let frame;
782
+ while ((frame = await filter.receive()) !== null) {
783
+ yield frame;
784
+ }
785
+ }
786
+ }
787
+ async function* bitStreamFilterStream(packets, bsf) {
788
+ // Process all packets through bitstream filter
789
+ for await (const packet of packets) {
790
+ try {
791
+ const filtered = await bsf.process(packet);
792
+ for (const outPacket of filtered) {
793
+ yield outPacket;
794
+ }
795
+ }
796
+ finally {
797
+ // Free the input packet after filtering
798
+ packet.free();
799
+ }
800
+ }
801
+ // Flush bitstream filter
802
+ for await (const packet of bsf.flushPackets()) {
803
+ yield packet;
804
+ }
805
+ }
806
+ // ============================================================================
807
+ // Type Guards
808
+ // ============================================================================
809
+ function isNamedInputs(obj) {
810
+ return obj && typeof obj === 'object' && !Array.isArray(obj) && !isAsyncIterable(obj) && !isMediaInput(obj);
811
+ }
812
+ function isAsyncIterable(obj) {
813
+ return obj && typeof obj[Symbol.asyncIterator] === 'function';
814
+ }
815
+ function isMediaInput(obj) {
816
+ return obj && typeof obj.packets === 'function' && typeof obj.video === 'function' && typeof obj.audio === 'function';
817
+ }
818
+ function isDecoder(obj) {
819
+ return obj && typeof obj.decode === 'function' && typeof obj.flush === 'function';
820
+ }
821
+ function isEncoder(obj) {
822
+ return obj && typeof obj.encode === 'function' && typeof obj.flush === 'function';
823
+ }
824
+ function isFilterAPI(obj) {
825
+ return obj && typeof obj.process === 'function' && typeof obj.receive === 'function';
826
+ }
827
+ function isBitStreamFilterAPI(obj) {
828
+ return obj && typeof obj.process === 'function' && typeof obj.flushPackets === 'function' && typeof obj.reset === 'function';
829
+ }
830
+ function isMediaOutput(obj) {
831
+ return obj && typeof obj.writePacket === 'function' && typeof obj.writeHeader === 'function';
832
+ }
833
+ function isPacket(obj) {
834
+ return obj && 'streamIndex' in obj && 'pts' in obj && 'dts' in obj;
835
+ }
836
+ //# sourceMappingURL=pipeline.js.map