node-av 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -59
- package/dist/api/bitstream-filter.d.ts +5 -2
- package/dist/api/bitstream-filter.js +7 -4
- package/dist/api/bitstream-filter.js.map +1 -1
- package/dist/api/decoder.d.ts +135 -119
- package/dist/api/decoder.js +195 -202
- package/dist/api/decoder.js.map +1 -1
- package/dist/api/encoder.d.ts +141 -78
- package/dist/api/encoder.js +241 -193
- package/dist/api/encoder.js.map +1 -1
- package/dist/api/filter-presets.d.ts +699 -573
- package/dist/api/filter-presets.js +1157 -840
- package/dist/api/filter-presets.js.map +1 -1
- package/dist/api/filter.d.ts +180 -157
- package/dist/api/filter.js +314 -366
- package/dist/api/filter.js.map +1 -1
- package/dist/api/hardware.d.ts +28 -29
- package/dist/api/hardware.js +80 -74
- package/dist/api/hardware.js.map +1 -1
- package/dist/api/index.d.ts +1 -1
- package/dist/api/index.js +1 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/io-stream.d.ts +6 -0
- package/dist/api/io-stream.js +6 -0
- package/dist/api/io-stream.js.map +1 -1
- package/dist/api/media-input.d.ts +2 -1
- package/dist/api/media-input.js +3 -8
- package/dist/api/media-input.js.map +1 -1
- package/dist/api/media-output.d.ts +37 -126
- package/dist/api/media-output.js +138 -206
- package/dist/api/media-output.js.map +1 -1
- package/dist/api/pipeline.d.ts +193 -0
- package/dist/api/pipeline.js +36 -42
- package/dist/api/pipeline.js.map +1 -1
- package/dist/api/types.d.ts +22 -57
- package/dist/api/utilities/audio-sample.d.ts +0 -8
- package/dist/api/utilities/audio-sample.js +0 -8
- package/dist/api/utilities/audio-sample.js.map +1 -1
- package/dist/api/utilities/channel-layout.d.ts +0 -8
- package/dist/api/utilities/channel-layout.js +0 -8
- package/dist/api/utilities/channel-layout.js.map +1 -1
- package/dist/api/utilities/image.d.ts +0 -8
- package/dist/api/utilities/image.js +0 -8
- package/dist/api/utilities/image.js.map +1 -1
- package/dist/api/utilities/index.d.ts +3 -3
- package/dist/api/utilities/index.js +3 -3
- package/dist/api/utilities/index.js.map +1 -1
- package/dist/api/utilities/media-type.d.ts +1 -9
- package/dist/api/utilities/media-type.js +1 -9
- package/dist/api/utilities/media-type.js.map +1 -1
- package/dist/api/utilities/pixel-format.d.ts +1 -9
- package/dist/api/utilities/pixel-format.js +1 -9
- package/dist/api/utilities/pixel-format.js.map +1 -1
- package/dist/api/utilities/sample-format.d.ts +1 -9
- package/dist/api/utilities/sample-format.js +1 -9
- package/dist/api/utilities/sample-format.js.map +1 -1
- package/dist/api/utilities/streaming.d.ts +0 -8
- package/dist/api/utilities/streaming.js +0 -8
- package/dist/api/utilities/streaming.js.map +1 -1
- package/dist/api/utilities/timestamp.d.ts +0 -8
- package/dist/api/utilities/timestamp.js +0 -8
- package/dist/api/utilities/timestamp.js.map +1 -1
- package/dist/api/utils.js +2 -0
- package/dist/api/utils.js.map +1 -1
- package/dist/constants/constants.d.ts +1 -1
- package/dist/constants/constants.js +2 -0
- package/dist/constants/constants.js.map +1 -1
- package/dist/lib/binding.d.ts +1 -0
- package/dist/lib/binding.js +2 -0
- package/dist/lib/binding.js.map +1 -1
- package/dist/lib/codec.d.ts +4 -4
- package/dist/lib/codec.js +4 -4
- package/dist/lib/dictionary.d.ts +2 -2
- package/dist/lib/dictionary.js +2 -2
- package/dist/lib/dictionary.js.map +1 -1
- package/dist/lib/error.d.ts +1 -1
- package/dist/lib/error.js +1 -1
- package/dist/lib/filter-context.d.ts +19 -2
- package/dist/lib/filter-context.js +15 -0
- package/dist/lib/filter-context.js.map +1 -1
- package/dist/lib/format-context.d.ts +18 -18
- package/dist/lib/format-context.js +20 -20
- package/dist/lib/format-context.js.map +1 -1
- package/dist/lib/frame.d.ts +43 -1
- package/dist/lib/frame.js +53 -0
- package/dist/lib/frame.js.map +1 -1
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/index.js +1 -1
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/native-types.d.ts +1 -0
- package/dist/lib/option.d.ts +176 -0
- package/dist/lib/option.js +176 -0
- package/dist/lib/option.js.map +1 -1
- package/dist/lib/utilities.d.ts +64 -1
- package/dist/lib/utilities.js +65 -0
- package/dist/lib/utilities.js.map +1 -1
- package/install/ffmpeg.js +0 -11
- package/package.json +16 -18
- package/release_notes.md +0 -48
package/dist/api/filter.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { AVERROR_EOF, AVFILTER_FLAG_HWDEVICE
|
|
2
|
-
import {
|
|
1
|
+
import { AVERROR_EAGAIN, AVERROR_EOF, AVFILTER_FLAG_HWDEVICE } from '../constants/constants.js';
|
|
2
|
+
import { FFmpegError, Filter, FilterGraph, FilterInOut, Frame } from '../lib/index.js';
|
|
3
|
+
import { avGetSampleFmtName } from '../lib/utilities.js';
|
|
3
4
|
/**
|
|
4
5
|
* High-level filter API for audio and video processing.
|
|
5
6
|
*
|
|
@@ -12,10 +13,12 @@ import { AVERROR_EAGAIN, avGetSampleFmtName, avIsHardwarePixelFormat, FFmpegErro
|
|
|
12
13
|
* ```typescript
|
|
13
14
|
* import { FilterAPI } from 'node-av/api';
|
|
14
15
|
*
|
|
15
|
-
* // Create video filter
|
|
16
|
-
* const filter = await FilterAPI.create('scale=1280:720',
|
|
16
|
+
* // Create video filter - initializes on first frame
|
|
17
|
+
* const filter = await FilterAPI.create('scale=1280:720', {
|
|
18
|
+
* timeBase: video.timeBase,
|
|
19
|
+
* });
|
|
17
20
|
*
|
|
18
|
-
* // Process frame
|
|
21
|
+
* // Process frame - first frame configures filter graph
|
|
19
22
|
* const output = await filter.process(inputFrame);
|
|
20
23
|
* if (output) {
|
|
21
24
|
* console.log(`Filtered frame: ${output.width}x${output.height}`);
|
|
@@ -25,134 +28,174 @@ import { AVERROR_EAGAIN, avGetSampleFmtName, avIsHardwarePixelFormat, FFmpegErro
|
|
|
25
28
|
*
|
|
26
29
|
* @example
|
|
27
30
|
* ```typescript
|
|
28
|
-
* // Hardware-accelerated filtering
|
|
29
|
-
* const
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* { hardware: hw }
|
|
34
|
-
* );
|
|
31
|
+
* // Hardware-accelerated filtering - hw context detected from frame
|
|
32
|
+
* const filter = await FilterAPI.create('hwupload,scale_cuda=1920:1080,hwdownload', {
|
|
33
|
+
* timeBase: video.timeBase,
|
|
34
|
+
* });
|
|
35
|
+
* // Hardware frames context will be automatically detected from first frame
|
|
35
36
|
* ```
|
|
36
37
|
*
|
|
37
38
|
* @see {@link FilterGraph} For low-level filter graph API
|
|
38
|
-
* @see {@link HardwareContext} For hardware acceleration
|
|
39
39
|
* @see {@link Frame} For frame operations
|
|
40
40
|
*/
|
|
41
41
|
export class FilterAPI {
|
|
42
|
-
graph
|
|
42
|
+
graph;
|
|
43
|
+
description;
|
|
44
|
+
options;
|
|
43
45
|
buffersrcCtx = null;
|
|
44
46
|
buffersinkCtx = null;
|
|
45
|
-
config;
|
|
46
|
-
mediaType;
|
|
47
47
|
initialized = false;
|
|
48
|
-
|
|
49
|
-
description;
|
|
50
|
-
options;
|
|
48
|
+
isClosed = false;
|
|
51
49
|
/**
|
|
52
|
-
* @param
|
|
50
|
+
* @param graph - Filter graph instance
|
|
53
51
|
* @param description - Filter description string
|
|
54
52
|
* @param options - Filter options
|
|
55
53
|
* @internal
|
|
56
54
|
*/
|
|
57
|
-
constructor(
|
|
58
|
-
this.
|
|
55
|
+
constructor(graph, description, options) {
|
|
56
|
+
this.graph = graph;
|
|
59
57
|
this.description = description;
|
|
60
58
|
this.options = options;
|
|
61
|
-
this.hardware = options.hardware;
|
|
62
|
-
this.mediaType = config.type === 'video' ? AVMEDIA_TYPE_VIDEO : AVMEDIA_TYPE_AUDIO;
|
|
63
59
|
}
|
|
64
60
|
/**
|
|
65
61
|
* Create a filter with specified description and configuration.
|
|
66
62
|
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
63
|
+
* Creates and allocates filter graph immediately.
|
|
64
|
+
* Filter configuration is completed on first frame with frame properties.
|
|
65
|
+
* Hardware frames context is automatically detected from input frames.
|
|
70
66
|
*
|
|
71
67
|
* Direct mapping to avfilter_graph_parse_ptr() and avfilter_graph_config().
|
|
72
68
|
*
|
|
73
69
|
* @param description - Filter graph description
|
|
74
|
-
* @param
|
|
75
|
-
* @param options - Filter options
|
|
70
|
+
* @param options - Filter options including required timeBase
|
|
76
71
|
* @returns Configured filter instance
|
|
77
72
|
*
|
|
78
73
|
* @throws {Error} If filter creation or configuration fails
|
|
74
|
+
*
|
|
79
75
|
* @throws {FFmpegError} If graph parsing or config fails
|
|
80
76
|
*
|
|
81
77
|
* @example
|
|
82
78
|
* ```typescript
|
|
83
79
|
* // Simple video filter
|
|
84
|
-
* const filter = await FilterAPI.create('scale=640:480',
|
|
80
|
+
* const filter = await FilterAPI.create('scale=640:480', {
|
|
81
|
+
* timeBase: video.timeBase
|
|
82
|
+
* });
|
|
85
83
|
* ```
|
|
86
84
|
*
|
|
87
85
|
* @example
|
|
88
86
|
* ```typescript
|
|
89
87
|
* // Complex filter chain
|
|
90
|
-
* const filter = await FilterAPI.create(
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* );
|
|
88
|
+
* const filter = await FilterAPI.create('crop=640:480:0:0,rotate=PI/4', {
|
|
89
|
+
* timeBase: video.timeBase
|
|
90
|
+
* });
|
|
94
91
|
* ```
|
|
95
92
|
*
|
|
96
93
|
* @example
|
|
97
94
|
* ```typescript
|
|
98
95
|
* // Audio filter
|
|
99
|
-
* const filter = await FilterAPI.create(
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
* );
|
|
96
|
+
* const filter = await FilterAPI.create('volume=0.5,aecho=0.8:0.9:1000:0.3', {
|
|
97
|
+
* timeBase: audio.timeBase
|
|
98
|
+
* });
|
|
103
99
|
* ```
|
|
104
100
|
*
|
|
105
101
|
* @see {@link process} For frame processing
|
|
106
102
|
* @see {@link FilterOptions} For configuration options
|
|
107
103
|
*/
|
|
108
|
-
static async create(description,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
pixelFormat: input.pixelFormat,
|
|
116
|
-
timeBase: input.timeBase,
|
|
117
|
-
frameRate: input.frameRate,
|
|
118
|
-
sampleAspectRatio: input.sampleAspectRatio,
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
config = {
|
|
123
|
-
type: 'audio',
|
|
124
|
-
sampleRate: input.sampleRate,
|
|
125
|
-
sampleFormat: input.sampleFormat,
|
|
126
|
-
channelLayout: input.channelLayout,
|
|
127
|
-
timeBase: input.timeBase,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
const filter = new FilterAPI(config, description, options);
|
|
131
|
-
// Check if any filters in the chain require hardware context
|
|
132
|
-
if (config.type === 'video') {
|
|
133
|
-
filter.checkHardwareRequirements(description, options);
|
|
104
|
+
static async create(description, options) {
|
|
105
|
+
// Create graph
|
|
106
|
+
const graph = new FilterGraph();
|
|
107
|
+
graph.alloc();
|
|
108
|
+
// Configure threading
|
|
109
|
+
if (options.threads !== undefined) {
|
|
110
|
+
graph.nbThreads = options.threads;
|
|
134
111
|
}
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
await filter.initialize(null);
|
|
112
|
+
// Configure scaler options
|
|
113
|
+
if (options.scaleSwsOpts) {
|
|
114
|
+
graph.scaleSwsOpts = options.scaleSwsOpts;
|
|
139
115
|
}
|
|
140
|
-
|
|
141
|
-
|
|
116
|
+
return new FilterAPI(graph, description, options);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Check if filter is open.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```typescript
|
|
123
|
+
* if (filter.isFilterOpen) {
|
|
124
|
+
* const output = await filter.process(frame);
|
|
125
|
+
* }
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
get isFilterOpen() {
|
|
129
|
+
return !this.isClosed;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Check if filter has been initialized.
|
|
133
|
+
*
|
|
134
|
+
* Returns true after first frame has been processed and filter graph configured.
|
|
135
|
+
* Useful for checking if filter has received frame properties.
|
|
136
|
+
*
|
|
137
|
+
* @returns true if filter graph has been built from first frame
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```typescript
|
|
141
|
+
* if (!filter.isFilterInitialized) {
|
|
142
|
+
* console.log('Filter will initialize on first frame');
|
|
143
|
+
* }
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
get isFilterInitialized() {
|
|
147
|
+
return this.initialized;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Check if filter is ready for processing.
|
|
151
|
+
*
|
|
152
|
+
* @returns true if initialized and ready
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```typescript
|
|
156
|
+
* if (filter.isReady()) {
|
|
157
|
+
* const output = await filter.process(frame);
|
|
158
|
+
* }
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
isReady() {
|
|
162
|
+
return this.initialized && this.buffersrcCtx !== null && this.buffersinkCtx !== null && !this.isClosed;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Get filter graph description.
|
|
166
|
+
*
|
|
167
|
+
* Returns human-readable graph structure.
|
|
168
|
+
* Useful for debugging filter chains.
|
|
169
|
+
*
|
|
170
|
+
* Direct mapping to avfilter_graph_dump().
|
|
171
|
+
*
|
|
172
|
+
* @returns Graph description or null if closed
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* ```typescript
|
|
176
|
+
* const description = filter.getGraphDescription();
|
|
177
|
+
* console.log('Filter graph:', description);
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
getGraphDescription() {
|
|
181
|
+
return !this.isClosed && this.initialized ? this.graph.dump() : null;
|
|
142
182
|
}
|
|
143
183
|
/**
|
|
144
184
|
* Process a frame through the filter.
|
|
145
185
|
*
|
|
146
186
|
* Applies filter operations to input frame.
|
|
187
|
+
* On first frame, automatically builds filter graph with frame properties.
|
|
147
188
|
* May buffer frames internally before producing output.
|
|
148
|
-
*
|
|
189
|
+
* Hardware frames context is automatically detected from frame.
|
|
190
|
+
* Returns null if filter is closed and frame is null.
|
|
149
191
|
*
|
|
150
192
|
* Direct mapping to av_buffersrc_add_frame() and av_buffersink_get_frame().
|
|
151
193
|
*
|
|
152
|
-
* @param frame - Input frame to process
|
|
194
|
+
* @param frame - Input frame to process (or null to flush)
|
|
153
195
|
* @returns Filtered frame or null if buffered
|
|
154
196
|
*
|
|
155
|
-
* @throws {Error} If filter
|
|
197
|
+
* @throws {Error} If filter is closed with non-null frame
|
|
198
|
+
*
|
|
156
199
|
* @throws {FFmpegError} If processing fails
|
|
157
200
|
*
|
|
158
201
|
* @example
|
|
@@ -166,26 +209,33 @@ export class FilterAPI {
|
|
|
166
209
|
*
|
|
167
210
|
* @example
|
|
168
211
|
* ```typescript
|
|
169
|
-
* // Process
|
|
212
|
+
* // Process frame - may buffer internally
|
|
170
213
|
* const output = await filter.process(frame);
|
|
171
|
-
* if (output)
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
* let buffered;
|
|
175
|
-
* while ((buffered = await filter.receive()) !== null) {
|
|
176
|
-
* yield buffered;
|
|
214
|
+
* if (output) {
|
|
215
|
+
* // Got output immediately
|
|
216
|
+
* yield output;
|
|
177
217
|
* }
|
|
218
|
+
* // For buffered frames, use the frames() async generator
|
|
178
219
|
* ```
|
|
179
220
|
*
|
|
180
|
-
* @see {@link
|
|
181
|
-
* @see {@link
|
|
221
|
+
* @see {@link frames} For processing frame streams
|
|
222
|
+
* @see {@link flush} For end-of-stream handling
|
|
182
223
|
*/
|
|
183
224
|
async process(frame) {
|
|
184
|
-
|
|
185
|
-
|
|
225
|
+
if (this.isClosed) {
|
|
226
|
+
if (!frame) {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
throw new Error('Filter is closed');
|
|
230
|
+
}
|
|
231
|
+
// Open filter if not already done
|
|
232
|
+
if (!this.initialized) {
|
|
233
|
+
if (!frame) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
186
236
|
await this.initialize(frame);
|
|
187
237
|
}
|
|
188
|
-
if (!this.
|
|
238
|
+
if (!this.buffersrcCtx || !this.buffersinkCtx) {
|
|
189
239
|
throw new Error('Filter not initialized');
|
|
190
240
|
}
|
|
191
241
|
// Send frame to filter
|
|
@@ -219,6 +269,7 @@ export class FilterAPI {
|
|
|
219
269
|
* @returns Array of all output frames
|
|
220
270
|
*
|
|
221
271
|
* @throws {Error} If filter not ready
|
|
272
|
+
*
|
|
222
273
|
* @throws {FFmpegError} If processing fails
|
|
223
274
|
*
|
|
224
275
|
* @example
|
|
@@ -250,48 +301,74 @@ export class FilterAPI {
|
|
|
250
301
|
return outputFrames;
|
|
251
302
|
}
|
|
252
303
|
/**
|
|
253
|
-
*
|
|
304
|
+
* Process frame stream through filter.
|
|
254
305
|
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
306
|
+
* High-level async generator for filtering frame streams.
|
|
307
|
+
* Automatically handles buffering and flushing.
|
|
308
|
+
* Frees input frames after processing.
|
|
257
309
|
*
|
|
258
|
-
*
|
|
310
|
+
* @param frames - Async generator of input frames
|
|
311
|
+
* @yields {Frame} Filtered frames
|
|
312
|
+
* @throws {Error} If filter not ready
|
|
259
313
|
*
|
|
260
|
-
* @
|
|
314
|
+
* @throws {FFmpegError} If processing fails
|
|
261
315
|
*
|
|
262
|
-
* @
|
|
263
|
-
*
|
|
316
|
+
* @example
|
|
317
|
+
* ```typescript
|
|
318
|
+
* // Filter decoded frames
|
|
319
|
+
* for await (const frame of filter.frames(decoder.frames(packets))) {
|
|
320
|
+
* await encoder.encode(frame);
|
|
321
|
+
* frame.free();
|
|
322
|
+
* }
|
|
323
|
+
* ```
|
|
264
324
|
*
|
|
265
325
|
* @example
|
|
266
326
|
* ```typescript
|
|
267
|
-
* //
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
327
|
+
* // Chain filters
|
|
328
|
+
* const filter1 = await FilterAPI.create('scale=640:480', {
|
|
329
|
+
* timeBase: video.timeBase
|
|
330
|
+
* });
|
|
331
|
+
* const filter2 = await FilterAPI.create('rotate=PI/4', {
|
|
332
|
+
* timeBase: video.timeBase
|
|
333
|
+
* });
|
|
334
|
+
*
|
|
335
|
+
* for await (const frame of filter2.frames(filter1.frames(input))) {
|
|
336
|
+
* // Process filtered frames
|
|
271
337
|
* frame.free();
|
|
272
338
|
* }
|
|
273
339
|
* ```
|
|
274
340
|
*
|
|
275
|
-
* @see {@link process} For
|
|
276
|
-
* @see {@link flush} For end-of-stream
|
|
341
|
+
* @see {@link process} For single frame processing
|
|
342
|
+
* @see {@link flush} For end-of-stream handling
|
|
277
343
|
*/
|
|
278
|
-
async
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
344
|
+
async *frames(frames) {
|
|
345
|
+
for await (const frame of frames) {
|
|
346
|
+
try {
|
|
347
|
+
// Process input frame
|
|
348
|
+
const output = await this.process(frame);
|
|
349
|
+
if (output) {
|
|
350
|
+
yield output;
|
|
351
|
+
}
|
|
352
|
+
// Drain any buffered frames
|
|
353
|
+
while (true) {
|
|
354
|
+
const buffered = await this.receive();
|
|
355
|
+
if (!buffered)
|
|
356
|
+
break;
|
|
357
|
+
yield buffered;
|
|
358
|
+
}
|
|
292
359
|
}
|
|
293
|
-
|
|
294
|
-
|
|
360
|
+
finally {
|
|
361
|
+
// Free the input frame after processing
|
|
362
|
+
frame.free();
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
// Flush and get remaining frames
|
|
366
|
+
await this.flush();
|
|
367
|
+
while (true) {
|
|
368
|
+
const remaining = await this.receive();
|
|
369
|
+
if (!remaining)
|
|
370
|
+
break;
|
|
371
|
+
yield remaining;
|
|
295
372
|
}
|
|
296
373
|
}
|
|
297
374
|
/**
|
|
@@ -299,10 +376,10 @@ export class FilterAPI {
|
|
|
299
376
|
*
|
|
300
377
|
* Sends null frame to flush buffered data.
|
|
301
378
|
* Must call receive() to get flushed frames.
|
|
379
|
+
* Does nothing if filter is closed or was never initialized.
|
|
302
380
|
*
|
|
303
381
|
* Direct mapping to av_buffersrc_add_frame(NULL).
|
|
304
382
|
*
|
|
305
|
-
* @throws {Error} If filter not ready
|
|
306
383
|
* @throws {FFmpegError} If flush fails
|
|
307
384
|
*
|
|
308
385
|
* @example
|
|
@@ -316,12 +393,13 @@ export class FilterAPI {
|
|
|
316
393
|
* ```
|
|
317
394
|
*
|
|
318
395
|
* @see {@link flushFrames} For async iteration
|
|
319
|
-
* @see {@link
|
|
396
|
+
* @see {@link frames} For complete pipeline
|
|
320
397
|
*/
|
|
321
398
|
async flush() {
|
|
322
|
-
if (!this.initialized || !this.buffersrcCtx) {
|
|
323
|
-
|
|
399
|
+
if (this.isClosed || !this.initialized || !this.buffersrcCtx) {
|
|
400
|
+
return;
|
|
324
401
|
}
|
|
402
|
+
// Send flush frame (null)
|
|
325
403
|
const ret = await this.buffersrcCtx.buffersrcAddFrame(null);
|
|
326
404
|
if (ret < 0 && ret !== AVERROR_EOF) {
|
|
327
405
|
FFmpegError.throwIfError(ret, 'Failed to flush filter');
|
|
@@ -332,9 +410,10 @@ export class FilterAPI {
|
|
|
332
410
|
*
|
|
333
411
|
* Convenient async generator for flushing.
|
|
334
412
|
* Combines flush and receive operations.
|
|
413
|
+
* Returns immediately if filter is closed or was never initialized.
|
|
414
|
+
*
|
|
415
|
+
* @yields {Frame} Remaining frames from filter
|
|
335
416
|
*
|
|
336
|
-
* @yields Remaining frames from filter
|
|
337
|
-
* @throws {Error} If filter not ready
|
|
338
417
|
* @throws {FFmpegError} If flush fails
|
|
339
418
|
*
|
|
340
419
|
* @example
|
|
@@ -349,9 +428,6 @@ export class FilterAPI {
|
|
|
349
428
|
* @see {@link frames} For complete pipeline
|
|
350
429
|
*/
|
|
351
430
|
async *flushFrames() {
|
|
352
|
-
if (!this.initialized || !this.buffersrcCtx) {
|
|
353
|
-
throw new Error('Filter not initialized');
|
|
354
|
-
}
|
|
355
431
|
// Send flush signal
|
|
356
432
|
await this.flush();
|
|
357
433
|
// Yield all remaining frames
|
|
@@ -361,69 +437,44 @@ export class FilterAPI {
|
|
|
361
437
|
}
|
|
362
438
|
}
|
|
363
439
|
/**
|
|
364
|
-
*
|
|
440
|
+
* Receive buffered frame from filter.
|
|
365
441
|
*
|
|
366
|
-
*
|
|
367
|
-
*
|
|
368
|
-
*
|
|
442
|
+
* Drains frames buffered by the filter.
|
|
443
|
+
* Call repeatedly until null to get all buffered frames.
|
|
444
|
+
* Returns null if filter is closed, not initialized, or no frames available.
|
|
369
445
|
*
|
|
370
|
-
*
|
|
371
|
-
* @yields Filtered frames
|
|
372
|
-
* @throws {Error} If filter not ready
|
|
373
|
-
* @throws {FFmpegError} If processing fails
|
|
446
|
+
* Direct mapping to av_buffersink_get_frame().
|
|
374
447
|
*
|
|
375
|
-
* @
|
|
376
|
-
*
|
|
377
|
-
*
|
|
378
|
-
* for await (const frame of filter.frames(decoder.frames(packets))) {
|
|
379
|
-
* await encoder.encode(frame);
|
|
380
|
-
* frame.free();
|
|
381
|
-
* }
|
|
382
|
-
* ```
|
|
448
|
+
* @returns Buffered frame or null if none available
|
|
449
|
+
*
|
|
450
|
+
* @throws {FFmpegError} If receiving fails
|
|
383
451
|
*
|
|
384
452
|
* @example
|
|
385
453
|
* ```typescript
|
|
386
|
-
*
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
*
|
|
390
|
-
* for await (const frame of filter2.frames(filter1.frames(input))) {
|
|
391
|
-
* // Process filtered frames
|
|
454
|
+
* let frame;
|
|
455
|
+
* while ((frame = await filter.receive()) !== null) {
|
|
456
|
+
* console.log(`Received frame: pts=${frame.pts}`);
|
|
392
457
|
* frame.free();
|
|
393
458
|
* }
|
|
394
459
|
* ```
|
|
395
|
-
*
|
|
396
|
-
* @see {@link process} For single frame processing
|
|
397
|
-
* @see {@link flush} For end-of-stream handling
|
|
398
460
|
*/
|
|
399
|
-
async
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
// Process input frame
|
|
403
|
-
const output = await this.process(frame);
|
|
404
|
-
if (output) {
|
|
405
|
-
yield output;
|
|
406
|
-
}
|
|
407
|
-
// Drain any buffered frames
|
|
408
|
-
while (true) {
|
|
409
|
-
const buffered = await this.receive();
|
|
410
|
-
if (!buffered)
|
|
411
|
-
break;
|
|
412
|
-
yield buffered;
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
finally {
|
|
416
|
-
// Free the input frame after processing
|
|
417
|
-
frame.free();
|
|
418
|
-
}
|
|
461
|
+
async receive() {
|
|
462
|
+
if (this.isClosed || !this.initialized || !this.buffersinkCtx) {
|
|
463
|
+
return null;
|
|
419
464
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
465
|
+
const frame = new Frame();
|
|
466
|
+
frame.alloc();
|
|
467
|
+
const ret = await this.buffersinkCtx.buffersinkGetFrame(frame);
|
|
468
|
+
if (ret >= 0) {
|
|
469
|
+
return frame;
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
frame.free();
|
|
473
|
+
if (ret === AVERROR_EAGAIN || ret === AVERROR_EOF) {
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
FFmpegError.throwIfError(ret, 'Failed to receive frame from filter');
|
|
477
|
+
return null;
|
|
427
478
|
}
|
|
428
479
|
}
|
|
429
480
|
/**
|
|
@@ -441,6 +492,7 @@ export class FilterAPI {
|
|
|
441
492
|
* @returns Response string from filter
|
|
442
493
|
*
|
|
443
494
|
* @throws {Error} If filter not ready
|
|
495
|
+
*
|
|
444
496
|
* @throws {FFmpegError} If command fails
|
|
445
497
|
*
|
|
446
498
|
* @example
|
|
@@ -453,7 +505,10 @@ export class FilterAPI {
|
|
|
453
505
|
* @see {@link queueCommand} For delayed commands
|
|
454
506
|
*/
|
|
455
507
|
sendCommand(target, cmd, arg, flags) {
|
|
456
|
-
if (
|
|
508
|
+
if (this.isClosed) {
|
|
509
|
+
throw new Error('Filter is closed');
|
|
510
|
+
}
|
|
511
|
+
if (!this.initialized) {
|
|
457
512
|
throw new Error('Filter not initialized');
|
|
458
513
|
}
|
|
459
514
|
const result = this.graph.sendCommand(target, cmd, arg, flags);
|
|
@@ -476,6 +531,7 @@ export class FilterAPI {
|
|
|
476
531
|
* @param ts - Timestamp for execution
|
|
477
532
|
* @param flags - Command flags
|
|
478
533
|
* @throws {Error} If filter not ready
|
|
534
|
+
*
|
|
479
535
|
* @throws {FFmpegError} If queue fails
|
|
480
536
|
*
|
|
481
537
|
* @example
|
|
@@ -487,64 +543,15 @@ export class FilterAPI {
|
|
|
487
543
|
* @see {@link sendCommand} For immediate commands
|
|
488
544
|
*/
|
|
489
545
|
queueCommand(target, cmd, arg, ts, flags) {
|
|
490
|
-
if (
|
|
546
|
+
if (this.isClosed) {
|
|
547
|
+
throw new Error('Filter is closed');
|
|
548
|
+
}
|
|
549
|
+
if (!this.initialized) {
|
|
491
550
|
throw new Error('Filter not initialized');
|
|
492
551
|
}
|
|
493
552
|
const ret = this.graph.queueCommand(target, cmd, arg, ts, flags);
|
|
494
553
|
FFmpegError.throwIfError(ret, 'Failed to queue filter command');
|
|
495
554
|
}
|
|
496
|
-
/**
|
|
497
|
-
* Get filter graph description.
|
|
498
|
-
*
|
|
499
|
-
* Returns human-readable graph structure.
|
|
500
|
-
* Useful for debugging filter chains.
|
|
501
|
-
*
|
|
502
|
-
* Direct mapping to avfilter_graph_dump().
|
|
503
|
-
*
|
|
504
|
-
* @returns Graph description or null if not initialized
|
|
505
|
-
*
|
|
506
|
-
* @example
|
|
507
|
-
* ```typescript
|
|
508
|
-
* const description = filter.getGraphDescription();
|
|
509
|
-
* console.log('Filter graph:', description);
|
|
510
|
-
* ```
|
|
511
|
-
*/
|
|
512
|
-
getGraphDescription() {
|
|
513
|
-
if (!this.initialized || !this.graph) {
|
|
514
|
-
return null;
|
|
515
|
-
}
|
|
516
|
-
return this.graph.dump();
|
|
517
|
-
}
|
|
518
|
-
/**
|
|
519
|
-
* Check if filter is ready for processing.
|
|
520
|
-
*
|
|
521
|
-
* @returns true if initialized and ready
|
|
522
|
-
*
|
|
523
|
-
* @example
|
|
524
|
-
* ```typescript
|
|
525
|
-
* if (filter.isReady()) {
|
|
526
|
-
* const output = await filter.process(frame);
|
|
527
|
-
* }
|
|
528
|
-
* ```
|
|
529
|
-
*/
|
|
530
|
-
isReady() {
|
|
531
|
-
return this.initialized && this.buffersrcCtx !== null && this.buffersinkCtx !== null;
|
|
532
|
-
}
|
|
533
|
-
/**
|
|
534
|
-
* Get media type of filter.
|
|
535
|
-
*
|
|
536
|
-
* @returns AVMEDIA_TYPE_VIDEO or AVMEDIA_TYPE_AUDIO
|
|
537
|
-
*
|
|
538
|
-
* @example
|
|
539
|
-
* ```typescript
|
|
540
|
-
* if (filter.getMediaType() === AVMEDIA_TYPE_VIDEO) {
|
|
541
|
-
* console.log('Video filter');
|
|
542
|
-
* }
|
|
543
|
-
* ```
|
|
544
|
-
*/
|
|
545
|
-
getMediaType() {
|
|
546
|
-
return this.mediaType;
|
|
547
|
-
}
|
|
548
555
|
/**
|
|
549
556
|
* Free filter resources.
|
|
550
557
|
*
|
|
@@ -553,62 +560,50 @@ export class FilterAPI {
|
|
|
553
560
|
*
|
|
554
561
|
* @example
|
|
555
562
|
* ```typescript
|
|
556
|
-
* filter.
|
|
563
|
+
* filter.close();
|
|
557
564
|
* ```
|
|
558
565
|
*
|
|
559
566
|
* @see {@link Symbol.dispose} For automatic cleanup
|
|
560
567
|
*/
|
|
561
|
-
|
|
562
|
-
if (this.
|
|
563
|
-
|
|
564
|
-
this.graph = null;
|
|
568
|
+
close() {
|
|
569
|
+
if (this.isClosed) {
|
|
570
|
+
return;
|
|
565
571
|
}
|
|
572
|
+
this.isClosed = true;
|
|
573
|
+
this.graph.free();
|
|
566
574
|
this.buffersrcCtx = null;
|
|
567
575
|
this.buffersinkCtx = null;
|
|
568
576
|
this.initialized = false;
|
|
569
577
|
}
|
|
570
578
|
/**
|
|
571
|
-
* Initialize filter graph.
|
|
579
|
+
* Initialize filter graph from first frame.
|
|
572
580
|
*
|
|
573
581
|
* Creates and configures filter graph components.
|
|
574
|
-
*
|
|
582
|
+
* Sets buffer source parameters from frame properties.
|
|
583
|
+
* Automatically configures hardware frames context if present.
|
|
584
|
+
*
|
|
585
|
+
* @param frame - First frame to process, provides format and hw context
|
|
575
586
|
*
|
|
576
|
-
* @param firstFrame - First frame for hardware detection (video only)
|
|
577
587
|
* @throws {Error} If initialization fails
|
|
588
|
+
*
|
|
578
589
|
* @throws {FFmpegError} If configuration fails
|
|
590
|
+
*
|
|
591
|
+
* @internal
|
|
579
592
|
*/
|
|
580
|
-
async initialize(
|
|
581
|
-
// Create
|
|
582
|
-
this.
|
|
583
|
-
this.graph.alloc();
|
|
584
|
-
// Configure threading
|
|
585
|
-
if (this.options.threads !== undefined) {
|
|
586
|
-
this.graph.nbThreads = this.options.threads;
|
|
587
|
-
}
|
|
588
|
-
// Configure scaler options
|
|
589
|
-
if (this.options.scaleSwsOpts) {
|
|
590
|
-
this.graph.scaleSwsOpts = this.options.scaleSwsOpts;
|
|
591
|
-
}
|
|
592
|
-
// Create buffer source with hw_frames_ctx if needed
|
|
593
|
-
if (firstFrame?.hwFramesCtx && this.config.type === 'video') {
|
|
594
|
-
this.createBufferSourceWithHwFrames(firstFrame);
|
|
595
|
-
}
|
|
596
|
-
else {
|
|
597
|
-
this.createBufferSource();
|
|
598
|
-
}
|
|
593
|
+
async initialize(frame) {
|
|
594
|
+
// Create buffer source
|
|
595
|
+
this.createBufferSource(frame);
|
|
599
596
|
// Create buffer sink
|
|
600
|
-
this.createBufferSink();
|
|
597
|
+
this.createBufferSink(frame);
|
|
601
598
|
// Parse filter description
|
|
602
599
|
this.parseFilterDescription(this.description);
|
|
603
600
|
// Set hw_device_ctx on hardware filters
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
filterCtx.hwDeviceCtx = this.hardware.deviceContext;
|
|
611
|
-
}
|
|
601
|
+
const filters = this.graph.filters;
|
|
602
|
+
if (filters) {
|
|
603
|
+
for (const filterCtx of filters) {
|
|
604
|
+
const filter = filterCtx.filter;
|
|
605
|
+
if (filter && (filter.flags & AVFILTER_FLAG_HWDEVICE) !== 0) {
|
|
606
|
+
filterCtx.hwDeviceCtx = frame.hwFramesCtx?.deviceRef ?? this.options.hardware?.deviceContext ?? null;
|
|
612
607
|
}
|
|
613
608
|
}
|
|
614
609
|
}
|
|
@@ -618,83 +613,71 @@ export class FilterAPI {
|
|
|
618
613
|
this.initialized = true;
|
|
619
614
|
}
|
|
620
615
|
/**
|
|
621
|
-
* Create buffer source with
|
|
616
|
+
* Create buffer source with frame parameters.
|
|
617
|
+
*
|
|
618
|
+
* Configures buffer source with frame properties including hardware context.
|
|
619
|
+
* Automatically detects video/audio and sets appropriate parameters.
|
|
620
|
+
*
|
|
621
|
+
* @param frame - Frame providing format, dimensions, and hw_frames_ctx
|
|
622
622
|
*
|
|
623
|
-
* @param frame - Frame with hw_frames_ctx
|
|
624
623
|
* @throws {Error} If creation fails
|
|
624
|
+
*
|
|
625
625
|
* @throws {FFmpegError} If configuration fails
|
|
626
|
-
*/
|
|
627
|
-
createBufferSourceWithHwFrames(frame) {
|
|
628
|
-
const filterName = 'buffer';
|
|
629
|
-
const bufferFilter = Filter.getByName(filterName);
|
|
630
|
-
if (!bufferFilter) {
|
|
631
|
-
throw new Error(`${filterName} filter not found`);
|
|
632
|
-
}
|
|
633
|
-
// Allocate filter without args
|
|
634
|
-
this.buffersrcCtx = this.graph.allocFilter(bufferFilter, 'in');
|
|
635
|
-
if (!this.buffersrcCtx) {
|
|
636
|
-
throw new Error('Failed to allocate buffer source');
|
|
637
|
-
}
|
|
638
|
-
// Set parameters including hw_frames_ctx
|
|
639
|
-
const cfg = this.config;
|
|
640
|
-
const ret = this.buffersrcCtx.buffersrcParametersSet({
|
|
641
|
-
width: cfg.width,
|
|
642
|
-
height: cfg.height,
|
|
643
|
-
format: cfg.pixelFormat,
|
|
644
|
-
timeBase: cfg.timeBase,
|
|
645
|
-
frameRate: cfg.frameRate,
|
|
646
|
-
sampleAspectRatio: cfg.sampleAspectRatio,
|
|
647
|
-
hwFramesCtx: frame.hwFramesCtx ?? undefined,
|
|
648
|
-
});
|
|
649
|
-
FFmpegError.throwIfError(ret, 'Failed to set buffer source parameters');
|
|
650
|
-
// Initialize filter
|
|
651
|
-
const initRet = this.buffersrcCtx.init(null);
|
|
652
|
-
FFmpegError.throwIfError(initRet, 'Failed to initialize buffer source');
|
|
653
|
-
}
|
|
654
|
-
/**
|
|
655
|
-
* Create standard buffer source.
|
|
656
626
|
*
|
|
657
|
-
* @
|
|
627
|
+
* @internal
|
|
658
628
|
*/
|
|
659
|
-
createBufferSource() {
|
|
660
|
-
const filterName =
|
|
629
|
+
createBufferSource(frame) {
|
|
630
|
+
const filterName = frame.isVideo() ? 'buffer' : 'abuffer';
|
|
661
631
|
const bufferFilter = Filter.getByName(filterName);
|
|
662
632
|
if (!bufferFilter) {
|
|
663
633
|
throw new Error(`${filterName} filter not found`);
|
|
664
634
|
}
|
|
665
|
-
//
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
args += `:frame_rate=${cfg.frameRate.num}/${cfg.frameRate.den}`;
|
|
672
|
-
}
|
|
673
|
-
if (cfg.sampleAspectRatio) {
|
|
674
|
-
args += `:pixel_aspect=${cfg.sampleAspectRatio.num}/${cfg.sampleAspectRatio.den}`;
|
|
635
|
+
// For audio, create with args. For video, use allocFilter + buffersrcParametersSet
|
|
636
|
+
if (frame.isVideo()) {
|
|
637
|
+
// Allocate filter without args
|
|
638
|
+
this.buffersrcCtx = this.graph.allocFilter(bufferFilter, 'in');
|
|
639
|
+
if (!this.buffersrcCtx) {
|
|
640
|
+
throw new Error('Failed to allocate buffer source');
|
|
675
641
|
}
|
|
642
|
+
const ret = this.buffersrcCtx.buffersrcParametersSet({
|
|
643
|
+
width: frame.width,
|
|
644
|
+
height: frame.height,
|
|
645
|
+
format: frame.format,
|
|
646
|
+
timeBase: this.options.timeBase,
|
|
647
|
+
frameRate: this.options.frameRate ?? frame.timeBase,
|
|
648
|
+
sampleAspectRatio: frame.sampleAspectRatio,
|
|
649
|
+
colorRange: frame.colorRange,
|
|
650
|
+
colorSpace: frame.colorSpace,
|
|
651
|
+
hwFramesCtx: frame.hwFramesCtx,
|
|
652
|
+
});
|
|
653
|
+
FFmpegError.throwIfError(ret, 'Failed to set buffer source parameters');
|
|
654
|
+
// Initialize filter
|
|
655
|
+
const initRet = this.buffersrcCtx.init(null);
|
|
656
|
+
FFmpegError.throwIfError(initRet, 'Failed to initialize buffer source');
|
|
676
657
|
}
|
|
677
658
|
else {
|
|
678
|
-
|
|
679
|
-
const
|
|
680
|
-
const channelLayout =
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
659
|
+
// For audio, create with args string
|
|
660
|
+
const formatName = avGetSampleFmtName(frame.format);
|
|
661
|
+
const channelLayout = frame.channelLayout.mask === 0n ? 'stereo' : frame.channelLayout.mask.toString();
|
|
662
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
663
|
+
const args = `time_base=${this.options.timeBase.num}/${this.options.timeBase.den}:sample_rate=${frame.sampleRate}:sample_fmt=${formatName}:channel_layout=${channelLayout}`;
|
|
664
|
+
this.buffersrcCtx = this.graph.createFilter(bufferFilter, 'in', args);
|
|
665
|
+
if (!this.buffersrcCtx) {
|
|
666
|
+
throw new Error('Failed to create audio buffer source');
|
|
667
|
+
}
|
|
686
668
|
}
|
|
687
669
|
}
|
|
688
670
|
/**
|
|
689
671
|
* Create buffer sink.
|
|
690
672
|
*
|
|
673
|
+
* @param frame - Frame
|
|
674
|
+
*
|
|
691
675
|
* @throws {Error} If creation fails
|
|
676
|
+
*
|
|
677
|
+
* @internal
|
|
692
678
|
*/
|
|
693
|
-
createBufferSink() {
|
|
694
|
-
|
|
695
|
-
throw new Error('Filter graph not initialized');
|
|
696
|
-
}
|
|
697
|
-
const filterName = this.config.type === 'video' ? 'buffersink' : 'abuffersink';
|
|
679
|
+
createBufferSink(frame) {
|
|
680
|
+
const filterName = frame.isVideo() ? 'buffersink' : 'abuffersink';
|
|
698
681
|
const sinkFilter = Filter.getByName(filterName);
|
|
699
682
|
if (!sinkFilter) {
|
|
700
683
|
throw new Error(`${filterName} filter not found`);
|
|
@@ -708,13 +691,14 @@ export class FilterAPI {
|
|
|
708
691
|
* Parse filter description and build graph.
|
|
709
692
|
*
|
|
710
693
|
* @param description - Filter description string
|
|
694
|
+
*
|
|
711
695
|
* @throws {Error} If parsing fails
|
|
696
|
+
*
|
|
712
697
|
* @throws {FFmpegError} If graph construction fails
|
|
698
|
+
*
|
|
699
|
+
* @internal
|
|
713
700
|
*/
|
|
714
701
|
parseFilterDescription(description) {
|
|
715
|
-
if (!this.graph) {
|
|
716
|
-
throw new Error('Filter graph not initialized');
|
|
717
|
-
}
|
|
718
702
|
if (!this.buffersrcCtx || !this.buffersinkCtx) {
|
|
719
703
|
throw new Error('Buffer filters not initialized');
|
|
720
704
|
}
|
|
@@ -743,60 +727,24 @@ export class FilterAPI {
|
|
|
743
727
|
inputs.free();
|
|
744
728
|
outputs.free();
|
|
745
729
|
}
|
|
746
|
-
/**
|
|
747
|
-
* Check hardware requirements for filters.
|
|
748
|
-
*
|
|
749
|
-
* @param description - Filter description
|
|
750
|
-
* @param options - Filter options
|
|
751
|
-
* @throws {Error} If hardware requirements not met
|
|
752
|
-
*/
|
|
753
|
-
checkHardwareRequirements(description, options) {
|
|
754
|
-
if (this.config.type !== 'video') {
|
|
755
|
-
return;
|
|
756
|
-
}
|
|
757
|
-
// Parse filter names from description
|
|
758
|
-
const filterNames = description
|
|
759
|
-
.split(',')
|
|
760
|
-
.map((f) => {
|
|
761
|
-
// Extract filter name (before = or : or whitespace)
|
|
762
|
-
const match = /^([a-zA-Z0-9_]+)/.exec(f.trim());
|
|
763
|
-
return match ? match[1] : null;
|
|
764
|
-
})
|
|
765
|
-
.filter(Boolean);
|
|
766
|
-
for (const filterName of filterNames) {
|
|
767
|
-
const lowLevelFilter = Filter.getByName(filterName);
|
|
768
|
-
if (!lowLevelFilter) {
|
|
769
|
-
// Filter will be validated later during graph parsing
|
|
770
|
-
continue;
|
|
771
|
-
}
|
|
772
|
-
if (!options.hardware) {
|
|
773
|
-
if (filterName === 'hwupload' || filterName === 'hwupload_cuda' || (lowLevelFilter.flags & AVFILTER_FLAG_HWDEVICE) !== 0) {
|
|
774
|
-
throw new Error(`Filter '${filterName}' requires a hardware context`);
|
|
775
|
-
}
|
|
776
|
-
else if (filterName === 'hwdownload' && !avIsHardwarePixelFormat(this.config.pixelFormat)) {
|
|
777
|
-
throw new Error(`Pixel Format '${this.config.pixelFormat}' is not hardware compatible`);
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
730
|
/**
|
|
783
731
|
* Dispose of filter.
|
|
784
732
|
*
|
|
785
733
|
* Implements Disposable interface for automatic cleanup.
|
|
786
|
-
* Equivalent to calling
|
|
734
|
+
* Equivalent to calling close().
|
|
787
735
|
*
|
|
788
736
|
* @example
|
|
789
737
|
* ```typescript
|
|
790
738
|
* {
|
|
791
|
-
* using filter = await FilterAPI.create('scale=640:480',
|
|
739
|
+
* using filter = await FilterAPI.create('scale=640:480', { ... });
|
|
792
740
|
* // Use filter...
|
|
793
741
|
* } // Automatically freed
|
|
794
742
|
* ```
|
|
795
743
|
*
|
|
796
|
-
* @see {@link
|
|
744
|
+
* @see {@link close} For manual cleanup
|
|
797
745
|
*/
|
|
798
746
|
[Symbol.dispose]() {
|
|
799
|
-
this.
|
|
747
|
+
this.close();
|
|
800
748
|
}
|
|
801
749
|
}
|
|
802
750
|
//# sourceMappingURL=filter.js.map
|