@revizly/node-av 5.2.2-beta.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 (254) hide show
  1. package/BUILD_LINUX.md +61 -0
  2. package/LICENSE.md +22 -0
  3. package/README.md +662 -0
  4. package/build_mac_local.sh +69 -0
  5. package/dist/api/audio-frame-buffer.d.ts +205 -0
  6. package/dist/api/audio-frame-buffer.js +287 -0
  7. package/dist/api/audio-frame-buffer.js.map +1 -0
  8. package/dist/api/bitstream-filter.d.ts +820 -0
  9. package/dist/api/bitstream-filter.js +1242 -0
  10. package/dist/api/bitstream-filter.js.map +1 -0
  11. package/dist/api/constants.d.ts +44 -0
  12. package/dist/api/constants.js +45 -0
  13. package/dist/api/constants.js.map +1 -0
  14. package/dist/api/data/test_av1.ivf +0 -0
  15. package/dist/api/data/test_h264.h264 +0 -0
  16. package/dist/api/data/test_hevc.h265 +0 -0
  17. package/dist/api/data/test_mjpeg.mjpeg +0 -0
  18. package/dist/api/data/test_vp8.ivf +0 -0
  19. package/dist/api/data/test_vp9.ivf +0 -0
  20. package/dist/api/decoder.d.ts +1088 -0
  21. package/dist/api/decoder.js +1775 -0
  22. package/dist/api/decoder.js.map +1 -0
  23. package/dist/api/demuxer.d.ts +1219 -0
  24. package/dist/api/demuxer.js +2081 -0
  25. package/dist/api/demuxer.js.map +1 -0
  26. package/dist/api/device.d.ts +586 -0
  27. package/dist/api/device.js +961 -0
  28. package/dist/api/device.js.map +1 -0
  29. package/dist/api/encoder.d.ts +1132 -0
  30. package/dist/api/encoder.js +1988 -0
  31. package/dist/api/encoder.js.map +1 -0
  32. package/dist/api/filter-complex.d.ts +821 -0
  33. package/dist/api/filter-complex.js +1604 -0
  34. package/dist/api/filter-complex.js.map +1 -0
  35. package/dist/api/filter-presets.d.ts +1286 -0
  36. package/dist/api/filter-presets.js +2152 -0
  37. package/dist/api/filter-presets.js.map +1 -0
  38. package/dist/api/filter.d.ts +1234 -0
  39. package/dist/api/filter.js +1976 -0
  40. package/dist/api/filter.js.map +1 -0
  41. package/dist/api/fmp4-stream.d.ts +426 -0
  42. package/dist/api/fmp4-stream.js +739 -0
  43. package/dist/api/fmp4-stream.js.map +1 -0
  44. package/dist/api/hardware.d.ts +651 -0
  45. package/dist/api/hardware.js +1260 -0
  46. package/dist/api/hardware.js.map +1 -0
  47. package/dist/api/index.d.ts +17 -0
  48. package/dist/api/index.js +32 -0
  49. package/dist/api/index.js.map +1 -0
  50. package/dist/api/io-stream.d.ts +307 -0
  51. package/dist/api/io-stream.js +282 -0
  52. package/dist/api/io-stream.js.map +1 -0
  53. package/dist/api/muxer.d.ts +957 -0
  54. package/dist/api/muxer.js +2002 -0
  55. package/dist/api/muxer.js.map +1 -0
  56. package/dist/api/pipeline.d.ts +607 -0
  57. package/dist/api/pipeline.js +1145 -0
  58. package/dist/api/pipeline.js.map +1 -0
  59. package/dist/api/utilities/async-queue.d.ts +120 -0
  60. package/dist/api/utilities/async-queue.js +211 -0
  61. package/dist/api/utilities/async-queue.js.map +1 -0
  62. package/dist/api/utilities/audio-sample.d.ts +117 -0
  63. package/dist/api/utilities/audio-sample.js +112 -0
  64. package/dist/api/utilities/audio-sample.js.map +1 -0
  65. package/dist/api/utilities/channel-layout.d.ts +76 -0
  66. package/dist/api/utilities/channel-layout.js +80 -0
  67. package/dist/api/utilities/channel-layout.js.map +1 -0
  68. package/dist/api/utilities/electron-shared-texture.d.ts +328 -0
  69. package/dist/api/utilities/electron-shared-texture.js +503 -0
  70. package/dist/api/utilities/electron-shared-texture.js.map +1 -0
  71. package/dist/api/utilities/image.d.ts +207 -0
  72. package/dist/api/utilities/image.js +213 -0
  73. package/dist/api/utilities/image.js.map +1 -0
  74. package/dist/api/utilities/index.d.ts +12 -0
  75. package/dist/api/utilities/index.js +25 -0
  76. package/dist/api/utilities/index.js.map +1 -0
  77. package/dist/api/utilities/media-type.d.ts +49 -0
  78. package/dist/api/utilities/media-type.js +53 -0
  79. package/dist/api/utilities/media-type.js.map +1 -0
  80. package/dist/api/utilities/pixel-format.d.ts +89 -0
  81. package/dist/api/utilities/pixel-format.js +97 -0
  82. package/dist/api/utilities/pixel-format.js.map +1 -0
  83. package/dist/api/utilities/sample-format.d.ts +129 -0
  84. package/dist/api/utilities/sample-format.js +141 -0
  85. package/dist/api/utilities/sample-format.js.map +1 -0
  86. package/dist/api/utilities/scheduler.d.ts +138 -0
  87. package/dist/api/utilities/scheduler.js +98 -0
  88. package/dist/api/utilities/scheduler.js.map +1 -0
  89. package/dist/api/utilities/streaming.d.ts +186 -0
  90. package/dist/api/utilities/streaming.js +309 -0
  91. package/dist/api/utilities/streaming.js.map +1 -0
  92. package/dist/api/utilities/timestamp.d.ts +193 -0
  93. package/dist/api/utilities/timestamp.js +206 -0
  94. package/dist/api/utilities/timestamp.js.map +1 -0
  95. package/dist/api/utilities/whisper-model.d.ts +310 -0
  96. package/dist/api/utilities/whisper-model.js +528 -0
  97. package/dist/api/utilities/whisper-model.js.map +1 -0
  98. package/dist/api/utils.d.ts +19 -0
  99. package/dist/api/utils.js +39 -0
  100. package/dist/api/utils.js.map +1 -0
  101. package/dist/api/whisper.d.ts +324 -0
  102. package/dist/api/whisper.js +362 -0
  103. package/dist/api/whisper.js.map +1 -0
  104. package/dist/constants/channel-layouts.d.ts +53 -0
  105. package/dist/constants/channel-layouts.js +57 -0
  106. package/dist/constants/channel-layouts.js.map +1 -0
  107. package/dist/constants/constants.d.ts +2325 -0
  108. package/dist/constants/constants.js +1887 -0
  109. package/dist/constants/constants.js.map +1 -0
  110. package/dist/constants/decoders.d.ts +633 -0
  111. package/dist/constants/decoders.js +641 -0
  112. package/dist/constants/decoders.js.map +1 -0
  113. package/dist/constants/encoders.d.ts +295 -0
  114. package/dist/constants/encoders.js +308 -0
  115. package/dist/constants/encoders.js.map +1 -0
  116. package/dist/constants/hardware.d.ts +26 -0
  117. package/dist/constants/hardware.js +27 -0
  118. package/dist/constants/hardware.js.map +1 -0
  119. package/dist/constants/index.d.ts +5 -0
  120. package/dist/constants/index.js +6 -0
  121. package/dist/constants/index.js.map +1 -0
  122. package/dist/ffmpeg/index.d.ts +99 -0
  123. package/dist/ffmpeg/index.js +115 -0
  124. package/dist/ffmpeg/index.js.map +1 -0
  125. package/dist/ffmpeg/utils.d.ts +31 -0
  126. package/dist/ffmpeg/utils.js +68 -0
  127. package/dist/ffmpeg/utils.js.map +1 -0
  128. package/dist/ffmpeg/version.d.ts +6 -0
  129. package/dist/ffmpeg/version.js +7 -0
  130. package/dist/ffmpeg/version.js.map +1 -0
  131. package/dist/index.d.ts +4 -0
  132. package/dist/index.js +9 -0
  133. package/dist/index.js.map +1 -0
  134. package/dist/lib/audio-fifo.d.ts +399 -0
  135. package/dist/lib/audio-fifo.js +431 -0
  136. package/dist/lib/audio-fifo.js.map +1 -0
  137. package/dist/lib/binding.d.ts +228 -0
  138. package/dist/lib/binding.js +60 -0
  139. package/dist/lib/binding.js.map +1 -0
  140. package/dist/lib/bitstream-filter-context.d.ts +379 -0
  141. package/dist/lib/bitstream-filter-context.js +441 -0
  142. package/dist/lib/bitstream-filter-context.js.map +1 -0
  143. package/dist/lib/bitstream-filter.d.ts +140 -0
  144. package/dist/lib/bitstream-filter.js +154 -0
  145. package/dist/lib/bitstream-filter.js.map +1 -0
  146. package/dist/lib/codec-context.d.ts +1071 -0
  147. package/dist/lib/codec-context.js +1354 -0
  148. package/dist/lib/codec-context.js.map +1 -0
  149. package/dist/lib/codec-parameters.d.ts +616 -0
  150. package/dist/lib/codec-parameters.js +761 -0
  151. package/dist/lib/codec-parameters.js.map +1 -0
  152. package/dist/lib/codec-parser.d.ts +201 -0
  153. package/dist/lib/codec-parser.js +213 -0
  154. package/dist/lib/codec-parser.js.map +1 -0
  155. package/dist/lib/codec.d.ts +586 -0
  156. package/dist/lib/codec.js +713 -0
  157. package/dist/lib/codec.js.map +1 -0
  158. package/dist/lib/device.d.ts +291 -0
  159. package/dist/lib/device.js +324 -0
  160. package/dist/lib/device.js.map +1 -0
  161. package/dist/lib/dictionary.d.ts +333 -0
  162. package/dist/lib/dictionary.js +372 -0
  163. package/dist/lib/dictionary.js.map +1 -0
  164. package/dist/lib/error.d.ts +242 -0
  165. package/dist/lib/error.js +303 -0
  166. package/dist/lib/error.js.map +1 -0
  167. package/dist/lib/fifo.d.ts +416 -0
  168. package/dist/lib/fifo.js +453 -0
  169. package/dist/lib/fifo.js.map +1 -0
  170. package/dist/lib/filter-context.d.ts +712 -0
  171. package/dist/lib/filter-context.js +789 -0
  172. package/dist/lib/filter-context.js.map +1 -0
  173. package/dist/lib/filter-graph-segment.d.ts +160 -0
  174. package/dist/lib/filter-graph-segment.js +171 -0
  175. package/dist/lib/filter-graph-segment.js.map +1 -0
  176. package/dist/lib/filter-graph.d.ts +641 -0
  177. package/dist/lib/filter-graph.js +704 -0
  178. package/dist/lib/filter-graph.js.map +1 -0
  179. package/dist/lib/filter-inout.d.ts +198 -0
  180. package/dist/lib/filter-inout.js +257 -0
  181. package/dist/lib/filter-inout.js.map +1 -0
  182. package/dist/lib/filter.d.ts +243 -0
  183. package/dist/lib/filter.js +272 -0
  184. package/dist/lib/filter.js.map +1 -0
  185. package/dist/lib/format-context.d.ts +1254 -0
  186. package/dist/lib/format-context.js +1379 -0
  187. package/dist/lib/format-context.js.map +1 -0
  188. package/dist/lib/frame-utils.d.ts +116 -0
  189. package/dist/lib/frame-utils.js +98 -0
  190. package/dist/lib/frame-utils.js.map +1 -0
  191. package/dist/lib/frame.d.ts +1222 -0
  192. package/dist/lib/frame.js +1435 -0
  193. package/dist/lib/frame.js.map +1 -0
  194. package/dist/lib/hardware-device-context.d.ts +362 -0
  195. package/dist/lib/hardware-device-context.js +383 -0
  196. package/dist/lib/hardware-device-context.js.map +1 -0
  197. package/dist/lib/hardware-frames-context.d.ts +419 -0
  198. package/dist/lib/hardware-frames-context.js +477 -0
  199. package/dist/lib/hardware-frames-context.js.map +1 -0
  200. package/dist/lib/index.d.ts +35 -0
  201. package/dist/lib/index.js +60 -0
  202. package/dist/lib/index.js.map +1 -0
  203. package/dist/lib/input-format.d.ts +249 -0
  204. package/dist/lib/input-format.js +306 -0
  205. package/dist/lib/input-format.js.map +1 -0
  206. package/dist/lib/io-context.d.ts +696 -0
  207. package/dist/lib/io-context.js +769 -0
  208. package/dist/lib/io-context.js.map +1 -0
  209. package/dist/lib/log.d.ts +174 -0
  210. package/dist/lib/log.js +184 -0
  211. package/dist/lib/log.js.map +1 -0
  212. package/dist/lib/native-types.d.ts +946 -0
  213. package/dist/lib/native-types.js +2 -0
  214. package/dist/lib/native-types.js.map +1 -0
  215. package/dist/lib/option.d.ts +927 -0
  216. package/dist/lib/option.js +1583 -0
  217. package/dist/lib/option.js.map +1 -0
  218. package/dist/lib/output-format.d.ts +180 -0
  219. package/dist/lib/output-format.js +213 -0
  220. package/dist/lib/output-format.js.map +1 -0
  221. package/dist/lib/packet.d.ts +501 -0
  222. package/dist/lib/packet.js +590 -0
  223. package/dist/lib/packet.js.map +1 -0
  224. package/dist/lib/rational.d.ts +251 -0
  225. package/dist/lib/rational.js +278 -0
  226. package/dist/lib/rational.js.map +1 -0
  227. package/dist/lib/software-resample-context.d.ts +552 -0
  228. package/dist/lib/software-resample-context.js +592 -0
  229. package/dist/lib/software-resample-context.js.map +1 -0
  230. package/dist/lib/software-scale-context.d.ts +344 -0
  231. package/dist/lib/software-scale-context.js +366 -0
  232. package/dist/lib/software-scale-context.js.map +1 -0
  233. package/dist/lib/stream.d.ts +379 -0
  234. package/dist/lib/stream.js +526 -0
  235. package/dist/lib/stream.js.map +1 -0
  236. package/dist/lib/sync-queue.d.ts +179 -0
  237. package/dist/lib/sync-queue.js +197 -0
  238. package/dist/lib/sync-queue.js.map +1 -0
  239. package/dist/lib/types.d.ts +34 -0
  240. package/dist/lib/types.js +2 -0
  241. package/dist/lib/types.js.map +1 -0
  242. package/dist/lib/utilities.d.ts +1127 -0
  243. package/dist/lib/utilities.js +1225 -0
  244. package/dist/lib/utilities.js.map +1 -0
  245. package/dist/utils/electron.d.ts +49 -0
  246. package/dist/utils/electron.js +63 -0
  247. package/dist/utils/electron.js.map +1 -0
  248. package/dist/utils/index.d.ts +4 -0
  249. package/dist/utils/index.js +5 -0
  250. package/dist/utils/index.js.map +1 -0
  251. package/install/check.js +121 -0
  252. package/install/ffmpeg.js +66 -0
  253. package/jellyfin-ffmpeg.patch +181 -0
  254. package/package.json +129 -0
@@ -0,0 +1,1145 @@
1
+ // ============================================================================
2
+ // Implementation
3
+ // ============================================================================
4
+ /**
5
+ * Pipeline implementation.
6
+ *
7
+ * Creates a processing pipeline from media components.
8
+ * Automatically handles type conversions and proper flushing order.
9
+ *
10
+ * @param args - Variable arguments depending on pipeline type
11
+ *
12
+ * @returns PipelineControl if output is present, AsyncGenerator otherwise
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * // Simple pipeline
17
+ * const control = pipeline(
18
+ * input,
19
+ * decoder,
20
+ * filter,
21
+ * encoder,
22
+ * output
23
+ * );
24
+ * await control.completion;
25
+ *
26
+ * // Named pipeline for muxing
27
+ * const control = pipeline(
28
+ * { video: videoInput, audio: audioInput },
29
+ * {
30
+ * video: [videoDecoder, scaleFilter, videoEncoder],
31
+ * audio: [audioDecoder, volumeFilter, audioEncoder]
32
+ * },
33
+ * output
34
+ * );
35
+ * await control.completion;
36
+ * ```
37
+ */
38
+ export function pipeline(...args) {
39
+ // Extract PipelineOptions if last arg is undefined or a PipelineOptions object.
40
+ // PipelineOptions is identified as a plain object whose only known key is 'signal'.
41
+ // This distinguishes it from NamedInputs/NamedStages/NamedOutputs which have stream name keys.
42
+ let pipelineOptions;
43
+ const lastArg = args[args.length - 1];
44
+ if (lastArg === undefined) {
45
+ args.pop();
46
+ }
47
+ else if (args.length > 0 && typeof lastArg === 'object' && lastArg !== null && Object.getPrototypeOf(lastArg) === Object.prototype && 'signal' in lastArg) {
48
+ pipelineOptions = args.pop();
49
+ }
50
+ pipelineOptions?.signal?.throwIfAborted();
51
+ // Detect pipeline type based on first argument
52
+ const firstArg = args[0];
53
+ const secondArg = args[1];
54
+ // Check for shared Demuxer + NamedStages pattern
55
+ if (isDemuxer(firstArg) && isNamedStages(secondArg)) {
56
+ // Convert shared input to NamedInputs based on stages keys
57
+ const sharedInput = firstArg;
58
+ const stages = secondArg;
59
+ const namedInputs = {};
60
+ // Create NamedInputs with shared input for all streams in stages
61
+ for (const streamName of Object.keys(stages)) {
62
+ namedInputs[streamName] = sharedInput;
63
+ }
64
+ if (args.length === 3) {
65
+ // Full named pipeline with output(s)
66
+ return runNamedPipeline(namedInputs, stages, args[2], pipelineOptions);
67
+ }
68
+ else {
69
+ // Partial named pipeline
70
+ return runNamedPartialPipeline(namedInputs, stages);
71
+ }
72
+ }
73
+ if (isNamedInputs(firstArg)) {
74
+ // Named pipeline (2 or 3 arguments)
75
+ if (args.length === 2) {
76
+ // Partial named pipeline - return generators
77
+ return runNamedPartialPipeline(args[0], args[1]);
78
+ }
79
+ else {
80
+ // Full named pipeline with output
81
+ return runNamedPipeline(args[0], args[1], args[2], pipelineOptions);
82
+ }
83
+ }
84
+ else if (isDemuxer(firstArg)) {
85
+ // Check if this is a stream copy (Demuxer → Muxer)
86
+ if (args.length === 2 && isMuxer(args[1])) {
87
+ // Stream copy all streams
88
+ return runDemuxerPipeline(args[0], args[1], pipelineOptions);
89
+ }
90
+ else {
91
+ // Simple pipeline starting with Demuxer
92
+ return runSimplePipeline(args, pipelineOptions);
93
+ }
94
+ }
95
+ else {
96
+ // Simple pipeline (variable arguments)
97
+ return runSimplePipeline(args, pipelineOptions);
98
+ }
99
+ }
100
+ // ============================================================================
101
+ // PipelineControl Implementation
102
+ // ============================================================================
103
+ /**
104
+ * Pipeline control implementation.
105
+ *
106
+ * @internal
107
+ */
108
+ class PipelineControlImpl {
109
+ _stopped = false;
110
+ _completion;
111
+ signalCleanup;
112
+ /**
113
+ * @param executionPromise - Promise that resolves when pipeline completes
114
+ *
115
+ * @param signal - Optional AbortSignal for cancellation
116
+ *
117
+ * @internal
118
+ */
119
+ constructor(executionPromise, signal) {
120
+ // Don't resolve immediately on stop, wait for the actual pipeline to finish
121
+ this._completion = executionPromise;
122
+ if (signal) {
123
+ const handler = () => this.stop();
124
+ signal.addEventListener('abort', handler, { once: true });
125
+ this.signalCleanup = () => signal.removeEventListener('abort', handler);
126
+ }
127
+ }
128
+ /**
129
+ * Stop the pipeline.
130
+ *
131
+ * @example
132
+ * ```typescript
133
+ * const control = pipeline(input, decoder, filter, encoder, output);
134
+ * control.stop();
135
+ * ```
136
+ *
137
+ * @see {@link PipelineControl.isStopped}
138
+ */
139
+ stop() {
140
+ this._stopped = true;
141
+ this.signalCleanup?.();
142
+ this.signalCleanup = undefined;
143
+ }
144
+ /**
145
+ * Check if pipeline is stopped.
146
+ *
147
+ * @returns True if stopped
148
+ *
149
+ * @example
150
+ * ```typescript
151
+ * const control = pipeline(input, decoder, filter, encoder, output);
152
+ * const isStopped = control.isStopped();
153
+ * ```
154
+ *
155
+ * @see {@link PipelineControl.stop}
156
+ */
157
+ isStopped() {
158
+ return this._stopped;
159
+ }
160
+ /**
161
+ * Get completion promise.
162
+ */
163
+ get completion() {
164
+ return this._completion;
165
+ }
166
+ }
167
+ // ============================================================================
168
+ // Demuxer Pipeline Implementation
169
+ // ============================================================================
170
+ /**
171
+ * Run a demuxer pipeline for stream copy.
172
+ *
173
+ * @param input - Media input source
174
+ *
175
+ * @param output - Media output destination
176
+ *
177
+ * @param options - Pipeline options for cancellation support
178
+ *
179
+ * @returns Pipeline control interface
180
+ *
181
+ * @internal
182
+ */
183
+ function runDemuxerPipeline(input, output, options) {
184
+ let control;
185
+ // eslint-disable-next-line prefer-const
186
+ control = new PipelineControlImpl(runDemuxerPipelineAsync(input, output, () => control?.isStopped() ?? false), options?.signal);
187
+ return control;
188
+ }
189
+ /**
190
+ * Run demuxer pipeline asynchronously.
191
+ *
192
+ * @param input - Media input source
193
+ *
194
+ * @param output - Media output destination
195
+ *
196
+ * @param shouldStop - Function to check if pipeline should stop
197
+ *
198
+ * @internal
199
+ */
200
+ async function runDemuxerPipelineAsync(input, output, shouldStop) {
201
+ // Get all streams from input
202
+ const videoStream = input.video();
203
+ const audioStream = input.audio();
204
+ const streams = [];
205
+ // Add video stream if present
206
+ if (videoStream) {
207
+ const outputIndex = output.addStream(videoStream);
208
+ streams.push({ stream: videoStream, index: outputIndex });
209
+ }
210
+ // Add audio stream if present
211
+ if (audioStream) {
212
+ const outputIndex = output.addStream(audioStream);
213
+ streams.push({ stream: audioStream, index: outputIndex });
214
+ }
215
+ // Add any other streams
216
+ const allStreams = input.streams;
217
+ for (const stream of allStreams) {
218
+ // Skip if already added
219
+ if (stream !== videoStream && stream !== audioStream) {
220
+ const outputIndex = output.addStream(stream);
221
+ streams.push({ stream, index: outputIndex });
222
+ }
223
+ }
224
+ // Get iterator to properly clean up on stop
225
+ const packetsIterable = input.packets();
226
+ const iterator = packetsIterable[Symbol.asyncIterator]();
227
+ try {
228
+ // Copy all packets
229
+ while (true) {
230
+ // Check if we should stop before getting next item
231
+ if (shouldStop()) {
232
+ break;
233
+ }
234
+ const { value: packet, done } = await iterator.next();
235
+ if (done)
236
+ break;
237
+ // Handle EOF signal (null packet from input means all streams are done)
238
+ if (packet === null) {
239
+ // Signal EOF for all streams
240
+ for (const mapping of streams) {
241
+ await output.writePacket(null, mapping.index);
242
+ }
243
+ break;
244
+ }
245
+ try {
246
+ // Find the corresponding output stream index
247
+ const mapping = streams.find((s) => s.stream.index === packet.streamIndex);
248
+ if (mapping) {
249
+ await output.writePacket(packet, mapping.index);
250
+ }
251
+ }
252
+ finally {
253
+ // Free the packet after use
254
+ if (packet && typeof packet.free === 'function') {
255
+ packet.free();
256
+ }
257
+ }
258
+ }
259
+ }
260
+ finally {
261
+ // Always clean up the generator to prevent memory leaks
262
+ if (iterator.return) {
263
+ await iterator.return(undefined);
264
+ }
265
+ }
266
+ await output.close();
267
+ }
268
+ // ============================================================================
269
+ // Simple Pipeline Implementation
270
+ // ============================================================================
271
+ /**
272
+ * Run a simple linear pipeline.
273
+ *
274
+ * @param args - Pipeline arguments
275
+ *
276
+ * @param options - Pipeline options for cancellation support
277
+ *
278
+ * @returns Pipeline control or async generator
279
+ *
280
+ * @internal
281
+ */
282
+ function runSimplePipeline(args, options) {
283
+ const [source, ...stages] = args;
284
+ // Check if last stage is Muxer (consumes stream)
285
+ const lastStage = stages[stages.length - 1];
286
+ const isOutput = isMuxer(lastStage);
287
+ // Track metadata through pipeline
288
+ const metadata = {};
289
+ // Store Demuxer reference if we have one
290
+ if (isDemuxer(source)) {
291
+ metadata.demuxer = source;
292
+ }
293
+ // Build the pipeline generator
294
+ // If output is present, exclude it from stages for processing
295
+ const processStages = isOutput ? stages.slice(0, -1) : stages;
296
+ // Process metadata first by walking through stages
297
+ for (const stage of processStages) {
298
+ if (isDecoder(stage)) {
299
+ metadata.decoder = stage;
300
+ }
301
+ else if (isEncoder(stage)) {
302
+ metadata.encoder = stage;
303
+ }
304
+ else if (isBitStreamFilterAPI(stage)) {
305
+ metadata.bitStreamFilter = stage;
306
+ }
307
+ }
308
+ // Convert Demuxer to packet stream if needed
309
+ // If we have a decoder or BSF, filter packets by stream index
310
+ let actualSource;
311
+ if (isDemuxer(source)) {
312
+ if (metadata.decoder) {
313
+ // Filter packets for the decoder's stream
314
+ const streamIndex = metadata.decoder.getStream().index;
315
+ actualSource = source.packets(streamIndex);
316
+ }
317
+ else if (metadata.bitStreamFilter) {
318
+ // Filter packets for the BSF's stream
319
+ const streamIndex = metadata.bitStreamFilter.getStream().index;
320
+ actualSource = source.packets(streamIndex);
321
+ }
322
+ else {
323
+ // No decoder or BSF, pass all packets
324
+ actualSource = source.packets();
325
+ }
326
+ }
327
+ else {
328
+ actualSource = source;
329
+ }
330
+ const generator = buildSimplePipeline(actualSource, processStages);
331
+ // If output, consume the generator
332
+ if (isOutput) {
333
+ let control;
334
+ // eslint-disable-next-line prefer-const
335
+ control = new PipelineControlImpl(consumeSimplePipeline(generator, lastStage, metadata, () => control?.isStopped() ?? false), options?.signal);
336
+ return control;
337
+ }
338
+ // Otherwise return the generator for further processing
339
+ return generator;
340
+ }
341
+ /**
342
+ * Build a simple pipeline generator.
343
+ *
344
+ * @param source - Source of packets or frames
345
+ *
346
+ * @param stages - Processing stages
347
+ *
348
+ * @yields {Packet | Frame} Processed packets or frames
349
+ *
350
+ * @internal
351
+ */
352
+ async function* buildSimplePipeline(source, stages) {
353
+ let stream = source;
354
+ for (const stage of stages) {
355
+ if (isDecoder(stage)) {
356
+ stream = stage.frames(stream);
357
+ }
358
+ else if (isEncoder(stage)) {
359
+ stream = stage.packets(stream);
360
+ }
361
+ else if (isFilterAPI(stage)) {
362
+ stream = stage.frames(stream);
363
+ }
364
+ else if (isBitStreamFilterAPI(stage)) {
365
+ stream = stage.packets(stream);
366
+ }
367
+ else if (Array.isArray(stage)) {
368
+ // Chain multiple filters or BSFs
369
+ for (const filter of stage) {
370
+ if (isFilterAPI(filter)) {
371
+ stream = filter.frames(stream);
372
+ }
373
+ else if (isBitStreamFilterAPI(filter)) {
374
+ stream = filter.packets(stream);
375
+ }
376
+ }
377
+ }
378
+ }
379
+ yield* stream;
380
+ }
381
+ /**
382
+ * Consume a simple pipeline stream and write to output.
383
+ *
384
+ * @param stream - Stream of packets or frames
385
+ *
386
+ * @param output - Media output destination
387
+ *
388
+ * @param metadata - Stream metadata
389
+ *
390
+ * @param shouldStop - Function to check if pipeline should stop
391
+ *
392
+ * @internal
393
+ */
394
+ async function consumeSimplePipeline(stream, output, metadata, shouldStop) {
395
+ // Add stream to output if we have encoder or decoder info
396
+ let streamIndex = 0;
397
+ if (metadata.encoder) {
398
+ // Encoding path
399
+ if (metadata.decoder) {
400
+ // Have decoder - use its stream for metadata/properties
401
+ const originalStream = metadata.decoder.getStream();
402
+ streamIndex = output.addStream(originalStream, { encoder: metadata.encoder });
403
+ }
404
+ else {
405
+ // Encoder-only mode (e.g., frame generator) - no input stream
406
+ streamIndex = output.addStream(metadata.encoder);
407
+ }
408
+ }
409
+ else if (metadata.decoder) {
410
+ // Stream copy - use decoder's original stream
411
+ const originalStream = metadata.decoder.getStream();
412
+ streamIndex = output.addStream(originalStream);
413
+ }
414
+ else if (metadata.bitStreamFilter) {
415
+ // BSF without encoder/decoder - use BSF's original stream
416
+ const originalStream = metadata.bitStreamFilter.getStream();
417
+ streamIndex = output.addStream(originalStream);
418
+ }
419
+ else {
420
+ // For direct Demuxer → Muxer, we redirect to runDemuxerPipeline
421
+ // This case shouldn't happen in simple pipeline
422
+ throw new Error('Cannot determine stream configuration. This is likely a bug in the pipeline.');
423
+ }
424
+ // Get iterator to properly clean up on stop
425
+ const iterator = stream[Symbol.asyncIterator]();
426
+ try {
427
+ // Process stream
428
+ while (true) {
429
+ // Check if we should stop before getting next item
430
+ if (shouldStop()) {
431
+ break;
432
+ }
433
+ const { value: item, done } = await iterator.next();
434
+ if (done)
435
+ break;
436
+ // Handle EOF signal
437
+ if (item === null) {
438
+ await output.writePacket(null, streamIndex);
439
+ break;
440
+ }
441
+ // Use explicit resource management for the item
442
+ try {
443
+ if (isPacket(item) || item === null) {
444
+ await output.writePacket(item, streamIndex);
445
+ }
446
+ else {
447
+ throw new Error('Cannot write frames directly to Muxer. Use an encoder first.');
448
+ }
449
+ }
450
+ finally {
451
+ // Free the packet/frame after use
452
+ if (item && typeof item.free === 'function') {
453
+ item.free();
454
+ }
455
+ }
456
+ }
457
+ }
458
+ finally {
459
+ // Always clean up the generator to prevent memory leaks
460
+ if (iterator.return) {
461
+ await iterator.return(undefined);
462
+ }
463
+ }
464
+ await output.close();
465
+ }
466
+ // ============================================================================
467
+ // Named Pipeline Implementation
468
+ // ============================================================================
469
+ /**
470
+ * Run a named partial pipeline.
471
+ *
472
+ * @param inputs - Named input sources
473
+ *
474
+ * @param stages - Named processing stages
475
+ *
476
+ * @returns Record of async generators
477
+ *
478
+ * @internal
479
+ */
480
+ function runNamedPartialPipeline(inputs, stages) {
481
+ const result = {};
482
+ for (const [streamName, streamStages] of Object.entries(stages)) {
483
+ const input = inputs[streamName];
484
+ if (!input) {
485
+ throw new Error(`No input found for stream: ${streamName}`);
486
+ }
487
+ // Get the appropriate stream based on the stream name
488
+ let stream = null;
489
+ switch (streamName) {
490
+ case 'video':
491
+ stream = input.video() ?? null;
492
+ break;
493
+ case 'audio':
494
+ stream = input.audio() ?? null;
495
+ break;
496
+ default:
497
+ // This should never happen
498
+ throw new Error(`Invalid stream name: ${streamName}. Must be 'video' or 'audio'.`);
499
+ }
500
+ if (!stream) {
501
+ throw new Error(`No ${streamName} stream found in input.`);
502
+ }
503
+ // Normalize stages: if array contains only undefined, treat as passthrough
504
+ // Also filter out undefined entries from the array
505
+ let normalizedStages = streamStages;
506
+ if (Array.isArray(streamStages)) {
507
+ const definedStages = streamStages.filter((stage) => stage !== undefined);
508
+ if (definedStages.length === 0) {
509
+ normalizedStages = 'passthrough';
510
+ }
511
+ else {
512
+ normalizedStages = definedStages;
513
+ }
514
+ }
515
+ if (normalizedStages === 'passthrough') {
516
+ // Direct passthrough - return input packets for this specific stream
517
+ result[streamName] = (async function* () {
518
+ for await (const packet of input.packets(stream.index)) {
519
+ yield packet;
520
+ }
521
+ })();
522
+ }
523
+ else {
524
+ // Process the stream - pass packets for this specific stream only
525
+ // Build pipeline for this stream (can return frames or packets)
526
+ const metadata = {};
527
+ const stages = normalizedStages;
528
+ result[streamName] = buildFlexibleNamedStreamPipeline(input.packets(stream.index), stages, metadata);
529
+ }
530
+ }
531
+ return result;
532
+ }
533
+ /**
534
+ * Run a named pipeline with outputs.
535
+ *
536
+ * @param inputs - Named input sources
537
+ *
538
+ * @param stages - Named processing stages
539
+ *
540
+ * @param output - Output destination(s)
541
+ *
542
+ * @param options - Pipeline options for cancellation support
543
+ *
544
+ * @returns Pipeline control interface
545
+ *
546
+ * @internal
547
+ */
548
+ function runNamedPipeline(inputs, stages, output, options) {
549
+ let control;
550
+ // eslint-disable-next-line prefer-const
551
+ control = new PipelineControlImpl(runNamedPipelineAsync(inputs, stages, output, () => control?.isStopped() ?? false), options?.signal);
552
+ return control;
553
+ }
554
+ /**
555
+ * Run named pipeline asynchronously.
556
+ *
557
+ * @param inputs - Named input sources
558
+ *
559
+ * @param stages - Named processing stages
560
+ *
561
+ * @param output - Output destination(s)
562
+ *
563
+ * @param shouldStop - Function to check if pipeline should stop
564
+ *
565
+ * @internal
566
+ */
567
+ async function runNamedPipelineAsync(inputs, stages, output, shouldStop) {
568
+ // Check if all inputs reference the same Demuxer instance
569
+ const inputValues = Object.values(inputs);
570
+ const allSameInput = inputValues.length > 1 && inputValues.every((input) => input === inputValues[0]);
571
+ // Track metadata for each stream
572
+ const streamMetadata = {};
573
+ // Process each named stream into generators
574
+ const processedStreams = {};
575
+ // If all inputs are the same instance, use Demuxer's built-in parallel packet generators
576
+ if (allSameInput) {
577
+ const sharedInput = inputValues[0];
578
+ // Single pass: collect metadata and build pipelines directly using input.packets(streamIndex)
579
+ for (const [streamName, streamStages] of Object.entries(stages)) {
580
+ const metadata = {};
581
+ streamMetadata[streamName] = metadata;
582
+ // Normalize stages
583
+ let normalizedStages = streamStages;
584
+ if (Array.isArray(streamStages)) {
585
+ const definedStages = streamStages.filter((stage) => stage !== undefined);
586
+ if (definedStages.length === 0) {
587
+ normalizedStages = 'passthrough';
588
+ }
589
+ else {
590
+ normalizedStages = definedStages;
591
+ }
592
+ }
593
+ // Determine stream index and build pipeline
594
+ let streamIndex;
595
+ if (normalizedStages !== 'passthrough') {
596
+ const stages = normalizedStages;
597
+ // Set stream type
598
+ metadata.type = streamName;
599
+ // Populate metadata by walking through ALL stages
600
+ for (const stage of stages) {
601
+ if (isDecoder(stage)) {
602
+ metadata.decoder = stage;
603
+ streamIndex ??= stage.getStream().index;
604
+ }
605
+ else if (isBitStreamFilterAPI(stage)) {
606
+ metadata.bitStreamFilter = stage;
607
+ streamIndex ??= stage.getStream().index;
608
+ }
609
+ else if (isEncoder(stage)) {
610
+ metadata.encoder = stage;
611
+ }
612
+ }
613
+ // If no decoder/BSF, use stream name to determine index
614
+ if (streamIndex === undefined) {
615
+ const stream = streamName === 'video' ? sharedInput.video() : sharedInput.audio();
616
+ if (!stream) {
617
+ throw new Error(`No ${streamName} stream found in input.`);
618
+ }
619
+ streamIndex = stream.index;
620
+ }
621
+ // Build pipeline with packets from this specific stream
622
+ processedStreams[streamName] = buildNamedStreamPipeline(sharedInput.packets(streamIndex), stages, metadata);
623
+ }
624
+ else {
625
+ // Passthrough - use Demuxer's built-in stream filtering
626
+ metadata.type = streamName;
627
+ metadata.demuxer = sharedInput;
628
+ const stream = streamName === 'video' ? sharedInput.video() : sharedInput.audio();
629
+ if (!stream) {
630
+ throw new Error(`No ${streamName} stream found in input for passthrough.`);
631
+ }
632
+ streamIndex = stream.index;
633
+ // Direct passthrough using input.packets(streamIndex)
634
+ processedStreams[streamName] = sharedInput.packets(streamIndex);
635
+ }
636
+ }
637
+ }
638
+ else {
639
+ // Original logic: separate inputs or single input
640
+ for (const [streamName, streamStages] of Object.entries(stages)) {
641
+ const metadata = {};
642
+ streamMetadata[streamName] = metadata;
643
+ const input = inputs[streamName];
644
+ if (!input) {
645
+ throw new Error(`No input found for stream: ${streamName}`);
646
+ }
647
+ // Normalize stages: if array contains only undefined, treat as passthrough
648
+ // Also filter out undefined entries from the array
649
+ let normalizedStages = streamStages;
650
+ if (Array.isArray(streamStages)) {
651
+ const definedStages = streamStages.filter((stage) => stage !== undefined);
652
+ if (definedStages.length === 0) {
653
+ normalizedStages = 'passthrough';
654
+ }
655
+ else {
656
+ normalizedStages = definedStages;
657
+ }
658
+ }
659
+ if (normalizedStages === 'passthrough') {
660
+ // Direct passthrough - no processing
661
+ let stream = null;
662
+ switch (streamName) {
663
+ case 'video':
664
+ stream = input.video() ?? null;
665
+ metadata.type = 'video';
666
+ break;
667
+ case 'audio':
668
+ stream = input.audio() ?? null;
669
+ metadata.type = 'audio';
670
+ break;
671
+ }
672
+ if (!stream) {
673
+ throw new Error(`No ${streamName} stream found in input for passthrough.`);
674
+ }
675
+ processedStreams[streamName] = input.packets(stream.index);
676
+ metadata.demuxer = input; // Track Demuxer for passthrough
677
+ }
678
+ else {
679
+ // Process the stream - normalizedStages is guaranteed to be an array here
680
+ const stages = normalizedStages;
681
+ // Pre-populate metadata by walking through stages
682
+ for (const stage of stages) {
683
+ if (isDecoder(stage)) {
684
+ metadata.decoder = stage;
685
+ }
686
+ else if (isEncoder(stage)) {
687
+ metadata.encoder = stage;
688
+ }
689
+ else if (isBitStreamFilterAPI(stage)) {
690
+ metadata.bitStreamFilter = stage;
691
+ }
692
+ }
693
+ // Get packets - filter by stream index based on decoder, BSF, or stream type
694
+ let packets;
695
+ if (metadata.decoder) {
696
+ const streamIndex = metadata.decoder.getStream().index;
697
+ packets = input.packets(streamIndex);
698
+ }
699
+ else if (metadata.bitStreamFilter) {
700
+ const streamIndex = metadata.bitStreamFilter.getStream().index;
701
+ packets = input.packets(streamIndex);
702
+ }
703
+ else {
704
+ // No decoder or BSF - determine stream by name
705
+ let stream = null;
706
+ switch (streamName) {
707
+ case 'video':
708
+ stream = input.video() ?? null;
709
+ break;
710
+ case 'audio':
711
+ stream = input.audio() ?? null;
712
+ break;
713
+ }
714
+ if (!stream) {
715
+ throw new Error(`No ${streamName} stream found in input.`);
716
+ }
717
+ packets = input.packets(stream.index);
718
+ }
719
+ // Build pipeline for this stream
720
+ processedStreams[streamName] = buildNamedStreamPipeline(packets, stages, metadata);
721
+ }
722
+ }
723
+ }
724
+ // Write to output(s)
725
+ if (isMuxer(output)) {
726
+ // Always write streams in parallel - Muxer's SyncQueue handles interleaving internally
727
+ const streamIndices = {};
728
+ // Add all streams to output first
729
+ for (const [name, meta] of Object.entries(streamMetadata)) {
730
+ if (meta.encoder) {
731
+ // Encoding path
732
+ if (meta.decoder) {
733
+ // Have decoder - use its stream for metadata/properties
734
+ const originalStream = meta.decoder.getStream();
735
+ streamIndices[name] = output.addStream(originalStream, { encoder: meta.encoder });
736
+ }
737
+ else {
738
+ // Encoder-only mode (e.g., frame generator) - no input stream
739
+ streamIndices[name] = output.addStream(meta.encoder);
740
+ }
741
+ }
742
+ else if (meta.decoder) {
743
+ // Stream copy - use decoder's original stream
744
+ const originalStream = meta.decoder.getStream();
745
+ streamIndices[name] = output.addStream(originalStream);
746
+ }
747
+ else if (meta.bitStreamFilter) {
748
+ // BSF - use BSF's original stream
749
+ const originalStream = meta.bitStreamFilter.getStream();
750
+ streamIndices[name] = output.addStream(originalStream);
751
+ }
752
+ else if (meta.demuxer) {
753
+ // Passthrough from Demuxer
754
+ const stream = name.includes('video') ? meta.demuxer.video() : meta.demuxer.audio();
755
+ if (!stream) {
756
+ throw new Error(`No matching stream found in Demuxer for ${name}`);
757
+ }
758
+ streamIndices[name] = output.addStream(stream);
759
+ }
760
+ else {
761
+ throw new Error(`Cannot determine stream configuration for ${name}. This is likely a bug in the pipeline.`);
762
+ }
763
+ }
764
+ // Write all streams in parallel - Muxer's SyncQueue handles interleaving
765
+ const promises = [];
766
+ for (const [name, stream] of Object.entries(processedStreams)) {
767
+ const streamIndex = streamIndices[name];
768
+ if (streamIndex !== undefined) {
769
+ promises.push(consumeStreamInParallel(stream, output, streamIndex, shouldStop));
770
+ }
771
+ }
772
+ await Promise.all(promises);
773
+ await output.close();
774
+ }
775
+ else {
776
+ // Multiple outputs - write each stream to its output
777
+ const outputs = output;
778
+ const promises = [];
779
+ for (const [streamName, stream] of Object.entries(processedStreams)) {
780
+ const streamOutput = outputs[streamName];
781
+ const metadata = streamMetadata[streamName];
782
+ if (streamOutput && metadata) {
783
+ promises.push(consumeNamedStream(stream, streamOutput, metadata, shouldStop));
784
+ }
785
+ }
786
+ await Promise.all(promises);
787
+ }
788
+ }
789
+ /**
790
+ * Build a flexible named stream pipeline.
791
+ *
792
+ * @param source - Source packets
793
+ *
794
+ * @param stages - Processing stages
795
+ *
796
+ * @param metadata - Stream metadata
797
+ *
798
+ * @yields {Packet | Frame} Processed packets or frames
799
+ *
800
+ * @internal
801
+ */
802
+ async function* buildFlexibleNamedStreamPipeline(source, stages, metadata) {
803
+ let stream = source;
804
+ for (const stage of stages) {
805
+ if (isDecoder(stage)) {
806
+ metadata.decoder = stage;
807
+ stream = stage.frames(stream);
808
+ }
809
+ else if (isEncoder(stage)) {
810
+ metadata.encoder = stage;
811
+ stream = stage.packets(stream);
812
+ }
813
+ else if (isFilterAPI(stage)) {
814
+ stream = stage.frames(stream);
815
+ }
816
+ else if (isBitStreamFilterAPI(stage)) {
817
+ metadata.bitStreamFilter = stage;
818
+ stream = stage.packets(stream);
819
+ }
820
+ else if (Array.isArray(stage)) {
821
+ // Chain multiple filters or BSFs
822
+ for (const filter of stage) {
823
+ if (isFilterAPI(filter)) {
824
+ stream = filter.frames(stream);
825
+ }
826
+ else if (isBitStreamFilterAPI(filter)) {
827
+ stream = filter.packets(stream);
828
+ }
829
+ }
830
+ }
831
+ }
832
+ // Yield whatever the pipeline produces (frames or packets)
833
+ yield* stream;
834
+ }
835
+ /**
836
+ * Build a named stream pipeline.
837
+ *
838
+ * @param source - Source packets
839
+ *
840
+ * @param stages - Processing stages
841
+ *
842
+ * @param metadata - Stream metadata
843
+ *
844
+ * @yields {Packet} Processed packets
845
+ *
846
+ * @internal
847
+ */
848
+ async function* buildNamedStreamPipeline(source, stages, metadata) {
849
+ let stream = source;
850
+ for (const stage of stages) {
851
+ if (isDecoder(stage)) {
852
+ metadata.decoder = stage;
853
+ stream = stage.frames(stream);
854
+ }
855
+ else if (isEncoder(stage)) {
856
+ metadata.encoder = stage;
857
+ stream = stage.packets(stream);
858
+ }
859
+ else if (isFilterAPI(stage)) {
860
+ stream = stage.frames(stream);
861
+ }
862
+ else if (isBitStreamFilterAPI(stage)) {
863
+ metadata.bitStreamFilter = stage;
864
+ stream = stage.packets(stream);
865
+ }
866
+ else if (Array.isArray(stage)) {
867
+ // Chain multiple filters or BSFs
868
+ for (const filter of stage) {
869
+ if (isFilterAPI(filter)) {
870
+ stream = filter.frames(stream);
871
+ }
872
+ else if (isBitStreamFilterAPI(filter)) {
873
+ stream = filter.packets(stream);
874
+ }
875
+ }
876
+ }
877
+ }
878
+ // Ensure we're yielding packets
879
+ for await (const item of stream) {
880
+ if (isPacket(item) || item === null) {
881
+ yield item;
882
+ }
883
+ else {
884
+ throw new Error('Named pipeline must end with packets (use encoder after filters)');
885
+ }
886
+ }
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
+ // Get iterator to properly clean up on stop
904
+ const iterator = stream[Symbol.asyncIterator]();
905
+ try {
906
+ // Write all packets (including EOF null)
907
+ while (true) {
908
+ // Check if we should stop before getting next item
909
+ if (shouldStop()) {
910
+ break;
911
+ }
912
+ const { value: packet, done } = await iterator.next();
913
+ if (done)
914
+ break;
915
+ try {
916
+ await output.writePacket(packet, streamIndex);
917
+ }
918
+ finally {
919
+ // Free the packet after use (but not null)
920
+ if (packet && typeof packet.free === 'function') {
921
+ packet.free();
922
+ }
923
+ }
924
+ }
925
+ }
926
+ finally {
927
+ // Always clean up the generator to prevent memory leaks
928
+ if (iterator.return) {
929
+ await iterator.return(undefined);
930
+ }
931
+ }
932
+ // Note: Don't close output here - it will be closed by the caller after all streams finish
933
+ }
934
+ /**
935
+ * Consume a named stream and write to output.
936
+ *
937
+ * @param stream - Stream of packets
938
+ *
939
+ * @param output - Media output destination
940
+ *
941
+ * @param metadata - Stream metadata
942
+ *
943
+ * @param shouldStop - Function to check if pipeline should stop
944
+ *
945
+ * @internal
946
+ */
947
+ async function consumeNamedStream(stream, output, metadata, shouldStop) {
948
+ // Add stream to output
949
+ let streamIndex = 0;
950
+ if (metadata.encoder) {
951
+ // Encoding path
952
+ if (metadata.decoder) {
953
+ // Have decoder - use its stream for metadata/properties
954
+ const originalStream = metadata.decoder.getStream();
955
+ streamIndex = output.addStream(originalStream, { encoder: metadata.encoder });
956
+ }
957
+ else {
958
+ // Encoder-only mode (e.g., frame generator) - no input stream
959
+ streamIndex = output.addStream(metadata.encoder);
960
+ }
961
+ }
962
+ else if (metadata.decoder) {
963
+ // Stream copy - use decoder's original stream
964
+ const originalStream = metadata.decoder.getStream();
965
+ streamIndex = output.addStream(originalStream);
966
+ }
967
+ else if (metadata.bitStreamFilter) {
968
+ // BSF - use BSF's original stream
969
+ const originalStream = metadata.bitStreamFilter.getStream();
970
+ streamIndex = output.addStream(originalStream);
971
+ }
972
+ else if (metadata.demuxer) {
973
+ // Passthrough from Demuxer - use type hint from metadata
974
+ const inputStream = metadata.type === 'video' ? metadata.demuxer.video() : metadata.demuxer.audio();
975
+ if (!inputStream) {
976
+ throw new Error(`No ${metadata.type} stream found in Demuxer`);
977
+ }
978
+ streamIndex = output.addStream(inputStream);
979
+ }
980
+ else {
981
+ // This should not happen with the new API
982
+ throw new Error('Cannot determine stream configuration. This is likely a bug in the pipeline.');
983
+ }
984
+ // Store for later use
985
+ metadata.streamIndex = streamIndex;
986
+ // Get iterator to properly clean up on stop
987
+ const iterator = stream[Symbol.asyncIterator]();
988
+ try {
989
+ // Write all packets (including EOF null)
990
+ while (true) {
991
+ // Check if we should stop before getting next item
992
+ if (shouldStop()) {
993
+ break;
994
+ }
995
+ const { value: packet, done } = await iterator.next();
996
+ if (done)
997
+ break;
998
+ try {
999
+ await output.writePacket(packet, streamIndex);
1000
+ }
1001
+ finally {
1002
+ // Free the packet after use (but not null)
1003
+ if (packet && typeof packet.free === 'function') {
1004
+ packet.free();
1005
+ }
1006
+ }
1007
+ }
1008
+ }
1009
+ finally {
1010
+ // Always clean up the generator to prevent memory leaks
1011
+ if (iterator.return) {
1012
+ await iterator.return(undefined);
1013
+ }
1014
+ }
1015
+ // Note: Output is closed by the caller after all streams finish
1016
+ }
1017
+ // ============================================================================
1018
+ // Type Guards
1019
+ // ============================================================================
1020
+ /**
1021
+ * Check if object is named inputs.
1022
+ *
1023
+ * @param obj - Object to check
1024
+ *
1025
+ * @returns True if object is NamedInputs
1026
+ *
1027
+ * @internal
1028
+ */
1029
+ function isNamedInputs(obj) {
1030
+ return obj && typeof obj === 'object' && !Array.isArray(obj) && !isAsyncIterable(obj) && !isDemuxer(obj);
1031
+ }
1032
+ /**
1033
+ * Check if object is named stages.
1034
+ *
1035
+ * @param obj - Object to check
1036
+ *
1037
+ * @returns True if object is NamedStages
1038
+ *
1039
+ * @internal
1040
+ */
1041
+ function isNamedStages(obj) {
1042
+ if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
1043
+ return false;
1044
+ }
1045
+ // Check if object has at least one stream name key (video or audio)
1046
+ const keys = Object.keys(obj);
1047
+ return keys.length > 0 && keys.every((key) => key === 'video' || key === 'audio');
1048
+ }
1049
+ /**
1050
+ * Check if object is async iterable.
1051
+ *
1052
+ * @param obj - Object to check
1053
+ *
1054
+ * @returns True if object is AsyncIterable
1055
+ *
1056
+ * @internal
1057
+ */
1058
+ function isAsyncIterable(obj) {
1059
+ return obj && typeof obj[Symbol.asyncIterator] === 'function';
1060
+ }
1061
+ /**
1062
+ * Check if object is Demuxer.
1063
+ *
1064
+ * @param obj - Object to check
1065
+ *
1066
+ * @returns True if object is Demuxer
1067
+ *
1068
+ * @internal
1069
+ */
1070
+ function isDemuxer(obj) {
1071
+ return obj && typeof obj.packets === 'function' && typeof obj.video === 'function' && typeof obj.audio === 'function';
1072
+ }
1073
+ /**
1074
+ * Check if object is Decoder.
1075
+ *
1076
+ * @param obj - Object to check
1077
+ *
1078
+ * @returns True if object is Decoder
1079
+ *
1080
+ * @internal
1081
+ */
1082
+ function isDecoder(obj) {
1083
+ return obj && typeof obj.decode === 'function' && typeof obj.flush === 'function';
1084
+ }
1085
+ /**
1086
+ * Check if object is Encoder.
1087
+ *
1088
+ * @param obj - Object to check
1089
+ *
1090
+ * @returns True if object is Encoder
1091
+ *
1092
+ * @internal
1093
+ */
1094
+ function isEncoder(obj) {
1095
+ return obj && typeof obj.encode === 'function' && typeof obj.flush === 'function';
1096
+ }
1097
+ /**
1098
+ * Check if object is FilterAPI.
1099
+ *
1100
+ * @param obj - Object to check
1101
+ *
1102
+ * @returns True if object is FilterAPI
1103
+ *
1104
+ * @internal
1105
+ */
1106
+ function isFilterAPI(obj) {
1107
+ return obj && typeof obj.process === 'function' && typeof obj.receive === 'function';
1108
+ }
1109
+ /**
1110
+ * Check if object is BitStreamFilterAPI.
1111
+ *
1112
+ * @param obj - Object to check
1113
+ *
1114
+ * @returns True if object is BitStreamFilterAPI
1115
+ *
1116
+ * @internal
1117
+ */
1118
+ function isBitStreamFilterAPI(obj) {
1119
+ return obj && typeof obj.filter === 'function' && typeof obj.flushPackets === 'function' && typeof obj.reset === 'function';
1120
+ }
1121
+ /**
1122
+ * Check if object is Muxer.
1123
+ *
1124
+ * @param obj - Object to check
1125
+ *
1126
+ * @returns True if object is Muxer
1127
+ *
1128
+ * @internal
1129
+ */
1130
+ function isMuxer(obj) {
1131
+ return obj && typeof obj.writePacket === 'function' && typeof obj.addStream === 'function';
1132
+ }
1133
+ /**
1134
+ * Check if object is Packet.
1135
+ *
1136
+ * @param obj - Object to check
1137
+ *
1138
+ * @returns True if object is Packet
1139
+ *
1140
+ * @internal
1141
+ */
1142
+ function isPacket(obj) {
1143
+ return obj && 'streamIndex' in obj && 'pts' in obj && 'dts' in obj;
1144
+ }
1145
+ //# sourceMappingURL=pipeline.js.map