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.
- package/CHANGELOG.md +8 -0
- package/LICENSE.md +22 -0
- package/README.md +377 -0
- package/binding.gyp +78 -0
- package/dist/api/bitstream-filter.d.ts +246 -0
- package/dist/api/bitstream-filter.js +369 -0
- package/dist/api/bitstream-filter.js.map +1 -0
- package/dist/api/decoder.d.ts +257 -0
- package/dist/api/decoder.js +424 -0
- package/dist/api/decoder.js.map +1 -0
- package/dist/api/encoder.d.ts +298 -0
- package/dist/api/encoder.js +574 -0
- package/dist/api/encoder.js.map +1 -0
- package/dist/api/filter.d.ts +457 -0
- package/dist/api/filter.js +876 -0
- package/dist/api/filter.js.map +1 -0
- package/dist/api/hardware.d.ts +318 -0
- package/dist/api/hardware.js +558 -0
- package/dist/api/hardware.js.map +1 -0
- package/dist/api/index.d.ts +12 -0
- package/dist/api/index.js +20 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/io-stream.d.ts +109 -0
- package/dist/api/io-stream.js +124 -0
- package/dist/api/io-stream.js.map +1 -0
- package/dist/api/media-input.d.ts +295 -0
- package/dist/api/media-input.js +456 -0
- package/dist/api/media-input.js.map +1 -0
- package/dist/api/media-output.d.ts +274 -0
- package/dist/api/media-output.js +486 -0
- package/dist/api/media-output.js.map +1 -0
- package/dist/api/pipeline.d.ts +117 -0
- package/dist/api/pipeline.js +836 -0
- package/dist/api/pipeline.js.map +1 -0
- package/dist/api/types.d.ts +440 -0
- package/dist/api/types.js +2 -0
- package/dist/api/types.js.map +1 -0
- package/dist/api/utilities/audio-sample.d.ts +115 -0
- package/dist/api/utilities/audio-sample.js +110 -0
- package/dist/api/utilities/audio-sample.js.map +1 -0
- package/dist/api/utilities/channel-layout.d.ts +83 -0
- package/dist/api/utilities/channel-layout.js +87 -0
- package/dist/api/utilities/channel-layout.js.map +1 -0
- package/dist/api/utilities/image.d.ts +177 -0
- package/dist/api/utilities/image.js +183 -0
- package/dist/api/utilities/image.js.map +1 -0
- package/dist/api/utilities/index.d.ts +8 -0
- package/dist/api/utilities/index.js +17 -0
- package/dist/api/utilities/index.js.map +1 -0
- package/dist/api/utilities/media-type.d.ts +56 -0
- package/dist/api/utilities/media-type.js +60 -0
- package/dist/api/utilities/media-type.js.map +1 -0
- package/dist/api/utilities/pixel-format.d.ts +94 -0
- package/dist/api/utilities/pixel-format.js +102 -0
- package/dist/api/utilities/pixel-format.js.map +1 -0
- package/dist/api/utilities/sample-format.d.ts +132 -0
- package/dist/api/utilities/sample-format.js +144 -0
- package/dist/api/utilities/sample-format.js.map +1 -0
- package/dist/api/utilities/streaming.d.ts +104 -0
- package/dist/api/utilities/streaming.js +137 -0
- package/dist/api/utilities/streaming.js.map +1 -0
- package/dist/api/utilities/timestamp.d.ts +187 -0
- package/dist/api/utilities/timestamp.js +200 -0
- package/dist/api/utilities/timestamp.js.map +1 -0
- package/dist/api/utils.d.ts +61 -0
- package/dist/api/utils.js +330 -0
- package/dist/api/utils.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/audio-fifo.d.ts +339 -0
- package/dist/lib/audio-fifo.js +365 -0
- package/dist/lib/audio-fifo.js.map +1 -0
- package/dist/lib/binding.d.ts +192 -0
- package/dist/lib/binding.js +70 -0
- package/dist/lib/binding.js.map +1 -0
- package/dist/lib/bitstream-filter-context.d.ts +345 -0
- package/dist/lib/bitstream-filter-context.js +407 -0
- package/dist/lib/bitstream-filter-context.js.map +1 -0
- package/dist/lib/bitstream-filter.d.ts +124 -0
- package/dist/lib/bitstream-filter.js +138 -0
- package/dist/lib/bitstream-filter.js.map +1 -0
- package/dist/lib/channel-layouts.d.ts +51 -0
- package/dist/lib/channel-layouts.js +55 -0
- package/dist/lib/channel-layouts.js.map +1 -0
- package/dist/lib/codec-context.d.ts +763 -0
- package/dist/lib/codec-context.js +974 -0
- package/dist/lib/codec-context.js.map +1 -0
- package/dist/lib/codec-parameters.d.ts +362 -0
- package/dist/lib/codec-parameters.js +460 -0
- package/dist/lib/codec-parameters.js.map +1 -0
- package/dist/lib/codec-parser.d.ts +185 -0
- package/dist/lib/codec-parser.js +193 -0
- package/dist/lib/codec-parser.js.map +1 -0
- package/dist/lib/codec.d.ts +432 -0
- package/dist/lib/codec.js +492 -0
- package/dist/lib/codec.js.map +1 -0
- package/dist/lib/constants.d.ts +2037 -0
- package/dist/lib/constants.js +1659 -0
- package/dist/lib/constants.js.map +1 -0
- package/dist/lib/dictionary.d.ts +371 -0
- package/dist/lib/dictionary.js +406 -0
- package/dist/lib/dictionary.js.map +1 -0
- package/dist/lib/error.d.ts +216 -0
- package/dist/lib/error.js +254 -0
- package/dist/lib/error.js.map +1 -0
- package/dist/lib/filter-context.d.ts +445 -0
- package/dist/lib/filter-context.js +505 -0
- package/dist/lib/filter-context.js.map +1 -0
- package/dist/lib/filter-graph.d.ts +556 -0
- package/dist/lib/filter-graph.js +608 -0
- package/dist/lib/filter-graph.js.map +1 -0
- package/dist/lib/filter-inout.d.ts +205 -0
- package/dist/lib/filter-inout.js +264 -0
- package/dist/lib/filter-inout.js.map +1 -0
- package/dist/lib/filter.d.ts +231 -0
- package/dist/lib/filter.js +260 -0
- package/dist/lib/filter.js.map +1 -0
- package/dist/lib/format-context.d.ts +798 -0
- package/dist/lib/format-context.js +845 -0
- package/dist/lib/format-context.js.map +1 -0
- package/dist/lib/frame.d.ts +784 -0
- package/dist/lib/frame.js +933 -0
- package/dist/lib/frame.js.map +1 -0
- package/dist/lib/hardware-device-context.d.ts +407 -0
- package/dist/lib/hardware-device-context.js +429 -0
- package/dist/lib/hardware-device-context.js.map +1 -0
- package/dist/lib/hardware-frames-context.d.ts +374 -0
- package/dist/lib/hardware-frames-context.js +430 -0
- package/dist/lib/hardware-frames-context.js.map +1 -0
- package/dist/lib/index.d.ts +31 -0
- package/dist/lib/index.js +54 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/input-format.d.ts +216 -0
- package/dist/lib/input-format.js +246 -0
- package/dist/lib/input-format.js.map +1 -0
- package/dist/lib/io-context.d.ts +495 -0
- package/dist/lib/io-context.js +550 -0
- package/dist/lib/io-context.js.map +1 -0
- package/dist/lib/log.d.ts +201 -0
- package/dist/lib/log.js +219 -0
- package/dist/lib/log.js.map +1 -0
- package/dist/lib/native-types.d.ts +719 -0
- package/dist/lib/native-types.js +2 -0
- package/dist/lib/native-types.js.map +1 -0
- package/dist/lib/option.d.ts +589 -0
- package/dist/lib/option.js +853 -0
- package/dist/lib/option.js.map +1 -0
- package/dist/lib/output-format.d.ts +179 -0
- package/dist/lib/output-format.js +205 -0
- package/dist/lib/output-format.js.map +1 -0
- package/dist/lib/packet.d.ts +487 -0
- package/dist/lib/packet.js +558 -0
- package/dist/lib/packet.js.map +1 -0
- package/dist/lib/rational.d.ts +210 -0
- package/dist/lib/rational.js +233 -0
- package/dist/lib/rational.js.map +1 -0
- package/dist/lib/software-resample-context.d.ts +572 -0
- package/dist/lib/software-resample-context.js +610 -0
- package/dist/lib/software-resample-context.js.map +1 -0
- package/dist/lib/software-scale-context.d.ts +290 -0
- package/dist/lib/software-scale-context.js +308 -0
- package/dist/lib/software-scale-context.js.map +1 -0
- package/dist/lib/stream.d.ts +322 -0
- package/dist/lib/stream.js +408 -0
- package/dist/lib/stream.js.map +1 -0
- package/dist/lib/types.d.ts +59 -0
- package/dist/lib/types.js +8 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/lib/utilities.d.ts +346 -0
- package/dist/lib/utilities.js +424 -0
- package/dist/lib/utilities.js.map +1 -0
- package/install/check.js +113 -0
- package/install/ffmpeg.js +163 -0
- 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
|