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.
Files changed (99) hide show
  1. package/README.md +37 -59
  2. package/dist/api/bitstream-filter.d.ts +5 -2
  3. package/dist/api/bitstream-filter.js +7 -4
  4. package/dist/api/bitstream-filter.js.map +1 -1
  5. package/dist/api/decoder.d.ts +135 -119
  6. package/dist/api/decoder.js +195 -202
  7. package/dist/api/decoder.js.map +1 -1
  8. package/dist/api/encoder.d.ts +141 -78
  9. package/dist/api/encoder.js +241 -193
  10. package/dist/api/encoder.js.map +1 -1
  11. package/dist/api/filter-presets.d.ts +699 -573
  12. package/dist/api/filter-presets.js +1157 -840
  13. package/dist/api/filter-presets.js.map +1 -1
  14. package/dist/api/filter.d.ts +180 -157
  15. package/dist/api/filter.js +314 -366
  16. package/dist/api/filter.js.map +1 -1
  17. package/dist/api/hardware.d.ts +28 -29
  18. package/dist/api/hardware.js +80 -74
  19. package/dist/api/hardware.js.map +1 -1
  20. package/dist/api/index.d.ts +1 -1
  21. package/dist/api/index.js +1 -1
  22. package/dist/api/index.js.map +1 -1
  23. package/dist/api/io-stream.d.ts +6 -0
  24. package/dist/api/io-stream.js +6 -0
  25. package/dist/api/io-stream.js.map +1 -1
  26. package/dist/api/media-input.d.ts +2 -1
  27. package/dist/api/media-input.js +3 -8
  28. package/dist/api/media-input.js.map +1 -1
  29. package/dist/api/media-output.d.ts +37 -126
  30. package/dist/api/media-output.js +138 -206
  31. package/dist/api/media-output.js.map +1 -1
  32. package/dist/api/pipeline.d.ts +193 -0
  33. package/dist/api/pipeline.js +36 -42
  34. package/dist/api/pipeline.js.map +1 -1
  35. package/dist/api/types.d.ts +22 -57
  36. package/dist/api/utilities/audio-sample.d.ts +0 -8
  37. package/dist/api/utilities/audio-sample.js +0 -8
  38. package/dist/api/utilities/audio-sample.js.map +1 -1
  39. package/dist/api/utilities/channel-layout.d.ts +0 -8
  40. package/dist/api/utilities/channel-layout.js +0 -8
  41. package/dist/api/utilities/channel-layout.js.map +1 -1
  42. package/dist/api/utilities/image.d.ts +0 -8
  43. package/dist/api/utilities/image.js +0 -8
  44. package/dist/api/utilities/image.js.map +1 -1
  45. package/dist/api/utilities/index.d.ts +3 -3
  46. package/dist/api/utilities/index.js +3 -3
  47. package/dist/api/utilities/index.js.map +1 -1
  48. package/dist/api/utilities/media-type.d.ts +1 -9
  49. package/dist/api/utilities/media-type.js +1 -9
  50. package/dist/api/utilities/media-type.js.map +1 -1
  51. package/dist/api/utilities/pixel-format.d.ts +1 -9
  52. package/dist/api/utilities/pixel-format.js +1 -9
  53. package/dist/api/utilities/pixel-format.js.map +1 -1
  54. package/dist/api/utilities/sample-format.d.ts +1 -9
  55. package/dist/api/utilities/sample-format.js +1 -9
  56. package/dist/api/utilities/sample-format.js.map +1 -1
  57. package/dist/api/utilities/streaming.d.ts +0 -8
  58. package/dist/api/utilities/streaming.js +0 -8
  59. package/dist/api/utilities/streaming.js.map +1 -1
  60. package/dist/api/utilities/timestamp.d.ts +0 -8
  61. package/dist/api/utilities/timestamp.js +0 -8
  62. package/dist/api/utilities/timestamp.js.map +1 -1
  63. package/dist/api/utils.js +2 -0
  64. package/dist/api/utils.js.map +1 -1
  65. package/dist/constants/constants.d.ts +1 -1
  66. package/dist/constants/constants.js +2 -0
  67. package/dist/constants/constants.js.map +1 -1
  68. package/dist/lib/binding.d.ts +1 -0
  69. package/dist/lib/binding.js +2 -0
  70. package/dist/lib/binding.js.map +1 -1
  71. package/dist/lib/codec.d.ts +4 -4
  72. package/dist/lib/codec.js +4 -4
  73. package/dist/lib/dictionary.d.ts +2 -2
  74. package/dist/lib/dictionary.js +2 -2
  75. package/dist/lib/dictionary.js.map +1 -1
  76. package/dist/lib/error.d.ts +1 -1
  77. package/dist/lib/error.js +1 -1
  78. package/dist/lib/filter-context.d.ts +19 -2
  79. package/dist/lib/filter-context.js +15 -0
  80. package/dist/lib/filter-context.js.map +1 -1
  81. package/dist/lib/format-context.d.ts +18 -18
  82. package/dist/lib/format-context.js +20 -20
  83. package/dist/lib/format-context.js.map +1 -1
  84. package/dist/lib/frame.d.ts +43 -1
  85. package/dist/lib/frame.js +53 -0
  86. package/dist/lib/frame.js.map +1 -1
  87. package/dist/lib/index.d.ts +1 -1
  88. package/dist/lib/index.js +1 -1
  89. package/dist/lib/index.js.map +1 -1
  90. package/dist/lib/native-types.d.ts +1 -0
  91. package/dist/lib/option.d.ts +176 -0
  92. package/dist/lib/option.js +176 -0
  93. package/dist/lib/option.js.map +1 -1
  94. package/dist/lib/utilities.d.ts +64 -1
  95. package/dist/lib/utilities.js +65 -0
  96. package/dist/lib/utilities.js.map +1 -1
  97. package/install/ffmpeg.js +0 -11
  98. package/package.json +16 -18
  99. 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
- * Base class for filter preset implementations.
6
- * Provides common filter building methods that can be overridden by hardware-specific implementations.
7
- *
8
- * This class defines the standard filter operations available in FFmpeg,
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
- * class CustomPresets extends FilterPresetBase {
16
- * override scale(width: number, height: number): string | null {
17
- * return `custom_scale=${width}:${height}`;
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 FilterPresetBase {
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
- * Creates a scale filter string.
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 Filter string or null if not supported
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
- const flags = options?.flags;
35
- const base = `scale=${width}:${height}`;
36
- return flags ? `${base}:flags=${flags}` : base;
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
- * Creates a crop filter string.
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 Filter string or null if not supported
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
- return `crop=${width}:${height}:${x}:${y}`;
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
- * Creates an FPS filter string to change frame rate.
308
+ * Adds an FPS filter to change frame rate.
54
309
  *
55
310
  * @param fps - Target frames per second
56
- * @returns Filter string or null if not supported
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
- return `fps=${fps}`;
322
+ this.add(`fps=${fps}`);
323
+ return this;
62
324
  }
63
325
  /**
64
- * Creates a format filter string to convert pixel format.
326
+ * Adds a format filter to convert pixel format.
65
327
  *
66
- * @param pixelFormat - Target pixel format(s) - can be string, AVPixelFormat enum, or array
67
- * @returns Filter string or null if not supported
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
- * presets.format('yuv420p');
73
- * presets.format(AV_PIX_FMT_YUV420P);
334
+ * chain.format(AV_PIX_FMT_YUV420P);
74
335
  *
75
- * // Multiple formats (creates a chain)
76
- * presets.format(['yuv420p', 'rgb24']);
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
- return formats.join(',');
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
- const formatName = typeof pixelFormat === 'string' ? pixelFormat : (avGetPixFmtName(pixelFormat) ?? 'yuv420p');
91
- return `format=${formatName}`;
355
+ return this;
92
356
  }
93
357
  /**
94
- * Creates a rotate filter string.
358
+ * Adds a rotate filter to the chain.
95
359
  *
96
360
  * @param angle - Rotation angle in degrees
97
- * @returns Filter string or null if not supported
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
- return `rotate=${angle}*PI/180`;
372
+ this.add(`rotate=${angle}*PI/180`);
373
+ return this;
103
374
  }
104
375
  /**
105
- * Creates a horizontal flip filter string.
376
+ * Adds a flip filter to the chain (hardware-specific).
377
+ * Falls back to hflip/vflip if hardware flip not available
106
378
  *
107
- * @returns Filter string or null if not supported
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
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#hflip | FFmpeg hflip filter}
437
+ * @example
438
+ * ```typescript
439
+ * const chain = FilterPresets.chain()
440
+ * .stack('x', 4)
441
+ * .build();
442
+ * ```
110
443
  */
111
- hflip() {
112
- return 'hflip';
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 vertical flip filter string.
461
+ * Creates a tonemap filter.
462
+ * Used for HDR to SDR conversion with hardware acceleration.
116
463
  *
117
- * @returns Filter string or null if not supported
464
+ * @param alg - Tonemapping algorithm (e.g., 'hable', 'reinhard', 'mobius', etc.)
465
+ * @param options - Tonemapping options
118
466
  *
119
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#vflip | FFmpeg vflip filter}
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
- vflip() {
122
- return 'vflip';
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
- return `fade=t=${type}:st=${start}:d=${duration}`;
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
- let filter = `overlay=${x}:${y}`;
158
- if (options) {
159
- for (const [key, value] of Object.entries(options)) {
160
- filter += `:${key}=${value}`;
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
- return filter;
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
- return `volume=${factor}`;
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
- const formatName = typeof sampleFormat === 'string' ? sampleFormat : (avGetSampleFmtName(sampleFormat) ?? 's16');
200
- let filter = `aformat=sample_fmts=${formatName}`;
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
- return filter;
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 a format filter to the chain.
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 pixelFormat - Target pixel format(s)
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
- * @see {@link FilterPresetBase.format}
370
- */
371
- format(pixelFormat) {
372
- return this.add(this.presets.format(pixelFormat));
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
- * @param angle - Rotation angle in degrees
378
- * @returns This instance for chaining
639
+ * // Drop samples instead of padding
640
+ * chain.asetnsamples(1024, false);
641
+ * ```
379
642
  *
380
- * @see {@link FilterPresetBase.rotate}
643
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#asetnsamples | FFmpeg asetnsamples filter}
381
644
  */
382
- rotate(angle) {
383
- return this.add(this.presets.rotate(angle));
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 a horizontal flip filter to the chain.
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
- * @see {@link FilterPresetBase.hflip}
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
- hflip() {
393
- return this.add(this.presets.hflip());
664
+ aresample(rate) {
665
+ this.add(`aresample=${rate}`);
666
+ return this;
394
667
  }
395
668
  /**
396
- * Adds a vertical flip filter to the chain.
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
- * @see {@link FilterPresetBase.vflip}
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
- vflip() {
403
- return this.add(this.presets.vflip());
683
+ atempo(factor) {
684
+ this.add(`atempo=${factor}`);
685
+ return this;
404
686
  }
405
687
  /**
406
- * Adds a fade filter to the chain.
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
- * @see {@link FilterPresetBase.fade}
414
- */
415
- fade(type, start, duration) {
416
- return this.add(this.presets.fade(type, start, duration));
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 FilterPresetBase.overlay}
701
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#afade | FFmpeg afade filter}
427
702
  */
428
- overlay(x = 0, y = 0, options) {
429
- return this.add(this.presets.overlay(x, y, options));
703
+ afade(type, start, duration) {
704
+ this.add(`afade=t=${type}:st=${start}:d=${duration}`);
705
+ return this;
430
706
  }
431
707
  /**
432
- * Adds a volume filter to the chain.
708
+ * Adds an amix filter to mix multiple audio streams.
433
709
  *
434
- * @param factor - Volume factor
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
- * @see {@link FilterPresetBase.volume}
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
- volume(factor) {
440
- return this.add(this.presets.volume(factor));
722
+ amix(inputs = 2, duration = 'longest') {
723
+ this.add(`amix=inputs=${inputs}:duration=${duration}`);
724
+ return this;
441
725
  }
442
726
  /**
443
- * Adds an audio format filter to the chain.
727
+ * Adds a pad filter to add padding to video.
728
+ * Essential for aspect ratio adjustments and letterboxing.
444
729
  *
445
- * @param sampleFormat - Target sample format
446
- * @param sampleRate - Target sample rate (optional)
447
- * @param channelLayout - Target channel layout (optional)
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
- * @see {@link FilterPresetBase.aformat}
451
- */
452
- aformat(sampleFormat, sampleRate, channelLayout) {
453
- return this.add(this.presets.aformat(sampleFormat, sampleRate, channelLayout));
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
- * @param factor - Tempo factor (0.5 to 2.0)
459
- * @returns This instance for chaining
742
+ * // Add 50px padding on all sides
743
+ * chain.pad('iw+100', 'ih+100');
744
+ * ```
460
745
  *
461
- * @see {@link FilterPresetBase.atempo}
746
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#pad | FFmpeg pad filter}
462
747
  */
463
- atempo(factor) {
464
- return this.add(this.presets.atempo(factor));
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 an audio fade filter to the chain.
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 duration - Fade duration in seconds
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
- * @see {@link FilterPresetBase.afade}
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
- afade(type, start, duration) {
477
- return this.add(this.presets.afade(type, start, duration));
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
- * Adds an amix filter to the chain.
785
+ * Creates a setpts filter string to change presentation timestamps.
786
+ * Essential for speed changes and timestamp manipulation.
481
787
  *
482
- * @param inputs - Number of inputs (default: 2)
483
- * @param duration - Duration mode (default: 'longest')
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
- * @see {@link FilterPresetBase.amix}
487
- */
488
- amix(inputs = 2, duration = 'longest') {
489
- return this.add(this.presets.amix(inputs, duration));
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
- * @param dir - Transpose direction (default: 0)
496
- * @returns This instance for chaining
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
- transpose(dir = 0) {
500
- if ('transpose' in this.presets) {
501
- return this.add(this.presets.transpose(dir));
502
- }
503
- return this.add(null);
805
+ setpts(expression) {
806
+ this.add(`setpts=${expression}`);
807
+ return this;
504
808
  }
505
809
  /**
506
- * Adds a tonemap filter to the chain (hardware-specific).
507
- * Only available for hardware presets that support tonemapping
810
+ * Creates an asetpts filter string for audio timestamp manipulation.
508
811
  *
509
- * @param options - Tonemapping options
510
- * @returns This instance for chaining
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
- tonemap(options) {
514
- if ('tonemap' in this.presets) {
515
- return this.add(this.presets.tonemap(options));
516
- }
517
- return this.add(null);
822
+ asetpts(expression) {
823
+ this.add(`asetpts=${expression}`);
824
+ return this;
518
825
  }
519
826
  /**
520
- * Adds a deinterlace filter to the chain (hardware-specific).
521
- * Only available for hardware presets that support deinterlacing
827
+ * Creates a transpose filter string for rotation/flipping.
828
+ * More efficient than rotate for 90-degree rotations.
522
829
  *
523
- * @param mode - Deinterlace mode (optional)
524
- * @returns This instance for chaining
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
- deinterlace(mode) {
528
- if ('deinterlace' in this.presets) {
529
- return this.add(this.presets.deinterlace(mode));
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.add(null);
885
+ return this;
532
886
  }
533
887
  /**
534
- * Adds a flip filter to the chain (hardware-specific).
535
- * Falls back to hflip/vflip if hardware flip not available
888
+ * Creates a setsar filter string to set sample aspect ratio.
889
+ * Important for correcting aspect ratio issues.
536
890
  *
537
- * @param direction - Flip direction ('h' or 'v')
538
- * @returns This instance for chaining
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
- flip(direction) {
542
- if ('flip' in this.presets) {
543
- return this.add(this.presets.flip(direction));
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
- * Adds a blur filter to the chain (hardware-specific).
550
- * Only available for hardware presets that support blur
907
+ * Creates a setdar filter string to set display aspect ratio.
551
908
  *
552
- * @param type - Blur type (default: 'avg')
553
- * @param radius - Blur radius (optional)
554
- * @returns This instance for chaining
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
- blur(type = 'avg', radius) {
558
- if ('blur' in this.presets) {
559
- return this.add(this.presets.blur(type, radius));
560
- }
561
- return this.add(null);
920
+ setdar(ratio) {
921
+ this.add(`setdar=${ratio}`);
922
+ return this;
562
923
  }
563
924
  /**
564
- * Adds a sharpen filter to the chain (hardware-specific).
565
- * Only available for hardware presets that support sharpening
925
+ * Adds an apad filter to add audio padding.
926
+ * Useful for ensuring minimum audio duration.
566
927
  *
567
- * @param amount - Sharpen amount (optional)
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
- sharpen(amount) {
572
- if ('sharpen' in this.presets) {
573
- return this.add(this.presets.sharpen(amount));
574
- }
575
- return this.add(null);
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
- * Adds a stack filter to the chain (hardware-specific).
579
- * Only available for hardware presets that support stacking
952
+ * Creates a deinterlace filter string.
953
+ * Essential for processing interlaced content.
580
954
  *
581
- * @param type - Stack type ('h' for horizontal, 'v' for vertical, 'x' for grid)
582
- * @param inputs - Number of inputs (default: 2)
583
- * @returns This instance for chaining
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
- stack(type, inputs = 2) {
587
- if ('stack' in this.presets) {
588
- return this.add(this.presets.stack(type, inputs));
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
- * @returns This instance for chaining
965
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#yadif | FFmpeg yadif filter}
596
966
  */
597
- hwupload() {
598
- if ('hwupload' in this.presets) {
599
- return this.add(this.presets.hwupload());
967
+ deinterlace(mode = 'yadif', options) {
968
+ if (!this.support.deinterlace) {
969
+ return this;
600
970
  }
601
- return this.add('hwupload');
602
- }
603
- /**
604
- * Adds a hwdownload filter to download frames from hardware.
605
- *
606
- * @returns This instance for chaining
607
- */
608
- hwdownload() {
609
- if ('hwdownload' in this.presets) {
610
- return this.add(this.presets.hwdownload());
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
- return this.add('hwdownload');
613
- }
614
- /**
615
- * Adds a hwmap filter to map frames between hardware devices.
616
- *
617
- * @param derive - Device to derive from (optional)
618
- * @returns This instance for chaining
619
- */
620
- hwmap(derive) {
621
- if ('hwmap' in this.presets) {
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.add(derive ? `hwmap=derive_device=${derive}` : 'hwmap');
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 new filter chain builder.
1017
+ * Creates a select filter string to select specific frames.
1018
+ * Powerful for extracting keyframes, specific frame types, etc.
666
1019
  *
667
- * @returns A new FilterChainBuilder instance
1020
+ * @param expression - Selection expression
1021
+ * @returns Filter string or null if not supported
668
1022
  *
669
1023
  * @example
670
1024
  * ```typescript
671
- * const filter = FilterPresets.chain()
672
- * .scale(1280, 720)
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
- * @param width - Target width
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
- static scale(width, height, flags) {
689
- const result = FilterPresets.instance.scale(width, height, { flags });
690
- return result ?? '';
1031
+ select(expression) {
1032
+ this.add(`select='${expression}'`);
1033
+ return this;
691
1034
  }
692
1035
  /**
693
- * Creates a crop filter string.
1036
+ * Creates an aselect filter string for audio selection.
694
1037
  *
695
- * @param width - Crop width
696
- * @param height - Crop height
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
- * @param fps - Target frame rate
709
- * @returns FPS filter string
710
- */
711
- static fps(fps) {
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
- * @param pixelFormat - Target pixel format(s)
719
- * @returns Format filter string
1046
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#aselect | FFmpeg aselect filter}
720
1047
  */
721
- static format(pixelFormat) {
722
- const result = FilterPresets.instance.format(pixelFormat);
723
- return result ?? '';
1048
+ aselect(expression) {
1049
+ this.add(`aselect='${expression}'`);
1050
+ return this;
724
1051
  }
725
1052
  /**
726
- * Creates a rotate filter string.
1053
+ * Creates a concat filter string to concatenate multiple inputs.
1054
+ * Essential for joining multiple video/audio segments.
727
1055
  *
728
- * @param angle - Rotation angle in degrees
729
- * @returns Rotate filter string
730
- */
731
- static rotate(angle) {
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
- * @returns Horizontal flip filter string
739
- */
740
- static hflip() {
741
- const result = FilterPresets.instance.hflip();
742
- return result ?? '';
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
- * @returns Vertical flip filter string
1067
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#concat | FFmpeg concat filter}
748
1068
  */
749
- static vflip() {
750
- const result = FilterPresets.instance.vflip();
751
- return result ?? '';
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 a fade filter string.
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 type - Fade type ('in' or 'out')
757
- * @param start - Start time in seconds
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
- * @param x - X position (default: 0)
769
- * @param y - Y position (default: 0)
770
- * @returns Overlay filter string
771
- */
772
- static overlay(x = 0, y = 0) {
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
- * @param factor - Volume multiplication factor
780
- * @returns Volume filter string
1086
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#amerge | FFmpeg amerge filter}
781
1087
  */
782
- static volume(factor) {
783
- const result = FilterPresets.instance.volume(factor);
784
- return result ?? '';
1088
+ amerge(inputs = 2) {
1089
+ this.add(`amerge=inputs=${inputs}`);
1090
+ return this;
785
1091
  }
786
1092
  /**
787
- * Creates an audio format filter string.
1093
+ * Creates a channelmap filter string to remap audio channels.
1094
+ * Critical for audio channel manipulation.
788
1095
  *
789
- * @param sampleFormat - Target sample format
790
- * @param sampleRate - Target sample rate (optional)
791
- * @param channelLayout - Target channel layout (optional)
792
- * @returns Audio format filter string
793
- */
794
- static aformat(sampleFormat, sampleRate, channelLayout) {
795
- const result = FilterPresets.instance.aformat(sampleFormat, sampleRate, channelLayout);
796
- return result ?? '';
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
- * @param factor - Tempo factor (0.5 to 2.0)
802
- * @returns Atempo filter string
1105
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#channelmap | FFmpeg channelmap filter}
803
1106
  */
804
- static atempo(factor) {
805
- const result = FilterPresets.instance.atempo(factor);
806
- return result ?? '';
1107
+ channelmap(map) {
1108
+ this.add(`channelmap=${map}`);
1109
+ return this;
807
1110
  }
808
1111
  /**
809
- * Creates an audio fade filter string.
1112
+ * Creates a channelsplit filter string to split audio channels.
810
1113
  *
811
- * @param type - Fade type ('in' or 'out')
812
- * @param start - Start time in seconds
813
- * @param duration - Fade duration in seconds
814
- * @returns Audio fade filter string
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
- static afade(type, start, duration) {
817
- const result = FilterPresets.instance.afade(type, start, duration);
818
- return result ?? '';
1125
+ channelsplit(channelLayout) {
1126
+ this.add(channelLayout ? `channelsplit=channel_layout=${channelLayout}` : 'channelsplit');
1127
+ return this;
819
1128
  }
820
1129
  /**
821
- * Creates an amix filter string.
1130
+ * Creates a loudnorm filter string for loudness normalization.
1131
+ * Essential for broadcast compliance and consistent audio levels.
822
1132
  *
823
- * @param inputs - Number of inputs (default: 2)
824
- * @param duration - Duration mode (default: 'longest')
825
- * @returns Amix filter string
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
- static amix(inputs = 2, duration = 'longest') {
828
- const result = FilterPresets.instance.amix(inputs, duration);
829
- return result ?? '';
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
- * @param deviceType - Hardware device type enum
862
- * @param deviceTypeName - Hardware device type name (e.g., 'cuda', 'vaapi')
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
- constructor(deviceType, deviceTypeName) {
865
- super();
866
- this.deviceType = deviceType;
867
- this.deviceTypeName = deviceTypeName;
868
- this.support = this.getSupport();
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
- * Checks if a filter is hardware-accelerated.
1175
+ * Adds a drawtext filter to overlay text on video.
872
1176
  *
873
- * @param filterName - Name of the filter to check
874
- * @returns True if the filter uses hardware acceleration
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
- * if (HardwareFilterPresets.isHardwareFilter('scale_cuda')) {
879
- * console.log('Hardware accelerated scaling');
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
- static isHardwareFilter(filterName) {
884
- const filter = Filter.getByName(filterName);
885
- if (!filter) {
886
- return false;
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
- // Check if filter has hardware device flag
889
- return (filter.flags & AVFILTER_FLAG_HWDEVICE) !== 0;
1205
+ this.add(filter);
1206
+ return this;
890
1207
  }
891
1208
  /**
892
- * Creates a hardware filter chain builder.
1209
+ * Adds a split filter to duplicate a video stream.
893
1210
  *
894
- * @returns A new HardwareFilterChainBuilder instance
1211
+ * @param outputs - Number of output streams (default: 2)
1212
+ * @returns This instance for chaining
895
1213
  *
896
1214
  * @example
897
1215
  * ```typescript
898
- * const filter = hw.chain()
899
- * .hwupload()
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
- chain() {
906
- return new HardwareFilterChainBuilder(this);
1222
+ split(outputs = 2) {
1223
+ this.add(`split=${outputs}`);
1224
+ return this;
907
1225
  }
908
1226
  /**
909
- * Creates a hardware-accelerated scale filter.
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 width - Target width
919
- * @param height - Target height
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#scale_005fcuda | FFmpeg scale_cuda filter}
1238
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#asplit | FFmpeg asplit filter}
925
1239
  */
926
- scale(width, height, options) {
927
- if (!this.support.scale) {
928
- return null;
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
- * Creates a hardware-accelerated overlay filter.
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
- * @param x - X position (default: 0)
959
- * @param y - Y position (default: 0)
960
- * @param options - Hardware-specific overlay options
961
- * @returns Hardware overlay filter string or null if not supported
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#overlay_005fcuda | FFmpeg overlay_cuda filter}
1256
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#adelay | FFmpeg adelay filter}
964
1257
  */
965
- overlay(x = 0, y = 0, options) {
966
- if (!this.support.overlay) {
967
- return null;
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
- * Creates a hardware-accelerated transpose filter.
1264
+ * Adds an aecho filter for audio echo effect.
981
1265
  *
982
- * Direction values:
983
- * - 0: 90 degrees counter-clockwise and vertical flip
984
- * - 1: 90 degrees clockwise
985
- * - 2: 90 degrees counter-clockwise
986
- * - 3: 90 degrees clockwise and vertical flip
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
- * @param dir - Transpose direction (default: 0)
989
- * @returns Hardware transpose filter string or null if not supported
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
- transpose(dir = 0) {
993
- if (!this.support.transpose) {
994
- return null;
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
- * Creates a hardware-accelerated tonemap filter.
1011
- * Used for HDR to SDR conversion with hardware acceleration.
1284
+ * Adds a highpass filter to remove low frequencies.
1012
1285
  *
1013
- * @param options - Tonemapping options (algorithm, parameters)
1014
- * @returns Hardware tonemap filter string or null if not supported
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
- tonemap(options) {
1018
- if (!this.support.tonemap) {
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 opts = Object.entries(options)
1026
- .map(([k, v]) => `${k}=${v}`)
1027
- .join(':');
1028
- filter += `=${opts}`;
1301
+ for (const [key, value] of Object.entries(options)) {
1302
+ filter += `:${key}=${value}`;
1303
+ }
1029
1304
  }
1030
- return filter;
1305
+ this.add(filter);
1306
+ return this;
1031
1307
  }
1032
1308
  /**
1033
- * Creates a hardware-accelerated deinterlace filter.
1309
+ * Adds a lowpass filter to remove high frequencies.
1034
1310
  *
1035
- * Different hardware types use different deinterlacers:
1036
- * - CUDA: yadif_cuda
1037
- * - VAAPI: deinterlace_vaapi
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
- * @param mode - Deinterlacing mode (optional)
1043
- * @returns Hardware deinterlace filter string or null if not supported
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
- deinterlace(mode) {
1047
- if (!this.support.deinterlace) {
1048
- return null;
1049
- }
1050
- switch (this.deviceType) {
1051
- case AV_HWDEVICE_TYPE_CUDA:
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
- * Creates a hardware-accelerated flip filter.
1067
- * Currently only Vulkan supports hardware flip filters.
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
- * @param direction - Flip direction ('h' for horizontal, 'v' for vertical)
1070
- * @returns Hardware flip filter string or null if not supported
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
- flip(direction) {
1074
- if (!this.support.flip) {
1075
- return null;
1076
- }
1077
- if (this.deviceType === AV_HWDEVICE_TYPE_VULKAN) {
1078
- return direction === 'h' ? 'hflip_vulkan' : 'vflip_vulkan';
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
- return null;
1353
+ this.add(filter);
1354
+ return this;
1081
1355
  }
1082
1356
  /**
1083
- * Creates a hardware-accelerated blur filter.
1357
+ * Adds an equalizer filter for frequency band adjustment.
1084
1358
  *
1085
- * Different hardware types support different blur filters:
1086
- * - CUDA: bilateral_cuda
1087
- * - Vulkan: avgblur_vulkan, gblur_vulkan
1088
- * - OpenCL: avgblur_opencl, boxblur_opencl
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
- * @param type - Blur type ('avg', 'gaussian', or 'box', default: 'avg')
1091
- * @param radius - Blur radius (optional)
1092
- * @returns Hardware blur filter string or null if not supported
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
- blur(type = 'avg', radius) {
1096
- if (!this.support.blur) {
1097
- return null;
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
- * Creates a hardware-accelerated sharpen filter.
1383
+ * Adds a compressor filter for dynamic range compression.
1112
1384
  *
1113
- * Hardware sharpening support:
1114
- * - VAAPI: sharpness_vaapi
1115
- * - OpenCL: unsharp_opencl
1116
- * - CUDA: sharpen_npp (NPP-based)
1385
+ * @param options - Compressor parameters
1386
+ * @returns This instance for chaining
1117
1387
  *
1118
- * @param amount - Sharpening amount (optional)
1119
- * @returns Hardware sharpen filter string or null if not supported
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
- sharpen(amount) {
1123
- if (!this.support.sharpen) {
1124
- return null;
1401
+ compressor(options) {
1402
+ if (!options || Object.keys(options).length === 0) {
1403
+ this.add('acompressor');
1125
1404
  }
1126
- switch (this.deviceType) {
1127
- case AV_HWDEVICE_TYPE_VAAPI:
1128
- return amount ? `sharpness_vaapi=sharpness=${amount}` : 'sharpness_vaapi';
1129
- case AV_HWDEVICE_TYPE_OPENCL:
1130
- return amount ? `unsharp_opencl=amount=${amount}` : 'unsharp_opencl';
1131
- case AV_HWDEVICE_TYPE_CUDA:
1132
- // CUDA uses NPP for sharpening
1133
- return 'sharpen_npp';
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
- * Creates a hardware-accelerated stack filter.
1140
- * Only VAAPI and QSV support hardware stacking.
1417
+ * Adds an atrim filter to trim audio.
1141
1418
  *
1142
- * @param type - Stack type ('h' for horizontal, 'v' for vertical, 'x' for grid)
1143
- * @param inputs - Number of inputs to stack (default: 2)
1144
- * @returns Hardware stack filter string or null if not supported
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
- stack(type, inputs = 2) {
1148
- if (!this.support.stack) {
1149
- return null;
1150
- }
1151
- if (this.deviceType === AV_HWDEVICE_TYPE_VAAPI || this.deviceType === AV_HWDEVICE_TYPE_QSV) {
1152
- return `${type}stack_${this.deviceTypeName}=inputs=${inputs}`;
1153
- }
1154
- return null;
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
- * Creates a hwupload filter to upload frames to hardware memory.
1158
- * CUDA uses hwupload_cuda, others use generic hwupload.
1441
+ * Adds a hwupload filter to upload frames to hardware.
1159
1442
  *
1160
- * @returns Hardware upload filter string
1443
+ * @returns This instance for chaining
1161
1444
  *
1162
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#hwupload | FFmpeg hwupload filter}
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
- return 'hwupload_cuda';
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 'hwupload';
1460
+ return this;
1169
1461
  }
1170
1462
  /**
1171
- * Creates a hwdownload filter to download frames from hardware memory.
1463
+ * Adds a hwdownload filter to download frames from hardware.
1172
1464
  *
1173
- * @returns Hardware download filter string
1465
+ * @returns This instance for chaining
1174
1466
  *
1175
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#hwdownload | FFmpeg hwdownload filter}
1467
+ * @example
1468
+ * ```typescript
1469
+ * const chain = FilterPresets.chain()
1470
+ * .scale(1920, 1080)
1471
+ * .hwdownload()
1472
+ * .build();
1473
+ * ```
1176
1474
  */
1177
1475
  hwdownload() {
1178
- return 'hwdownload';
1476
+ this.add('hwdownload');
1477
+ return this;
1179
1478
  }
1180
1479
  /**
1181
- * Creates a hwmap filter to map frames between hardware devices.
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
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#hwmap | FFmpeg hwmap filter}
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
- return derive ? `hwmap=derive_device=${derive}` : 'hwmap';
1501
+ this.add(derive ? `hwmap=derive_device=${derive}` : 'hwmap');
1502
+ return this;
1190
1503
  }
1191
1504
  /**
1192
- * Gets the filter capabilities for this hardware type.
1505
+ * Adds a filter to the chain.
1193
1506
  *
1194
- * @returns Object describing which filters are supported
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
- * const caps = hw.getCapabilities();
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
- getCapabilities() {
1205
- return this.support;
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
- switch (this.deviceType) {
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