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
|
@@ -2,78 +2,339 @@ import { AV_HWDEVICE_TYPE_CUDA, AV_HWDEVICE_TYPE_D3D11VA, AV_HWDEVICE_TYPE_D3D12
|
|
|
2
2
|
import { Filter } from '../lib/filter.js';
|
|
3
3
|
import { avGetPixFmtName, avGetSampleFmtName } from '../lib/utilities.js';
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* with each method returning a filter string that can be used in a filter graph.
|
|
10
|
-
* Hardware-specific implementations may override these methods to use optimized
|
|
11
|
-
* hardware filters instead of software implementations.
|
|
5
|
+
* Filter preset builder for composing filter chains.
|
|
6
|
+
* Supports both software and hardware-accelerated filters.
|
|
7
|
+
* Automatically selects appropriate filter implementations based on hardware context.
|
|
8
|
+
* Uses fluent interface pattern for chaining multiple filters.
|
|
12
9
|
*
|
|
13
10
|
* @example
|
|
14
11
|
* ```typescript
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
12
|
+
* // Software filter chain
|
|
13
|
+
* const filter = FilterPreset.chain()
|
|
14
|
+
* .scale(1920, 1080)
|
|
15
|
+
* .fps(30)
|
|
16
|
+
* .fade('in', 0, 2)
|
|
17
|
+
* .build();
|
|
18
|
+
*
|
|
19
|
+
* // Hardware-accelerated filter chain
|
|
20
|
+
* const hw = HardwareContext.auto();
|
|
21
|
+
* const hwFilter = FilterPreset.chain(hw)
|
|
22
|
+
* .scale(1920, 1080)
|
|
23
|
+
* .blur('gaussian', 5)
|
|
24
|
+
* .build();
|
|
20
25
|
* ```
|
|
21
26
|
*/
|
|
22
|
-
export class
|
|
27
|
+
export class FilterPreset {
|
|
28
|
+
hardware;
|
|
29
|
+
filters = [];
|
|
30
|
+
support;
|
|
31
|
+
constructor(hardware) {
|
|
32
|
+
this.hardware = hardware;
|
|
33
|
+
this.support = this.getSupport();
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Checks if a filter is hardware-accelerated.
|
|
37
|
+
*
|
|
38
|
+
* @param filterName - Name of the filter to check
|
|
39
|
+
* @returns True if the filter uses hardware acceleration
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* if (FilterPreset.isHardwareFilter('scale_cuda')) {
|
|
44
|
+
* console.log('Hardware accelerated scaling');
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
static isHardwareFilter(filterName) {
|
|
49
|
+
const filter = Filter.getByName(filterName);
|
|
50
|
+
if (!filter) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
// Check if filter has hardware device flag
|
|
54
|
+
return (filter.flags & AVFILTER_FLAG_HWDEVICE) !== 0;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Creates a new filter chain builder.
|
|
58
|
+
*
|
|
59
|
+
* @param hardware - Optional hardware context for hardware-accelerated filters
|
|
60
|
+
* @returns A new FilterPreset instance for chaining
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* // Software filter chain
|
|
65
|
+
* const filter = FilterPreset.chain()
|
|
66
|
+
* .scale(1280, 720)
|
|
67
|
+
* .fps(30)
|
|
68
|
+
* .build();
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* // Hardware filter chain
|
|
74
|
+
* const hw = HardwareContext.auto();
|
|
75
|
+
* const filter = FilterPreset.chain(hw)
|
|
76
|
+
* .scale(1280, 720)
|
|
77
|
+
* .deinterlace()
|
|
78
|
+
* .build();
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
static chain(hardware) {
|
|
82
|
+
const preset = new FilterPreset(hardware);
|
|
83
|
+
return preset;
|
|
84
|
+
}
|
|
23
85
|
/**
|
|
24
|
-
*
|
|
86
|
+
* Adds a custom filter string to the chain.
|
|
87
|
+
*
|
|
88
|
+
* @param filter - Custom filter string
|
|
89
|
+
* @returns This instance for chaining
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```typescript
|
|
93
|
+
* chain.custom('myfilter=param1:param2')
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
custom(filter) {
|
|
97
|
+
return this.add(filter);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Builds the final filter string.
|
|
101
|
+
*
|
|
102
|
+
* @param separator - Separator between filters (default: ',')
|
|
103
|
+
* @returns Combined filter string
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```typescript
|
|
107
|
+
* const filterString = chain.build() // "scale=1920:1080,fps=30"
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
build(separator = ',') {
|
|
111
|
+
return this.filters.join(separator);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Returns the filters as an array.
|
|
115
|
+
*
|
|
116
|
+
* @returns Array of filter strings
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```typescript
|
|
120
|
+
* const filters = chain.toArray() // ["scale=1920:1080", "fps=30"]
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
toArray() {
|
|
124
|
+
return [...this.filters];
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Adds a scale filter to the chain.
|
|
128
|
+
* Automatically selects hardware-specific scaler if hardware context is set.
|
|
25
129
|
*
|
|
26
130
|
* @param width - Target width in pixels
|
|
27
131
|
* @param height - Target height in pixels
|
|
28
|
-
* @param options - Additional scaling options (e.g., flags for algorithm)
|
|
29
|
-
* @returns
|
|
132
|
+
* @param options - Additional scaling options (e.g., flags for algorithm, npp for CUDA)
|
|
133
|
+
* @returns This instance for chaining
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```typescript
|
|
137
|
+
* chain.scale(1920, 1080) // Scale to Full HD
|
|
138
|
+
* chain.scale(640, 480, { flags: 'lanczos' }) // With specific algorithm
|
|
139
|
+
* chain.scale(1920, 1080, { npp: true }) // Use NPP for CUDA
|
|
140
|
+
* ```
|
|
30
141
|
*
|
|
31
142
|
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#scale | FFmpeg scale filter}
|
|
32
143
|
*/
|
|
33
144
|
scale(width, height, options) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
145
|
+
if (!this.support.scale) {
|
|
146
|
+
return this;
|
|
147
|
+
}
|
|
148
|
+
if (this.hardware) {
|
|
149
|
+
// Special handling for different hardware scalers
|
|
150
|
+
let filterName;
|
|
151
|
+
if (this.hardware.deviceType === AV_HWDEVICE_TYPE_CUDA && options?.npp) {
|
|
152
|
+
filterName = 'scale_npp';
|
|
153
|
+
}
|
|
154
|
+
else if (this.hardware.deviceType === AV_HWDEVICE_TYPE_RKMPP) {
|
|
155
|
+
filterName = 'scale_rkrga'; // RKMPP uses RGA for scaling
|
|
156
|
+
}
|
|
157
|
+
else if (this.hardware.deviceType === AV_HWDEVICE_TYPE_VIDEOTOOLBOX) {
|
|
158
|
+
filterName = 'scale_vt'; // VideoToolbox uses scale_vt
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
filterName = `scale_${this.hardware.deviceTypeName}`;
|
|
162
|
+
}
|
|
163
|
+
let filter = `${filterName}=${width}:${height}`;
|
|
164
|
+
if (options) {
|
|
165
|
+
for (const [key, value] of Object.entries(options)) {
|
|
166
|
+
if (key !== 'npp') {
|
|
167
|
+
// Skip our special npp flag
|
|
168
|
+
filter += `:${key}=${value}`;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
this.add(filter);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
const flags = options?.flags;
|
|
176
|
+
const base = `scale=${width}:${height}`;
|
|
177
|
+
const result = flags ? `${base}:flags=${flags}` : base;
|
|
178
|
+
this.add(result);
|
|
179
|
+
}
|
|
180
|
+
return this;
|
|
37
181
|
}
|
|
38
182
|
/**
|
|
39
|
-
*
|
|
183
|
+
* Adds a crop filter to the chain.
|
|
40
184
|
*
|
|
41
185
|
* @param width - Width of the cropped area
|
|
42
186
|
* @param height - Height of the cropped area
|
|
43
187
|
* @param x - X coordinate of top-left corner (default: 0)
|
|
44
188
|
* @param y - Y coordinate of top-left corner (default: 0)
|
|
45
|
-
* @returns
|
|
189
|
+
* @returns This instance for chaining
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```typescript
|
|
193
|
+
* chain.crop(640, 480, 100, 100) // Crop 640x480 area starting at (100,100)
|
|
194
|
+
* chain.crop(1280, 720) // Crop from top-left corner
|
|
195
|
+
* ```
|
|
46
196
|
*
|
|
47
197
|
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#crop | FFmpeg crop filter}
|
|
48
198
|
*/
|
|
49
199
|
crop(width, height, x = 0, y = 0) {
|
|
50
|
-
|
|
200
|
+
this.add(`crop=${width}:${height}:${x}:${y}`);
|
|
201
|
+
return this;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Adds a blur filter to the chain (hardware-specific).
|
|
205
|
+
* Only available for hardware presets that support blur
|
|
206
|
+
*
|
|
207
|
+
* @param type - Blur type (default: 'avg')
|
|
208
|
+
* @param radius - Blur radius (optional)
|
|
209
|
+
*
|
|
210
|
+
* @returns This instance for chaining
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```typescript
|
|
214
|
+
* const chain = FilterPresets.chain()
|
|
215
|
+
* .blur('gaussian', 5)
|
|
216
|
+
* .build();
|
|
217
|
+
* ```
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* ```typescript
|
|
221
|
+
* const chain = FilterPresets.chain()
|
|
222
|
+
* .blur('box')
|
|
223
|
+
* .build();
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
blur(type = 'avg', radius) {
|
|
227
|
+
if (!this.support.blur) {
|
|
228
|
+
return this;
|
|
229
|
+
}
|
|
230
|
+
if (this.hardware) {
|
|
231
|
+
let filter = null;
|
|
232
|
+
switch (this.hardware.deviceType) {
|
|
233
|
+
case AV_HWDEVICE_TYPE_CUDA:
|
|
234
|
+
filter = radius ? `bilateral_cuda=sigmaS=${radius}` : 'bilateral_cuda';
|
|
235
|
+
break;
|
|
236
|
+
case AV_HWDEVICE_TYPE_VULKAN:
|
|
237
|
+
filter = type === 'gaussian' ? (radius ? `gblur_vulkan=sigma=${radius}` : 'gblur_vulkan') : radius ? `avgblur_vulkan=sizeX=${radius}` : 'avgblur_vulkan';
|
|
238
|
+
break;
|
|
239
|
+
case AV_HWDEVICE_TYPE_OPENCL:
|
|
240
|
+
filter = type === 'box' ? (radius ? `boxblur_opencl=luma_radius=${radius}` : 'boxblur_opencl') : radius ? `avgblur_opencl=sizeX=${radius}` : 'avgblur_opencl';
|
|
241
|
+
break;
|
|
242
|
+
default:
|
|
243
|
+
filter = null;
|
|
244
|
+
}
|
|
245
|
+
if (filter) {
|
|
246
|
+
this.add(filter);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
const filter = type === 'gaussian' ? (radius ? `gblur=sigma=${radius}` : 'gblur') : radius ? `avgblur=sizeX=${radius}` : 'avgblur';
|
|
251
|
+
this.add(filter);
|
|
252
|
+
}
|
|
253
|
+
return this;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Adds a sharpen filter to the chain (hardware-specific).
|
|
257
|
+
* Only available for hardware presets that support sharpening
|
|
258
|
+
*
|
|
259
|
+
* @param amount - Sharpen amount (optional)
|
|
260
|
+
*
|
|
261
|
+
* @returns This instance for chaining
|
|
262
|
+
*
|
|
263
|
+
* @example
|
|
264
|
+
* ```typescript
|
|
265
|
+
* const chain = FilterPresets.chain()
|
|
266
|
+
* .sharpen(1.5)
|
|
267
|
+
* .build();
|
|
268
|
+
* ```
|
|
269
|
+
*
|
|
270
|
+
* @example
|
|
271
|
+
* ```typescript
|
|
272
|
+
* const chain = FilterPresets.chain()
|
|
273
|
+
* .sharpen()
|
|
274
|
+
* .build();
|
|
275
|
+
* ```
|
|
276
|
+
*/
|
|
277
|
+
sharpen(amount) {
|
|
278
|
+
if (!this.support.sharpen) {
|
|
279
|
+
return this;
|
|
280
|
+
}
|
|
281
|
+
if (this.hardware) {
|
|
282
|
+
let filter = null;
|
|
283
|
+
switch (this.hardware.deviceType) {
|
|
284
|
+
case AV_HWDEVICE_TYPE_VAAPI:
|
|
285
|
+
filter = amount ? `sharpness_vaapi=sharpness=${amount}` : 'sharpness_vaapi';
|
|
286
|
+
break;
|
|
287
|
+
case AV_HWDEVICE_TYPE_OPENCL:
|
|
288
|
+
filter = amount ? `unsharp_opencl=amount=${amount}` : 'unsharp_opencl';
|
|
289
|
+
break;
|
|
290
|
+
case AV_HWDEVICE_TYPE_CUDA:
|
|
291
|
+
// CUDA uses NPP for sharpening
|
|
292
|
+
filter = 'sharpen_npp';
|
|
293
|
+
break;
|
|
294
|
+
default:
|
|
295
|
+
filter = null;
|
|
296
|
+
}
|
|
297
|
+
if (filter) {
|
|
298
|
+
this.add(filter);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
const filter = amount ? `unsharp=amount=${amount}` : 'unsharp';
|
|
303
|
+
this.add(filter);
|
|
304
|
+
}
|
|
305
|
+
return this;
|
|
51
306
|
}
|
|
52
307
|
/**
|
|
53
|
-
*
|
|
308
|
+
* Adds an FPS filter to change frame rate.
|
|
54
309
|
*
|
|
55
310
|
* @param fps - Target frames per second
|
|
56
|
-
* @returns
|
|
311
|
+
* @returns This instance for chaining
|
|
312
|
+
*
|
|
313
|
+
* @example
|
|
314
|
+
* ```typescript
|
|
315
|
+
* chain.fps(30) // Convert to 30 FPS
|
|
316
|
+
* chain.fps(23.976) // Film frame rate
|
|
317
|
+
* ```
|
|
57
318
|
*
|
|
58
319
|
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#fps | FFmpeg fps filter}
|
|
59
320
|
*/
|
|
60
321
|
fps(fps) {
|
|
61
|
-
|
|
322
|
+
this.add(`fps=${fps}`);
|
|
323
|
+
return this;
|
|
62
324
|
}
|
|
63
325
|
/**
|
|
64
|
-
*
|
|
326
|
+
* Adds a format filter to convert pixel format.
|
|
65
327
|
*
|
|
66
|
-
* @param pixelFormat - Target pixel format(s) -
|
|
67
|
-
* @returns
|
|
328
|
+
* @param pixelFormat - Target pixel format(s) - AVPixelFormat enum, or array
|
|
329
|
+
* @returns This instance for chaining
|
|
68
330
|
*
|
|
69
331
|
* @example
|
|
70
332
|
* ```typescript
|
|
71
333
|
* // Single format
|
|
72
|
-
*
|
|
73
|
-
* presets.format(AV_PIX_FMT_YUV420P);
|
|
334
|
+
* chain.format(AV_PIX_FMT_YUV420P);
|
|
74
335
|
*
|
|
75
|
-
* // Multiple formats (
|
|
76
|
-
*
|
|
336
|
+
* // Multiple formats (tries formats in order)
|
|
337
|
+
* chain.format([AV_PIX_FMT_YUV420P, AV_PIX_FMT_RGB24]);
|
|
77
338
|
* ```
|
|
78
339
|
*
|
|
79
340
|
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#format | FFmpeg format filter}
|
|
@@ -85,41 +346,163 @@ export class FilterPresetBase {
|
|
|
85
346
|
const formatName = typeof fmt === 'string' ? fmt : (avGetPixFmtName(fmt) ?? 'yuv420p');
|
|
86
347
|
return `format=${formatName}`;
|
|
87
348
|
});
|
|
88
|
-
|
|
349
|
+
this.add(formats.join(','));
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
const formatName = typeof pixelFormat === 'string' ? pixelFormat : (avGetPixFmtName(pixelFormat) ?? 'yuv420p');
|
|
353
|
+
this.add(`format=${formatName}`);
|
|
89
354
|
}
|
|
90
|
-
|
|
91
|
-
return `format=${formatName}`;
|
|
355
|
+
return this;
|
|
92
356
|
}
|
|
93
357
|
/**
|
|
94
|
-
*
|
|
358
|
+
* Adds a rotate filter to the chain.
|
|
95
359
|
*
|
|
96
360
|
* @param angle - Rotation angle in degrees
|
|
97
|
-
* @returns
|
|
361
|
+
* @returns This instance for chaining
|
|
362
|
+
*
|
|
363
|
+
* @example
|
|
364
|
+
* ```typescript
|
|
365
|
+
* chain.rotate(90) // Rotate 90 degrees clockwise
|
|
366
|
+
* chain.rotate(-45) // Rotate 45 degrees counter-clockwise
|
|
367
|
+
* ```
|
|
98
368
|
*
|
|
99
369
|
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#rotate | FFmpeg rotate filter}
|
|
100
370
|
*/
|
|
101
371
|
rotate(angle) {
|
|
102
|
-
|
|
372
|
+
this.add(`rotate=${angle}*PI/180`);
|
|
373
|
+
return this;
|
|
103
374
|
}
|
|
104
375
|
/**
|
|
105
|
-
*
|
|
376
|
+
* Adds a flip filter to the chain (hardware-specific).
|
|
377
|
+
* Falls back to hflip/vflip if hardware flip not available
|
|
106
378
|
*
|
|
107
|
-
* @
|
|
379
|
+
* @param direction - Flip direction ('h' or 'v')
|
|
380
|
+
*
|
|
381
|
+
* @returns This instance for chaining
|
|
382
|
+
*
|
|
383
|
+
* @example
|
|
384
|
+
* ```typescript
|
|
385
|
+
* const chain = FilterPresets.chain()
|
|
386
|
+
* .flip('h')
|
|
387
|
+
* .build();
|
|
388
|
+
* ```
|
|
389
|
+
*
|
|
390
|
+
* @example
|
|
391
|
+
* ```typescript
|
|
392
|
+
* const chain = FilterPresets.chain()
|
|
393
|
+
* .flip('v')
|
|
394
|
+
* .build();
|
|
395
|
+
* ```
|
|
396
|
+
*/
|
|
397
|
+
flip(direction) {
|
|
398
|
+
if (!this.support.flip) {
|
|
399
|
+
return this;
|
|
400
|
+
}
|
|
401
|
+
if (this.hardware) {
|
|
402
|
+
if (this.hardware.deviceType === AV_HWDEVICE_TYPE_VULKAN) {
|
|
403
|
+
if (direction === 'v') {
|
|
404
|
+
this.add('vflip_vulkan');
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
this.add('hflip_vulkan');
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
if (direction === 'v') {
|
|
413
|
+
this.add('vflip');
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
this.add('hflip');
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return this;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Adds a stack filter to the chain (hardware-specific).
|
|
423
|
+
* Only available for hardware presets that support stacking
|
|
424
|
+
*
|
|
425
|
+
* @param type - Stack type ('h' for horizontal, 'v' for vertical, 'x' for grid)
|
|
426
|
+
* @param inputs - Number of inputs (default: 2)
|
|
427
|
+
*
|
|
428
|
+
* @returns This instance for chaining
|
|
429
|
+
*
|
|
430
|
+
* @example
|
|
431
|
+
* ```typescript
|
|
432
|
+
* const chain = FilterPresets.chain()
|
|
433
|
+
* .stack('h', 2)
|
|
434
|
+
* .build();
|
|
435
|
+
* ```
|
|
108
436
|
*
|
|
109
|
-
* @
|
|
437
|
+
* @example
|
|
438
|
+
* ```typescript
|
|
439
|
+
* const chain = FilterPresets.chain()
|
|
440
|
+
* .stack('x', 4)
|
|
441
|
+
* .build();
|
|
442
|
+
* ```
|
|
110
443
|
*/
|
|
111
|
-
|
|
112
|
-
|
|
444
|
+
stack(type, inputs = 2) {
|
|
445
|
+
if (!this.support.stack) {
|
|
446
|
+
return this;
|
|
447
|
+
}
|
|
448
|
+
if (this.hardware) {
|
|
449
|
+
if (this.hardware.deviceType === AV_HWDEVICE_TYPE_VAAPI || this.hardware.deviceType === AV_HWDEVICE_TYPE_QSV) {
|
|
450
|
+
const filter = `${type}stack_${this.hardware.deviceTypeName}=inputs=${inputs}`;
|
|
451
|
+
this.add(filter);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
const filter = type === 'h' ? `hstack=inputs=${inputs}` : type === 'v' ? `vstack=inputs=${inputs}` : `xstack=inputs=${inputs}`;
|
|
456
|
+
this.add(filter);
|
|
457
|
+
}
|
|
458
|
+
return this;
|
|
113
459
|
}
|
|
114
460
|
/**
|
|
115
|
-
* Creates a
|
|
461
|
+
* Creates a tonemap filter.
|
|
462
|
+
* Used for HDR to SDR conversion with hardware acceleration.
|
|
116
463
|
*
|
|
117
|
-
* @
|
|
464
|
+
* @param alg - Tonemapping algorithm (e.g., 'hable', 'reinhard', 'mobius', etc.)
|
|
465
|
+
* @param options - Tonemapping options
|
|
118
466
|
*
|
|
119
|
-
* @
|
|
467
|
+
* @returns Hardware tonemap filter string or null if not supported
|
|
468
|
+
*
|
|
469
|
+
* @example
|
|
470
|
+
* ```typescript
|
|
471
|
+
* const filter = hwPresets.tonemap();
|
|
472
|
+
* ```
|
|
473
|
+
*
|
|
474
|
+
* @example
|
|
475
|
+
* ```typescript
|
|
476
|
+
* const filter = hwPresets.tonemap({ tonemap: 'hable', desat: '0' });
|
|
477
|
+
* ```
|
|
120
478
|
*/
|
|
121
|
-
|
|
122
|
-
|
|
479
|
+
tonemap(alg, options) {
|
|
480
|
+
if (!this.support.tonemap) {
|
|
481
|
+
return this;
|
|
482
|
+
}
|
|
483
|
+
if (this.hardware) {
|
|
484
|
+
// VideoToolbox uses different filter name
|
|
485
|
+
const filterName = this.hardware.deviceType === AV_HWDEVICE_TYPE_VIDEOTOOLBOX ? 'tonemap_videotoolbox' : `tonemap_${this.hardware.deviceTypeName}`;
|
|
486
|
+
let filter = `${filterName}=${alg}`;
|
|
487
|
+
if (options) {
|
|
488
|
+
const opts = Object.entries(options)
|
|
489
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
490
|
+
.join(':');
|
|
491
|
+
filter += `=${opts}`;
|
|
492
|
+
}
|
|
493
|
+
this.add(filter);
|
|
494
|
+
}
|
|
495
|
+
else {
|
|
496
|
+
let filter = `tonemap=${alg}`;
|
|
497
|
+
if (options) {
|
|
498
|
+
const opts = Object.entries(options)
|
|
499
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
500
|
+
.join(':');
|
|
501
|
+
filter += `=${opts}`;
|
|
502
|
+
}
|
|
503
|
+
this.add(filter);
|
|
504
|
+
}
|
|
505
|
+
return this;
|
|
123
506
|
}
|
|
124
507
|
/**
|
|
125
508
|
* Creates a fade filter string for video.
|
|
@@ -129,10 +512,17 @@ export class FilterPresetBase {
|
|
|
129
512
|
* @param duration - Fade duration in seconds
|
|
130
513
|
* @returns Filter string or null if not supported
|
|
131
514
|
*
|
|
515
|
+
* @example
|
|
516
|
+
* ```typescript
|
|
517
|
+
* presets.fade('in', 0, 2) // 2-second fade in from start
|
|
518
|
+
* presets.fade('out', 10, 1) // 1-second fade out at 10 seconds
|
|
519
|
+
* ```
|
|
520
|
+
*
|
|
132
521
|
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#fade | FFmpeg fade filter}
|
|
133
522
|
*/
|
|
134
523
|
fade(type, start, duration) {
|
|
135
|
-
|
|
524
|
+
this.add(`fade=t=${type}:st=${start}:d=${duration}`);
|
|
525
|
+
return this;
|
|
136
526
|
}
|
|
137
527
|
/**
|
|
138
528
|
* Creates an overlay filter string to composite two video streams.
|
|
@@ -154,13 +544,30 @@ export class FilterPresetBase {
|
|
|
154
544
|
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#overlay | FFmpeg overlay filter}
|
|
155
545
|
*/
|
|
156
546
|
overlay(x = 0, y = 0, options) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
547
|
+
if (!this.support.overlay) {
|
|
548
|
+
return this;
|
|
549
|
+
}
|
|
550
|
+
if (this.hardware) {
|
|
551
|
+
// Special handling for RKMPP which uses RGA
|
|
552
|
+
const filterName = this.hardware.deviceType === AV_HWDEVICE_TYPE_RKMPP ? 'overlay_rkrga' : `overlay_${this.hardware.deviceTypeName}`;
|
|
553
|
+
let filter = `${filterName}=${x}:${y}`;
|
|
554
|
+
if (options) {
|
|
555
|
+
for (const [key, value] of Object.entries(options)) {
|
|
556
|
+
filter += `:${key}=${value}`;
|
|
557
|
+
}
|
|
161
558
|
}
|
|
559
|
+
this.add(filter);
|
|
162
560
|
}
|
|
163
|
-
|
|
561
|
+
else {
|
|
562
|
+
let filter = `overlay=${x}:${y}`;
|
|
563
|
+
if (options) {
|
|
564
|
+
for (const [key, value] of Object.entries(options)) {
|
|
565
|
+
filter += `:${key}=${value}`;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
this.add(filter);
|
|
569
|
+
}
|
|
570
|
+
return this;
|
|
164
571
|
}
|
|
165
572
|
/**
|
|
166
573
|
* Creates a volume filter string for audio.
|
|
@@ -168,10 +575,17 @@ export class FilterPresetBase {
|
|
|
168
575
|
* @param factor - Volume multiplication factor (1.0 = unchanged, 2.0 = double)
|
|
169
576
|
* @returns Filter string or null if not supported
|
|
170
577
|
*
|
|
578
|
+
* @example
|
|
579
|
+
* ```typescript
|
|
580
|
+
* presets.volume(0.5) // Reduce volume by 50%
|
|
581
|
+
* presets.volume(1.5) // Increase volume by 50%
|
|
582
|
+
* ```
|
|
583
|
+
*
|
|
171
584
|
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#volume | FFmpeg volume filter}
|
|
172
585
|
*/
|
|
173
586
|
volume(factor) {
|
|
174
|
-
|
|
587
|
+
this.add(`volume=${factor}`);
|
|
588
|
+
return this;
|
|
175
589
|
}
|
|
176
590
|
/**
|
|
177
591
|
* Creates an audio format filter string.
|
|
@@ -196,1013 +610,915 @@ export class FilterPresetBase {
|
|
|
196
610
|
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#aformat | FFmpeg aformat filter}
|
|
197
611
|
*/
|
|
198
612
|
aformat(sampleFormat, sampleRate, channelLayout) {
|
|
199
|
-
|
|
200
|
-
|
|
613
|
+
let sampleFormats = '';
|
|
614
|
+
if (!Array.isArray(sampleFormat)) {
|
|
615
|
+
sampleFormat = [sampleFormat];
|
|
616
|
+
}
|
|
617
|
+
sampleFormats = sampleFormat.map((fmt) => (typeof fmt === 'string' ? fmt : (avGetSampleFmtName(fmt) ?? 's16'))).join('|');
|
|
618
|
+
let filter = `aformat=sample_fmts=${sampleFormats}`;
|
|
201
619
|
if (sampleRate)
|
|
202
620
|
filter += `:sample_rates=${sampleRate}`;
|
|
203
|
-
if (channelLayout)
|
|
204
|
-
filter += `:channel_layouts=${channelLayout}`;
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Creates an atempo filter string to change audio playback speed.
|
|
209
|
-
* Factor must be between 0.5 and 2.0. For larger changes, chain multiple atempo filters.
|
|
210
|
-
*
|
|
211
|
-
* @param factor - Tempo factor (0.5 = half speed, 2.0 = double speed)
|
|
212
|
-
* @returns Filter string or null if not supported
|
|
213
|
-
*
|
|
214
|
-
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#atempo | FFmpeg atempo filter}
|
|
215
|
-
*/
|
|
216
|
-
atempo(factor) {
|
|
217
|
-
return `atempo=${factor}`;
|
|
218
|
-
}
|
|
219
|
-
/**
|
|
220
|
-
* Creates an audio fade filter string.
|
|
221
|
-
*
|
|
222
|
-
* @param type - Fade type ('in' or 'out')
|
|
223
|
-
* @param start - Start time in seconds
|
|
224
|
-
* @param duration - Fade duration in seconds
|
|
225
|
-
* @returns Filter string or null if not supported
|
|
226
|
-
*
|
|
227
|
-
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#afade | FFmpeg afade filter}
|
|
228
|
-
*/
|
|
229
|
-
afade(type, start, duration) {
|
|
230
|
-
return `afade=t=${type}:st=${start}:d=${duration}`;
|
|
231
|
-
}
|
|
232
|
-
/**
|
|
233
|
-
* Creates an amix filter string to mix multiple audio streams.
|
|
234
|
-
*
|
|
235
|
-
* @param inputs - Number of input streams to mix (default: 2)
|
|
236
|
-
* @param duration - How to determine output duration (default: 'longest')
|
|
237
|
-
* @returns Filter string or null if not supported
|
|
238
|
-
*
|
|
239
|
-
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#amix | FFmpeg amix filter}
|
|
240
|
-
*/
|
|
241
|
-
amix(inputs = 2, duration = 'longest') {
|
|
242
|
-
return `amix=inputs=${inputs}:duration=${duration}`;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
/**
|
|
246
|
-
* Filter chain builder for composing multiple filters.
|
|
247
|
-
* Allows fluent API for building complex filter graphs by chaining filter operations.
|
|
248
|
-
*
|
|
249
|
-
* @example
|
|
250
|
-
* ```typescript
|
|
251
|
-
* const chain = new FilterChain()
|
|
252
|
-
* .add('scale=1920:1080')
|
|
253
|
-
* .add('fps=30')
|
|
254
|
-
* .custom('rotate=45*PI/180')
|
|
255
|
-
* .build();
|
|
256
|
-
* // Result: "scale=1920:1080,fps=30,rotate=45*PI/180"
|
|
257
|
-
* ```
|
|
258
|
-
*/
|
|
259
|
-
export class FilterChain {
|
|
260
|
-
filters = [];
|
|
261
|
-
/**
|
|
262
|
-
* Adds a filter to the chain.
|
|
263
|
-
*
|
|
264
|
-
* @param filter - Filter string to add (ignored if null/undefined)
|
|
265
|
-
* @returns This instance for chaining
|
|
266
|
-
*/
|
|
267
|
-
add(filter) {
|
|
268
|
-
if (filter) {
|
|
269
|
-
this.filters.push(filter);
|
|
270
|
-
}
|
|
271
|
-
return this;
|
|
272
|
-
}
|
|
273
|
-
/**
|
|
274
|
-
* Adds a custom filter string to the chain.
|
|
275
|
-
*
|
|
276
|
-
* @param filter - Custom filter string
|
|
277
|
-
* @returns This instance for chaining
|
|
278
|
-
*/
|
|
279
|
-
custom(filter) {
|
|
280
|
-
return this.add(filter);
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* Builds the final filter string.
|
|
284
|
-
*
|
|
285
|
-
* @param separator - Separator between filters (default: ',')
|
|
286
|
-
* @returns Combined filter string
|
|
287
|
-
*/
|
|
288
|
-
build(separator = ',') {
|
|
289
|
-
return this.filters.join(separator);
|
|
290
|
-
}
|
|
291
|
-
/**
|
|
292
|
-
* Returns the filters as an array.
|
|
293
|
-
*
|
|
294
|
-
* @returns Array of filter strings
|
|
295
|
-
*/
|
|
296
|
-
toArray() {
|
|
297
|
-
return [...this.filters];
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
/**
|
|
301
|
-
* Base chain builder with common filter methods.
|
|
302
|
-
* Provides a fluent API for building filter chains using preset methods.
|
|
303
|
-
*
|
|
304
|
-
* @template T The preset type this builder uses
|
|
305
|
-
*
|
|
306
|
-
* @example
|
|
307
|
-
* ```typescript
|
|
308
|
-
* const chain = new ChainBuilderBase(presets)
|
|
309
|
-
* .scale(1920, 1080)
|
|
310
|
-
* .fps(30)
|
|
311
|
-
* .fade('in', 0, 2)
|
|
312
|
-
* .build();
|
|
313
|
-
* ```
|
|
314
|
-
*/
|
|
315
|
-
export class ChainBuilderBase extends FilterChain {
|
|
316
|
-
presets;
|
|
317
|
-
/**
|
|
318
|
-
* @param presets - The filter presets to use
|
|
319
|
-
* @internal
|
|
320
|
-
*/
|
|
321
|
-
constructor(presets) {
|
|
322
|
-
super();
|
|
323
|
-
this.presets = presets;
|
|
324
|
-
}
|
|
325
|
-
/**
|
|
326
|
-
* Adds a scale filter to the chain.
|
|
327
|
-
*
|
|
328
|
-
* @param width - Target width
|
|
329
|
-
* @param height - Target height
|
|
330
|
-
* @param options - Additional scaling options
|
|
331
|
-
* @returns This instance for chaining
|
|
332
|
-
*
|
|
333
|
-
* @see {@link FilterPresetBase.scale}
|
|
334
|
-
*/
|
|
335
|
-
scale(width, height, options) {
|
|
336
|
-
return this.add(this.presets.scale(width, height, options));
|
|
337
|
-
}
|
|
338
|
-
/**
|
|
339
|
-
* Adds a crop filter to the chain.
|
|
340
|
-
*
|
|
341
|
-
* @param width - Crop width
|
|
342
|
-
* @param height - Crop height
|
|
343
|
-
* @param x - X position (default: 0)
|
|
344
|
-
* @param y - Y position (default: 0)
|
|
345
|
-
* @returns This instance for chaining
|
|
346
|
-
*
|
|
347
|
-
* @see {@link FilterPresetBase.crop}
|
|
348
|
-
*/
|
|
349
|
-
crop(width, height, x = 0, y = 0) {
|
|
350
|
-
return this.add(this.presets.crop(width, height, x, y));
|
|
351
|
-
}
|
|
352
|
-
/**
|
|
353
|
-
* Adds an FPS filter to the chain.
|
|
354
|
-
*
|
|
355
|
-
* @param fps - Target frame rate
|
|
356
|
-
* @returns This instance for chaining
|
|
357
|
-
*
|
|
358
|
-
* @see {@link FilterPresetBase.fps}
|
|
359
|
-
*/
|
|
360
|
-
fps(fps) {
|
|
361
|
-
return this.add(this.presets.fps(fps));
|
|
621
|
+
if (channelLayout)
|
|
622
|
+
filter += `:channel_layouts=${channelLayout}`;
|
|
623
|
+
this.add(filter);
|
|
624
|
+
return this;
|
|
362
625
|
}
|
|
363
626
|
/**
|
|
364
|
-
* Adds
|
|
627
|
+
* Adds an asetnsamples filter to set the number of samples per frame.
|
|
628
|
+
* This is crucial for encoders like Opus that require specific frame sizes.
|
|
365
629
|
*
|
|
366
|
-
* @param
|
|
630
|
+
* @param samples - Number of samples per frame
|
|
631
|
+
* @param padding - Whether to pad or drop samples (default: true)
|
|
367
632
|
* @returns This instance for chaining
|
|
368
633
|
*
|
|
369
|
-
* @
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
}
|
|
374
|
-
/**
|
|
375
|
-
* Adds a rotate filter to the chain.
|
|
634
|
+
* @example
|
|
635
|
+
* ```typescript
|
|
636
|
+
* // For Opus encoder (requires 960 samples)
|
|
637
|
+
* chain.asetnsamples(960);
|
|
376
638
|
*
|
|
377
|
-
*
|
|
378
|
-
*
|
|
639
|
+
* // Drop samples instead of padding
|
|
640
|
+
* chain.asetnsamples(1024, false);
|
|
641
|
+
* ```
|
|
379
642
|
*
|
|
380
|
-
* @see {@link
|
|
643
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#asetnsamples | FFmpeg asetnsamples filter}
|
|
381
644
|
*/
|
|
382
|
-
|
|
383
|
-
|
|
645
|
+
asetnsamples(samples, padding = true) {
|
|
646
|
+
const p = padding ? 1 : 0;
|
|
647
|
+
this.add(`asetnsamples=n=${samples}:p=${p}`);
|
|
648
|
+
return this;
|
|
384
649
|
}
|
|
385
650
|
/**
|
|
386
|
-
* Adds
|
|
651
|
+
* Adds an aresample filter to change audio sample rate.
|
|
387
652
|
*
|
|
653
|
+
* @param rate - Target sample rate in Hz
|
|
388
654
|
* @returns This instance for chaining
|
|
389
655
|
*
|
|
390
|
-
* @
|
|
656
|
+
* @example
|
|
657
|
+
* ```typescript
|
|
658
|
+
* chain.aresample(44100) // Convert to 44.1 kHz
|
|
659
|
+
* chain.aresample(48000) // Convert to 48 kHz
|
|
660
|
+
* ```
|
|
661
|
+
*
|
|
662
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#aresample | FFmpeg aresample filter}
|
|
391
663
|
*/
|
|
392
|
-
|
|
393
|
-
|
|
664
|
+
aresample(rate) {
|
|
665
|
+
this.add(`aresample=${rate}`);
|
|
666
|
+
return this;
|
|
394
667
|
}
|
|
395
668
|
/**
|
|
396
|
-
* Adds
|
|
669
|
+
* Adds an atempo filter to change audio playback speed.
|
|
670
|
+
* Factor must be between 0.5 and 2.0. For larger changes, chain multiple atempo filters.
|
|
397
671
|
*
|
|
672
|
+
* @param factor - Tempo factor (0.5 = half speed, 2.0 = double speed)
|
|
398
673
|
* @returns This instance for chaining
|
|
399
674
|
*
|
|
400
|
-
* @
|
|
675
|
+
* @example
|
|
676
|
+
* ```typescript
|
|
677
|
+
* chain.atempo(1.5) // 1.5x speed
|
|
678
|
+
* chain.atempo(0.8) // Slow down to 80% speed
|
|
679
|
+
* ```
|
|
680
|
+
*
|
|
681
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#atempo | FFmpeg atempo filter}
|
|
401
682
|
*/
|
|
402
|
-
|
|
403
|
-
|
|
683
|
+
atempo(factor) {
|
|
684
|
+
this.add(`atempo=${factor}`);
|
|
685
|
+
return this;
|
|
404
686
|
}
|
|
405
687
|
/**
|
|
406
|
-
* Adds
|
|
688
|
+
* Adds an audio fade filter.
|
|
407
689
|
*
|
|
408
690
|
* @param type - Fade type ('in' or 'out')
|
|
409
691
|
* @param start - Start time in seconds
|
|
410
692
|
* @param duration - Fade duration in seconds
|
|
411
693
|
* @returns This instance for chaining
|
|
412
694
|
*
|
|
413
|
-
* @
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
/**
|
|
419
|
-
* Adds an overlay filter to the chain.
|
|
420
|
-
*
|
|
421
|
-
* @param x - X position (default: 0)
|
|
422
|
-
* @param y - Y position (default: 0)
|
|
423
|
-
* @param options - Additional overlay options
|
|
424
|
-
* @returns This instance for chaining
|
|
695
|
+
* @example
|
|
696
|
+
* ```typescript
|
|
697
|
+
* chain.afade('in', 0, 3) // 3-second audio fade in
|
|
698
|
+
* chain.afade('out', 20, 2) // 2-second fade out at 20s
|
|
699
|
+
* ```
|
|
425
700
|
*
|
|
426
|
-
* @see {@link
|
|
701
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#afade | FFmpeg afade filter}
|
|
427
702
|
*/
|
|
428
|
-
|
|
429
|
-
|
|
703
|
+
afade(type, start, duration) {
|
|
704
|
+
this.add(`afade=t=${type}:st=${start}:d=${duration}`);
|
|
705
|
+
return this;
|
|
430
706
|
}
|
|
431
707
|
/**
|
|
432
|
-
* Adds
|
|
708
|
+
* Adds an amix filter to mix multiple audio streams.
|
|
433
709
|
*
|
|
434
|
-
* @param
|
|
710
|
+
* @param inputs - Number of input streams to mix (default: 2)
|
|
711
|
+
* @param duration - How to determine output duration (default: 'longest')
|
|
435
712
|
* @returns This instance for chaining
|
|
436
713
|
*
|
|
437
|
-
* @
|
|
714
|
+
* @example
|
|
715
|
+
* ```typescript
|
|
716
|
+
* chain.amix(3, 'longest') // Mix 3 audio streams
|
|
717
|
+
* chain.amix(2, 'first') // Mix 2 streams, use first's duration
|
|
718
|
+
* ```
|
|
719
|
+
*
|
|
720
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#amix | FFmpeg amix filter}
|
|
438
721
|
*/
|
|
439
|
-
|
|
440
|
-
|
|
722
|
+
amix(inputs = 2, duration = 'longest') {
|
|
723
|
+
this.add(`amix=inputs=${inputs}:duration=${duration}`);
|
|
724
|
+
return this;
|
|
441
725
|
}
|
|
442
726
|
/**
|
|
443
|
-
* Adds
|
|
727
|
+
* Adds a pad filter to add padding to video.
|
|
728
|
+
* Essential for aspect ratio adjustments and letterboxing.
|
|
444
729
|
*
|
|
445
|
-
* @param
|
|
446
|
-
* @param
|
|
447
|
-
* @param
|
|
730
|
+
* @param width - Output width (can use expressions like 'iw+100')
|
|
731
|
+
* @param height - Output height (can use expressions like 'ih+100')
|
|
732
|
+
* @param x - X position of input video (default: '(ow-iw)/2' for center)
|
|
733
|
+
* @param y - Y position of input video (default: '(oh-ih)/2' for center)
|
|
734
|
+
* @param color - Padding color (default: 'black')
|
|
448
735
|
* @returns This instance for chaining
|
|
449
736
|
*
|
|
450
|
-
* @
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
}
|
|
455
|
-
/**
|
|
456
|
-
* Adds an atempo filter to the chain.
|
|
737
|
+
* @example
|
|
738
|
+
* ```typescript
|
|
739
|
+
* // Add black bars for 16:9 aspect ratio
|
|
740
|
+
* chain.pad('iw', 'iw*9/16');
|
|
457
741
|
*
|
|
458
|
-
*
|
|
459
|
-
*
|
|
742
|
+
* // Add 50px padding on all sides
|
|
743
|
+
* chain.pad('iw+100', 'ih+100');
|
|
744
|
+
* ```
|
|
460
745
|
*
|
|
461
|
-
* @see {@link
|
|
746
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#pad | FFmpeg pad filter}
|
|
462
747
|
*/
|
|
463
|
-
|
|
464
|
-
|
|
748
|
+
pad(width, height, x, y, color = 'black') {
|
|
749
|
+
let filter = `pad=${width}:${height}`;
|
|
750
|
+
if (x !== undefined)
|
|
751
|
+
filter += `:${x}`;
|
|
752
|
+
if (y !== undefined)
|
|
753
|
+
filter += `:${y}`;
|
|
754
|
+
filter += `:${color}`;
|
|
755
|
+
this.add(filter);
|
|
756
|
+
return this;
|
|
465
757
|
}
|
|
466
758
|
/**
|
|
467
|
-
* Adds
|
|
759
|
+
* Adds a trim filter to cut a portion of the stream.
|
|
760
|
+
* Crucial for cutting segments from media.
|
|
468
761
|
*
|
|
469
|
-
* @param type - Fade type ('in' or 'out')
|
|
470
762
|
* @param start - Start time in seconds
|
|
471
|
-
* @param
|
|
763
|
+
* @param end - End time in seconds (optional)
|
|
764
|
+
* @param duration - Duration in seconds (optional, alternative to end)
|
|
472
765
|
* @returns This instance for chaining
|
|
473
766
|
*
|
|
474
|
-
* @
|
|
767
|
+
* @example
|
|
768
|
+
* ```typescript
|
|
769
|
+
* chain.trim(10, 30) // Extract from 10s to 30s
|
|
770
|
+
* chain.trim(5, undefined, 10) // Extract 10s starting at 5s
|
|
771
|
+
* ```
|
|
772
|
+
*
|
|
773
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#trim | FFmpeg trim filter}
|
|
475
774
|
*/
|
|
476
|
-
|
|
477
|
-
|
|
775
|
+
trim(start, end, duration) {
|
|
776
|
+
let filter = `trim=start=${start}`;
|
|
777
|
+
if (end !== undefined)
|
|
778
|
+
filter += `:end=${end}`;
|
|
779
|
+
if (duration !== undefined)
|
|
780
|
+
filter += `:duration=${duration}`;
|
|
781
|
+
this.add(filter);
|
|
782
|
+
return this;
|
|
478
783
|
}
|
|
479
784
|
/**
|
|
480
|
-
*
|
|
785
|
+
* Creates a setpts filter string to change presentation timestamps.
|
|
786
|
+
* Essential for speed changes and timestamp manipulation.
|
|
481
787
|
*
|
|
482
|
-
* @param
|
|
483
|
-
* @
|
|
484
|
-
* @returns This instance for chaining
|
|
788
|
+
* @param expression - PTS expression (e.g., 'PTS*2' for half speed, 'PTS/2' for double speed)
|
|
789
|
+
* @returns Filter string or null if not supported
|
|
485
790
|
*
|
|
486
|
-
* @
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
}
|
|
491
|
-
/**
|
|
492
|
-
* Adds a transpose filter to the chain (hardware-specific).
|
|
493
|
-
* Only available for hardware presets that support transpose
|
|
791
|
+
* @example
|
|
792
|
+
* ```typescript
|
|
793
|
+
* // Double speed
|
|
794
|
+
* presets.setpts('PTS/2');
|
|
494
795
|
*
|
|
495
|
-
*
|
|
496
|
-
*
|
|
796
|
+
* // Half speed
|
|
797
|
+
* presets.setpts('PTS*2');
|
|
798
|
+
*
|
|
799
|
+
* // Reset timestamps
|
|
800
|
+
* presets.setpts('PTS-STARTPTS');
|
|
801
|
+
* ```
|
|
497
802
|
*
|
|
803
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#setpts | FFmpeg setpts filter}
|
|
498
804
|
*/
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
}
|
|
503
|
-
return this.add(null);
|
|
805
|
+
setpts(expression) {
|
|
806
|
+
this.add(`setpts=${expression}`);
|
|
807
|
+
return this;
|
|
504
808
|
}
|
|
505
809
|
/**
|
|
506
|
-
*
|
|
507
|
-
* Only available for hardware presets that support tonemapping
|
|
810
|
+
* Creates an asetpts filter string for audio timestamp manipulation.
|
|
508
811
|
*
|
|
509
|
-
* @param
|
|
510
|
-
* @returns
|
|
812
|
+
* @param expression - PTS expression
|
|
813
|
+
* @returns Filter string or null if not supported
|
|
814
|
+
*
|
|
815
|
+
* @example
|
|
816
|
+
* ```typescript
|
|
817
|
+
* presets.asetpts('PTS-STARTPTS') // Reset timestamps to start from 0
|
|
818
|
+
* ```
|
|
511
819
|
*
|
|
820
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#asetpts | FFmpeg asetpts filter}
|
|
512
821
|
*/
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
}
|
|
517
|
-
return this.add(null);
|
|
822
|
+
asetpts(expression) {
|
|
823
|
+
this.add(`asetpts=${expression}`);
|
|
824
|
+
return this;
|
|
518
825
|
}
|
|
519
826
|
/**
|
|
520
|
-
*
|
|
521
|
-
*
|
|
827
|
+
* Creates a transpose filter string for rotation/flipping.
|
|
828
|
+
* More efficient than rotate for 90-degree rotations.
|
|
522
829
|
*
|
|
523
|
-
* @param mode -
|
|
524
|
-
* @returns
|
|
830
|
+
* @param mode - Transpose mode (0-3, or named constants)
|
|
831
|
+
* @returns Filter string or null if not supported
|
|
832
|
+
*
|
|
833
|
+
* @example
|
|
834
|
+
* ```typescript
|
|
835
|
+
* presets.transpose(1) // Rotate 90 degrees clockwise
|
|
836
|
+
* presets.transpose('cclock') // Rotate 90 degrees counter-clockwise
|
|
837
|
+
* ```
|
|
525
838
|
*
|
|
839
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#transpose | FFmpeg transpose filter}
|
|
526
840
|
*/
|
|
527
|
-
|
|
528
|
-
if (
|
|
529
|
-
return this
|
|
841
|
+
transpose(mode) {
|
|
842
|
+
if (!this.support.transpose) {
|
|
843
|
+
return this;
|
|
844
|
+
}
|
|
845
|
+
if (this.hardware) {
|
|
846
|
+
// Convert string modes to numbers
|
|
847
|
+
let dir;
|
|
848
|
+
if (typeof mode === 'string') {
|
|
849
|
+
switch (mode) {
|
|
850
|
+
case 'clock':
|
|
851
|
+
dir = 1;
|
|
852
|
+
break;
|
|
853
|
+
case 'cclock':
|
|
854
|
+
dir = 2;
|
|
855
|
+
break;
|
|
856
|
+
case 'clock_flip':
|
|
857
|
+
dir = 3;
|
|
858
|
+
break;
|
|
859
|
+
case 'cclock_flip':
|
|
860
|
+
dir = 0;
|
|
861
|
+
break;
|
|
862
|
+
default:
|
|
863
|
+
dir = 0;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
else {
|
|
867
|
+
dir = mode;
|
|
868
|
+
}
|
|
869
|
+
// Special handling for different hardware transpose implementations
|
|
870
|
+
let filterName;
|
|
871
|
+
if (this.hardware.deviceType === AV_HWDEVICE_TYPE_CUDA) {
|
|
872
|
+
filterName = 'transpose_cuda'; // Uses transpose_cuda from patch, not NPP
|
|
873
|
+
}
|
|
874
|
+
else if (this.hardware.deviceType === AV_HWDEVICE_TYPE_VIDEOTOOLBOX) {
|
|
875
|
+
filterName = 'transpose_vt'; // CoreImage-based transpose
|
|
876
|
+
}
|
|
877
|
+
else {
|
|
878
|
+
filterName = `transpose_${this.hardware.deviceTypeName}`;
|
|
879
|
+
}
|
|
880
|
+
this.add(`${filterName}=dir=${dir}`);
|
|
881
|
+
}
|
|
882
|
+
else {
|
|
883
|
+
this.add(`transpose=${mode}`);
|
|
530
884
|
}
|
|
531
|
-
return this
|
|
885
|
+
return this;
|
|
532
886
|
}
|
|
533
887
|
/**
|
|
534
|
-
*
|
|
535
|
-
*
|
|
888
|
+
* Creates a setsar filter string to set sample aspect ratio.
|
|
889
|
+
* Important for correcting aspect ratio issues.
|
|
536
890
|
*
|
|
537
|
-
* @param
|
|
538
|
-
* @returns
|
|
891
|
+
* @param ratio - Aspect ratio (e.g., '1:1', '16:9', or number)
|
|
892
|
+
* @returns Filter string or null if not supported
|
|
893
|
+
*
|
|
894
|
+
* @example
|
|
895
|
+
* ```typescript
|
|
896
|
+
* presets.setsar('1:1') // Square pixels
|
|
897
|
+
* presets.setsar(1.333) // 4:3 aspect ratio
|
|
898
|
+
* ```
|
|
539
899
|
*
|
|
900
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#setsar | FFmpeg setsar/setdar filter}
|
|
540
901
|
*/
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
}
|
|
545
|
-
// Fallback to hflip/vflip
|
|
546
|
-
return direction === 'h' ? this.hflip() : this.vflip();
|
|
902
|
+
setsar(ratio) {
|
|
903
|
+
this.add(`setsar=${ratio}`);
|
|
904
|
+
return this;
|
|
547
905
|
}
|
|
548
906
|
/**
|
|
549
|
-
*
|
|
550
|
-
* Only available for hardware presets that support blur
|
|
907
|
+
* Creates a setdar filter string to set display aspect ratio.
|
|
551
908
|
*
|
|
552
|
-
* @param
|
|
553
|
-
* @
|
|
554
|
-
*
|
|
909
|
+
* @param ratio - Aspect ratio (e.g., '16:9', '4:3')
|
|
910
|
+
* @returns Filter string or null if not supported
|
|
911
|
+
*
|
|
912
|
+
* @example
|
|
913
|
+
* ```typescript
|
|
914
|
+
* presets.setdar('16:9') // Widescreen
|
|
915
|
+
* presets.setdar('4:3') // Traditional TV aspect
|
|
916
|
+
* ```
|
|
555
917
|
*
|
|
918
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#setsar | FFmpeg setsar/setdar filter}
|
|
556
919
|
*/
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
}
|
|
561
|
-
return this.add(null);
|
|
920
|
+
setdar(ratio) {
|
|
921
|
+
this.add(`setdar=${ratio}`);
|
|
922
|
+
return this;
|
|
562
923
|
}
|
|
563
924
|
/**
|
|
564
|
-
* Adds
|
|
565
|
-
*
|
|
925
|
+
* Adds an apad filter to add audio padding.
|
|
926
|
+
* Useful for ensuring minimum audio duration.
|
|
566
927
|
*
|
|
567
|
-
* @param
|
|
928
|
+
* @param wholeDuration - Minimum duration in seconds (optional)
|
|
929
|
+
* @param padDuration - Amount of padding to add in seconds (optional)
|
|
568
930
|
* @returns This instance for chaining
|
|
569
931
|
*
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
932
|
+
* @example
|
|
933
|
+
* ```typescript
|
|
934
|
+
* chain.apad(30) // Ensure at least 30 seconds total
|
|
935
|
+
* chain.apad(undefined, 5) // Add 5 seconds of padding
|
|
936
|
+
* ```
|
|
937
|
+
*
|
|
938
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#apad | FFmpeg apad filter}
|
|
939
|
+
*/
|
|
940
|
+
apad(wholeDuration, padDuration) {
|
|
941
|
+
if (!wholeDuration && !padDuration)
|
|
942
|
+
return this;
|
|
943
|
+
let filter = 'apad';
|
|
944
|
+
if (wholeDuration)
|
|
945
|
+
filter += `=whole_dur=${wholeDuration}`;
|
|
946
|
+
if (padDuration)
|
|
947
|
+
filter += wholeDuration ? `:pad_dur=${padDuration}` : `=pad_dur=${padDuration}`;
|
|
948
|
+
this.add(filter);
|
|
949
|
+
return this;
|
|
576
950
|
}
|
|
577
951
|
/**
|
|
578
|
-
*
|
|
579
|
-
*
|
|
952
|
+
* Creates a deinterlace filter string.
|
|
953
|
+
* Essential for processing interlaced content.
|
|
580
954
|
*
|
|
581
|
-
* @param
|
|
582
|
-
* @param
|
|
583
|
-
* @returns
|
|
955
|
+
* @param mode - Deinterlace mode (default: 'yadif')
|
|
956
|
+
* @param options - Additional options for the filter
|
|
957
|
+
* @returns Filter string or null if not supported
|
|
584
958
|
*
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
return this.add(null);
|
|
591
|
-
}
|
|
592
|
-
/**
|
|
593
|
-
* Adds a hwupload filter to upload frames to hardware.
|
|
959
|
+
* @example
|
|
960
|
+
* ```typescript
|
|
961
|
+
* presets.deinterlace('yadif') // Standard deinterlacing
|
|
962
|
+
* presets.deinterlace('bwdif') // Bob Weaver deinterlacing
|
|
963
|
+
* ```
|
|
594
964
|
*
|
|
595
|
-
* @
|
|
965
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#yadif | FFmpeg yadif filter}
|
|
596
966
|
*/
|
|
597
|
-
|
|
598
|
-
if (
|
|
599
|
-
return this
|
|
967
|
+
deinterlace(mode = 'yadif', options) {
|
|
968
|
+
if (!this.support.deinterlace) {
|
|
969
|
+
return this;
|
|
600
970
|
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
971
|
+
if (this.hardware) {
|
|
972
|
+
let filter = null;
|
|
973
|
+
switch (this.hardware.deviceType) {
|
|
974
|
+
case AV_HWDEVICE_TYPE_CUDA:
|
|
975
|
+
filter = mode ? `yadif_cuda=mode=${mode}` : 'yadif_cuda';
|
|
976
|
+
break;
|
|
977
|
+
case AV_HWDEVICE_TYPE_VAAPI:
|
|
978
|
+
filter = mode ? `deinterlace_vaapi=mode=${mode}` : 'deinterlace_vaapi';
|
|
979
|
+
break;
|
|
980
|
+
case AV_HWDEVICE_TYPE_QSV:
|
|
981
|
+
filter = mode ? `deinterlace_qsv=mode=${mode}` : 'deinterlace_qsv';
|
|
982
|
+
break;
|
|
983
|
+
case AV_HWDEVICE_TYPE_VULKAN:
|
|
984
|
+
filter = mode ? `bwdif_vulkan=mode=${mode}` : 'bwdif_vulkan';
|
|
985
|
+
break;
|
|
986
|
+
case AV_HWDEVICE_TYPE_VIDEOTOOLBOX:
|
|
987
|
+
filter = mode ? `yadif_videotoolbox=mode=${mode}` : 'yadif_videotoolbox';
|
|
988
|
+
break;
|
|
989
|
+
default:
|
|
990
|
+
filter = null;
|
|
991
|
+
}
|
|
992
|
+
if (filter) {
|
|
993
|
+
if (options) {
|
|
994
|
+
const params = [];
|
|
995
|
+
for (const [key, value] of Object.entries(options)) {
|
|
996
|
+
params.push(`${key}=${value}`);
|
|
997
|
+
}
|
|
998
|
+
filter += '=' + params.join(':');
|
|
999
|
+
}
|
|
1000
|
+
this.add(filter);
|
|
1001
|
+
}
|
|
611
1002
|
}
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
return this.add(this.presets.hwmap(derive));
|
|
1003
|
+
else {
|
|
1004
|
+
let filter = mode;
|
|
1005
|
+
if (options) {
|
|
1006
|
+
const params = [];
|
|
1007
|
+
for (const [key, value] of Object.entries(options)) {
|
|
1008
|
+
params.push(`${key}=${value}`);
|
|
1009
|
+
}
|
|
1010
|
+
filter += '=' + params.join(':');
|
|
1011
|
+
}
|
|
1012
|
+
this.add(filter);
|
|
623
1013
|
}
|
|
624
|
-
return this
|
|
1014
|
+
return this;
|
|
625
1015
|
}
|
|
626
|
-
}
|
|
627
|
-
/**
|
|
628
|
-
* Fluent filter chain builder with preset methods.
|
|
629
|
-
* Provides a convenient API for building filter chains using standard presets.
|
|
630
|
-
*
|
|
631
|
-
* @example
|
|
632
|
-
* ```typescript
|
|
633
|
-
* const filter = FilterPresets.chain()
|
|
634
|
-
* .scale(1920, 1080)
|
|
635
|
-
* .fps(30)
|
|
636
|
-
* .fade('in', 0, 2)
|
|
637
|
-
* .format('yuv420p')
|
|
638
|
-
* .build();
|
|
639
|
-
* ```
|
|
640
|
-
*/
|
|
641
|
-
export class FilterChainBuilder extends ChainBuilderBase {
|
|
642
|
-
}
|
|
643
|
-
/**
|
|
644
|
-
* Standard filter presets for software filtering.
|
|
645
|
-
* Provides static methods for creating common filter strings and
|
|
646
|
-
* a chain builder for composing complex filter graphs.
|
|
647
|
-
*
|
|
648
|
-
* @example
|
|
649
|
-
* ```typescript
|
|
650
|
-
* // Static methods for individual filters
|
|
651
|
-
* const scaleFilter = FilterPresets.scale(1920, 1080);
|
|
652
|
-
* const fpsFilter = FilterPresets.fps(30);
|
|
653
|
-
*
|
|
654
|
-
* // Chain builder for complex graphs
|
|
655
|
-
* const chain = FilterPresets.chain()
|
|
656
|
-
* .scale(1920, 1080)
|
|
657
|
-
* .fps(30)
|
|
658
|
-
* .fade('in', 0, 2)
|
|
659
|
-
* .build();
|
|
660
|
-
* ```
|
|
661
|
-
*/
|
|
662
|
-
export class FilterPresets extends FilterPresetBase {
|
|
663
|
-
static instance = new FilterPresets();
|
|
664
1016
|
/**
|
|
665
|
-
* Creates a
|
|
1017
|
+
* Creates a select filter string to select specific frames.
|
|
1018
|
+
* Powerful for extracting keyframes, specific frame types, etc.
|
|
666
1019
|
*
|
|
667
|
-
* @
|
|
1020
|
+
* @param expression - Selection expression
|
|
1021
|
+
* @returns Filter string or null if not supported
|
|
668
1022
|
*
|
|
669
1023
|
* @example
|
|
670
1024
|
* ```typescript
|
|
671
|
-
*
|
|
672
|
-
*
|
|
673
|
-
* .fps(30)
|
|
674
|
-
* .build();
|
|
1025
|
+
* presets.select('eq(pict_type,I)') // Select only keyframes
|
|
1026
|
+
* presets.select('not(mod(n,10))') // Select every 10th frame
|
|
675
1027
|
* ```
|
|
676
|
-
*/
|
|
677
|
-
static chain() {
|
|
678
|
-
return new FilterChainBuilder(FilterPresets.instance);
|
|
679
|
-
}
|
|
680
|
-
/**
|
|
681
|
-
* Creates a scale filter string.
|
|
682
1028
|
*
|
|
683
|
-
* @
|
|
684
|
-
* @param height - Target height
|
|
685
|
-
* @param flags - Scaling algorithm flags (optional)
|
|
686
|
-
* @returns Scale filter string
|
|
1029
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#select | FFmpeg select filter}
|
|
687
1030
|
*/
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
return
|
|
1031
|
+
select(expression) {
|
|
1032
|
+
this.add(`select='${expression}'`);
|
|
1033
|
+
return this;
|
|
691
1034
|
}
|
|
692
1035
|
/**
|
|
693
|
-
* Creates
|
|
1036
|
+
* Creates an aselect filter string for audio selection.
|
|
694
1037
|
*
|
|
695
|
-
* @param
|
|
696
|
-
* @
|
|
697
|
-
* @param x - X position (default: 0)
|
|
698
|
-
* @param y - Y position (default: 0)
|
|
699
|
-
* @returns Crop filter string
|
|
700
|
-
*/
|
|
701
|
-
static crop(width, height, x = 0, y = 0) {
|
|
702
|
-
const result = FilterPresets.instance.crop(width, height, x, y);
|
|
703
|
-
return result ?? '';
|
|
704
|
-
}
|
|
705
|
-
/**
|
|
706
|
-
* Creates an FPS filter string.
|
|
1038
|
+
* @param expression - Selection expression
|
|
1039
|
+
* @returns Filter string or null if not supported
|
|
707
1040
|
*
|
|
708
|
-
* @
|
|
709
|
-
*
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
const result = FilterPresets.instance.fps(fps);
|
|
713
|
-
return result ?? '';
|
|
714
|
-
}
|
|
715
|
-
/**
|
|
716
|
-
* Creates a format filter string.
|
|
1041
|
+
* @example
|
|
1042
|
+
* ```typescript
|
|
1043
|
+
* presets.aselect('between(t,10,20)') // Select audio between 10-20 seconds
|
|
1044
|
+
* ```
|
|
717
1045
|
*
|
|
718
|
-
* @
|
|
719
|
-
* @returns Format filter string
|
|
1046
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#aselect | FFmpeg aselect filter}
|
|
720
1047
|
*/
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
return
|
|
1048
|
+
aselect(expression) {
|
|
1049
|
+
this.add(`aselect='${expression}'`);
|
|
1050
|
+
return this;
|
|
724
1051
|
}
|
|
725
1052
|
/**
|
|
726
|
-
* Creates a
|
|
1053
|
+
* Creates a concat filter string to concatenate multiple inputs.
|
|
1054
|
+
* Essential for joining multiple video/audio segments.
|
|
727
1055
|
*
|
|
728
|
-
* @param
|
|
729
|
-
* @
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
const result = FilterPresets.instance.rotate(angle);
|
|
733
|
-
return result ?? '';
|
|
734
|
-
}
|
|
735
|
-
/**
|
|
736
|
-
* Creates a horizontal flip filter string.
|
|
1056
|
+
* @param n - Number of input segments
|
|
1057
|
+
* @param v - Number of output video streams (0 or 1)
|
|
1058
|
+
* @param a - Number of output audio streams (0 or 1)
|
|
1059
|
+
* @returns Filter string or null if not supported
|
|
737
1060
|
*
|
|
738
|
-
* @
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
}
|
|
744
|
-
/**
|
|
745
|
-
* Creates a vertical flip filter string.
|
|
1061
|
+
* @example
|
|
1062
|
+
* ```typescript
|
|
1063
|
+
* presets.concat(3, 1, 1) // Join 3 segments with video and audio
|
|
1064
|
+
* presets.concat(2, 1, 0) // Join 2 video-only segments
|
|
1065
|
+
* ```
|
|
746
1066
|
*
|
|
747
|
-
* @
|
|
1067
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#concat | FFmpeg concat filter}
|
|
748
1068
|
*/
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
return
|
|
1069
|
+
concat(n, v = 1, a = 1) {
|
|
1070
|
+
this.add(`concat=n=${n}:v=${v}:a=${a}`);
|
|
1071
|
+
return this;
|
|
752
1072
|
}
|
|
753
1073
|
/**
|
|
754
|
-
* Creates
|
|
1074
|
+
* Creates an amerge filter string to merge multiple audio streams into one.
|
|
1075
|
+
* Different from amix - this creates multi-channel output.
|
|
755
1076
|
*
|
|
756
|
-
* @param
|
|
757
|
-
* @
|
|
758
|
-
* @param duration - Fade duration in seconds
|
|
759
|
-
* @returns Fade filter string
|
|
760
|
-
*/
|
|
761
|
-
static fade(type, start, duration) {
|
|
762
|
-
const result = FilterPresets.instance.fade(type, start, duration);
|
|
763
|
-
return result ?? '';
|
|
764
|
-
}
|
|
765
|
-
/**
|
|
766
|
-
* Creates an overlay filter string.
|
|
1077
|
+
* @param inputs - Number of input streams
|
|
1078
|
+
* @returns Filter string or null if not supported
|
|
767
1079
|
*
|
|
768
|
-
* @
|
|
769
|
-
*
|
|
770
|
-
*
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
const result = FilterPresets.instance.overlay(x, y);
|
|
774
|
-
return result ?? '';
|
|
775
|
-
}
|
|
776
|
-
/**
|
|
777
|
-
* Creates a volume filter string.
|
|
1080
|
+
* @example
|
|
1081
|
+
* ```typescript
|
|
1082
|
+
* presets.amerge(2) // Merge 2 mono streams to stereo
|
|
1083
|
+
* presets.amerge(6) // Merge 6 channels for 5.1 surround
|
|
1084
|
+
* ```
|
|
778
1085
|
*
|
|
779
|
-
* @
|
|
780
|
-
* @returns Volume filter string
|
|
1086
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#amerge | FFmpeg amerge filter}
|
|
781
1087
|
*/
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
return
|
|
1088
|
+
amerge(inputs = 2) {
|
|
1089
|
+
this.add(`amerge=inputs=${inputs}`);
|
|
1090
|
+
return this;
|
|
785
1091
|
}
|
|
786
1092
|
/**
|
|
787
|
-
* Creates
|
|
1093
|
+
* Creates a channelmap filter string to remap audio channels.
|
|
1094
|
+
* Critical for audio channel manipulation.
|
|
788
1095
|
*
|
|
789
|
-
* @param
|
|
790
|
-
* @
|
|
791
|
-
*
|
|
792
|
-
* @
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
}
|
|
798
|
-
/**
|
|
799
|
-
* Creates an atempo filter string.
|
|
1096
|
+
* @param map - Channel mapping (e.g., '0-0|1-1' or 'FL-FR|FR-FL' to swap stereo)
|
|
1097
|
+
* @returns Filter string or null if not supported
|
|
1098
|
+
*
|
|
1099
|
+
* @example
|
|
1100
|
+
* ```typescript
|
|
1101
|
+
* presets.channelmap('FL-FR|FR-FL') // Swap left and right channels
|
|
1102
|
+
* presets.channelmap('0-0|0-1') // Duplicate mono to stereo
|
|
1103
|
+
* ```
|
|
800
1104
|
*
|
|
801
|
-
* @
|
|
802
|
-
* @returns Atempo filter string
|
|
1105
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#channelmap | FFmpeg channelmap filter}
|
|
803
1106
|
*/
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
return
|
|
1107
|
+
channelmap(map) {
|
|
1108
|
+
this.add(`channelmap=${map}`);
|
|
1109
|
+
return this;
|
|
807
1110
|
}
|
|
808
1111
|
/**
|
|
809
|
-
* Creates
|
|
1112
|
+
* Creates a channelsplit filter string to split audio channels.
|
|
810
1113
|
*
|
|
811
|
-
* @param
|
|
812
|
-
* @
|
|
813
|
-
*
|
|
814
|
-
* @
|
|
1114
|
+
* @param channelLayout - Channel layout to split (optional)
|
|
1115
|
+
* @returns Filter string or null if not supported
|
|
1116
|
+
*
|
|
1117
|
+
* @example
|
|
1118
|
+
* ```typescript
|
|
1119
|
+
* presets.channelsplit('stereo') // Split stereo to 2 mono
|
|
1120
|
+
* presets.channelsplit('5.1') // Split 5.1 to individual channels
|
|
1121
|
+
* ```
|
|
1122
|
+
*
|
|
1123
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#channelsplit | FFmpeg channelsplit filter}
|
|
815
1124
|
*/
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
return
|
|
1125
|
+
channelsplit(channelLayout) {
|
|
1126
|
+
this.add(channelLayout ? `channelsplit=channel_layout=${channelLayout}` : 'channelsplit');
|
|
1127
|
+
return this;
|
|
819
1128
|
}
|
|
820
1129
|
/**
|
|
821
|
-
* Creates
|
|
1130
|
+
* Creates a loudnorm filter string for loudness normalization.
|
|
1131
|
+
* Essential for broadcast compliance and consistent audio levels.
|
|
822
1132
|
*
|
|
823
|
-
* @param
|
|
824
|
-
* @param
|
|
825
|
-
* @
|
|
1133
|
+
* @param I - Integrated loudness target (default: -24 LUFS)
|
|
1134
|
+
* @param TP - True peak (default: -2 dBTP)
|
|
1135
|
+
* @param LRA - Loudness range (default: 7 LU)
|
|
1136
|
+
* @returns Filter string or null if not supported
|
|
1137
|
+
*
|
|
1138
|
+
* @example
|
|
1139
|
+
* ```typescript
|
|
1140
|
+
* presets.loudnorm(-23, -1, 7) // EBU R128 broadcast standard
|
|
1141
|
+
* presets.loudnorm(-16, -1.5, 11) // Streaming platforms standard
|
|
1142
|
+
* ```
|
|
1143
|
+
*
|
|
1144
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#loudnorm | FFmpeg loudnorm filter}
|
|
826
1145
|
*/
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
return
|
|
1146
|
+
loudnorm(I = -24, TP = -2, LRA = 7) {
|
|
1147
|
+
this.add(`loudnorm=I=${I}:TP=${TP}:LRA=${LRA}`);
|
|
1148
|
+
return this;
|
|
830
1149
|
}
|
|
831
|
-
}
|
|
832
|
-
/**
|
|
833
|
-
* Hardware-accelerated filter presets.
|
|
834
|
-
* Provides optimized filter implementations for specific hardware types,
|
|
835
|
-
* with automatic fallback when operations aren't supported.
|
|
836
|
-
*
|
|
837
|
-
* @example
|
|
838
|
-
* ```typescript
|
|
839
|
-
* // Create hardware presets for CUDA
|
|
840
|
-
* const hw = new HardwareFilterPresets(AV_HWDEVICE_TYPE_CUDA, 'cuda');
|
|
841
|
-
*
|
|
842
|
-
* // Check capabilities
|
|
843
|
-
* if (hw.support.scale) {
|
|
844
|
-
* const scaleFilter = hw.scale(1920, 1080);
|
|
845
|
-
* }
|
|
846
|
-
*
|
|
847
|
-
* // Use chain builder
|
|
848
|
-
* const chain = hw.chain()
|
|
849
|
-
* .hwupload()
|
|
850
|
-
* .scale(1920, 1080)
|
|
851
|
-
* .tonemap()
|
|
852
|
-
* .hwdownload()
|
|
853
|
-
* .build();
|
|
854
|
-
* ```
|
|
855
|
-
*/
|
|
856
|
-
export class HardwareFilterPresets extends FilterPresetBase {
|
|
857
|
-
deviceType;
|
|
858
|
-
deviceTypeName;
|
|
859
|
-
support;
|
|
860
1150
|
/**
|
|
861
|
-
*
|
|
862
|
-
*
|
|
1151
|
+
* Creates a compand filter string for audio compression/expansion.
|
|
1152
|
+
* Important for dynamic range control.
|
|
1153
|
+
*
|
|
1154
|
+
* @param attacks - Attack times
|
|
1155
|
+
* @param decays - Decay times
|
|
1156
|
+
* @param points - Transfer function points
|
|
1157
|
+
* @param gain - Output gain
|
|
1158
|
+
* @returns Filter string or null if not supported
|
|
1159
|
+
*
|
|
1160
|
+
* @example
|
|
1161
|
+
* ```typescript
|
|
1162
|
+
* presets.compand('0.3|0.3', '1|1', '-90/-60|-60/-40|-40/-30|-20/-20', 6)
|
|
1163
|
+
* ```
|
|
1164
|
+
*
|
|
1165
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#compand | FFmpeg compand filter}
|
|
863
1166
|
*/
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
this.
|
|
1167
|
+
compand(attacks, decays, points, gain) {
|
|
1168
|
+
let filter = `compand=attacks=${attacks}:decays=${decays}:points=${points}`;
|
|
1169
|
+
if (gain !== undefined)
|
|
1170
|
+
filter += `:gain=${gain}`;
|
|
1171
|
+
this.add(filter);
|
|
1172
|
+
return this;
|
|
869
1173
|
}
|
|
870
1174
|
/**
|
|
871
|
-
*
|
|
1175
|
+
* Adds a drawtext filter to overlay text on video.
|
|
872
1176
|
*
|
|
873
|
-
* @param
|
|
874
|
-
* @
|
|
1177
|
+
* @param text - Text to display
|
|
1178
|
+
* @param options - Text rendering options
|
|
1179
|
+
* @returns This instance for chaining
|
|
875
1180
|
*
|
|
876
1181
|
* @example
|
|
877
1182
|
* ```typescript
|
|
878
|
-
*
|
|
879
|
-
*
|
|
880
|
-
*
|
|
1183
|
+
* chain.drawtext('Hello World', { x: 10, y: 10, fontsize: 24 })
|
|
1184
|
+
* chain.drawtext('Timestamp', {
|
|
1185
|
+
* x: 10,
|
|
1186
|
+
* y: 10,
|
|
1187
|
+
* fontsize: 24,
|
|
1188
|
+
* fontcolor: 'white',
|
|
1189
|
+
* fontfile: '/path/to/font.ttf'
|
|
1190
|
+
* })
|
|
881
1191
|
* ```
|
|
1192
|
+
*
|
|
1193
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#drawtext | FFmpeg drawtext filter}
|
|
882
1194
|
*/
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
1195
|
+
drawtext(text, options) {
|
|
1196
|
+
let filter = `drawtext=text='${text.replace(/'/g, "\\'").replace(/"/g, '\\"')}'`;
|
|
1197
|
+
for (const [key, value] of Object.entries(options)) {
|
|
1198
|
+
if (key === 'fontfile' && typeof value === 'string') {
|
|
1199
|
+
filter += `:${key}='${value}'`;
|
|
1200
|
+
}
|
|
1201
|
+
else {
|
|
1202
|
+
filter += `:${key}=${value}`;
|
|
1203
|
+
}
|
|
887
1204
|
}
|
|
888
|
-
|
|
889
|
-
return
|
|
1205
|
+
this.add(filter);
|
|
1206
|
+
return this;
|
|
890
1207
|
}
|
|
891
1208
|
/**
|
|
892
|
-
*
|
|
1209
|
+
* Adds a split filter to duplicate a video stream.
|
|
893
1210
|
*
|
|
894
|
-
* @
|
|
1211
|
+
* @param outputs - Number of output streams (default: 2)
|
|
1212
|
+
* @returns This instance for chaining
|
|
895
1213
|
*
|
|
896
1214
|
* @example
|
|
897
1215
|
* ```typescript
|
|
898
|
-
*
|
|
899
|
-
*
|
|
900
|
-
* .scale(1920, 1080)
|
|
901
|
-
* .hwdownload()
|
|
902
|
-
* .build();
|
|
1216
|
+
* chain.split() // Split into 2 outputs
|
|
1217
|
+
* chain.split(3) // Split into 3 outputs
|
|
903
1218
|
* ```
|
|
1219
|
+
*
|
|
1220
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#split | FFmpeg split filter}
|
|
904
1221
|
*/
|
|
905
|
-
|
|
906
|
-
|
|
1222
|
+
split(outputs = 2) {
|
|
1223
|
+
this.add(`split=${outputs}`);
|
|
1224
|
+
return this;
|
|
907
1225
|
}
|
|
908
1226
|
/**
|
|
909
|
-
*
|
|
910
|
-
*
|
|
911
|
-
* Different hardware types use different scale filters:
|
|
912
|
-
* - CUDA: scale_cuda or scale_npp (with npp option)
|
|
913
|
-
* - VAAPI: scale_vaapi
|
|
914
|
-
* - QSV: scale_qsv
|
|
915
|
-
* - VideoToolbox: scale_vt
|
|
916
|
-
* - RKMPP: scale_rkrga
|
|
1227
|
+
* Adds an asplit filter to duplicate an audio stream.
|
|
917
1228
|
*
|
|
918
|
-
* @param
|
|
919
|
-
* @
|
|
920
|
-
* @param options - Hardware-specific scaling options
|
|
921
|
-
* @returns Hardware scale filter string or null if not supported
|
|
1229
|
+
* @param outputs - Number of output streams (default: 2)
|
|
1230
|
+
* @returns This instance for chaining
|
|
922
1231
|
*
|
|
1232
|
+
* @example
|
|
1233
|
+
* ```typescript
|
|
1234
|
+
* chain.asplit() // Split into 2 outputs
|
|
1235
|
+
* chain.asplit(3) // Split into 3 outputs
|
|
1236
|
+
* ```
|
|
923
1237
|
*
|
|
924
|
-
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#
|
|
1238
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#asplit | FFmpeg asplit filter}
|
|
925
1239
|
*/
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
}
|
|
930
|
-
// Special handling for different hardware scalers
|
|
931
|
-
let filterName;
|
|
932
|
-
if (this.deviceType === AV_HWDEVICE_TYPE_CUDA && options?.npp) {
|
|
933
|
-
filterName = 'scale_npp';
|
|
934
|
-
}
|
|
935
|
-
else if (this.deviceType === AV_HWDEVICE_TYPE_RKMPP) {
|
|
936
|
-
filterName = 'scale_rkrga'; // RKMPP uses RGA for scaling
|
|
937
|
-
}
|
|
938
|
-
else if (this.deviceType === AV_HWDEVICE_TYPE_VIDEOTOOLBOX) {
|
|
939
|
-
filterName = 'scale_vt'; // VideoToolbox uses scale_vt
|
|
940
|
-
}
|
|
941
|
-
else {
|
|
942
|
-
filterName = `scale_${this.deviceTypeName}`;
|
|
943
|
-
}
|
|
944
|
-
let filter = `${filterName}=${width}:${height}`;
|
|
945
|
-
if (options) {
|
|
946
|
-
for (const [key, value] of Object.entries(options)) {
|
|
947
|
-
if (key !== 'npp') {
|
|
948
|
-
// Skip our special npp flag
|
|
949
|
-
filter += `:${key}=${value}`;
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
return filter;
|
|
1240
|
+
asplit(outputs = 2) {
|
|
1241
|
+
this.add(`asplit=${outputs}`);
|
|
1242
|
+
return this;
|
|
954
1243
|
}
|
|
955
1244
|
/**
|
|
956
|
-
*
|
|
1245
|
+
* Adds an adelay filter to delay audio by specified milliseconds.
|
|
1246
|
+
*
|
|
1247
|
+
* @param delays - Delay in milliseconds (single value or array for multiple channels)
|
|
1248
|
+
* @returns This instance for chaining
|
|
957
1249
|
*
|
|
958
|
-
* @
|
|
959
|
-
*
|
|
960
|
-
*
|
|
961
|
-
*
|
|
1250
|
+
* @example
|
|
1251
|
+
* ```typescript
|
|
1252
|
+
* chain.adelay(100) // Delay all channels by 100ms
|
|
1253
|
+
* chain.adelay([100, 200]) // Delay first channel by 100ms, second by 200ms
|
|
1254
|
+
* ```
|
|
962
1255
|
*
|
|
963
|
-
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#
|
|
1256
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#adelay | FFmpeg adelay filter}
|
|
964
1257
|
*/
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
// Special handling for RKMPP which uses RGA
|
|
970
|
-
const filterName = this.deviceType === AV_HWDEVICE_TYPE_RKMPP ? 'overlay_rkrga' : `overlay_${this.deviceTypeName}`;
|
|
971
|
-
let filter = `${filterName}=${x}:${y}`;
|
|
972
|
-
if (options) {
|
|
973
|
-
for (const [key, value] of Object.entries(options)) {
|
|
974
|
-
filter += `:${key}=${value}`;
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
return filter;
|
|
1258
|
+
adelay(delays) {
|
|
1259
|
+
const delayStr = Array.isArray(delays) ? delays.join('|') : delays.toString();
|
|
1260
|
+
this.add(`adelay=${delayStr}`);
|
|
1261
|
+
return this;
|
|
978
1262
|
}
|
|
979
1263
|
/**
|
|
980
|
-
*
|
|
1264
|
+
* Adds an aecho filter for audio echo effect.
|
|
981
1265
|
*
|
|
982
|
-
*
|
|
983
|
-
*
|
|
984
|
-
* -
|
|
985
|
-
* -
|
|
986
|
-
*
|
|
1266
|
+
* @param in_gain - Input gain (0-1)
|
|
1267
|
+
* @param out_gain - Output gain (0-1)
|
|
1268
|
+
* @param delays - Delay in milliseconds
|
|
1269
|
+
* @param decays - Decay factor (0-1)
|
|
1270
|
+
* @returns This instance for chaining
|
|
987
1271
|
*
|
|
988
|
-
* @
|
|
989
|
-
*
|
|
1272
|
+
* @example
|
|
1273
|
+
* ```typescript
|
|
1274
|
+
* chain.aecho(0.8, 0.9, 1000, 0.3) // Echo with 1 second delay
|
|
1275
|
+
* ```
|
|
990
1276
|
*
|
|
1277
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#aecho | FFmpeg aecho filter}
|
|
991
1278
|
*/
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
}
|
|
996
|
-
// Special handling for different hardware transpose implementations
|
|
997
|
-
let filterName;
|
|
998
|
-
if (this.deviceType === AV_HWDEVICE_TYPE_CUDA) {
|
|
999
|
-
filterName = 'transpose_cuda'; // Uses transpose_cuda from patch, not NPP
|
|
1000
|
-
}
|
|
1001
|
-
else if (this.deviceType === AV_HWDEVICE_TYPE_VIDEOTOOLBOX) {
|
|
1002
|
-
filterName = 'transpose_vt'; // CoreImage-based transpose
|
|
1003
|
-
}
|
|
1004
|
-
else {
|
|
1005
|
-
filterName = `transpose_${this.deviceTypeName}`;
|
|
1006
|
-
}
|
|
1007
|
-
return `${filterName}=dir=${dir}`;
|
|
1279
|
+
aecho(in_gain, out_gain, delays, decays) {
|
|
1280
|
+
this.add(`aecho=${in_gain}:${out_gain}:${delays}:${decays}`);
|
|
1281
|
+
return this;
|
|
1008
1282
|
}
|
|
1009
1283
|
/**
|
|
1010
|
-
*
|
|
1011
|
-
* Used for HDR to SDR conversion with hardware acceleration.
|
|
1284
|
+
* Adds a highpass filter to remove low frequencies.
|
|
1012
1285
|
*
|
|
1013
|
-
* @param
|
|
1014
|
-
* @
|
|
1286
|
+
* @param frequency - Cutoff frequency in Hz
|
|
1287
|
+
* @param options - Additional filter options
|
|
1288
|
+
* @returns This instance for chaining
|
|
1289
|
+
*
|
|
1290
|
+
* @example
|
|
1291
|
+
* ```typescript
|
|
1292
|
+
* chain.highpass(200) // Remove frequencies below 200Hz
|
|
1293
|
+
* chain.highpass(200, { width_type: 'q', width: 1 })
|
|
1294
|
+
* ```
|
|
1015
1295
|
*
|
|
1296
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#highpass | FFmpeg highpass filter}
|
|
1016
1297
|
*/
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
return null;
|
|
1020
|
-
}
|
|
1021
|
-
// VideoToolbox uses different filter name
|
|
1022
|
-
const filterName = this.deviceType === AV_HWDEVICE_TYPE_VIDEOTOOLBOX ? 'tonemap_videotoolbox' : `tonemap_${this.deviceTypeName}`;
|
|
1023
|
-
let filter = filterName;
|
|
1298
|
+
highpass(frequency, options) {
|
|
1299
|
+
let filter = `highpass=f=${frequency}`;
|
|
1024
1300
|
if (options) {
|
|
1025
|
-
const
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
filter += `=${opts}`;
|
|
1301
|
+
for (const [key, value] of Object.entries(options)) {
|
|
1302
|
+
filter += `:${key}=${value}`;
|
|
1303
|
+
}
|
|
1029
1304
|
}
|
|
1030
|
-
|
|
1305
|
+
this.add(filter);
|
|
1306
|
+
return this;
|
|
1031
1307
|
}
|
|
1032
1308
|
/**
|
|
1033
|
-
*
|
|
1309
|
+
* Adds a lowpass filter to remove high frequencies.
|
|
1034
1310
|
*
|
|
1035
|
-
*
|
|
1036
|
-
* -
|
|
1037
|
-
*
|
|
1038
|
-
* - QSV: deinterlace_qsv
|
|
1039
|
-
* - Vulkan: bwdif_vulkan
|
|
1040
|
-
* - VideoToolbox: yadif_videotoolbox
|
|
1311
|
+
* @param frequency - Cutoff frequency in Hz
|
|
1312
|
+
* @param options - Additional filter options
|
|
1313
|
+
* @returns This instance for chaining
|
|
1041
1314
|
*
|
|
1042
|
-
* @
|
|
1043
|
-
*
|
|
1315
|
+
* @example
|
|
1316
|
+
* ```typescript
|
|
1317
|
+
* chain.lowpass(5000) // Remove frequencies above 5000Hz
|
|
1318
|
+
* ```
|
|
1044
1319
|
*
|
|
1320
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#lowpass | FFmpeg lowpass filter}
|
|
1045
1321
|
*/
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
return mode ? `yadif_cuda=mode=${mode}` : 'yadif_cuda';
|
|
1053
|
-
case AV_HWDEVICE_TYPE_VAAPI:
|
|
1054
|
-
return mode ? `deinterlace_vaapi=mode=${mode}` : 'deinterlace_vaapi';
|
|
1055
|
-
case AV_HWDEVICE_TYPE_QSV:
|
|
1056
|
-
return mode ? `deinterlace_qsv=mode=${mode}` : 'deinterlace_qsv';
|
|
1057
|
-
case AV_HWDEVICE_TYPE_VULKAN:
|
|
1058
|
-
return mode ? `bwdif_vulkan=mode=${mode}` : 'bwdif_vulkan';
|
|
1059
|
-
case AV_HWDEVICE_TYPE_VIDEOTOOLBOX:
|
|
1060
|
-
return mode ? `yadif_videotoolbox=mode=${mode}` : 'yadif_videotoolbox';
|
|
1061
|
-
default:
|
|
1062
|
-
return null;
|
|
1322
|
+
lowpass(frequency, options) {
|
|
1323
|
+
let filter = `lowpass=f=${frequency}`;
|
|
1324
|
+
if (options) {
|
|
1325
|
+
for (const [key, value] of Object.entries(options)) {
|
|
1326
|
+
filter += `:${key}=${value}`;
|
|
1327
|
+
}
|
|
1063
1328
|
}
|
|
1329
|
+
this.add(filter);
|
|
1330
|
+
return this;
|
|
1064
1331
|
}
|
|
1065
1332
|
/**
|
|
1066
|
-
*
|
|
1067
|
-
*
|
|
1333
|
+
* Adds a bandpass filter to keep only a frequency band.
|
|
1334
|
+
*
|
|
1335
|
+
* @param frequency - Center frequency in Hz
|
|
1336
|
+
* @param options - Additional filter options
|
|
1337
|
+
* @returns This instance for chaining
|
|
1068
1338
|
*
|
|
1069
|
-
* @
|
|
1070
|
-
*
|
|
1339
|
+
* @example
|
|
1340
|
+
* ```typescript
|
|
1341
|
+
* chain.bandpass(1000) // Keep frequencies around 1000Hz
|
|
1342
|
+
* ```
|
|
1071
1343
|
*
|
|
1344
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#bandpass | FFmpeg bandpass filter}
|
|
1072
1345
|
*/
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1346
|
+
bandpass(frequency, options) {
|
|
1347
|
+
let filter = `bandpass=f=${frequency}`;
|
|
1348
|
+
if (options) {
|
|
1349
|
+
for (const [key, value] of Object.entries(options)) {
|
|
1350
|
+
filter += `:${key}=${value}`;
|
|
1351
|
+
}
|
|
1079
1352
|
}
|
|
1080
|
-
|
|
1353
|
+
this.add(filter);
|
|
1354
|
+
return this;
|
|
1081
1355
|
}
|
|
1082
1356
|
/**
|
|
1083
|
-
*
|
|
1357
|
+
* Adds an equalizer filter for frequency band adjustment.
|
|
1084
1358
|
*
|
|
1085
|
-
*
|
|
1086
|
-
* -
|
|
1087
|
-
* -
|
|
1088
|
-
* -
|
|
1359
|
+
* @param frequency - Center frequency in Hz
|
|
1360
|
+
* @param width - Band width
|
|
1361
|
+
* @param gain - Gain in dB
|
|
1362
|
+
* @param width_type - Width type (optional)
|
|
1363
|
+
* @returns This instance for chaining
|
|
1089
1364
|
*
|
|
1090
|
-
* @
|
|
1091
|
-
*
|
|
1092
|
-
*
|
|
1365
|
+
* @example
|
|
1366
|
+
* ```typescript
|
|
1367
|
+
* chain.equalizer(1000, 2, 5) // Boost 1000Hz by 5dB
|
|
1368
|
+
* chain.equalizer(1000, 2, 5, 'q') // Use Q factor for width
|
|
1369
|
+
* ```
|
|
1093
1370
|
*
|
|
1371
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#equalizer | FFmpeg equalizer filter}
|
|
1094
1372
|
*/
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
switch (this.deviceType) {
|
|
1100
|
-
case AV_HWDEVICE_TYPE_CUDA:
|
|
1101
|
-
return radius ? `bilateral_cuda=sigmaS=${radius}` : 'bilateral_cuda';
|
|
1102
|
-
case AV_HWDEVICE_TYPE_VULKAN:
|
|
1103
|
-
return type === 'gaussian' ? (radius ? `gblur_vulkan=sigma=${radius}` : 'gblur_vulkan') : radius ? `avgblur_vulkan=sizeX=${radius}` : 'avgblur_vulkan';
|
|
1104
|
-
case AV_HWDEVICE_TYPE_OPENCL:
|
|
1105
|
-
return type === 'box' ? (radius ? `boxblur_opencl=luma_radius=${radius}` : 'boxblur_opencl') : radius ? `avgblur_opencl=sizeX=${radius}` : 'avgblur_opencl';
|
|
1106
|
-
default:
|
|
1107
|
-
return null;
|
|
1373
|
+
equalizer(frequency, width, gain, width_type) {
|
|
1374
|
+
let filter = `equalizer=f=${frequency}`;
|
|
1375
|
+
if (width_type) {
|
|
1376
|
+
filter += `:width_type=${width_type}`;
|
|
1108
1377
|
}
|
|
1378
|
+
filter += `:width=${width}:gain=${gain}`;
|
|
1379
|
+
this.add(filter);
|
|
1380
|
+
return this;
|
|
1109
1381
|
}
|
|
1110
1382
|
/**
|
|
1111
|
-
*
|
|
1383
|
+
* Adds a compressor filter for dynamic range compression.
|
|
1112
1384
|
*
|
|
1113
|
-
*
|
|
1114
|
-
*
|
|
1115
|
-
* - OpenCL: unsharp_opencl
|
|
1116
|
-
* - CUDA: sharpen_npp (NPP-based)
|
|
1385
|
+
* @param options - Compressor parameters
|
|
1386
|
+
* @returns This instance for chaining
|
|
1117
1387
|
*
|
|
1118
|
-
* @
|
|
1119
|
-
*
|
|
1388
|
+
* @example
|
|
1389
|
+
* ```typescript
|
|
1390
|
+
* chain.compressor() // Default compression
|
|
1391
|
+
* chain.compressor({
|
|
1392
|
+
* threshold: 0.5,
|
|
1393
|
+
* ratio: 4,
|
|
1394
|
+
* attack: 5,
|
|
1395
|
+
* release: 50
|
|
1396
|
+
* })
|
|
1397
|
+
* ```
|
|
1120
1398
|
*
|
|
1399
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#acompressor | FFmpeg acompressor filter}
|
|
1121
1400
|
*/
|
|
1122
|
-
|
|
1123
|
-
if (!
|
|
1124
|
-
|
|
1401
|
+
compressor(options) {
|
|
1402
|
+
if (!options || Object.keys(options).length === 0) {
|
|
1403
|
+
this.add('acompressor');
|
|
1125
1404
|
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
default:
|
|
1135
|
-
return null;
|
|
1405
|
+
else {
|
|
1406
|
+
let filter = 'acompressor';
|
|
1407
|
+
const params = [];
|
|
1408
|
+
for (const [key, value] of Object.entries(options)) {
|
|
1409
|
+
params.push(`${key}=${value}`);
|
|
1410
|
+
}
|
|
1411
|
+
filter += '=' + params.join(':');
|
|
1412
|
+
this.add(filter);
|
|
1136
1413
|
}
|
|
1414
|
+
return this;
|
|
1137
1415
|
}
|
|
1138
1416
|
/**
|
|
1139
|
-
*
|
|
1140
|
-
* Only VAAPI and QSV support hardware stacking.
|
|
1417
|
+
* Adds an atrim filter to trim audio.
|
|
1141
1418
|
*
|
|
1142
|
-
* @param
|
|
1143
|
-
* @param
|
|
1144
|
-
* @
|
|
1419
|
+
* @param start - Start time in seconds
|
|
1420
|
+
* @param end - End time in seconds (optional)
|
|
1421
|
+
* @param duration - Duration in seconds (optional)
|
|
1422
|
+
* @returns This instance for chaining
|
|
1423
|
+
*
|
|
1424
|
+
* @example
|
|
1425
|
+
* ```typescript
|
|
1426
|
+
* chain.atrim(10, 20) // Extract audio from 10s to 20s
|
|
1427
|
+
* ```
|
|
1145
1428
|
*
|
|
1429
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#atrim | FFmpeg atrim filter}
|
|
1146
1430
|
*/
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
if (
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
return
|
|
1431
|
+
atrim(start, end, duration) {
|
|
1432
|
+
let filter = `atrim=start=${start}`;
|
|
1433
|
+
if (end !== undefined)
|
|
1434
|
+
filter += `:end=${end}`;
|
|
1435
|
+
if (duration !== undefined)
|
|
1436
|
+
filter += `:duration=${duration}`;
|
|
1437
|
+
this.add(filter);
|
|
1438
|
+
return this;
|
|
1155
1439
|
}
|
|
1156
1440
|
/**
|
|
1157
|
-
*
|
|
1158
|
-
* CUDA uses hwupload_cuda, others use generic hwupload.
|
|
1441
|
+
* Adds a hwupload filter to upload frames to hardware.
|
|
1159
1442
|
*
|
|
1160
|
-
* @returns
|
|
1443
|
+
* @returns This instance for chaining
|
|
1161
1444
|
*
|
|
1162
|
-
* @
|
|
1445
|
+
* @example
|
|
1446
|
+
* ```typescript
|
|
1447
|
+
* const chain = FilterPresets.chain()
|
|
1448
|
+
* .hwupload()
|
|
1449
|
+
* .scale(1920, 1080)
|
|
1450
|
+
* .build();
|
|
1451
|
+
* ```
|
|
1163
1452
|
*/
|
|
1164
1453
|
hwupload() {
|
|
1165
|
-
if (this.deviceType === AV_HWDEVICE_TYPE_CUDA) {
|
|
1166
|
-
|
|
1454
|
+
if (this.hardware?.deviceType === AV_HWDEVICE_TYPE_CUDA) {
|
|
1455
|
+
this.add('hwupload_cuda');
|
|
1456
|
+
}
|
|
1457
|
+
else {
|
|
1458
|
+
this.add('hwupload');
|
|
1167
1459
|
}
|
|
1168
|
-
return
|
|
1460
|
+
return this;
|
|
1169
1461
|
}
|
|
1170
1462
|
/**
|
|
1171
|
-
*
|
|
1463
|
+
* Adds a hwdownload filter to download frames from hardware.
|
|
1172
1464
|
*
|
|
1173
|
-
* @returns
|
|
1465
|
+
* @returns This instance for chaining
|
|
1174
1466
|
*
|
|
1175
|
-
* @
|
|
1467
|
+
* @example
|
|
1468
|
+
* ```typescript
|
|
1469
|
+
* const chain = FilterPresets.chain()
|
|
1470
|
+
* .scale(1920, 1080)
|
|
1471
|
+
* .hwdownload()
|
|
1472
|
+
* .build();
|
|
1473
|
+
* ```
|
|
1176
1474
|
*/
|
|
1177
1475
|
hwdownload() {
|
|
1178
|
-
|
|
1476
|
+
this.add('hwdownload');
|
|
1477
|
+
return this;
|
|
1179
1478
|
}
|
|
1180
1479
|
/**
|
|
1181
|
-
*
|
|
1480
|
+
* Adds a hwmap filter to map frames between hardware devices.
|
|
1182
1481
|
*
|
|
1183
1482
|
* @param derive - Device to derive from (optional)
|
|
1184
|
-
* @returns Hardware map filter string
|
|
1185
1483
|
*
|
|
1186
|
-
* @
|
|
1484
|
+
* @returns This instance for chaining
|
|
1485
|
+
*
|
|
1486
|
+
* @example
|
|
1487
|
+
* ```typescript
|
|
1488
|
+
* const chain = FilterPresets.chain()
|
|
1489
|
+
* .hwmap('cuda')
|
|
1490
|
+
* .build();
|
|
1491
|
+
* ```
|
|
1492
|
+
*
|
|
1493
|
+
* @example
|
|
1494
|
+
* ```typescript
|
|
1495
|
+
* const chain = FilterPresets.chain()
|
|
1496
|
+
* .hwmap()
|
|
1497
|
+
* .build();
|
|
1498
|
+
* ```
|
|
1187
1499
|
*/
|
|
1188
1500
|
hwmap(derive) {
|
|
1189
|
-
|
|
1501
|
+
this.add(derive ? `hwmap=derive_device=${derive}` : 'hwmap');
|
|
1502
|
+
return this;
|
|
1190
1503
|
}
|
|
1191
1504
|
/**
|
|
1192
|
-
*
|
|
1505
|
+
* Adds a filter to the chain.
|
|
1193
1506
|
*
|
|
1194
|
-
* @
|
|
1507
|
+
* @param filter - Filter string to add (ignored if null/undefined)
|
|
1508
|
+
* @returns This instance for chaining
|
|
1195
1509
|
*
|
|
1196
1510
|
* @example
|
|
1197
1511
|
* ```typescript
|
|
1198
|
-
*
|
|
1199
|
-
* if (caps.scale && caps.overlay) {
|
|
1200
|
-
* console.log('Hardware supports scaling and overlay');
|
|
1201
|
-
* }
|
|
1512
|
+
* chain.add('scale=1920:1080')
|
|
1202
1513
|
* ```
|
|
1514
|
+
*
|
|
1515
|
+
* @internal
|
|
1203
1516
|
*/
|
|
1204
|
-
|
|
1205
|
-
|
|
1517
|
+
add(filter) {
|
|
1518
|
+
if (filter) {
|
|
1519
|
+
this.filters.push(filter);
|
|
1520
|
+
}
|
|
1521
|
+
return this;
|
|
1206
1522
|
}
|
|
1207
1523
|
/**
|
|
1208
1524
|
* Determines filter support for the hardware type.
|
|
@@ -1212,7 +1528,25 @@ export class HardwareFilterPresets extends FilterPresetBase {
|
|
|
1212
1528
|
* @internal
|
|
1213
1529
|
*/
|
|
1214
1530
|
getSupport() {
|
|
1215
|
-
|
|
1531
|
+
if (!this.hardware) {
|
|
1532
|
+
// Software-only - all filters supported
|
|
1533
|
+
return {
|
|
1534
|
+
scale: true,
|
|
1535
|
+
overlay: true,
|
|
1536
|
+
transpose: true,
|
|
1537
|
+
tonemap: true,
|
|
1538
|
+
deinterlace: true,
|
|
1539
|
+
denoise: true,
|
|
1540
|
+
flip: true,
|
|
1541
|
+
blur: true,
|
|
1542
|
+
sharpen: true,
|
|
1543
|
+
chromakey: true,
|
|
1544
|
+
colorspace: true,
|
|
1545
|
+
pad: true,
|
|
1546
|
+
stack: true,
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1549
|
+
switch (this.hardware.deviceType) {
|
|
1216
1550
|
case AV_HWDEVICE_TYPE_CUDA:
|
|
1217
1551
|
return {
|
|
1218
1552
|
scale: true, // scale_cuda
|
|
@@ -1387,21 +1721,4 @@ export class HardwareFilterPresets extends FilterPresetBase {
|
|
|
1387
1721
|
}
|
|
1388
1722
|
}
|
|
1389
1723
|
}
|
|
1390
|
-
/**
|
|
1391
|
-
* Hardware filter chain builder with fluent API.
|
|
1392
|
-
* Automatically skips unsupported filters (returns null) allowing graceful fallback.
|
|
1393
|
-
*
|
|
1394
|
-
* @example
|
|
1395
|
-
* ```typescript
|
|
1396
|
-
* const hw = new HardwareFilterPresets(AV_HWDEVICE_TYPE_CUDA, 'cuda');
|
|
1397
|
-
* const chain = hw.chain()
|
|
1398
|
-
* .hwupload()
|
|
1399
|
-
* .scale(1920, 1080)
|
|
1400
|
-
* .tonemap() // Skipped if not supported
|
|
1401
|
-
* .hwdownload()
|
|
1402
|
-
* .build();
|
|
1403
|
-
* ```
|
|
1404
|
-
*/
|
|
1405
|
-
export class HardwareFilterChainBuilder extends ChainBuilderBase {
|
|
1406
|
-
}
|
|
1407
1724
|
//# sourceMappingURL=filter-presets.js.map
|