node-av 1.3.0 → 2.1.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 (127) hide show
  1. package/README.md +47 -40
  2. package/binding.gyp +12 -0
  3. package/dist/api/bitstream-filter.d.ts +134 -2
  4. package/dist/api/bitstream-filter.js +200 -2
  5. package/dist/api/bitstream-filter.js.map +1 -1
  6. package/dist/api/decoder.d.ts +261 -105
  7. package/dist/api/decoder.js +384 -171
  8. package/dist/api/decoder.js.map +1 -1
  9. package/dist/api/encoder.d.ts +338 -74
  10. package/dist/api/encoder.js +546 -188
  11. package/dist/api/encoder.js.map +1 -1
  12. package/dist/api/filter-presets.d.ts +479 -1513
  13. package/dist/api/filter-presets.js +1044 -2005
  14. package/dist/api/filter-presets.js.map +1 -1
  15. package/dist/api/filter.d.ts +370 -150
  16. package/dist/api/filter.js +647 -364
  17. package/dist/api/filter.js.map +1 -1
  18. package/dist/api/hardware.d.ts +25 -31
  19. package/dist/api/hardware.js +36 -70
  20. package/dist/api/hardware.js.map +1 -1
  21. package/dist/api/index.d.ts +1 -1
  22. package/dist/api/index.js +1 -1
  23. package/dist/api/index.js.map +1 -1
  24. package/dist/api/io-stream.d.ts +6 -0
  25. package/dist/api/io-stream.js +2 -0
  26. package/dist/api/io-stream.js.map +1 -1
  27. package/dist/api/media-input.d.ts +208 -2
  28. package/dist/api/media-input.js +356 -8
  29. package/dist/api/media-input.js.map +1 -1
  30. package/dist/api/media-output.d.ts +142 -104
  31. package/dist/api/media-output.js +446 -179
  32. package/dist/api/media-output.js.map +1 -1
  33. package/dist/api/pipeline.d.ts +82 -17
  34. package/dist/api/pipeline.js +80 -42
  35. package/dist/api/pipeline.js.map +1 -1
  36. package/dist/api/types.d.ts +24 -57
  37. package/dist/api/utils.js +2 -0
  38. package/dist/api/utils.js.map +1 -1
  39. package/dist/lib/audio-fifo.d.ts +103 -0
  40. package/dist/lib/audio-fifo.js +109 -0
  41. package/dist/lib/audio-fifo.js.map +1 -1
  42. package/dist/lib/binding.d.ts +1 -0
  43. package/dist/lib/binding.js.map +1 -1
  44. package/dist/lib/bitstream-filter-context.d.ts +79 -0
  45. package/dist/lib/bitstream-filter-context.js +83 -0
  46. package/dist/lib/bitstream-filter-context.js.map +1 -1
  47. package/dist/lib/bitstream-filter.d.ts +2 -0
  48. package/dist/lib/bitstream-filter.js +2 -0
  49. package/dist/lib/bitstream-filter.js.map +1 -1
  50. package/dist/lib/codec-context.d.ts +168 -0
  51. package/dist/lib/codec-context.js +178 -0
  52. package/dist/lib/codec-context.js.map +1 -1
  53. package/dist/lib/codec-parameters.d.ts +3 -0
  54. package/dist/lib/codec-parameters.js +3 -0
  55. package/dist/lib/codec-parameters.js.map +1 -1
  56. package/dist/lib/codec-parser.d.ts +6 -0
  57. package/dist/lib/codec-parser.js +6 -0
  58. package/dist/lib/codec-parser.js.map +1 -1
  59. package/dist/lib/codec.d.ts +12 -0
  60. package/dist/lib/codec.js +12 -0
  61. package/dist/lib/codec.js.map +1 -1
  62. package/dist/lib/dictionary.d.ts +18 -2
  63. package/dist/lib/dictionary.js +18 -2
  64. package/dist/lib/dictionary.js.map +1 -1
  65. package/dist/lib/error.d.ts +8 -0
  66. package/dist/lib/error.js +9 -0
  67. package/dist/lib/error.js.map +1 -1
  68. package/dist/lib/filter-context.d.ts +119 -2
  69. package/dist/lib/filter-context.js +119 -0
  70. package/dist/lib/filter-context.js.map +1 -1
  71. package/dist/lib/filter-graph.d.ts +80 -0
  72. package/dist/lib/filter-graph.js +84 -0
  73. package/dist/lib/filter-graph.js.map +1 -1
  74. package/dist/lib/filter-inout.d.ts +1 -0
  75. package/dist/lib/filter-inout.js +1 -0
  76. package/dist/lib/filter-inout.js.map +1 -1
  77. package/dist/lib/filter.d.ts +2 -0
  78. package/dist/lib/filter.js +2 -0
  79. package/dist/lib/filter.js.map +1 -1
  80. package/dist/lib/format-context.d.ts +356 -20
  81. package/dist/lib/format-context.js +375 -23
  82. package/dist/lib/format-context.js.map +1 -1
  83. package/dist/lib/frame.d.ts +84 -1
  84. package/dist/lib/frame.js +96 -0
  85. package/dist/lib/frame.js.map +1 -1
  86. package/dist/lib/hardware-device-context.d.ts +8 -0
  87. package/dist/lib/hardware-device-context.js +8 -0
  88. package/dist/lib/hardware-device-context.js.map +1 -1
  89. package/dist/lib/hardware-frames-context.d.ts +55 -0
  90. package/dist/lib/hardware-frames-context.js +57 -0
  91. package/dist/lib/hardware-frames-context.js.map +1 -1
  92. package/dist/lib/input-format.d.ts +43 -3
  93. package/dist/lib/input-format.js +48 -0
  94. package/dist/lib/input-format.js.map +1 -1
  95. package/dist/lib/io-context.d.ts +212 -0
  96. package/dist/lib/io-context.js +228 -0
  97. package/dist/lib/io-context.js.map +1 -1
  98. package/dist/lib/log.d.ts +2 -0
  99. package/dist/lib/log.js +2 -0
  100. package/dist/lib/log.js.map +1 -1
  101. package/dist/lib/native-types.d.ts +39 -1
  102. package/dist/lib/option.d.ts +90 -0
  103. package/dist/lib/option.js +97 -0
  104. package/dist/lib/option.js.map +1 -1
  105. package/dist/lib/output-format.d.ts +4 -0
  106. package/dist/lib/output-format.js +4 -0
  107. package/dist/lib/output-format.js.map +1 -1
  108. package/dist/lib/packet.d.ts +7 -0
  109. package/dist/lib/packet.js +7 -0
  110. package/dist/lib/packet.js.map +1 -1
  111. package/dist/lib/rational.d.ts +1 -0
  112. package/dist/lib/rational.js +1 -0
  113. package/dist/lib/rational.js.map +1 -1
  114. package/dist/lib/software-resample-context.d.ts +64 -0
  115. package/dist/lib/software-resample-context.js +66 -0
  116. package/dist/lib/software-resample-context.js.map +1 -1
  117. package/dist/lib/software-scale-context.d.ts +98 -0
  118. package/dist/lib/software-scale-context.js +102 -0
  119. package/dist/lib/software-scale-context.js.map +1 -1
  120. package/dist/lib/stream.d.ts +1 -0
  121. package/dist/lib/stream.js +1 -0
  122. package/dist/lib/stream.js.map +1 -1
  123. package/dist/lib/utilities.d.ts +60 -0
  124. package/dist/lib/utilities.js +60 -0
  125. package/dist/lib/utilities.js.map +1 -1
  126. package/package.json +18 -18
  127. package/release_notes.md +0 -29
@@ -1,2578 +1,1616 @@
1
1
  import { AV_HWDEVICE_TYPE_CUDA, AV_HWDEVICE_TYPE_D3D11VA, AV_HWDEVICE_TYPE_D3D12VA, AV_HWDEVICE_TYPE_DRM, AV_HWDEVICE_TYPE_DXVA2, AV_HWDEVICE_TYPE_MEDIACODEC, AV_HWDEVICE_TYPE_OPENCL, AV_HWDEVICE_TYPE_QSV, AV_HWDEVICE_TYPE_RKMPP, AV_HWDEVICE_TYPE_VAAPI, AV_HWDEVICE_TYPE_VDPAU, AV_HWDEVICE_TYPE_VIDEOTOOLBOX, AV_HWDEVICE_TYPE_VULKAN, AVFILTER_FLAG_HWDEVICE, } from '../constants/constants.js';
2
2
  import { Filter } from '../lib/filter.js';
3
- import { HardwareDeviceContext } from '../lib/hardware-device-context.js';
4
3
  import { avGetPixFmtName, avGetSampleFmtName } from '../lib/utilities.js';
5
4
  /**
6
- * Base class for filter preset implementations.
7
- * Provides common filter building methods that can be overridden by hardware-specific implementations.
8
- *
9
- * This class defines the standard filter operations available in FFmpeg,
10
- * with each method returning a filter string that can be used in a filter graph.
11
- * Hardware-specific implementations may override these methods to use optimized
12
- * 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.
13
9
  *
14
10
  * @example
15
11
  * ```typescript
16
- * class CustomPresets extends FilterPresetBase {
17
- * override scale(width: number, height: number): string | null {
18
- * return `custom_scale=${width}:${height}`;
19
- * }
20
- * }
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();
21
25
  * ```
22
26
  */
23
- export class FilterPresetBase {
24
- /**
25
- * Creates a scale filter string.
26
- *
27
- * @param width - Target width in pixels
28
- * @param height - Target height in pixels
29
- * @param options - Additional scaling options (e.g., flags for algorithm)
30
- * @returns Filter string or null if not supported
31
- *
32
- * @example
33
- * ```typescript
34
- * presets.scale(1920, 1080) // Scale to Full HD
35
- * presets.scale(640, 480, { flags: 'lanczos' }) // With specific algorithm
36
- * ```
37
- *
38
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#scale | FFmpeg scale filter}
39
- */
40
- scale(width, height, options) {
41
- const flags = options?.flags;
42
- const base = `scale=${width}:${height}`;
43
- return flags ? `${base}:flags=${flags}` : base;
44
- }
45
- /**
46
- * Creates a crop filter string.
47
- *
48
- * @param width - Width of the cropped area
49
- * @param height - Height of the cropped area
50
- * @param x - X coordinate of top-left corner (default: 0)
51
- * @param y - Y coordinate of top-left corner (default: 0)
52
- * @returns Filter string or null if not supported
53
- *
54
- * @example
55
- * ```typescript
56
- * presets.crop(640, 480, 100, 100) // Crop 640x480 area starting at (100,100)
57
- * presets.crop(1280, 720) // Crop from top-left corner
58
- * ```
59
- *
60
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#crop | FFmpeg crop filter}
61
- */
62
- crop(width, height, x = 0, y = 0) {
63
- return `crop=${width}:${height}:${x}:${y}`;
64
- }
65
- /**
66
- * Creates an FPS filter string to change frame rate.
67
- *
68
- * @param fps - Target frames per second
69
- * @returns Filter string or null if not supported
70
- *
71
- * @example
72
- * ```typescript
73
- * presets.fps(30) // Convert to 30 FPS
74
- * presets.fps(23.976) // Film frame rate
75
- * ```
76
- *
77
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#fps | FFmpeg fps filter}
78
- */
79
- fps(fps) {
80
- return `fps=${fps}`;
81
- }
82
- /**
83
- * Creates a format filter string to convert pixel format.
84
- *
85
- * @param pixelFormat - Target pixel format(s) - can be string, AVPixelFormat enum, or array
86
- * @returns Filter string or null if not supported
87
- *
88
- * @example
89
- * ```typescript
90
- * // Single format
91
- * presets.format('yuv420p');
92
- * presets.format(AV_PIX_FMT_YUV420P);
93
- *
94
- * // Multiple formats (creates a chain)
95
- * presets.format(['yuv420p', 'rgb24']);
96
- * ```
97
- *
98
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#format | FFmpeg format filter}
99
- */
100
- format(pixelFormat) {
101
- if (Array.isArray(pixelFormat)) {
102
- // Create a chain of format filters
103
- const formats = pixelFormat.map((fmt) => {
104
- const formatName = typeof fmt === 'string' ? fmt : (avGetPixFmtName(fmt) ?? 'yuv420p');
105
- return `format=${formatName}`;
106
- });
107
- return formats.join(',');
108
- }
109
- const formatName = typeof pixelFormat === 'string' ? pixelFormat : (avGetPixFmtName(pixelFormat) ?? 'yuv420p');
110
- return `format=${formatName}`;
111
- }
112
- /**
113
- * Creates a rotate filter string.
114
- *
115
- * @param angle - Rotation angle in degrees
116
- * @returns Filter string or null if not supported
117
- *
118
- * @example
119
- * ```typescript
120
- * presets.rotate(90) // Rotate 90 degrees clockwise
121
- * presets.rotate(-45) // Rotate 45 degrees counter-clockwise
122
- * ```
123
- *
124
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#rotate | FFmpeg rotate filter}
125
- */
126
- rotate(angle) {
127
- return `rotate=${angle}*PI/180`;
128
- }
129
- /**
130
- * Creates a horizontal flip filter string.
131
- *
132
- * @returns Filter string or null if not supported
133
- *
134
- * @example
135
- * ```typescript
136
- * presets.hflip() // Mirror horizontally
137
- * ```
138
- *
139
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#hflip | FFmpeg hflip filter}
140
- */
141
- hflip() {
142
- return 'hflip';
143
- }
144
- /**
145
- * Creates a vertical flip filter string.
146
- *
147
- * @returns Filter string or null if not supported
148
- *
149
- * @example
150
- * ```typescript
151
- * presets.vflip() // Flip upside down
152
- * ```
153
- *
154
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#vflip | FFmpeg vflip filter}
155
- */
156
- vflip() {
157
- return 'vflip';
158
- }
159
- /**
160
- * Creates a fade filter string for video.
161
- *
162
- * @param type - Fade type ('in' or 'out')
163
- * @param start - Start time in seconds
164
- * @param duration - Fade duration in seconds
165
- * @returns Filter string or null if not supported
166
- *
167
- * @example
168
- * ```typescript
169
- * presets.fade('in', 0, 2) // 2-second fade in from start
170
- * presets.fade('out', 10, 1) // 1-second fade out at 10 seconds
171
- * ```
172
- *
173
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#fade | FFmpeg fade filter}
174
- */
175
- fade(type, start, duration) {
176
- return `fade=t=${type}:st=${start}:d=${duration}`;
177
- }
178
- /**
179
- * Creates an overlay filter string to composite two video streams.
180
- *
181
- * @param x - X position for overlay (default: 0)
182
- * @param y - Y position for overlay (default: 0)
183
- * @param options - Additional overlay options
184
- * @returns Filter string or null if not supported
185
- *
186
- * @example
187
- * ```typescript
188
- * // Basic overlay at position
189
- * presets.overlay(100, 50);
190
- *
191
- * // With additional options
192
- * presets.overlay(0, 0, { format: 'yuv420' });
193
- * ```
194
- *
195
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#overlay | FFmpeg overlay filter}
196
- */
197
- overlay(x = 0, y = 0, options) {
198
- let filter = `overlay=${x}:${y}`;
199
- if (options) {
200
- for (const [key, value] of Object.entries(options)) {
201
- filter += `:${key}=${value}`;
202
- }
203
- }
204
- return filter;
205
- }
206
- /**
207
- * Creates a volume filter string for audio.
208
- *
209
- * @param factor - Volume multiplication factor (1.0 = unchanged, 2.0 = double)
210
- * @returns Filter string or null if not supported
211
- *
212
- * @example
213
- * ```typescript
214
- * presets.volume(0.5) // Reduce volume by 50%
215
- * presets.volume(1.5) // Increase volume by 50%
216
- * ```
217
- *
218
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#volume | FFmpeg volume filter}
219
- */
220
- volume(factor) {
221
- return `volume=${factor}`;
222
- }
223
- /**
224
- * Creates an audio format filter string.
225
- *
226
- * @param sampleFormat - Target sample format (e.g., 's16', 'fltp')
227
- * @param sampleRate - Target sample rate in Hz (optional)
228
- * @param channelLayout - Target channel layout (optional)
229
- * @returns Filter string or null if not supported
230
- *
231
- * @example
232
- * ```typescript
233
- * // Change sample format only
234
- * presets.aformat('s16');
235
- *
236
- * // Change format and sample rate
237
- * presets.aformat('fltp', 48000);
238
- *
239
- * // Full conversion
240
- * presets.aformat('s16', 44100, 'stereo');
241
- * ```
242
- *
243
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#aformat | FFmpeg aformat filter}
244
- */
245
- aformat(sampleFormat, sampleRate, channelLayout) {
246
- const formatName = typeof sampleFormat === 'string' ? sampleFormat : (avGetSampleFmtName(sampleFormat) ?? 's16');
247
- let filter = `aformat=sample_fmts=${formatName}`;
248
- if (sampleRate)
249
- filter += `:sample_rates=${sampleRate}`;
250
- if (channelLayout)
251
- filter += `:channel_layouts=${channelLayout}`;
252
- return filter;
253
- }
254
- /**
255
- * Creates an asetnsamples filter string to set the number of samples per frame.
256
- * This is crucial for encoders like Opus that require specific frame sizes.
257
- *
258
- * @param samples - Number of samples per frame
259
- * @param padding - Whether to pad or drop samples (default: true)
260
- * @returns Filter string or null if not supported
261
- *
262
- * @example
263
- * ```typescript
264
- * // For Opus encoder (requires 960 samples)
265
- * presets.asetnsamples(960);
266
- *
267
- * // Drop samples instead of padding
268
- * presets.asetnsamples(1024, false);
269
- * ```
270
- *
271
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#asetnsamples | FFmpeg asetnsamples filter}
272
- */
273
- asetnsamples(samples, padding = true) {
274
- const p = padding ? 1 : 0;
275
- return `asetnsamples=n=${samples}:p=${p}`;
276
- }
277
- /**
278
- * Creates an atempo filter string to change audio playback speed.
279
- * Factor must be between 0.5 and 2.0. For larger changes, chain multiple atempo filters.
280
- *
281
- * @param factor - Tempo factor (0.5 = half speed, 2.0 = double speed)
282
- * @returns Filter string or null if not supported
283
- *
284
- * @example
285
- * ```typescript
286
- * presets.atempo(1.5) // 1.5x speed
287
- * presets.atempo(0.8) // Slow down to 80% speed
288
- * ```
289
- *
290
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#atempo | FFmpeg atempo filter}
291
- */
292
- atempo(factor) {
293
- return `atempo=${factor}`;
294
- }
295
- /**
296
- * Creates an audio fade filter string.
297
- *
298
- * @param type - Fade type ('in' or 'out')
299
- * @param start - Start time in seconds
300
- * @param duration - Fade duration in seconds
301
- * @returns Filter string or null if not supported
302
- *
303
- * @example
304
- * ```typescript
305
- * presets.afade('in', 0, 3) // 3-second audio fade in
306
- * presets.afade('out', 20, 2) // 2-second fade out at 20s
307
- * ```
308
- *
309
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#afade | FFmpeg afade filter}
310
- */
311
- afade(type, start, duration) {
312
- return `afade=t=${type}:st=${start}:d=${duration}`;
27
+ export class FilterPreset {
28
+ hardware;
29
+ filters = [];
30
+ support;
31
+ constructor(hardware) {
32
+ this.hardware = hardware;
33
+ this.support = this.getSupport();
313
34
  }
314
35
  /**
315
- * Creates an amix filter string to mix multiple audio streams.
316
- *
317
- * @param inputs - Number of input streams to mix (default: 2)
318
- * @param duration - How to determine output duration (default: 'longest')
319
- * @returns Filter string or null if not supported
320
- *
321
- * @example
322
- * ```typescript
323
- * presets.amix(3, 'longest') // Mix 3 audio streams
324
- * presets.amix(2, 'first') // Mix 2 streams, use first's duration
325
- * ```
36
+ * Checks if a filter is hardware-accelerated.
326
37
  *
327
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#amix | FFmpeg amix filter}
328
- */
329
- amix(inputs = 2, duration = 'longest') {
330
- return `amix=inputs=${inputs}:duration=${duration}`;
331
- }
332
- /**
333
- * Creates a pad filter string to add padding to video.
334
- * Essential for aspect ratio adjustments and letterboxing.
335
- *
336
- * @param width - Output width (can use expressions like 'iw+100')
337
- * @param height - Output height (can use expressions like 'ih+100')
338
- * @param x - X position of input video (default: '(ow-iw)/2' for center)
339
- * @param y - Y position of input video (default: '(oh-ih)/2' for center)
340
- * @param color - Padding color (default: 'black')
341
- * @returns Filter string or null if not supported
342
- *
343
- * @example
344
- * ```typescript
345
- * // Add black bars for 16:9 aspect ratio
346
- * presets.pad('iw', 'iw*9/16');
347
- *
348
- * // Add 50px padding on all sides
349
- * presets.pad('iw+100', 'ih+100');
350
- * ```
351
- *
352
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#pad | FFmpeg pad filter}
353
- */
354
- pad(width, height, x, y, color = 'black') {
355
- let filter = `pad=${width}:${height}`;
356
- if (x !== undefined)
357
- filter += `:${x}`;
358
- if (y !== undefined)
359
- filter += `:${y}`;
360
- filter += `:${color}`;
361
- return filter;
362
- }
363
- /**
364
- * Creates a trim filter string to cut a portion of the stream.
365
- * Crucial for cutting segments from media.
366
- *
367
- * @param start - Start time in seconds
368
- * @param end - End time in seconds (optional)
369
- * @param duration - Duration in seconds (optional, alternative to end)
370
- * @returns Filter string or null if not supported
371
- *
372
- * @example
373
- * ```typescript
374
- * presets.trim(10, 30) // Extract from 10s to 30s
375
- * presets.trim(5, undefined, 10) // Extract 10s starting at 5s
376
- * ```
377
- *
378
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#trim | FFmpeg trim filter}
379
- */
380
- trim(start, end, duration) {
381
- let filter = `trim=start=${start}`;
382
- if (end !== undefined)
383
- filter += `:end=${end}`;
384
- if (duration !== undefined)
385
- filter += `:duration=${duration}`;
386
- return filter;
387
- }
388
- /**
389
- * Creates a setpts filter string to change presentation timestamps.
390
- * Essential for speed changes and timestamp manipulation.
391
- *
392
- * @param expression - PTS expression (e.g., 'PTS*2' for half speed, 'PTS/2' for double speed)
393
- * @returns Filter string or null if not supported
394
- *
395
- * @example
396
- * ```typescript
397
- * // Double speed
398
- * presets.setpts('PTS/2');
399
- *
400
- * // Half speed
401
- * presets.setpts('PTS*2');
402
- *
403
- * // Reset timestamps
404
- * presets.setpts('PTS-STARTPTS');
405
- * ```
406
- *
407
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#setpts | FFmpeg setpts filter}
408
- */
409
- setpts(expression) {
410
- return `setpts=${expression}`;
411
- }
412
- /**
413
- * Creates an asetpts filter string for audio timestamp manipulation.
414
- *
415
- * @param expression - PTS expression
416
- * @returns Filter string or null if not supported
417
- *
418
- * @example
419
- * ```typescript
420
- * presets.asetpts('PTS-STARTPTS') // Reset timestamps to start from 0
421
- * ```
422
- *
423
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#asetpts | FFmpeg asetpts filter}
424
- */
425
- asetpts(expression) {
426
- return `asetpts=${expression}`;
427
- }
428
- /**
429
- * Creates a transpose filter string for rotation/flipping.
430
- * More efficient than rotate for 90-degree rotations.
431
- *
432
- * @param mode - Transpose mode (0-3, or named constants)
433
- * @returns Filter string or null if not supported
434
- *
435
- * @example
436
- * ```typescript
437
- * presets.transpose(1) // Rotate 90 degrees clockwise
438
- * presets.transpose('cclock') // Rotate 90 degrees counter-clockwise
439
- * ```
440
- *
441
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#transpose | FFmpeg transpose filter}
442
- */
443
- transpose(mode) {
444
- return `transpose=${mode}`;
445
- }
446
- /**
447
- * Creates a setsar filter string to set sample aspect ratio.
448
- * Important for correcting aspect ratio issues.
449
- *
450
- * @param ratio - Aspect ratio (e.g., '1:1', '16:9', or number)
451
- * @returns Filter string or null if not supported
452
- *
453
- * @example
454
- * ```typescript
455
- * presets.setsar('1:1') // Square pixels
456
- * presets.setsar(1.333) // 4:3 aspect ratio
457
- * ```
458
- *
459
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#setsar | FFmpeg setsar/setdar filter}
460
- */
461
- setsar(ratio) {
462
- return `setsar=${ratio}`;
463
- }
464
- /**
465
- * Creates a setdar filter string to set display aspect ratio.
466
- *
467
- * @param ratio - Aspect ratio (e.g., '16:9', '4:3')
468
- * @returns Filter string or null if not supported
469
- *
470
- * @example
471
- * ```typescript
472
- * presets.setdar('16:9') // Widescreen
473
- * presets.setdar('4:3') // Traditional TV aspect
474
- * ```
475
- *
476
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#setsar | FFmpeg setsar/setdar filter}
477
- */
478
- setdar(ratio) {
479
- return `setdar=${ratio}`;
480
- }
481
- /**
482
- * Creates an apad filter string to add audio padding.
483
- * Useful for ensuring minimum audio duration.
484
- *
485
- * @param wholeDuration - Minimum duration in seconds (optional)
486
- * @param padDuration - Amount of padding to add in seconds (optional)
487
- * @returns Filter string or null if not supported
488
- *
489
- * @example
490
- * ```typescript
491
- * presets.apad(30) // Ensure at least 30 seconds total
492
- * presets.apad(undefined, 5) // Add 5 seconds of padding
493
- * ```
494
- *
495
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#apad | FFmpeg apad filter}
496
- */
497
- apad(wholeDuration, padDuration) {
498
- if (!wholeDuration && !padDuration)
499
- return 'apad';
500
- let filter = 'apad';
501
- if (wholeDuration)
502
- filter += `=whole_dur=${wholeDuration}`;
503
- if (padDuration)
504
- filter += wholeDuration ? `:pad_dur=${padDuration}` : `=pad_dur=${padDuration}`;
505
- return filter;
506
- }
507
- /**
508
- * Creates a deinterlace filter string.
509
- * Essential for processing interlaced content.
510
- *
511
- * @param mode - Deinterlace mode (default: 'yadif')
512
- * @returns Filter string or null if not supported
513
- *
514
- * @example
515
- * ```typescript
516
- * presets.deinterlace('yadif') // Standard deinterlacing
517
- * presets.deinterlace('bwdif') // Bob Weaver deinterlacing
518
- * ```
519
- *
520
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#yadif | FFmpeg yadif filter}
521
- */
522
- deinterlace(mode = 'yadif') {
523
- return mode;
524
- }
525
- /**
526
- * Creates a select filter string to select specific frames.
527
- * Powerful for extracting keyframes, specific frame types, etc.
528
- *
529
- * @param expression - Selection expression
530
- * @returns Filter string or null if not supported
531
- *
532
- * @example
533
- * ```typescript
534
- * presets.select('eq(pict_type,I)') // Select only keyframes
535
- * presets.select('not(mod(n,10))') // Select every 10th frame
536
- * ```
537
- *
538
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#select | FFmpeg select filter}
539
- */
540
- select(expression) {
541
- return `select='${expression}'`;
542
- }
543
- /**
544
- * Creates an aselect filter string for audio selection.
545
- *
546
- * @param expression - Selection expression
547
- * @returns Filter string or null if not supported
548
- *
549
- * @example
550
- * ```typescript
551
- * presets.aselect('between(t,10,20)') // Select audio between 10-20 seconds
552
- * ```
553
- *
554
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#aselect | FFmpeg aselect filter}
555
- */
556
- aselect(expression) {
557
- return `aselect='${expression}'`;
558
- }
559
- /**
560
- * Creates a concat filter string to concatenate multiple inputs.
561
- * Essential for joining multiple video/audio segments.
562
- *
563
- * @param n - Number of input segments
564
- * @param v - Number of output video streams (0 or 1)
565
- * @param a - Number of output audio streams (0 or 1)
566
- * @returns Filter string or null if not supported
567
- *
568
- * @example
569
- * ```typescript
570
- * presets.concat(3, 1, 1) // Join 3 segments with video and audio
571
- * presets.concat(2, 1, 0) // Join 2 video-only segments
572
- * ```
573
- *
574
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#concat | FFmpeg concat filter}
575
- */
576
- concat(n, v = 1, a = 1) {
577
- return `concat=n=${n}:v=${v}:a=${a}`;
578
- }
579
- /**
580
- * Creates an amerge filter string to merge multiple audio streams into one.
581
- * Different from amix - this creates multi-channel output.
582
- *
583
- * @param inputs - Number of input streams
584
- * @returns Filter string or null if not supported
585
- *
586
- * @example
587
- * ```typescript
588
- * presets.amerge(2) // Merge 2 mono streams to stereo
589
- * presets.amerge(6) // Merge 6 channels for 5.1 surround
590
- * ```
591
- *
592
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#amerge | FFmpeg amerge filter}
593
- */
594
- amerge(inputs = 2) {
595
- return `amerge=inputs=${inputs}`;
596
- }
597
- /**
598
- * Creates a channelmap filter string to remap audio channels.
599
- * Critical for audio channel manipulation.
600
- *
601
- * @param map - Channel mapping (e.g., '0-0|1-1' or 'FL-FR|FR-FL' to swap stereo)
602
- * @returns Filter string or null if not supported
603
- *
604
- * @example
605
- * ```typescript
606
- * presets.channelmap('FL-FR|FR-FL') // Swap left and right channels
607
- * presets.channelmap('0-0|0-1') // Duplicate mono to stereo
608
- * ```
609
- *
610
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#channelmap | FFmpeg channelmap filter}
611
- */
612
- channelmap(map) {
613
- return `channelmap=${map}`;
614
- }
615
- /**
616
- * Creates a channelsplit filter string to split audio channels.
617
- *
618
- * @param channelLayout - Channel layout to split (optional)
619
- * @returns Filter string or null if not supported
620
- *
621
- * @example
622
- * ```typescript
623
- * presets.channelsplit('stereo') // Split stereo to 2 mono
624
- * presets.channelsplit('5.1') // Split 5.1 to individual channels
625
- * ```
626
- *
627
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#channelsplit | FFmpeg channelsplit filter}
628
- */
629
- channelsplit(channelLayout) {
630
- return channelLayout ? `channelsplit=channel_layout=${channelLayout}` : 'channelsplit';
631
- }
632
- /**
633
- * Creates a loudnorm filter string for loudness normalization.
634
- * Essential for broadcast compliance and consistent audio levels.
635
- *
636
- * @param I - Integrated loudness target (default: -24 LUFS)
637
- * @param TP - True peak (default: -2 dBTP)
638
- * @param LRA - Loudness range (default: 7 LU)
639
- * @returns Filter string or null if not supported
640
- *
641
- * @example
642
- * ```typescript
643
- * presets.loudnorm(-23, -1, 7) // EBU R128 broadcast standard
644
- * presets.loudnorm(-16, -1.5, 11) // Streaming platforms standard
645
- * ```
646
- *
647
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#loudnorm | FFmpeg loudnorm filter}
648
- */
649
- loudnorm(I = -24, TP = -2, LRA = 7) {
650
- return `loudnorm=I=${I}:TP=${TP}:LRA=${LRA}`;
651
- }
652
- /**
653
- * Creates a compand filter string for audio compression/expansion.
654
- * Important for dynamic range control.
655
- *
656
- * @param attacks - Attack times
657
- * @param decays - Decay times
658
- * @param points - Transfer function points
659
- * @param gain - Output gain
660
- * @returns Filter string or null if not supported
661
- *
662
- * @example
663
- * ```typescript
664
- * presets.compand('0.3|0.3', '1|1', '-90/-60|-60/-40|-40/-30|-20/-20', 6)
665
- * ```
666
- *
667
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#compand | FFmpeg compand filter}
668
- */
669
- compand(attacks, decays, points, gain) {
670
- let filter = `compand=attacks=${attacks}:decays=${decays}:points=${points}`;
671
- if (gain !== undefined)
672
- filter += `:gain=${gain}`;
673
- return filter;
674
- }
675
- }
676
- /**
677
- * Filter chain builder for composing multiple filters.
678
- * Allows fluent API for building complex filter graphs by chaining filter operations.
679
- *
680
- * @example
681
- * ```typescript
682
- * const chain = new FilterChain()
683
- * .add('scale=1920:1080')
684
- * .add('fps=30')
685
- * .custom('rotate=45*PI/180')
686
- * .build();
687
- * // Result: "scale=1920:1080,fps=30,rotate=45*PI/180"
688
- * ```
689
- */
690
- export class FilterChain {
691
- filters = [];
692
- /**
693
- * Adds a filter to the chain.
694
- *
695
- * @param filter - Filter string to add (ignored if null/undefined)
696
- * @returns This instance for chaining
697
- *
698
- * @example
699
- * ```typescript
700
- * chain.add('scale=1920:1080')
701
- * ```
702
- */
703
- add(filter) {
704
- if (filter) {
705
- this.filters.push(filter);
706
- }
707
- return this;
708
- }
709
- /**
710
- * Adds a custom filter string to the chain.
711
- *
712
- * @param filter - Custom filter string
713
- * @returns This instance for chaining
714
- *
715
- * @example
716
- * ```typescript
717
- * chain.custom('myfilter=param1:param2')
718
- * ```
719
- */
720
- custom(filter) {
721
- return this.add(filter);
722
- }
723
- /**
724
- * Builds the final filter string.
725
- *
726
- * @param separator - Separator between filters (default: ',')
727
- * @returns Combined filter string
728
- *
729
- * @example
730
- * ```typescript
731
- * const filterString = chain.build() // "scale=1920:1080,fps=30"
732
- * ```
733
- */
734
- build(separator = ',') {
735
- return this.filters.join(separator);
736
- }
737
- /**
738
- * Returns the filters as an array.
739
- *
740
- * @returns Array of filter strings
741
- *
742
- * @example
743
- * ```typescript
744
- * const filters = chain.toArray() // ["scale=1920:1080", "fps=30"]
745
- * ```
746
- */
747
- toArray() {
748
- return [...this.filters];
749
- }
750
- }
751
- /**
752
- * Base chain builder with common filter methods.
753
- * Provides a fluent API for building filter chains using preset methods.
754
- *
755
- * @template T The preset type this builder uses
756
- *
757
- * @example
758
- * ```typescript
759
- * const chain = new ChainBuilderBase(presets)
760
- * .scale(1920, 1080)
761
- * .fps(30)
762
- * .fade('in', 0, 2)
763
- * .build();
764
- * ```
765
- */
766
- export class ChainBuilderBase extends FilterChain {
767
- presets;
768
- /**
769
- * @param presets - The filter presets to use
770
- * @internal
771
- */
772
- constructor(presets) {
773
- super();
774
- this.presets = presets;
775
- }
776
- /**
777
- * Adds a scale filter to the chain.
778
- *
779
- * @param width - Target width
780
- * @param height - Target height
781
- * @param options - Additional scaling options
782
- *
783
- * @returns This instance for chaining
784
- *
785
- * @example
786
- * ```typescript
787
- * const chain = FilterPresets.chain()
788
- * .scale(1920, 1080)
789
- * .build();
790
- * ```
791
- *
792
- * @example
793
- * ```typescript
794
- * const chain = FilterPresets.chain()
795
- * .scale(1280, 720, { flags: 'lanczos' })
796
- * .build();
797
- * ```
798
- *
799
- * @see {@link FilterPresetBase.scale}
800
- */
801
- scale(width, height, options) {
802
- return this.add(this.presets.scale(width, height, options));
803
- }
804
- /**
805
- * Adds a crop filter to the chain.
806
- *
807
- * @param width - Crop width
808
- * @param height - Crop height
809
- * @param x - X position (default: 0)
810
- * @param y - Y position (default: 0)
811
- *
812
- * @returns This instance for chaining
813
- *
814
- * @example
815
- * ```typescript
816
- * const chain = FilterPresets.chain()
817
- * .crop(640, 480)
818
- * .build();
819
- * ```
820
- *
821
- * @example
822
- * ```typescript
823
- * const chain = FilterPresets.chain()
824
- * .crop(1920, 1080, 100, 50)
825
- * .build();
826
- * ```
827
- *
828
- * @see {@link FilterPresetBase.crop}
829
- */
830
- crop(width, height, x = 0, y = 0) {
831
- return this.add(this.presets.crop(width, height, x, y));
832
- }
833
- /**
834
- * Adds an FPS filter to the chain.
835
- *
836
- * @param fps - Target frame rate
837
- *
838
- * @returns This instance for chaining
839
- *
840
- * @example
841
- * ```typescript
842
- * const chain = FilterPresets.chain()
843
- * .fps(30)
844
- * .build();
845
- * ```
846
- *
847
- * @example
848
- * ```typescript
849
- * const chain = FilterPresets.chain()
850
- * .fps(23.976)
851
- * .build();
852
- * ```
853
- *
854
- * @see {@link FilterPresetBase.fps}
855
- */
856
- fps(fps) {
857
- return this.add(this.presets.fps(fps));
858
- }
859
- /**
860
- * Adds a format filter to the chain.
861
- *
862
- * @param pixelFormat - Target pixel format(s)
863
- *
864
- * @returns This instance for chaining
865
- *
866
- * @example
867
- * ```typescript
868
- * const chain = FilterPresets.chain()
869
- * .format('yuv420p')
870
- * .build();
871
- * ```
872
- *
873
- * @example
874
- * ```typescript
875
- * const chain = FilterPresets.chain()
876
- * .format(AV_PIX_FMT_RGB24)
877
- * .build();
878
- * ```
879
- *
880
- * @see {@link FilterPresetBase.format}
881
- */
882
- format(pixelFormat) {
883
- return this.add(this.presets.format(pixelFormat));
884
- }
885
- /**
886
- * Adds a rotate filter to the chain.
887
- *
888
- * @param angle - Rotation angle in degrees
889
- *
890
- * @returns This instance for chaining
891
- *
892
- * @example
893
- * ```typescript
894
- * const chain = FilterPresets.chain()
895
- * .rotate(45)
896
- * .build();
897
- * ```
898
- *
899
- * @example
900
- * ```typescript
901
- * const chain = FilterPresets.chain()
902
- * .rotate(-90)
903
- * .build();
904
- * ```
905
- *
906
- * @see {@link FilterPresetBase.rotate}
907
- */
908
- rotate(angle) {
909
- return this.add(this.presets.rotate(angle));
910
- }
911
- /**
912
- * Adds a horizontal flip filter to the chain.
913
- *
914
- * @returns This instance for chaining
915
- *
916
- * @example
917
- * ```typescript
918
- * const chain = FilterPresets.chain()
919
- * .hflip()
920
- * .build();
921
- * ```
922
- *
923
- * @see {@link FilterPresetBase.hflip}
924
- */
925
- hflip() {
926
- return this.add(this.presets.hflip());
927
- }
928
- /**
929
- * Adds a vertical flip filter to the chain.
930
- *
931
- * @returns This instance for chaining
932
- *
933
- * @example
934
- * ```typescript
935
- * const chain = FilterPresets.chain()
936
- * .vflip()
937
- * .build();
938
- * ```
939
- *
940
- * @see {@link FilterPresetBase.vflip}
941
- */
942
- vflip() {
943
- return this.add(this.presets.vflip());
944
- }
945
- /**
946
- * Adds a fade filter to the chain.
947
- *
948
- * @param type - Fade type ('in' or 'out')
949
- * @param start - Start time in seconds
950
- * @param duration - Fade duration in seconds
951
- *
952
- * @returns This instance for chaining
953
- *
954
- * @example
955
- * ```typescript
956
- * const chain = FilterPresets.chain()
957
- * .fade('in', 0, 2)
958
- * .build();
959
- * ```
960
- *
961
- * @example
962
- * ```typescript
963
- * const chain = FilterPresets.chain()
964
- * .fade('out', 10, 1.5)
965
- * .build();
966
- * ```
967
- *
968
- * @see {@link FilterPresetBase.fade}
969
- */
970
- fade(type, start, duration) {
971
- return this.add(this.presets.fade(type, start, duration));
972
- }
973
- /**
974
- * Adds an overlay filter to the chain.
975
- *
976
- * @param x - X position (default: 0)
977
- * @param y - Y position (default: 0)
978
- * @param options - Additional overlay options
979
- *
980
- * @returns This instance for chaining
981
- *
982
- * @example
983
- * ```typescript
984
- * const chain = FilterPresets.chain()
985
- * .overlay(100, 50)
986
- * .build();
987
- * ```
988
- *
989
- * @example
990
- * ```typescript
991
- * const chain = FilterPresets.chain()
992
- * .overlay(10, 10, { enable: 'between(t,5,10)' })
993
- * .build();
994
- * ```
995
- *
996
- * @see {@link FilterPresetBase.overlay}
997
- */
998
- overlay(x = 0, y = 0, options) {
999
- return this.add(this.presets.overlay(x, y, options));
1000
- }
1001
- /**
1002
- * Adds a volume filter to the chain.
1003
- *
1004
- * @param factor - Volume factor
1005
- *
1006
- * @returns This instance for chaining
1007
- *
1008
- * @example
1009
- * ```typescript
1010
- * const chain = FilterPresets.chain()
1011
- * .volume(0.5)
1012
- * .build();
1013
- * ```
1014
- *
1015
- * @example
1016
- * ```typescript
1017
- * const chain = FilterPresets.chain()
1018
- * .volume(2.0)
1019
- * .build();
1020
- * ```
1021
- *
1022
- * @see {@link FilterPresetBase.volume}
1023
- */
1024
- volume(factor) {
1025
- return this.add(this.presets.volume(factor));
1026
- }
1027
- /**
1028
- * Adds an audio format filter to the chain.
1029
- *
1030
- * @param sampleFormat - Target sample format
1031
- * @param sampleRate - Target sample rate (optional)
1032
- * @param channelLayout - Target channel layout (optional)
1033
- *
1034
- * @returns This instance for chaining
1035
- *
1036
- * @example
1037
- * ```typescript
1038
- * const chain = FilterPresets.chain()
1039
- * .aformat(AV_SAMPLE_FMT_FLT, 48000, 'stereo')
1040
- * .build();
1041
- * ```
1042
- *
1043
- * @example
1044
- * ```typescript
1045
- * const chain = FilterPresets.chain()
1046
- * .aformat('s16', 44100)
1047
- * .build();
1048
- * ```
1049
- *
1050
- * @see {@link FilterPresetBase.aformat}
1051
- */
1052
- aformat(sampleFormat, sampleRate, channelLayout) {
1053
- return this.add(this.presets.aformat(sampleFormat, sampleRate, channelLayout));
1054
- }
1055
- /**
1056
- * Adds an asetnsamples filter to the chain.
1057
- *
1058
- * @param samples - Number of samples per frame
1059
- * @param padding - Whether to pad or drop samples (default: true)
1060
- *
1061
- * @returns This instance for chaining
1062
- *
1063
- * @example
1064
- * ```typescript
1065
- * const chain = FilterPresets.chain()
1066
- * .asetnsamples(960)
1067
- * .build();
1068
- * ```
1069
- *
1070
- * @example
1071
- * ```typescript
1072
- * const chain = FilterPresets.chain()
1073
- * .asetnsamples(1024, false)
1074
- * .build();
1075
- * ```
1076
- *
1077
- * @see {@link FilterPresetBase.asetnsamples}
1078
- */
1079
- asetnsamples(samples, padding = true) {
1080
- return this.add(this.presets.asetnsamples(samples, padding));
1081
- }
1082
- /**
1083
- * Adds an atempo filter to the chain.
1084
- *
1085
- * @param factor - Tempo factor (0.5 to 2.0)
1086
- *
1087
- * @returns This instance for chaining
1088
- *
1089
- * @example
1090
- * ```typescript
1091
- * const chain = FilterPresets.chain()
1092
- * .atempo(1.5)
1093
- * .build();
1094
- * ```
1095
- *
1096
- * @example
1097
- * ```typescript
1098
- * const chain = FilterPresets.chain()
1099
- * .atempo(0.8)
1100
- * .build();
1101
- * ```
1102
- *
1103
- * @see {@link FilterPresetBase.atempo}
1104
- */
1105
- atempo(factor) {
1106
- return this.add(this.presets.atempo(factor));
1107
- }
1108
- /**
1109
- * Adds an audio fade filter to the chain.
1110
- *
1111
- * @param type - Fade type ('in' or 'out')
1112
- * @param start - Start time in seconds
1113
- * @param duration - Fade duration in seconds
1114
- *
1115
- * @returns This instance for chaining
1116
- *
1117
- * @example
1118
- * ```typescript
1119
- * const chain = FilterPresets.chain()
1120
- * .afade('in', 0, 3)
1121
- * .build();
1122
- * ```
38
+ * @param filterName - Name of the filter to check
39
+ *
40
+ * @returns True if the filter uses hardware acceleration
1123
41
  *
1124
42
  * @example
1125
43
  * ```typescript
1126
- * const chain = FilterPresets.chain()
1127
- * .afade('out', 25, 2)
1128
- * .build();
44
+ * if (FilterPreset.isHardwareFilter('scale_cuda')) {
45
+ * console.log('Hardware accelerated scaling');
46
+ * }
1129
47
  * ```
1130
- *
1131
- * @see {@link FilterPresetBase.afade}
1132
48
  */
1133
- afade(type, start, duration) {
1134
- return this.add(this.presets.afade(type, start, duration));
49
+ static isHardwareFilter(filterName) {
50
+ const filter = Filter.getByName(filterName);
51
+ if (!filter) {
52
+ return false;
53
+ }
54
+ // Check if filter has hardware device flag
55
+ return (filter.flags & AVFILTER_FLAG_HWDEVICE) !== 0;
1135
56
  }
1136
57
  /**
1137
- * Adds an amix filter to the chain.
58
+ * Creates a new filter chain builder.
1138
59
  *
1139
- * @param inputs - Number of inputs (default: 2)
1140
- * @param duration - Duration mode (default: 'longest')
60
+ * @param hardware - Optional hardware context for hardware-accelerated filters
1141
61
  *
1142
- * @returns This instance for chaining
62
+ * @returns A new FilterPreset instance for chaining
1143
63
  *
1144
64
  * @example
1145
65
  * ```typescript
1146
- * const chain = FilterPresets.chain()
1147
- * .amix(3, 'longest')
66
+ * // Software filter chain
67
+ * const filter = FilterPreset.chain()
68
+ * .scale(1280, 720)
69
+ * .fps(30)
1148
70
  * .build();
1149
71
  * ```
1150
72
  *
1151
73
  * @example
1152
74
  * ```typescript
1153
- * const chain = FilterPresets.chain()
1154
- * .amix(2, 'first')
75
+ * // Hardware filter chain
76
+ * const hw = HardwareContext.auto();
77
+ * const filter = FilterPreset.chain(hw)
78
+ * .scale(1280, 720)
79
+ * .deinterlace()
1155
80
  * .build();
1156
81
  * ```
1157
- *
1158
- * @see {@link FilterPresetBase.amix}
1159
82
  */
1160
- amix(inputs = 2, duration = 'longest') {
1161
- return this.add(this.presets.amix(inputs, duration));
83
+ static chain(hardware) {
84
+ const preset = new FilterPreset(hardware);
85
+ return preset;
1162
86
  }
1163
- // ========== New Critical Filter Chain Methods ==========
1164
87
  /**
1165
- * Adds a pad filter to the chain.
88
+ * Adds a custom filter string to the chain.
1166
89
  *
1167
- * @param width - Target width
1168
- * @param height - Target height
1169
- * @param x - X position of input
1170
- * @param y - Y position of input
1171
- * @param color - Padding color
90
+ * @param filter - Custom filter string
1172
91
  *
1173
92
  * @returns This instance for chaining
1174
93
  *
1175
94
  * @example
1176
95
  * ```typescript
1177
- * chain.pad('iw*2', 'ih*2') // Double the canvas size
96
+ * chain.custom('myfilter=param1:param2')
1178
97
  * ```
1179
- *
1180
- * @see {@link FilterPresetBase.pad}
1181
98
  */
1182
- pad(width, height, x, y, color = 'black') {
1183
- return this.add(this.presets.pad(width, height, x, y, color));
99
+ custom(filter) {
100
+ return this.add(filter);
1184
101
  }
1185
102
  /**
1186
- * Adds a trim filter to the chain.
103
+ * Builds the final filter string.
1187
104
  *
1188
- * @param start - Start time in seconds
1189
- * @param end - End time in seconds
1190
- * @param duration - Duration in seconds
105
+ * @param separator - Separator between filters (default: ',')
1191
106
  *
1192
- * @returns This instance for chaining
107
+ * @returns Combined filter string
1193
108
  *
1194
109
  * @example
1195
110
  * ```typescript
1196
- * chain.trim(10, 20) // Extract 10 seconds from t=10 to t=20
111
+ * const filterString = chain.build() // "scale=1920:1080,fps=30"
1197
112
  * ```
1198
- *
1199
- * @see {@link FilterPresetBase.trim}
1200
113
  */
1201
- trim(start, end, duration) {
1202
- return this.add(this.presets.trim(start, end, duration));
114
+ build(separator = ',') {
115
+ return this.filters.join(separator);
1203
116
  }
1204
117
  /**
1205
- * Adds a setpts filter to the chain.
1206
- *
1207
- * @param expression - PTS expression
118
+ * Returns the filters as an array.
1208
119
  *
1209
- * @returns This instance for chaining
120
+ * @returns Array of filter strings
1210
121
  *
1211
122
  * @example
1212
123
  * ```typescript
1213
- * chain.setpts('PTS/2') // Double playback speed
124
+ * const filters = chain.toArray() // ["scale=1920:1080", "fps=30"]
1214
125
  * ```
1215
- *
1216
- * @see {@link FilterPresetBase.setpts}
1217
126
  */
1218
- setpts(expression) {
1219
- return this.add(this.presets.setpts(expression));
127
+ toArray() {
128
+ return [...this.filters];
1220
129
  }
1221
130
  /**
1222
- * Adds an asetpts filter to the chain.
131
+ * Adds a scale filter to the chain.
132
+ * Automatically selects hardware-specific scaler if hardware context is set.
1223
133
  *
1224
- * @param expression - PTS expression
134
+ * @param width - Target width in pixels
135
+ *
136
+ * @param height - Target height in pixels
137
+ *
138
+ * @param options - Additional scaling options (e.g., flags for algorithm, npp for CUDA)
1225
139
  *
1226
140
  * @returns This instance for chaining
1227
141
  *
1228
142
  * @example
1229
143
  * ```typescript
1230
- * chain.asetpts('PTS-STARTPTS') // Reset audio timestamps
144
+ * chain.scale(1920, 1080) // Scale to Full HD
145
+ * chain.scale(640, 480, { flags: 'lanczos' }) // With specific algorithm
146
+ * chain.scale(1920, 1080, { npp: true }) // Use NPP for CUDA
1231
147
  * ```
1232
148
  *
1233
- * @see {@link FilterPresetBase.asetpts}
149
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#scale | FFmpeg scale filter}
1234
150
  */
1235
- asetpts(expression) {
1236
- return this.add(this.presets.asetpts(expression));
151
+ scale(width, height, options) {
152
+ if (!this.support.scale) {
153
+ return this;
154
+ }
155
+ if (this.hardware) {
156
+ // Special handling for different hardware scalers
157
+ let filterName;
158
+ if (this.hardware.deviceType === AV_HWDEVICE_TYPE_CUDA && options?.npp) {
159
+ filterName = 'scale_npp';
160
+ }
161
+ else if (this.hardware.deviceType === AV_HWDEVICE_TYPE_RKMPP) {
162
+ filterName = 'scale_rkrga'; // RKMPP uses RGA for scaling
163
+ }
164
+ else if (this.hardware.deviceType === AV_HWDEVICE_TYPE_VIDEOTOOLBOX) {
165
+ filterName = 'scale_vt'; // VideoToolbox uses scale_vt
166
+ }
167
+ else {
168
+ filterName = `scale_${this.hardware.deviceTypeName}`;
169
+ }
170
+ let filter = `${filterName}=${width}:${height}`;
171
+ if (options) {
172
+ for (const [key, value] of Object.entries(options)) {
173
+ if (key !== 'npp') {
174
+ // Skip our special npp flag
175
+ filter += `:${key}=${value}`;
176
+ }
177
+ }
178
+ }
179
+ this.add(filter);
180
+ }
181
+ else {
182
+ const flags = options?.flags;
183
+ const base = `scale=${width}:${height}`;
184
+ const result = flags ? `${base}:flags=${flags}` : base;
185
+ this.add(result);
186
+ }
187
+ return this;
1237
188
  }
1238
189
  /**
1239
- * Adds a setsar filter to the chain.
1240
- *
1241
- * @param ratio - Sample aspect ratio
190
+ * Adds a crop filter to the chain.
1242
191
  *
1243
- * @returns This instance for chaining
192
+ * @param width - Width of the cropped area
1244
193
  *
1245
- * @example
1246
- * ```typescript
1247
- * chain.setsar('1:1') // Square pixels
1248
- * ```
194
+ * @param height - Height of the cropped area
1249
195
  *
1250
- * @see {@link FilterPresetBase.setsar}
1251
- */
1252
- setsar(ratio) {
1253
- return this.add(this.presets.setsar(ratio));
1254
- }
1255
- /**
1256
- * Adds a setdar filter to the chain.
196
+ * @param x - X coordinate of top-left corner (default: 0)
1257
197
  *
1258
- * @param ratio - Display aspect ratio
198
+ * @param y - Y coordinate of top-left corner (default: 0)
1259
199
  *
1260
200
  * @returns This instance for chaining
1261
201
  *
1262
202
  * @example
1263
203
  * ```typescript
1264
- * chain.setdar('16:9') // Set widescreen aspect
204
+ * chain.crop(640, 480, 100, 100) // Crop 640x480 area starting at (100,100)
205
+ * chain.crop(1280, 720) // Crop from top-left corner
1265
206
  * ```
1266
207
  *
1267
- * @see {@link FilterPresetBase.setdar}
208
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#crop | FFmpeg crop filter}
1268
209
  */
1269
- setdar(ratio) {
1270
- return this.add(this.presets.setdar(ratio));
210
+ crop(width, height, x = 0, y = 0) {
211
+ this.add(`crop=${width}:${height}:${x}:${y}`);
212
+ return this;
1271
213
  }
1272
214
  /**
1273
- * Adds an apad filter to the chain.
215
+ * Adds a blur filter to the chain (hardware-specific).
216
+ * Only available for hardware presets that support blur
217
+ *
218
+ * @param type - Blur type (default: 'avg')
1274
219
  *
1275
- * @param wholeDuration - Minimum total duration
1276
- * @param padDuration - Padding duration to add
220
+ * @param radius - Blur radius (optional)
1277
221
  *
1278
222
  * @returns This instance for chaining
1279
223
  *
1280
224
  * @example
1281
225
  * ```typescript
1282
- * chain.apad(10) // Ensure at least 10 seconds of audio
226
+ * const chain = FilterPresets.chain()
227
+ * .blur('gaussian', 5)
228
+ * .build();
1283
229
  * ```
1284
230
  *
1285
- * @see {@link FilterPresetBase.apad}
231
+ * @example
232
+ * ```typescript
233
+ * const chain = FilterPresets.chain()
234
+ * .blur('box')
235
+ * .build();
236
+ * ```
1286
237
  */
1287
- apad(wholeDuration, padDuration) {
1288
- return this.add(this.presets.apad(wholeDuration, padDuration));
238
+ blur(type = 'avg', radius) {
239
+ if (!this.support.blur) {
240
+ return this;
241
+ }
242
+ if (this.hardware) {
243
+ let filter = null;
244
+ switch (this.hardware.deviceType) {
245
+ case AV_HWDEVICE_TYPE_CUDA:
246
+ filter = radius ? `bilateral_cuda=sigmaS=${radius}` : 'bilateral_cuda';
247
+ break;
248
+ case AV_HWDEVICE_TYPE_VULKAN:
249
+ filter = type === 'gaussian' ? (radius ? `gblur_vulkan=sigma=${radius}` : 'gblur_vulkan') : radius ? `avgblur_vulkan=sizeX=${radius}` : 'avgblur_vulkan';
250
+ break;
251
+ case AV_HWDEVICE_TYPE_OPENCL:
252
+ filter = type === 'box' ? (radius ? `boxblur_opencl=luma_radius=${radius}` : 'boxblur_opencl') : radius ? `avgblur_opencl=sizeX=${radius}` : 'avgblur_opencl';
253
+ break;
254
+ default:
255
+ filter = null;
256
+ }
257
+ if (filter) {
258
+ this.add(filter);
259
+ }
260
+ }
261
+ else {
262
+ const filter = type === 'gaussian' ? (radius ? `gblur=sigma=${radius}` : 'gblur') : radius ? `avgblur=sizeX=${radius}` : 'avgblur';
263
+ this.add(filter);
264
+ }
265
+ return this;
1289
266
  }
1290
267
  /**
1291
- * Adds a select filter to the chain.
268
+ * Adds a sharpen filter to the chain (hardware-specific).
269
+ * Only available for hardware presets that support sharpening
1292
270
  *
1293
- * @param expression - Selection expression
271
+ * @param amount - Sharpen amount (optional)
1294
272
  *
1295
273
  * @returns This instance for chaining
1296
274
  *
1297
275
  * @example
1298
276
  * ```typescript
1299
- * chain.select('eq(pict_type,I)') // Select only I-frames
277
+ * const chain = FilterPresets.chain()
278
+ * .sharpen(1.5)
279
+ * .build();
1300
280
  * ```
1301
281
  *
1302
- * @see {@link FilterPresetBase.select}
282
+ * @example
283
+ * ```typescript
284
+ * const chain = FilterPresets.chain()
285
+ * .sharpen()
286
+ * .build();
287
+ * ```
1303
288
  */
1304
- select(expression) {
1305
- return this.add(this.presets.select(expression));
289
+ sharpen(amount) {
290
+ if (!this.support.sharpen) {
291
+ return this;
292
+ }
293
+ if (this.hardware) {
294
+ let filter = null;
295
+ switch (this.hardware.deviceType) {
296
+ case AV_HWDEVICE_TYPE_VAAPI:
297
+ filter = amount ? `sharpness_vaapi=sharpness=${amount}` : 'sharpness_vaapi';
298
+ break;
299
+ case AV_HWDEVICE_TYPE_OPENCL:
300
+ filter = amount ? `unsharp_opencl=amount=${amount}` : 'unsharp_opencl';
301
+ break;
302
+ case AV_HWDEVICE_TYPE_CUDA:
303
+ // CUDA uses NPP for sharpening
304
+ filter = 'sharpen_npp';
305
+ break;
306
+ default:
307
+ filter = null;
308
+ }
309
+ if (filter) {
310
+ this.add(filter);
311
+ }
312
+ }
313
+ else {
314
+ const filter = amount ? `unsharp=amount=${amount}` : 'unsharp';
315
+ this.add(filter);
316
+ }
317
+ return this;
1306
318
  }
1307
319
  /**
1308
- * Adds an aselect filter to the chain.
320
+ * Adds an FPS filter to change frame rate.
1309
321
  *
1310
- * @param expression - Selection expression
322
+ * @param fps - Target frames per second
1311
323
  *
1312
324
  * @returns This instance for chaining
1313
325
  *
1314
326
  * @example
1315
327
  * ```typescript
1316
- * chain.aselect('between(t,10,20)') // Select audio between 10-20s
328
+ * chain.fps(30) // Convert to 30 FPS
329
+ * chain.fps(23.976) // Film frame rate
1317
330
  * ```
1318
331
  *
1319
- * @see {@link FilterPresetBase.aselect}
332
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#fps | FFmpeg fps filter}
1320
333
  */
1321
- aselect(expression) {
1322
- return this.add(this.presets.aselect(expression));
334
+ fps(fps) {
335
+ this.add(`fps=${fps}`);
336
+ return this;
1323
337
  }
1324
338
  /**
1325
- * Adds a concat filter to the chain.
339
+ * Adds a format filter to convert pixel format.
1326
340
  *
1327
- * @param n - Number of input segments
1328
- * @param v - Number of video streams
1329
- * @param a - Number of audio streams
341
+ * @param pixelFormat - Target pixel format(s) - AVPixelFormat enum, or array
1330
342
  *
1331
343
  * @returns This instance for chaining
1332
344
  *
1333
345
  * @example
1334
346
  * ```typescript
1335
- * chain.concat(3, 1, 1) // Concatenate 3 segments with video and audio
347
+ * // Single format
348
+ * chain.format(AV_PIX_FMT_YUV420P);
349
+ *
350
+ * // Multiple formats (tries formats in order)
351
+ * chain.format([AV_PIX_FMT_YUV420P, AV_PIX_FMT_RGB24]);
1336
352
  * ```
1337
353
  *
1338
- * @see {@link FilterPresetBase.concat}
354
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#format | FFmpeg format filter}
1339
355
  */
1340
- concat(n, v = 1, a = 1) {
1341
- return this.add(this.presets.concat(n, v, a));
356
+ format(pixelFormat) {
357
+ if (Array.isArray(pixelFormat)) {
358
+ // Create a chain of format filters
359
+ const formats = pixelFormat.map((fmt) => {
360
+ const formatName = typeof fmt === 'string' ? fmt : (avGetPixFmtName(fmt) ?? 'yuv420p');
361
+ return `format=${formatName}`;
362
+ });
363
+ this.add(formats.join(','));
364
+ }
365
+ else {
366
+ const formatName = typeof pixelFormat === 'string' ? pixelFormat : (avGetPixFmtName(pixelFormat) ?? 'yuv420p');
367
+ this.add(`format=${formatName}`);
368
+ }
369
+ return this;
1342
370
  }
1343
371
  /**
1344
- * Adds an amerge filter to the chain.
372
+ * Adds a rotate filter to the chain.
1345
373
  *
1346
- * @param inputs - Number of input streams
374
+ * @param angle - Rotation angle in degrees
1347
375
  *
1348
376
  * @returns This instance for chaining
1349
377
  *
1350
378
  * @example
1351
379
  * ```typescript
1352
- * chain.amerge(2) // Merge 2 audio streams
380
+ * chain.rotate(90) // Rotate 90 degrees clockwise
381
+ * chain.rotate(-45) // Rotate 45 degrees counter-clockwise
1353
382
  * ```
1354
383
  *
1355
- * @see {@link FilterPresetBase.amerge}
384
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#rotate | FFmpeg rotate filter}
1356
385
  */
1357
- amerge(inputs = 2) {
1358
- return this.add(this.presets.amerge(inputs));
386
+ rotate(angle) {
387
+ this.add(`rotate=${angle}*PI/180`);
388
+ return this;
1359
389
  }
1360
390
  /**
1361
- * Adds a channelmap filter to the chain.
391
+ * Adds a flip filter to the chain (hardware-specific).
392
+ * Falls back to hflip/vflip if hardware flip not available
1362
393
  *
1363
- * @param map - Channel mapping string
394
+ * @param direction - Flip direction ('h' or 'v')
1364
395
  *
1365
396
  * @returns This instance for chaining
1366
397
  *
1367
398
  * @example
1368
399
  * ```typescript
1369
- * chain.channelmap('FL-FR|FR-FL') // Swap stereo channels
400
+ * const chain = FilterPresets.chain()
401
+ * .flip('h')
402
+ * .build();
1370
403
  * ```
1371
404
  *
1372
- * @see {@link FilterPresetBase.channelmap}
405
+ * @example
406
+ * ```typescript
407
+ * const chain = FilterPresets.chain()
408
+ * .flip('v')
409
+ * .build();
410
+ * ```
1373
411
  */
1374
- channelmap(map) {
1375
- return this.add(this.presets.channelmap(map));
412
+ flip(direction) {
413
+ if (!this.support.flip) {
414
+ return this;
415
+ }
416
+ if (this.hardware) {
417
+ if (this.hardware.deviceType === AV_HWDEVICE_TYPE_VULKAN) {
418
+ if (direction === 'v') {
419
+ this.add('vflip_vulkan');
420
+ }
421
+ else {
422
+ this.add('hflip_vulkan');
423
+ }
424
+ }
425
+ }
426
+ else {
427
+ if (direction === 'v') {
428
+ this.add('vflip');
429
+ }
430
+ else {
431
+ this.add('hflip');
432
+ }
433
+ }
434
+ return this;
1376
435
  }
1377
436
  /**
1378
- * Adds a channelsplit filter to the chain.
437
+ * Adds a stack filter to the chain (hardware-specific).
438
+ * Only available for hardware presets that support stacking
1379
439
  *
1380
- * @param channelLayout - Channel layout to split
440
+ * @param type - Stack type ('h' for horizontal, 'v' for vertical, 'x' for grid)
441
+ *
442
+ * @param inputs - Number of inputs (default: 2)
1381
443
  *
1382
444
  * @returns This instance for chaining
1383
445
  *
1384
446
  * @example
1385
447
  * ```typescript
1386
- * chain.channelsplit('stereo') // Split stereo into two mono streams
448
+ * const chain = FilterPresets.chain()
449
+ * .stack('h', 2)
450
+ * .build();
1387
451
  * ```
1388
452
  *
1389
- * @see {@link FilterPresetBase.channelsplit}
453
+ * @example
454
+ * ```typescript
455
+ * const chain = FilterPresets.chain()
456
+ * .stack('x', 4)
457
+ * .build();
458
+ * ```
1390
459
  */
1391
- channelsplit(channelLayout) {
1392
- return this.add(this.presets.channelsplit(channelLayout));
460
+ stack(type, inputs = 2) {
461
+ if (!this.support.stack) {
462
+ return this;
463
+ }
464
+ if (this.hardware) {
465
+ if (this.hardware.deviceType === AV_HWDEVICE_TYPE_VAAPI || this.hardware.deviceType === AV_HWDEVICE_TYPE_QSV) {
466
+ const filter = `${type}stack_${this.hardware.deviceTypeName}=inputs=${inputs}`;
467
+ this.add(filter);
468
+ }
469
+ }
470
+ else {
471
+ const filter = type === 'h' ? `hstack=inputs=${inputs}` : type === 'v' ? `vstack=inputs=${inputs}` : `xstack=inputs=${inputs}`;
472
+ this.add(filter);
473
+ }
474
+ return this;
1393
475
  }
1394
476
  /**
1395
- * Adds a loudnorm filter to the chain.
477
+ * Creates a tonemap filter.
478
+ * Used for HDR to SDR conversion with hardware acceleration.
1396
479
  *
1397
- * @param I - Integrated loudness target (LUFS)
1398
- * @param TP - True peak (dBTP)
1399
- * @param LRA - Loudness range (LU)
480
+ * @param alg - Tonemapping algorithm (e.g., 'hable', 'reinhard', 'mobius', etc.)
1400
481
  *
1401
- * @returns This instance for chaining
482
+ * @param options - Tonemapping options
483
+ *
484
+ * @returns Hardware tonemap filter string or null if not supported
1402
485
  *
1403
486
  * @example
1404
487
  * ```typescript
1405
- * chain.loudnorm(-16, -1.5, 11) // Streaming loudness standard
488
+ * const filter = hwPresets.tonemap();
1406
489
  * ```
1407
490
  *
1408
- * @see {@link FilterPresetBase.loudnorm}
491
+ * @example
492
+ * ```typescript
493
+ * const filter = hwPresets.tonemap({ tonemap: 'hable', desat: '0' });
494
+ * ```
1409
495
  */
1410
- loudnorm(I = -24, TP = -2, LRA = 7) {
1411
- return this.add(this.presets.loudnorm(I, TP, LRA));
496
+ tonemap(alg, options) {
497
+ if (!this.support.tonemap) {
498
+ return this;
499
+ }
500
+ if (this.hardware) {
501
+ // VideoToolbox uses different filter name
502
+ const filterName = this.hardware.deviceType === AV_HWDEVICE_TYPE_VIDEOTOOLBOX ? 'tonemap_videotoolbox' : `tonemap_${this.hardware.deviceTypeName}`;
503
+ let filter = `${filterName}=${alg}`;
504
+ if (options) {
505
+ const opts = Object.entries(options)
506
+ .map(([k, v]) => `${k}=${v}`)
507
+ .join(':');
508
+ filter += `=${opts}`;
509
+ }
510
+ this.add(filter);
511
+ }
512
+ else {
513
+ let filter = `tonemap=${alg}`;
514
+ if (options) {
515
+ const opts = Object.entries(options)
516
+ .map(([k, v]) => `${k}=${v}`)
517
+ .join(':');
518
+ filter += `=${opts}`;
519
+ }
520
+ this.add(filter);
521
+ }
522
+ return this;
1412
523
  }
1413
524
  /**
1414
- * Adds a compand filter to the chain.
525
+ * Creates a fade filter string for video.
1415
526
  *
1416
- * @param attacks - Attack times
1417
- * @param decays - Decay times
1418
- * @param points - Transfer function points
1419
- * @param gain - Output gain
527
+ * @param type - Fade type ('in' or 'out')
1420
528
  *
1421
- * @returns This instance for chaining
529
+ * @param start - Start time in seconds
530
+ *
531
+ * @param duration - Fade duration in seconds
532
+ *
533
+ * @returns Filter string or null if not supported
1422
534
  *
1423
535
  * @example
1424
536
  * ```typescript
1425
- * chain.compand('0.3|0.3', '1|1', '-90/-60|-60/-40|-40/-30|-20/-20', 6)
537
+ * presets.fade('in', 0, 2) // 2-second fade in from start
538
+ * presets.fade('out', 10, 1) // 1-second fade out at 10 seconds
1426
539
  * ```
1427
540
  *
1428
- * @see {@link FilterPresetBase.compand}
541
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#fade | FFmpeg fade filter}
1429
542
  */
1430
- compand(attacks, decays, points, gain) {
1431
- return this.add(this.presets.compand(attacks, decays, points, gain));
543
+ fade(type, start, duration) {
544
+ this.add(`fade=t=${type}:st=${start}:d=${duration}`);
545
+ return this;
1432
546
  }
1433
547
  /**
1434
- * Adds a transpose filter to the chain (hardware-specific).
1435
- * Only available for hardware presets that support transpose
548
+ * Creates an overlay filter string to composite two video streams.
1436
549
  *
1437
- * @param mode - Transpose mode (number or string)
550
+ * @param x - X position for overlay (default: 0)
1438
551
  *
1439
- * @returns This instance for chaining
552
+ * @param y - Y position for overlay (default: 0)
1440
553
  *
1441
- * @example
1442
- * ```typescript
1443
- * const chain = FilterPresets.chain()
1444
- * .transpose('clock')
1445
- * .build();
1446
- * ```
554
+ * @param options - Additional overlay options
555
+ *
556
+ * @returns Filter string or null if not supported
1447
557
  *
1448
558
  * @example
1449
559
  * ```typescript
1450
- * const chain = FilterPresets.chain()
1451
- * .transpose('cclock_flip')
1452
- * .build();
560
+ * // Basic overlay at position
561
+ * presets.overlay(100, 50);
562
+ *
563
+ * // With additional options
564
+ * presets.overlay(0, 0, { format: 'yuv420' });
1453
565
  * ```
566
+ *
567
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#overlay | FFmpeg overlay filter}
1454
568
  */
1455
- transpose(mode = 0) {
1456
- if ('transpose' in this.presets) {
1457
- return this.add(this.presets.transpose(mode));
569
+ overlay(x = 0, y = 0, options) {
570
+ if (!this.support.overlay) {
571
+ return this;
572
+ }
573
+ if (this.hardware) {
574
+ // Special handling for RKMPP which uses RGA
575
+ const filterName = this.hardware.deviceType === AV_HWDEVICE_TYPE_RKMPP ? 'overlay_rkrga' : `overlay_${this.hardware.deviceTypeName}`;
576
+ let filter = `${filterName}=${x}:${y}`;
577
+ if (options) {
578
+ for (const [key, value] of Object.entries(options)) {
579
+ filter += `:${key}=${value}`;
580
+ }
581
+ }
582
+ this.add(filter);
583
+ }
584
+ else {
585
+ let filter = `overlay=${x}:${y}`;
586
+ if (options) {
587
+ for (const [key, value] of Object.entries(options)) {
588
+ filter += `:${key}=${value}`;
589
+ }
590
+ }
591
+ this.add(filter);
1458
592
  }
1459
- return this.add(null);
593
+ return this;
1460
594
  }
1461
595
  /**
1462
- * Adds a tonemap filter to the chain (hardware-specific).
1463
- * Only available for hardware presets that support tonemapping
596
+ * Creates a volume filter string for audio.
1464
597
  *
1465
- * @param options - Tonemapping options
598
+ * @param factor - Volume multiplication factor (1.0 = unchanged, 2.0 = double)
1466
599
  *
1467
- * @returns This instance for chaining
600
+ * @returns Filter string or null if not supported
1468
601
  *
1469
602
  * @example
1470
603
  * ```typescript
1471
- * const chain = FilterPresets.chain()
1472
- * .tonemap()
1473
- * .build();
604
+ * presets.volume(0.5) // Reduce volume by 50%
605
+ * presets.volume(1.5) // Increase volume by 50%
1474
606
  * ```
1475
607
  *
1476
- * @example
1477
- * ```typescript
1478
- * const chain = FilterPresets.chain()
1479
- * .tonemap({ tonemap: 'hable', desat: '0' })
1480
- * .build();
1481
- * ```
608
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#volume | FFmpeg volume filter}
1482
609
  */
1483
- tonemap(options) {
1484
- if ('tonemap' in this.presets) {
1485
- return this.add(this.presets.tonemap(options));
1486
- }
1487
- return this.add(null);
610
+ volume(factor) {
611
+ this.add(`volume=${factor}`);
612
+ return this;
1488
613
  }
1489
614
  /**
1490
- * Adds a deinterlace filter to the chain (hardware-specific).
1491
- * Only available for hardware presets that support deinterlacing
615
+ * Creates an audio format filter string.
1492
616
  *
1493
- * @param mode - Deinterlace mode (optional)
617
+ * @param sampleFormat - Target sample format (e.g., 's16', 'fltp')
1494
618
  *
1495
- * @returns This instance for chaining
619
+ * @param sampleRate - Target sample rate in Hz (optional)
1496
620
  *
1497
- * @example
1498
- * ```typescript
1499
- * const chain = FilterPresets.chain()
1500
- * .deinterlace()
1501
- * .build();
1502
- * ```
621
+ * @param channelLayout - Target channel layout (optional)
622
+ *
623
+ * @returns Filter string or null if not supported
1503
624
  *
1504
625
  * @example
1505
626
  * ```typescript
1506
- * const chain = FilterPresets.chain()
1507
- * .deinterlace('yadif')
1508
- * .build();
627
+ * // Change sample format only
628
+ * presets.aformat('s16');
629
+ *
630
+ * // Change format and sample rate
631
+ * presets.aformat('fltp', 48000);
632
+ *
633
+ * // Full conversion
634
+ * presets.aformat('s16', 44100, 'stereo');
1509
635
  * ```
636
+ *
637
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#aformat | FFmpeg aformat filter}
1510
638
  */
1511
- deinterlace(mode) {
1512
- if ('deinterlace' in this.presets) {
1513
- return this.add(this.presets.deinterlace(mode));
639
+ aformat(sampleFormat, sampleRate, channelLayout) {
640
+ let sampleFormats = '';
641
+ if (!Array.isArray(sampleFormat)) {
642
+ sampleFormat = [sampleFormat];
1514
643
  }
1515
- return this.add(null);
644
+ sampleFormats = sampleFormat.map((fmt) => (typeof fmt === 'string' ? fmt : (avGetSampleFmtName(fmt) ?? 's16'))).join('|');
645
+ let filter = `aformat=sample_fmts=${sampleFormats}`;
646
+ if (sampleRate)
647
+ filter += `:sample_rates=${sampleRate}`;
648
+ if (channelLayout)
649
+ filter += `:channel_layouts=${channelLayout}`;
650
+ this.add(filter);
651
+ return this;
1516
652
  }
1517
653
  /**
1518
- * Adds a flip filter to the chain (hardware-specific).
1519
- * Falls back to hflip/vflip if hardware flip not available
654
+ * Adds an asetnsamples filter to set the number of samples per frame.
655
+ * This is crucial for encoders like Opus that require specific frame sizes.
1520
656
  *
1521
- * @param direction - Flip direction ('h' or 'v')
657
+ * @param samples - Number of samples per frame
658
+ *
659
+ * @param padding - Whether to pad or drop samples (default: true)
1522
660
  *
1523
661
  * @returns This instance for chaining
1524
662
  *
1525
663
  * @example
1526
664
  * ```typescript
1527
- * const chain = FilterPresets.chain()
1528
- * .flip('h')
1529
- * .build();
1530
- * ```
665
+ * // For Opus encoder (requires 960 samples)
666
+ * chain.asetnsamples(960);
1531
667
  *
1532
- * @example
1533
- * ```typescript
1534
- * const chain = FilterPresets.chain()
1535
- * .flip('v')
1536
- * .build();
668
+ * // Drop samples instead of padding
669
+ * chain.asetnsamples(1024, false);
1537
670
  * ```
671
+ *
672
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#asetnsamples | FFmpeg asetnsamples filter}
1538
673
  */
1539
- flip(direction) {
1540
- if ('flip' in this.presets) {
1541
- return this.add(this.presets.flip(direction));
1542
- }
1543
- // Fallback to hflip/vflip
1544
- return direction === 'h' ? this.hflip() : this.vflip();
674
+ asetnsamples(samples, padding = true) {
675
+ const p = padding ? 1 : 0;
676
+ this.add(`asetnsamples=n=${samples}:p=${p}`);
677
+ return this;
1545
678
  }
1546
679
  /**
1547
- * Adds a blur filter to the chain (hardware-specific).
1548
- * Only available for hardware presets that support blur
680
+ * Adds an aresample filter to change audio sample rate.
1549
681
  *
1550
- * @param type - Blur type (default: 'avg')
1551
- * @param radius - Blur radius (optional)
682
+ * @param rate - Target sample rate in Hz
1552
683
  *
1553
684
  * @returns This instance for chaining
1554
685
  *
1555
686
  * @example
1556
687
  * ```typescript
1557
- * const chain = FilterPresets.chain()
1558
- * .blur('gaussian', 5)
1559
- * .build();
688
+ * chain.aresample(44100) // Convert to 44.1 kHz
689
+ * chain.aresample(48000) // Convert to 48 kHz
1560
690
  * ```
1561
691
  *
1562
- * @example
1563
- * ```typescript
1564
- * const chain = FilterPresets.chain()
1565
- * .blur('box')
1566
- * .build();
1567
- * ```
692
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#aresample | FFmpeg aresample filter}
1568
693
  */
1569
- blur(type = 'avg', radius) {
1570
- if ('blur' in this.presets) {
1571
- return this.add(this.presets.blur(type, radius));
1572
- }
1573
- return this.add(null);
694
+ aresample(rate) {
695
+ this.add(`aresample=${rate}`);
696
+ return this;
1574
697
  }
1575
698
  /**
1576
- * Adds a sharpen filter to the chain (hardware-specific).
1577
- * Only available for hardware presets that support sharpening
699
+ * Adds an atempo filter to change audio playback speed.
700
+ * Factor must be between 0.5 and 2.0. For larger changes, chain multiple atempo filters.
1578
701
  *
1579
- * @param amount - Sharpen amount (optional)
702
+ * @param factor - Tempo factor (0.5 = half speed, 2.0 = double speed)
1580
703
  *
1581
704
  * @returns This instance for chaining
1582
705
  *
1583
706
  * @example
1584
707
  * ```typescript
1585
- * const chain = FilterPresets.chain()
1586
- * .sharpen(1.5)
1587
- * .build();
708
+ * chain.atempo(1.5) // 1.5x speed
709
+ * chain.atempo(0.8) // Slow down to 80% speed
1588
710
  * ```
1589
711
  *
1590
- * @example
1591
- * ```typescript
1592
- * const chain = FilterPresets.chain()
1593
- * .sharpen()
1594
- * .build();
1595
- * ```
712
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#atempo | FFmpeg atempo filter}
1596
713
  */
1597
- sharpen(amount) {
1598
- if ('sharpen' in this.presets) {
1599
- return this.add(this.presets.sharpen(amount));
1600
- }
1601
- return this.add(null);
714
+ atempo(factor) {
715
+ this.add(`atempo=${factor}`);
716
+ return this;
1602
717
  }
1603
718
  /**
1604
- * Adds a stack filter to the chain (hardware-specific).
1605
- * Only available for hardware presets that support stacking
719
+ * Adds an audio fade filter.
1606
720
  *
1607
- * @param type - Stack type ('h' for horizontal, 'v' for vertical, 'x' for grid)
1608
- * @param inputs - Number of inputs (default: 2)
721
+ * @param type - Fade type ('in' or 'out')
722
+ *
723
+ * @param start - Start time in seconds
724
+ *
725
+ * @param duration - Fade duration in seconds
1609
726
  *
1610
727
  * @returns This instance for chaining
1611
728
  *
1612
729
  * @example
1613
730
  * ```typescript
1614
- * const chain = FilterPresets.chain()
1615
- * .stack('h', 2)
1616
- * .build();
731
+ * chain.afade('in', 0, 3) // 3-second audio fade in
732
+ * chain.afade('out', 20, 2) // 2-second fade out at 20s
1617
733
  * ```
1618
734
  *
1619
- * @example
1620
- * ```typescript
1621
- * const chain = FilterPresets.chain()
1622
- * .stack('x', 4)
1623
- * .build();
1624
- * ```
735
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#afade | FFmpeg afade filter}
1625
736
  */
1626
- stack(type, inputs = 2) {
1627
- if ('stack' in this.presets) {
1628
- return this.add(this.presets.stack(type, inputs));
1629
- }
1630
- return this.add(null);
737
+ afade(type, start, duration) {
738
+ this.add(`afade=t=${type}:st=${start}:d=${duration}`);
739
+ return this;
1631
740
  }
1632
741
  /**
1633
- * Adds a hwupload filter to upload frames to hardware.
742
+ * Adds an amix filter to mix multiple audio streams.
1634
743
  *
1635
- * @returns This instance for chaining
744
+ * @param inputs - Number of input streams to mix (default: 2)
1636
745
  *
1637
- * @example
1638
- * ```typescript
1639
- * const chain = FilterPresets.chain()
1640
- * .hwupload()
1641
- * .scale(1920, 1080)
1642
- * .build();
1643
- * ```
1644
- */
1645
- hwupload() {
1646
- if ('hwupload' in this.presets) {
1647
- return this.add(this.presets.hwupload());
1648
- }
1649
- return this.add('hwupload');
1650
- }
1651
- /**
1652
- * Adds a hwdownload filter to download frames from hardware.
746
+ * @param duration - How to determine output duration (default: 'longest')
1653
747
  *
1654
748
  * @returns This instance for chaining
1655
749
  *
1656
750
  * @example
1657
751
  * ```typescript
1658
- * const chain = FilterPresets.chain()
1659
- * .scale(1920, 1080)
1660
- * .hwdownload()
1661
- * .build();
752
+ * chain.amix(3, 'longest') // Mix 3 audio streams
753
+ * chain.amix(2, 'first') // Mix 2 streams, use first's duration
1662
754
  * ```
755
+ *
756
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#amix | FFmpeg amix filter}
1663
757
  */
1664
- hwdownload() {
1665
- if ('hwdownload' in this.presets) {
1666
- return this.add(this.presets.hwdownload());
1667
- }
1668
- return this.add('hwdownload');
758
+ amix(inputs = 2, duration = 'longest') {
759
+ this.add(`amix=inputs=${inputs}:duration=${duration}`);
760
+ return this;
1669
761
  }
1670
762
  /**
1671
- * Adds a hwmap filter to map frames between hardware devices.
763
+ * Adds a pad filter to add padding to video.
764
+ * Essential for aspect ratio adjustments and letterboxing.
1672
765
  *
1673
- * @param derive - Device to derive from (optional)
766
+ * @param width - Output width (can use expressions like 'iw+100')
767
+ *
768
+ * @param height - Output height (can use expressions like 'ih+100')
769
+ *
770
+ * @param x - X position of input video (default: '(ow-iw)/2' for center)
771
+ *
772
+ * @param y - Y position of input video (default: '(oh-ih)/2' for center)
773
+ *
774
+ * @param color - Padding color (default: 'black')
1674
775
  *
1675
776
  * @returns This instance for chaining
1676
777
  *
1677
778
  * @example
1678
779
  * ```typescript
1679
- * const chain = FilterPresets.chain()
1680
- * .hwmap('cuda')
1681
- * .build();
1682
- * ```
780
+ * // Add black bars for 16:9 aspect ratio
781
+ * chain.pad('iw', 'iw*9/16');
1683
782
  *
1684
- * @example
1685
- * ```typescript
1686
- * const chain = FilterPresets.chain()
1687
- * .hwmap()
1688
- * .build();
783
+ * // Add 50px padding on all sides
784
+ * chain.pad('iw+100', 'ih+100');
1689
785
  * ```
786
+ *
787
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#pad | FFmpeg pad filter}
1690
788
  */
1691
- hwmap(derive) {
1692
- if ('hwmap' in this.presets) {
1693
- return this.add(this.presets.hwmap(derive));
1694
- }
1695
- return this.add(derive ? `hwmap=derive_device=${derive}` : 'hwmap');
789
+ pad(width, height, x, y, color = 'black') {
790
+ let filter = `pad=${width}:${height}`;
791
+ if (x !== undefined)
792
+ filter += `:${x}`;
793
+ if (y !== undefined)
794
+ filter += `:${y}`;
795
+ filter += `:${color}`;
796
+ this.add(filter);
797
+ return this;
1696
798
  }
1697
- }
1698
- /**
1699
- * Fluent filter chain builder with preset methods.
1700
- * Provides a convenient API for building filter chains using standard presets.
1701
- *
1702
- * @example
1703
- * ```typescript
1704
- * const filter = FilterPresets.chain()
1705
- * .scale(1920, 1080)
1706
- * .fps(30)
1707
- * .fade('in', 0, 2)
1708
- * .format('yuv420p')
1709
- * .build();
1710
- * ```
1711
- */
1712
- export class FilterChainBuilder extends ChainBuilderBase {
1713
- }
1714
- /**
1715
- * Standard filter presets for software filtering.
1716
- * Provides static methods for creating common filter strings and
1717
- * a chain builder for composing complex filter graphs.
1718
- *
1719
- * @example
1720
- * ```typescript
1721
- * // Static methods for individual filters
1722
- * const scaleFilter = FilterPresets.scale(1920, 1080);
1723
- * const fpsFilter = FilterPresets.fps(30);
1724
- *
1725
- * // Chain builder for complex graphs
1726
- * const chain = FilterPresets.chain()
1727
- * .scale(1920, 1080)
1728
- * .fps(30)
1729
- * .fade('in', 0, 2)
1730
- * .build();
1731
- * ```
1732
- */
1733
- export class FilterPresets extends FilterPresetBase {
1734
- static instance = new FilterPresets();
1735
799
  /**
1736
- * Creates a new filter chain builder.
800
+ * Adds a trim filter to cut a portion of the stream.
801
+ * Crucial for cutting segments from media.
802
+ *
803
+ * @param start - Start time in seconds
804
+ *
805
+ * @param end - End time in seconds (optional)
806
+ *
807
+ * @param duration - Duration in seconds (optional, alternative to end)
1737
808
  *
1738
- * @returns A new FilterChainBuilder instance
809
+ * @returns This instance for chaining
1739
810
  *
1740
811
  * @example
1741
812
  * ```typescript
1742
- * const filter = FilterPresets.chain()
1743
- * .scale(1280, 720)
1744
- * .fps(30)
1745
- * .build();
813
+ * chain.trim(10, 30) // Extract from 10s to 30s
814
+ * chain.trim(5, undefined, 10) // Extract 10s starting at 5s
1746
815
  * ```
816
+ *
817
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#trim | FFmpeg trim filter}
1747
818
  */
1748
- static chain() {
1749
- return new FilterChainBuilder(FilterPresets.instance);
819
+ trim(start, end, duration) {
820
+ let filter = `trim=start=${start}`;
821
+ if (end !== undefined)
822
+ filter += `:end=${end}`;
823
+ if (duration !== undefined)
824
+ filter += `:duration=${duration}`;
825
+ this.add(filter);
826
+ return this;
1750
827
  }
1751
828
  /**
1752
- * Creates a scale filter string.
829
+ * Creates a setpts filter string to change presentation timestamps.
830
+ * Essential for speed changes and timestamp manipulation.
1753
831
  *
1754
- * @param width - Target width
1755
- * @param height - Target height
1756
- * @param flags - Scaling algorithm flags (optional)
832
+ * @param expression - PTS expression (e.g., 'PTS*2' for half speed, 'PTS/2' for double speed)
1757
833
  *
1758
- * @returns Scale filter string
834
+ * @returns Filter string or null if not supported
1759
835
  *
1760
836
  * @example
1761
837
  * ```typescript
1762
- * const filter = FilterPresets.scale(1920, 1080);
1763
- * ```
838
+ * // Double speed
839
+ * presets.setpts('PTS/2');
1764
840
  *
1765
- * @example
1766
- * ```typescript
1767
- * const filter = FilterPresets.scale(1280, 720, 'lanczos');
841
+ * // Half speed
842
+ * presets.setpts('PTS*2');
843
+ *
844
+ * // Reset timestamps
845
+ * presets.setpts('PTS-STARTPTS');
1768
846
  * ```
847
+ *
848
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#setpts | FFmpeg setpts filter}
1769
849
  */
1770
- static scale(width, height, flags) {
1771
- const result = FilterPresets.instance.scale(width, height, { flags });
1772
- return result ?? '';
850
+ setpts(expression) {
851
+ this.add(`setpts=${expression}`);
852
+ return this;
1773
853
  }
1774
854
  /**
1775
- * Creates a crop filter string.
855
+ * Creates an asetpts filter string for audio timestamp manipulation.
1776
856
  *
1777
- * @param width - Crop width
1778
- * @param height - Crop height
1779
- * @param x - X position (default: 0)
1780
- * @param y - Y position (default: 0)
857
+ * @param expression - PTS expression
1781
858
  *
1782
- * @returns Crop filter string
859
+ * @returns Filter string or null if not supported
1783
860
  *
1784
861
  * @example
1785
862
  * ```typescript
1786
- * const filter = FilterPresets.crop(640, 480);
863
+ * presets.asetpts('PTS-STARTPTS') // Reset timestamps to start from 0
1787
864
  * ```
1788
865
  *
1789
- * @example
1790
- * ```typescript
1791
- * const filter = FilterPresets.crop(1920, 1080, 100, 50);
1792
- * ```
866
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#asetpts | FFmpeg asetpts filter}
1793
867
  */
1794
- static crop(width, height, x = 0, y = 0) {
1795
- const result = FilterPresets.instance.crop(width, height, x, y);
1796
- return result ?? '';
868
+ asetpts(expression) {
869
+ this.add(`asetpts=${expression}`);
870
+ return this;
1797
871
  }
1798
872
  /**
1799
- * Creates an FPS filter string.
873
+ * Creates a transpose filter string for rotation/flipping.
874
+ * More efficient than rotate for 90-degree rotations.
1800
875
  *
1801
- * @param fps - Target frame rate
876
+ * @param mode - Transpose mode (0-3, or named constants)
1802
877
  *
1803
- * @returns FPS filter string
878
+ * @returns Filter string or null if not supported
1804
879
  *
1805
880
  * @example
1806
881
  * ```typescript
1807
- * const filter = FilterPresets.fps(30);
882
+ * presets.transpose(1) // Rotate 90 degrees clockwise
883
+ * presets.transpose('cclock') // Rotate 90 degrees counter-clockwise
1808
884
  * ```
1809
885
  *
1810
- * @example
1811
- * ```typescript
1812
- * const filter = FilterPresets.fps(23.976);
1813
- * ```
886
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#transpose | FFmpeg transpose filter}
1814
887
  */
1815
- static fps(fps) {
1816
- const result = FilterPresets.instance.fps(fps);
1817
- return result ?? '';
888
+ transpose(mode) {
889
+ if (!this.support.transpose) {
890
+ return this;
891
+ }
892
+ if (this.hardware) {
893
+ // Convert string modes to numbers
894
+ let dir;
895
+ if (typeof mode === 'string') {
896
+ switch (mode) {
897
+ case 'clock':
898
+ dir = 1;
899
+ break;
900
+ case 'cclock':
901
+ dir = 2;
902
+ break;
903
+ case 'clock_flip':
904
+ dir = 3;
905
+ break;
906
+ case 'cclock_flip':
907
+ dir = 0;
908
+ break;
909
+ default:
910
+ dir = 0;
911
+ }
912
+ }
913
+ else {
914
+ dir = mode;
915
+ }
916
+ // Special handling for different hardware transpose implementations
917
+ let filterName;
918
+ if (this.hardware.deviceType === AV_HWDEVICE_TYPE_CUDA) {
919
+ filterName = 'transpose_cuda'; // Uses transpose_cuda from patch, not NPP
920
+ }
921
+ else if (this.hardware.deviceType === AV_HWDEVICE_TYPE_VIDEOTOOLBOX) {
922
+ filterName = 'transpose_vt'; // CoreImage-based transpose
923
+ }
924
+ else {
925
+ filterName = `transpose_${this.hardware.deviceTypeName}`;
926
+ }
927
+ this.add(`${filterName}=dir=${dir}`);
928
+ }
929
+ else {
930
+ this.add(`transpose=${mode}`);
931
+ }
932
+ return this;
1818
933
  }
1819
934
  /**
1820
- * Creates a format filter string.
935
+ * Creates a setsar filter string to set sample aspect ratio.
936
+ * Important for correcting aspect ratio issues.
1821
937
  *
1822
- * @param pixelFormat - Target pixel format(s)
938
+ * @param ratio - Aspect ratio (e.g., '1:1', '16:9', or number)
1823
939
  *
1824
- * @returns Format filter string
940
+ * @returns Filter string or null if not supported
1825
941
  *
1826
942
  * @example
1827
943
  * ```typescript
1828
- * const filter = FilterPresets.format('yuv420p');
944
+ * presets.setsar('1:1') // Square pixels
945
+ * presets.setsar(1.333) // 4:3 aspect ratio
1829
946
  * ```
1830
947
  *
1831
- * @example
1832
- * ```typescript
1833
- * const filter = FilterPresets.format(AV_PIX_FMT_RGB24);
1834
- * ```
948
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#setsar | FFmpeg setsar/setdar filter}
1835
949
  */
1836
- static format(pixelFormat) {
1837
- const result = FilterPresets.instance.format(pixelFormat);
1838
- return result ?? '';
950
+ setsar(ratio) {
951
+ this.add(`setsar=${ratio}`);
952
+ return this;
1839
953
  }
1840
954
  /**
1841
- * Creates a rotate filter string.
955
+ * Creates a setdar filter string to set display aspect ratio.
1842
956
  *
1843
- * @param angle - Rotation angle in degrees
957
+ * @param ratio - Aspect ratio (e.g., '16:9', '4:3')
1844
958
  *
1845
- * @returns Rotate filter string
959
+ * @returns Filter string or null if not supported
1846
960
  *
1847
961
  * @example
1848
962
  * ```typescript
1849
- * const filter = FilterPresets.rotate(45);
963
+ * presets.setdar('16:9') // Widescreen
964
+ * presets.setdar('4:3') // Traditional TV aspect
1850
965
  * ```
1851
966
  *
1852
- * @example
1853
- * ```typescript
1854
- * const filter = FilterPresets.rotate(-90);
1855
- * ```
967
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#setsar | FFmpeg setsar/setdar filter}
1856
968
  */
1857
- static rotate(angle) {
1858
- const result = FilterPresets.instance.rotate(angle);
1859
- return result ?? '';
969
+ setdar(ratio) {
970
+ this.add(`setdar=${ratio}`);
971
+ return this;
1860
972
  }
1861
973
  /**
1862
- * Creates a horizontal flip filter string.
974
+ * Adds an apad filter to add audio padding.
975
+ * Useful for ensuring minimum audio duration.
976
+ *
977
+ * @param wholeDuration - Minimum duration in seconds (optional)
978
+ *
979
+ * @param padDuration - Amount of padding to add in seconds (optional)
1863
980
  *
1864
- * @returns Horizontal flip filter string
981
+ * @returns This instance for chaining
1865
982
  *
1866
983
  * @example
1867
984
  * ```typescript
1868
- * const filter = FilterPresets.hflip();
985
+ * chain.apad(30) // Ensure at least 30 seconds total
986
+ * chain.apad(undefined, 5) // Add 5 seconds of padding
1869
987
  * ```
988
+ *
989
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#apad | FFmpeg apad filter}
1870
990
  */
1871
- static hflip() {
1872
- const result = FilterPresets.instance.hflip();
1873
- return result ?? '';
991
+ apad(wholeDuration, padDuration) {
992
+ if (!wholeDuration && !padDuration)
993
+ return this;
994
+ let filter = 'apad';
995
+ if (wholeDuration)
996
+ filter += `=whole_dur=${wholeDuration}`;
997
+ if (padDuration)
998
+ filter += wholeDuration ? `:pad_dur=${padDuration}` : `=pad_dur=${padDuration}`;
999
+ this.add(filter);
1000
+ return this;
1874
1001
  }
1875
1002
  /**
1876
- * Creates a vertical flip filter string.
1003
+ * Creates a deinterlace filter string.
1004
+ * Essential for processing interlaced content.
1005
+ *
1006
+ * @param mode - Deinterlace mode (default: 'yadif')
1877
1007
  *
1878
- * @returns Vertical flip filter string
1008
+ * @param options - Additional options for the filter
1009
+ *
1010
+ * @returns Filter string or null if not supported
1879
1011
  *
1880
1012
  * @example
1881
1013
  * ```typescript
1882
- * const filter = FilterPresets.vflip();
1014
+ * presets.deinterlace('yadif') // Standard deinterlacing
1015
+ * presets.deinterlace('bwdif') // Bob Weaver deinterlacing
1883
1016
  * ```
1017
+ *
1018
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#yadif | FFmpeg yadif filter}
1884
1019
  */
1885
- static vflip() {
1886
- const result = FilterPresets.instance.vflip();
1887
- return result ?? '';
1020
+ deinterlace(mode = 'yadif', options) {
1021
+ if (!this.support.deinterlace) {
1022
+ return this;
1023
+ }
1024
+ if (this.hardware) {
1025
+ let filter = null;
1026
+ switch (this.hardware.deviceType) {
1027
+ case AV_HWDEVICE_TYPE_CUDA:
1028
+ filter = mode ? `yadif_cuda=mode=${mode}` : 'yadif_cuda';
1029
+ break;
1030
+ case AV_HWDEVICE_TYPE_VAAPI:
1031
+ filter = mode ? `deinterlace_vaapi=mode=${mode}` : 'deinterlace_vaapi';
1032
+ break;
1033
+ case AV_HWDEVICE_TYPE_QSV:
1034
+ filter = mode ? `deinterlace_qsv=mode=${mode}` : 'deinterlace_qsv';
1035
+ break;
1036
+ case AV_HWDEVICE_TYPE_VULKAN:
1037
+ filter = mode ? `bwdif_vulkan=mode=${mode}` : 'bwdif_vulkan';
1038
+ break;
1039
+ case AV_HWDEVICE_TYPE_VIDEOTOOLBOX:
1040
+ filter = mode ? `yadif_videotoolbox=mode=${mode}` : 'yadif_videotoolbox';
1041
+ break;
1042
+ default:
1043
+ filter = null;
1044
+ }
1045
+ if (filter) {
1046
+ if (options) {
1047
+ const params = [];
1048
+ for (const [key, value] of Object.entries(options)) {
1049
+ params.push(`${key}=${value}`);
1050
+ }
1051
+ filter += '=' + params.join(':');
1052
+ }
1053
+ this.add(filter);
1054
+ }
1055
+ }
1056
+ else {
1057
+ let filter = mode;
1058
+ if (options) {
1059
+ const params = [];
1060
+ for (const [key, value] of Object.entries(options)) {
1061
+ params.push(`${key}=${value}`);
1062
+ }
1063
+ filter += '=' + params.join(':');
1064
+ }
1065
+ this.add(filter);
1066
+ }
1067
+ return this;
1888
1068
  }
1889
1069
  /**
1890
- * Creates a fade filter string.
1070
+ * Creates a select filter string to select specific frames.
1071
+ * Powerful for extracting keyframes, specific frame types, etc.
1891
1072
  *
1892
- * @param type - Fade type ('in' or 'out')
1893
- * @param start - Start time in seconds
1894
- * @param duration - Fade duration in seconds
1073
+ * @param expression - Selection expression
1895
1074
  *
1896
- * @returns Fade filter string
1075
+ * @returns Filter string or null if not supported
1897
1076
  *
1898
1077
  * @example
1899
1078
  * ```typescript
1900
- * const filter = FilterPresets.fade('in', 0, 2);
1079
+ * presets.select('eq(pict_type,I)') // Select only keyframes
1080
+ * presets.select('not(mod(n,10))') // Select every 10th frame
1901
1081
  * ```
1902
1082
  *
1903
- * @example
1904
- * ```typescript
1905
- * const filter = FilterPresets.fade('out', 10, 1.5);
1906
- * ```
1083
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#select | FFmpeg select filter}
1907
1084
  */
1908
- static fade(type, start, duration) {
1909
- const result = FilterPresets.instance.fade(type, start, duration);
1910
- return result ?? '';
1085
+ select(expression) {
1086
+ this.add(`select='${expression}'`);
1087
+ return this;
1911
1088
  }
1912
1089
  /**
1913
- * Creates an overlay filter string.
1090
+ * Creates an aselect filter string for audio selection.
1914
1091
  *
1915
- * @param x - X position (default: 0)
1916
- * @param y - Y position (default: 0)
1092
+ * @param expression - Selection expression
1917
1093
  *
1918
- * @returns Overlay filter string
1094
+ * @returns Filter string or null if not supported
1919
1095
  *
1920
1096
  * @example
1921
1097
  * ```typescript
1922
- * const filter = FilterPresets.overlay(100, 50);
1098
+ * presets.aselect('between(t,10,20)') // Select audio between 10-20 seconds
1923
1099
  * ```
1924
1100
  *
1925
- * @example
1926
- * ```typescript
1927
- * const filter = FilterPresets.overlay();
1928
- * ```
1101
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#aselect | FFmpeg aselect filter}
1929
1102
  */
1930
- static overlay(x = 0, y = 0) {
1931
- const result = FilterPresets.instance.overlay(x, y);
1932
- return result ?? '';
1103
+ aselect(expression) {
1104
+ this.add(`aselect='${expression}'`);
1105
+ return this;
1933
1106
  }
1934
1107
  /**
1935
- * Creates a volume filter string.
1108
+ * Creates a concat filter string to concatenate multiple inputs.
1109
+ * Essential for joining multiple video/audio segments.
1110
+ *
1111
+ * @param n - Number of input segments
1936
1112
  *
1937
- * @param factor - Volume multiplication factor
1113
+ * @param v - Number of output video streams (0 or 1)
1938
1114
  *
1939
- * @returns Volume filter string
1115
+ * @param a - Number of output audio streams (0 or 1)
1940
1116
  *
1941
- * @example
1942
- * ```typescript
1943
- * const filter = FilterPresets.volume(0.5);
1944
- * ```
1117
+ * @returns Filter string or null if not supported
1945
1118
  *
1946
1119
  * @example
1947
1120
  * ```typescript
1948
- * const filter = FilterPresets.volume(2.0);
1121
+ * presets.concat(3, 1, 1) // Join 3 segments with video and audio
1122
+ * presets.concat(2, 1, 0) // Join 2 video-only segments
1949
1123
  * ```
1124
+ *
1125
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#concat | FFmpeg concat filter}
1950
1126
  */
1951
- static volume(factor) {
1952
- const result = FilterPresets.instance.volume(factor);
1953
- return result ?? '';
1127
+ concat(n, v = 1, a = 1) {
1128
+ this.add(`concat=n=${n}:v=${v}:a=${a}`);
1129
+ return this;
1954
1130
  }
1955
1131
  /**
1956
- * Creates an audio format filter string.
1132
+ * Creates an amerge filter string to merge multiple audio streams into one.
1133
+ * Different from amix - this creates multi-channel output.
1957
1134
  *
1958
- * @param sampleFormat - Target sample format
1959
- * @param sampleRate - Target sample rate (optional)
1960
- * @param channelLayout - Target channel layout (optional)
1135
+ * @param inputs - Number of input streams
1961
1136
  *
1962
- * @returns Audio format filter string
1137
+ * @returns Filter string or null if not supported
1963
1138
  *
1964
1139
  * @example
1965
1140
  * ```typescript
1966
- * const filter = FilterPresets.aformat(AV_SAMPLE_FMT_FLT, 48000, 'stereo');
1141
+ * presets.amerge(2) // Merge 2 mono streams to stereo
1142
+ * presets.amerge(6) // Merge 6 channels for 5.1 surround
1967
1143
  * ```
1968
1144
  *
1969
- * @example
1970
- * ```typescript
1971
- * const filter = FilterPresets.aformat('s16', 44100);
1972
- * ```
1145
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#amerge | FFmpeg amerge filter}
1973
1146
  */
1974
- static aformat(sampleFormat, sampleRate, channelLayout) {
1975
- const result = FilterPresets.instance.aformat(sampleFormat, sampleRate, channelLayout);
1976
- return result ?? '';
1147
+ amerge(inputs = 2) {
1148
+ this.add(`amerge=inputs=${inputs}`);
1149
+ return this;
1977
1150
  }
1978
1151
  /**
1979
- * Creates an asetnsamples filter string.
1152
+ * Creates a channelmap filter string to remap audio channels.
1153
+ * Critical for audio channel manipulation.
1980
1154
  *
1981
- * @param samples - Number of samples per frame
1982
- * @param padding - Whether to pad or drop samples (default: true)
1155
+ * @param map - Channel mapping (e.g., '0-0|1-1' or 'FL-FR|FR-FL' to swap stereo)
1983
1156
  *
1984
- * @returns Asetnsamples filter string
1157
+ * @returns Filter string or null if not supported
1985
1158
  *
1986
1159
  * @example
1987
1160
  * ```typescript
1988
- * const filter = FilterPresets.asetnsamples(960);
1161
+ * presets.channelmap('FL-FR|FR-FL') // Swap left and right channels
1162
+ * presets.channelmap('0-0|0-1') // Duplicate mono to stereo
1989
1163
  * ```
1990
1164
  *
1165
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#channelmap | FFmpeg channelmap filter}
1166
+ */
1167
+ channelmap(map) {
1168
+ this.add(`channelmap=${map}`);
1169
+ return this;
1170
+ }
1171
+ /**
1172
+ * Creates a channelsplit filter string to split audio channels.
1173
+ *
1174
+ * @param channelLayout - Channel layout to split (optional)
1175
+ *
1176
+ * @returns Filter string or null if not supported
1177
+ *
1991
1178
  * @example
1992
1179
  * ```typescript
1993
- * const filter = FilterPresets.asetnsamples(1024, false);
1180
+ * presets.channelsplit('stereo') // Split stereo to 2 mono
1181
+ * presets.channelsplit('5.1') // Split 5.1 to individual channels
1994
1182
  * ```
1183
+ *
1184
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#channelsplit | FFmpeg channelsplit filter}
1995
1185
  */
1996
- static asetnsamples(samples, padding = true) {
1997
- const result = FilterPresets.instance.asetnsamples(samples, padding);
1998
- return result ?? '';
1186
+ channelsplit(channelLayout) {
1187
+ this.add(channelLayout ? `channelsplit=channel_layout=${channelLayout}` : 'channelsplit');
1188
+ return this;
1999
1189
  }
2000
1190
  /**
2001
- * Creates an atempo filter string.
1191
+ * Creates a loudnorm filter string for loudness normalization.
1192
+ * Essential for broadcast compliance and consistent audio levels.
1193
+ *
1194
+ * @param I - Integrated loudness target (default: -24 LUFS)
2002
1195
  *
2003
- * @param factor - Tempo factor (0.5 to 2.0)
1196
+ * @param TP - True peak (default: -2 dBTP)
1197
+ *
1198
+ * @param LRA - Loudness range (default: 7 LU)
2004
1199
  *
2005
- * @returns Atempo filter string
1200
+ * @returns Filter string or null if not supported
2006
1201
  *
2007
1202
  * @example
2008
1203
  * ```typescript
2009
- * const filter = FilterPresets.atempo(1.5);
1204
+ * presets.loudnorm(-23, -1, 7) // EBU R128 broadcast standard
1205
+ * presets.loudnorm(-16, -1.5, 11) // Streaming platforms standard
2010
1206
  * ```
2011
1207
  *
1208
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#loudnorm | FFmpeg loudnorm filter}
1209
+ */
1210
+ loudnorm(I = -24, TP = -2, LRA = 7) {
1211
+ this.add(`loudnorm=I=${I}:TP=${TP}:LRA=${LRA}`);
1212
+ return this;
1213
+ }
1214
+ /**
1215
+ * Creates a compand filter string for audio compression/expansion.
1216
+ * Important for dynamic range control.
1217
+ *
1218
+ * @param attacks - Attack times
1219
+ *
1220
+ * @param decays - Decay times
1221
+ *
1222
+ * @param points - Transfer function points
1223
+ *
1224
+ * @param gain - Output gain
1225
+ *
1226
+ * @returns Filter string or null if not supported
1227
+ *
2012
1228
  * @example
2013
1229
  * ```typescript
2014
- * const filter = FilterPresets.atempo(0.8);
1230
+ * presets.compand('0.3|0.3', '1|1', '-90/-60|-60/-40|-40/-30|-20/-20', 6)
2015
1231
  * ```
1232
+ *
1233
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#compand | FFmpeg compand filter}
2016
1234
  */
2017
- static atempo(factor) {
2018
- const result = FilterPresets.instance.atempo(factor);
2019
- return result ?? '';
1235
+ compand(attacks, decays, points, gain) {
1236
+ let filter = `compand=attacks=${attacks}:decays=${decays}:points=${points}`;
1237
+ if (gain !== undefined)
1238
+ filter += `:gain=${gain}`;
1239
+ this.add(filter);
1240
+ return this;
2020
1241
  }
2021
1242
  /**
2022
- * Creates an audio fade filter string.
1243
+ * Adds a drawtext filter to overlay text on video.
2023
1244
  *
2024
- * @param type - Fade type ('in' or 'out')
2025
- * @param start - Start time in seconds
2026
- * @param duration - Fade duration in seconds
1245
+ * @param text - Text to display
2027
1246
  *
2028
- * @returns Audio fade filter string
1247
+ * @param options - Text rendering options
2029
1248
  *
2030
- * @example
2031
- * ```typescript
2032
- * const filter = FilterPresets.afade('in', 0, 3);
2033
- * ```
1249
+ * @returns This instance for chaining
2034
1250
  *
2035
1251
  * @example
2036
1252
  * ```typescript
2037
- * const filter = FilterPresets.afade('out', 25, 2);
1253
+ * chain.drawtext('Hello World', { x: 10, y: 10, fontsize: 24 })
1254
+ * chain.drawtext('Timestamp', {
1255
+ * x: 10,
1256
+ * y: 10,
1257
+ * fontsize: 24,
1258
+ * fontcolor: 'white',
1259
+ * fontfile: '/path/to/font.ttf'
1260
+ * })
2038
1261
  * ```
1262
+ *
1263
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#drawtext | FFmpeg drawtext filter}
2039
1264
  */
2040
- static afade(type, start, duration) {
2041
- const result = FilterPresets.instance.afade(type, start, duration);
2042
- return result ?? '';
1265
+ drawtext(text, options) {
1266
+ let filter = `drawtext=text='${text.replace(/'/g, "\\'").replace(/"/g, '\\"')}'`;
1267
+ for (const [key, value] of Object.entries(options)) {
1268
+ if (key === 'fontfile' && typeof value === 'string') {
1269
+ filter += `:${key}='${value}'`;
1270
+ }
1271
+ else {
1272
+ filter += `:${key}=${value}`;
1273
+ }
1274
+ }
1275
+ this.add(filter);
1276
+ return this;
2043
1277
  }
2044
1278
  /**
2045
- * Creates an amix filter string.
1279
+ * Adds a split filter to duplicate a video stream.
2046
1280
  *
2047
- * @param inputs - Number of inputs (default: 2)
2048
- * @param duration - Duration mode (default: 'longest')
1281
+ * @param outputs - Number of output streams (default: 2)
2049
1282
  *
2050
- * @returns Amix filter string
1283
+ * @returns This instance for chaining
2051
1284
  *
2052
1285
  * @example
2053
1286
  * ```typescript
2054
- * const filter = FilterPresets.amix(3, 'longest');
1287
+ * chain.split() // Split into 2 outputs
1288
+ * chain.split(3) // Split into 3 outputs
2055
1289
  * ```
2056
1290
  *
2057
- * @example
2058
- * ```typescript
2059
- * const filter = FilterPresets.amix(2, 'first');
2060
- * ```
2061
- */
2062
- static amix(inputs = 2, duration = 'longest') {
2063
- const result = FilterPresets.instance.amix(inputs, duration);
2064
- return result ?? '';
2065
- }
2066
- }
2067
- /**
2068
- * Hardware-accelerated filter presets.
2069
- * Provides optimized filter implementations for specific hardware types,
2070
- * with automatic fallback when operations aren't supported.
2071
- *
2072
- * @example
2073
- * ```typescript
2074
- * // Create hardware presets for CUDA
2075
- * const hw = new HardwareFilterPresets(AV_HWDEVICE_TYPE_CUDA);
2076
- *
2077
- * // Check capabilities
2078
- * if (hw.support.scale) {
2079
- * const scaleFilter = hw.scale(1920, 1080);
2080
- * }
2081
- *
2082
- * // Use chain builder
2083
- * const chain = hw.chain()
2084
- * .hwupload()
2085
- * .scale(1920, 1080)
2086
- * .tonemap()
2087
- * .hwdownload()
2088
- * .build();
2089
- * ```
2090
- */
2091
- export class HardwareFilterPresets extends FilterPresetBase {
2092
- deviceType;
2093
- deviceTypeName;
2094
- support;
2095
- /**
2096
- * @param deviceType - Hardware device type enum
2097
- * @param deviceTypeName - Optional hardware device type name (e.g., 'cuda', 'vaapi')
1291
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#split | FFmpeg split filter}
2098
1292
  */
2099
- constructor(deviceType, deviceTypeName) {
2100
- super();
2101
- this.deviceType = deviceType;
2102
- this.deviceTypeName = deviceTypeName ?? HardwareDeviceContext.getTypeName(deviceType);
2103
- this.support = this.getSupport();
1293
+ split(outputs = 2) {
1294
+ this.add(`split=${outputs}`);
1295
+ return this;
2104
1296
  }
2105
1297
  /**
2106
- * Checks if a filter is hardware-accelerated.
1298
+ * Adds an asplit filter to duplicate an audio stream.
2107
1299
  *
2108
- * @param filterName - Name of the filter to check
2109
- * @returns True if the filter uses hardware acceleration
1300
+ * @param outputs - Number of output streams (default: 2)
1301
+ *
1302
+ * @returns This instance for chaining
2110
1303
  *
2111
1304
  * @example
2112
1305
  * ```typescript
2113
- * if (HardwareFilterPresets.isHardwareFilter('scale_cuda')) {
2114
- * console.log('Hardware accelerated scaling');
2115
- * }
1306
+ * chain.asplit() // Split into 2 outputs
1307
+ * chain.asplit(3) // Split into 3 outputs
2116
1308
  * ```
1309
+ *
1310
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#asplit | FFmpeg asplit filter}
2117
1311
  */
2118
- static isHardwareFilter(filterName) {
2119
- const filter = Filter.getByName(filterName);
2120
- if (!filter) {
2121
- return false;
2122
- }
2123
- // Check if filter has hardware device flag
2124
- return (filter.flags & AVFILTER_FLAG_HWDEVICE) !== 0;
1312
+ asplit(outputs = 2) {
1313
+ this.add(`asplit=${outputs}`);
1314
+ return this;
2125
1315
  }
2126
1316
  /**
2127
- * Creates a hardware filter chain builder.
1317
+ * Adds an adelay filter to delay audio by specified milliseconds.
2128
1318
  *
2129
- * @returns A new HardwareFilterChainBuilder instance
1319
+ * @param delays - Delay in milliseconds (single value or array for multiple channels)
1320
+ *
1321
+ * @returns This instance for chaining
2130
1322
  *
2131
1323
  * @example
2132
1324
  * ```typescript
2133
- * const filter = hw.chain()
2134
- * .hwupload()
2135
- * .scale(1920, 1080)
2136
- * .hwdownload()
2137
- * .build();
1325
+ * chain.adelay(100) // Delay all channels by 100ms
1326
+ * chain.adelay([100, 200]) // Delay first channel by 100ms, second by 200ms
2138
1327
  * ```
1328
+ *
1329
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#adelay | FFmpeg adelay filter}
2139
1330
  */
2140
- chain() {
2141
- return new HardwareFilterChainBuilder(this);
1331
+ adelay(delays) {
1332
+ const delayStr = Array.isArray(delays) ? delays.join('|') : delays.toString();
1333
+ this.add(`adelay=${delayStr}`);
1334
+ return this;
2142
1335
  }
2143
1336
  /**
2144
- * Creates a hardware-accelerated scale filter.
1337
+ * Adds an aecho filter for audio echo effect.
2145
1338
  *
2146
- * Different hardware types use different scale filters:
2147
- * - CUDA: scale_cuda or scale_npp (with npp option)
2148
- * - VAAPI: scale_vaapi
2149
- * - QSV: scale_qsv
2150
- * - VideoToolbox: scale_vt
2151
- * - RKMPP: scale_rkrga
1339
+ * @param in_gain - Input gain (0-1)
2152
1340
  *
2153
- * @param width - Target width
2154
- * @param height - Target height
2155
- * @param options - Hardware-specific scaling options
1341
+ * @param out_gain - Output gain (0-1)
2156
1342
  *
2157
- * @returns Hardware scale filter string or null if not supported
1343
+ * @param delays - Delay in milliseconds
2158
1344
  *
2159
- * @example
2160
- * ```typescript
2161
- * const filter = hwPresets.scale(1920, 1080);
2162
- * ```
1345
+ * @param decays - Decay factor (0-1)
1346
+ *
1347
+ * @returns This instance for chaining
2163
1348
  *
2164
1349
  * @example
2165
1350
  * ```typescript
2166
- * const filter = hwPresets.scale(1280, 720, { npp: true });
1351
+ * chain.aecho(0.8, 0.9, 1000, 0.3) // Echo with 1 second delay
2167
1352
  * ```
2168
1353
  *
2169
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#scale_005fcuda | FFmpeg scale_cuda filter}
1354
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#aecho | FFmpeg aecho filter}
2170
1355
  */
2171
- scale(width, height, options) {
2172
- if (!this.support.scale) {
2173
- return null;
2174
- }
2175
- // Special handling for different hardware scalers
2176
- let filterName;
2177
- if (this.deviceType === AV_HWDEVICE_TYPE_CUDA && options?.npp) {
2178
- filterName = 'scale_npp';
2179
- }
2180
- else if (this.deviceType === AV_HWDEVICE_TYPE_RKMPP) {
2181
- filterName = 'scale_rkrga'; // RKMPP uses RGA for scaling
2182
- }
2183
- else if (this.deviceType === AV_HWDEVICE_TYPE_VIDEOTOOLBOX) {
2184
- filterName = 'scale_vt'; // VideoToolbox uses scale_vt
2185
- }
2186
- else {
2187
- filterName = `scale_${this.deviceTypeName}`;
2188
- }
2189
- let filter = `${filterName}=${width}:${height}`;
2190
- if (options) {
2191
- for (const [key, value] of Object.entries(options)) {
2192
- if (key !== 'npp') {
2193
- // Skip our special npp flag
2194
- filter += `:${key}=${value}`;
2195
- }
2196
- }
2197
- }
2198
- return filter;
1356
+ aecho(in_gain, out_gain, delays, decays) {
1357
+ this.add(`aecho=${in_gain}:${out_gain}:${delays}:${decays}`);
1358
+ return this;
2199
1359
  }
2200
1360
  /**
2201
- * Creates a hardware-accelerated overlay filter.
1361
+ * Adds a highpass filter to remove low frequencies.
2202
1362
  *
2203
- * @param x - X position (default: 0)
2204
- * @param y - Y position (default: 0)
2205
- * @param options - Hardware-specific overlay options
1363
+ * @param frequency - Cutoff frequency in Hz
2206
1364
  *
2207
- * @returns Hardware overlay filter string or null if not supported
1365
+ * @param options - Additional filter options
2208
1366
  *
2209
- * @example
2210
- * ```typescript
2211
- * const filter = hwPresets.overlay(100, 50);
2212
- * ```
1367
+ * @returns This instance for chaining
2213
1368
  *
2214
1369
  * @example
2215
1370
  * ```typescript
2216
- * const filter = hwPresets.overlay(0, 0, { eof_action: 'pass' });
1371
+ * chain.highpass(200) // Remove frequencies below 200Hz
1372
+ * chain.highpass(200, { width_type: 'q', width: 1 })
2217
1373
  * ```
2218
1374
  *
2219
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#overlay_005fcuda | FFmpeg overlay_cuda filter}
1375
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#highpass | FFmpeg highpass filter}
2220
1376
  */
2221
- overlay(x = 0, y = 0, options) {
2222
- if (!this.support.overlay) {
2223
- return null;
2224
- }
2225
- // Special handling for RKMPP which uses RGA
2226
- const filterName = this.deviceType === AV_HWDEVICE_TYPE_RKMPP ? 'overlay_rkrga' : `overlay_${this.deviceTypeName}`;
2227
- let filter = `${filterName}=${x}:${y}`;
1377
+ highpass(frequency, options) {
1378
+ let filter = `highpass=f=${frequency}`;
2228
1379
  if (options) {
2229
1380
  for (const [key, value] of Object.entries(options)) {
2230
1381
  filter += `:${key}=${value}`;
2231
1382
  }
2232
1383
  }
2233
- return filter;
1384
+ this.add(filter);
1385
+ return this;
2234
1386
  }
2235
1387
  /**
2236
- * Creates a hardware-accelerated transpose filter.
1388
+ * Adds a lowpass filter to remove high frequencies.
2237
1389
  *
2238
- * Direction values:
2239
- * - 0: 90 degrees counter-clockwise and vertical flip
2240
- * - 1 / 'clock': 90 degrees clockwise
2241
- * - 2 / 'cclock': 90 degrees counter-clockwise
2242
- * - 3 / 'clock_flip': 90 degrees clockwise and vertical flip
1390
+ * @param frequency - Cutoff frequency in Hz
2243
1391
  *
2244
- * @param mode - Transpose mode (number or string)
1392
+ * @param options - Additional filter options
2245
1393
  *
2246
- * @returns Hardware transpose filter string or null if not supported
1394
+ * @returns This instance for chaining
2247
1395
  *
2248
1396
  * @example
2249
1397
  * ```typescript
2250
- * const filter = hwPresets.transpose('clock');
1398
+ * chain.lowpass(5000) // Remove frequencies above 5000Hz
2251
1399
  * ```
2252
1400
  *
2253
- * @example
2254
- * ```typescript
2255
- * const filter = hwPresets.transpose(2);
2256
- * ```
1401
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#lowpass | FFmpeg lowpass filter}
2257
1402
  */
2258
- transpose(mode) {
2259
- if (!this.support.transpose) {
2260
- return null;
2261
- }
2262
- // Convert string modes to numbers
2263
- let dir;
2264
- if (typeof mode === 'string') {
2265
- switch (mode) {
2266
- case 'clock':
2267
- dir = 1;
2268
- break;
2269
- case 'cclock':
2270
- dir = 2;
2271
- break;
2272
- case 'clock_flip':
2273
- dir = 3;
2274
- break;
2275
- case 'cclock_flip':
2276
- dir = 0;
2277
- break;
2278
- default:
2279
- dir = 0;
1403
+ lowpass(frequency, options) {
1404
+ let filter = `lowpass=f=${frequency}`;
1405
+ if (options) {
1406
+ for (const [key, value] of Object.entries(options)) {
1407
+ filter += `:${key}=${value}`;
2280
1408
  }
2281
1409
  }
2282
- else {
2283
- dir = mode;
2284
- }
2285
- // Special handling for different hardware transpose implementations
2286
- let filterName;
2287
- if (this.deviceType === AV_HWDEVICE_TYPE_CUDA) {
2288
- filterName = 'transpose_cuda'; // Uses transpose_cuda from patch, not NPP
2289
- }
2290
- else if (this.deviceType === AV_HWDEVICE_TYPE_VIDEOTOOLBOX) {
2291
- filterName = 'transpose_vt'; // CoreImage-based transpose
2292
- }
2293
- else {
2294
- filterName = `transpose_${this.deviceTypeName}`;
2295
- }
2296
- return `${filterName}=dir=${dir}`;
1410
+ this.add(filter);
1411
+ return this;
2297
1412
  }
2298
1413
  /**
2299
- * Creates a hardware-accelerated tonemap filter.
2300
- * Used for HDR to SDR conversion with hardware acceleration.
1414
+ * Adds a bandpass filter to keep only a frequency band.
2301
1415
  *
2302
- * @param options - Tonemapping options (algorithm, parameters)
1416
+ * @param frequency - Center frequency in Hz
2303
1417
  *
2304
- * @returns Hardware tonemap filter string or null if not supported
1418
+ * @param options - Additional filter options
2305
1419
  *
2306
- * @example
2307
- * ```typescript
2308
- * const filter = hwPresets.tonemap();
2309
- * ```
1420
+ * @returns This instance for chaining
2310
1421
  *
2311
1422
  * @example
2312
1423
  * ```typescript
2313
- * const filter = hwPresets.tonemap({ tonemap: 'hable', desat: '0' });
1424
+ * chain.bandpass(1000) // Keep frequencies around 1000Hz
2314
1425
  * ```
1426
+ *
1427
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#bandpass | FFmpeg bandpass filter}
2315
1428
  */
2316
- tonemap(options) {
2317
- if (!this.support.tonemap) {
2318
- return null;
2319
- }
2320
- // VideoToolbox uses different filter name
2321
- const filterName = this.deviceType === AV_HWDEVICE_TYPE_VIDEOTOOLBOX ? 'tonemap_videotoolbox' : `tonemap_${this.deviceTypeName}`;
2322
- let filter = filterName;
1429
+ bandpass(frequency, options) {
1430
+ let filter = `bandpass=f=${frequency}`;
2323
1431
  if (options) {
2324
- const opts = Object.entries(options)
2325
- .map(([k, v]) => `${k}=${v}`)
2326
- .join(':');
2327
- filter += `=${opts}`;
1432
+ for (const [key, value] of Object.entries(options)) {
1433
+ filter += `:${key}=${value}`;
1434
+ }
2328
1435
  }
2329
- return filter;
1436
+ this.add(filter);
1437
+ return this;
2330
1438
  }
2331
1439
  /**
2332
- * Creates a hardware-accelerated deinterlace filter.
2333
- *
2334
- * Different hardware types use different deinterlacers:
2335
- * - CUDA: yadif_cuda
2336
- * - VAAPI: deinterlace_vaapi
2337
- * - QSV: deinterlace_qsv
2338
- * - Vulkan: bwdif_vulkan
2339
- * - VideoToolbox: yadif_videotoolbox
1440
+ * Adds an equalizer filter for frequency band adjustment.
2340
1441
  *
2341
- * @param mode - Deinterlacing mode (optional)
1442
+ * @param frequency - Center frequency in Hz
2342
1443
  *
2343
- * @returns Hardware deinterlace filter string or null if not supported
2344
- *
2345
- * @example
2346
- * ```typescript
2347
- * const filter = hwPresets.deinterlace();
2348
- * ```
1444
+ * @param width - Band width
2349
1445
  *
2350
- * @example
2351
- * ```typescript
2352
- * const filter = hwPresets.deinterlace('send_field');
2353
- * ```
2354
- */
2355
- deinterlace(mode) {
2356
- if (!this.support.deinterlace) {
2357
- return null;
2358
- }
2359
- switch (this.deviceType) {
2360
- case AV_HWDEVICE_TYPE_CUDA:
2361
- return mode ? `yadif_cuda=mode=${mode}` : 'yadif_cuda';
2362
- case AV_HWDEVICE_TYPE_VAAPI:
2363
- return mode ? `deinterlace_vaapi=mode=${mode}` : 'deinterlace_vaapi';
2364
- case AV_HWDEVICE_TYPE_QSV:
2365
- return mode ? `deinterlace_qsv=mode=${mode}` : 'deinterlace_qsv';
2366
- case AV_HWDEVICE_TYPE_VULKAN:
2367
- return mode ? `bwdif_vulkan=mode=${mode}` : 'bwdif_vulkan';
2368
- case AV_HWDEVICE_TYPE_VIDEOTOOLBOX:
2369
- return mode ? `yadif_videotoolbox=mode=${mode}` : 'yadif_videotoolbox';
2370
- default:
2371
- return null;
2372
- }
2373
- }
2374
- /**
2375
- * Creates a hardware-accelerated flip filter.
2376
- * Currently only Vulkan supports hardware flip filters.
1446
+ * @param gain - Gain in dB
2377
1447
  *
2378
- * @param direction - Flip direction ('h' for horizontal, 'v' for vertical)
1448
+ * @param width_type - Width type (optional)
2379
1449
  *
2380
- * @returns Hardware flip filter string or null if not supported
1450
+ * @returns This instance for chaining
2381
1451
  *
2382
1452
  * @example
2383
1453
  * ```typescript
2384
- * const filter = hwPresets.flip('h');
1454
+ * chain.equalizer(1000, 2, 5) // Boost 1000Hz by 5dB
1455
+ * chain.equalizer(1000, 2, 5, 'q') // Use Q factor for width
2385
1456
  * ```
2386
1457
  *
2387
- * @example
2388
- * ```typescript
2389
- * const filter = hwPresets.flip('v');
2390
- * ```
1458
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#equalizer | FFmpeg equalizer filter}
2391
1459
  */
2392
- flip(direction) {
2393
- if (!this.support.flip) {
2394
- return null;
2395
- }
2396
- if (this.deviceType === AV_HWDEVICE_TYPE_VULKAN) {
2397
- return direction === 'h' ? 'hflip_vulkan' : 'vflip_vulkan';
1460
+ equalizer(frequency, width, gain, width_type) {
1461
+ let filter = `equalizer=f=${frequency}`;
1462
+ if (width_type) {
1463
+ filter += `:width_type=${width_type}`;
2398
1464
  }
2399
- return null;
1465
+ filter += `:width=${width}:gain=${gain}`;
1466
+ this.add(filter);
1467
+ return this;
2400
1468
  }
2401
1469
  /**
2402
- * Creates a hardware-accelerated blur filter.
2403
- *
2404
- * Different hardware types support different blur filters:
2405
- * - CUDA: bilateral_cuda
2406
- * - Vulkan: avgblur_vulkan, gblur_vulkan
2407
- * - OpenCL: avgblur_opencl, boxblur_opencl
1470
+ * Adds a compressor filter for dynamic range compression.
2408
1471
  *
2409
- * @param type - Blur type ('avg', 'gaussian', or 'box', default: 'avg')
2410
- * @param radius - Blur radius (optional)
1472
+ * @param options - Compressor parameters
2411
1473
  *
2412
- * @returns Hardware blur filter string or null if not supported
1474
+ * @returns This instance for chaining
2413
1475
  *
2414
1476
  * @example
2415
1477
  * ```typescript
2416
- * const filter = hwPresets.blur('gaussian', 5);
1478
+ * chain.compressor() // Default compression
1479
+ * chain.compressor({
1480
+ * threshold: 0.5,
1481
+ * ratio: 4,
1482
+ * attack: 5,
1483
+ * release: 50
1484
+ * })
2417
1485
  * ```
2418
1486
  *
2419
- * @example
2420
- * ```typescript
2421
- * const filter = hwPresets.blur('avg');
2422
- * ```
1487
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#acompressor | FFmpeg acompressor filter}
2423
1488
  */
2424
- blur(type = 'avg', radius) {
2425
- if (!this.support.blur) {
2426
- return null;
1489
+ compressor(options) {
1490
+ if (!options || Object.keys(options).length === 0) {
1491
+ this.add('acompressor');
2427
1492
  }
2428
- switch (this.deviceType) {
2429
- case AV_HWDEVICE_TYPE_CUDA:
2430
- return radius ? `bilateral_cuda=sigmaS=${radius}` : 'bilateral_cuda';
2431
- case AV_HWDEVICE_TYPE_VULKAN:
2432
- return type === 'gaussian' ? (radius ? `gblur_vulkan=sigma=${radius}` : 'gblur_vulkan') : radius ? `avgblur_vulkan=sizeX=${radius}` : 'avgblur_vulkan';
2433
- case AV_HWDEVICE_TYPE_OPENCL:
2434
- return type === 'box' ? (radius ? `boxblur_opencl=luma_radius=${radius}` : 'boxblur_opencl') : radius ? `avgblur_opencl=sizeX=${radius}` : 'avgblur_opencl';
2435
- default:
2436
- return null;
1493
+ else {
1494
+ let filter = 'acompressor';
1495
+ const params = [];
1496
+ for (const [key, value] of Object.entries(options)) {
1497
+ params.push(`${key}=${value}`);
1498
+ }
1499
+ filter += '=' + params.join(':');
1500
+ this.add(filter);
2437
1501
  }
1502
+ return this;
2438
1503
  }
2439
1504
  /**
2440
- * Creates a hardware-accelerated sharpen filter.
2441
- *
2442
- * Hardware sharpening support:
2443
- * - VAAPI: sharpness_vaapi
2444
- * - OpenCL: unsharp_opencl
2445
- * - CUDA: sharpen_npp (NPP-based)
2446
- *
2447
- * @param amount - Sharpening amount (optional)
2448
- *
2449
- * @returns Hardware sharpen filter string or null if not supported
1505
+ * Adds an atrim filter to trim audio.
2450
1506
  *
2451
- * @example
2452
- * ```typescript
2453
- * const filter = hwPresets.sharpen(1.5);
2454
- * ```
1507
+ * @param start - Start time in seconds
2455
1508
  *
2456
- * @example
2457
- * ```typescript
2458
- * const filter = hwPresets.sharpen();
2459
- * ```
2460
- */
2461
- sharpen(amount) {
2462
- if (!this.support.sharpen) {
2463
- return null;
2464
- }
2465
- switch (this.deviceType) {
2466
- case AV_HWDEVICE_TYPE_VAAPI:
2467
- return amount ? `sharpness_vaapi=sharpness=${amount}` : 'sharpness_vaapi';
2468
- case AV_HWDEVICE_TYPE_OPENCL:
2469
- return amount ? `unsharp_opencl=amount=${amount}` : 'unsharp_opencl';
2470
- case AV_HWDEVICE_TYPE_CUDA:
2471
- // CUDA uses NPP for sharpening
2472
- return 'sharpen_npp';
2473
- default:
2474
- return null;
2475
- }
2476
- }
2477
- /**
2478
- * Creates a hardware-accelerated stack filter.
2479
- * Only VAAPI and QSV support hardware stacking.
1509
+ * @param end - End time in seconds (optional)
2480
1510
  *
2481
- * @param type - Stack type ('h' for horizontal, 'v' for vertical, 'x' for grid)
2482
- * @param inputs - Number of inputs to stack (default: 2)
1511
+ * @param duration - Duration in seconds (optional)
2483
1512
  *
2484
- * @returns Hardware stack filter string or null if not supported
1513
+ * @returns This instance for chaining
2485
1514
  *
2486
1515
  * @example
2487
1516
  * ```typescript
2488
- * const filter = hwPresets.stack('h', 2);
1517
+ * chain.atrim(10, 20) // Extract audio from 10s to 20s
2489
1518
  * ```
2490
1519
  *
2491
- * @example
2492
- * ```typescript
2493
- * const filter = hwPresets.stack('x', 4);
2494
- * ```
1520
+ * @see {@link https://ffmpeg.org/ffmpeg-filters.html#atrim | FFmpeg atrim filter}
2495
1521
  */
2496
- stack(type, inputs = 2) {
2497
- if (!this.support.stack) {
2498
- return null;
2499
- }
2500
- if (this.deviceType === AV_HWDEVICE_TYPE_VAAPI || this.deviceType === AV_HWDEVICE_TYPE_QSV) {
2501
- return `${type}stack_${this.deviceTypeName}=inputs=${inputs}`;
2502
- }
2503
- return null;
1522
+ atrim(start, end, duration) {
1523
+ let filter = `atrim=start=${start}`;
1524
+ if (end !== undefined)
1525
+ filter += `:end=${end}`;
1526
+ if (duration !== undefined)
1527
+ filter += `:duration=${duration}`;
1528
+ this.add(filter);
1529
+ return this;
2504
1530
  }
2505
1531
  /**
2506
- * Creates a hwupload filter to upload frames to hardware memory.
2507
- * CUDA uses hwupload_cuda, others use generic hwupload.
1532
+ * Adds a hwupload filter to upload frames to hardware.
2508
1533
  *
2509
- * @returns Hardware upload filter string
1534
+ * @returns This instance for chaining
2510
1535
  *
2511
1536
  * @example
2512
1537
  * ```typescript
2513
- * const filter = hwPresets.hwupload();
1538
+ * const chain = FilterPresets.chain()
1539
+ * .hwupload()
1540
+ * .scale(1920, 1080)
1541
+ * .build();
2514
1542
  * ```
2515
- *
2516
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#hwupload | FFmpeg hwupload filter}
2517
1543
  */
2518
1544
  hwupload() {
2519
- if (this.deviceType === AV_HWDEVICE_TYPE_CUDA) {
2520
- return 'hwupload_cuda';
1545
+ if (this.hardware?.deviceType === AV_HWDEVICE_TYPE_CUDA) {
1546
+ this.add('hwupload_cuda');
2521
1547
  }
2522
- return 'hwupload';
1548
+ else {
1549
+ this.add('hwupload');
1550
+ }
1551
+ return this;
2523
1552
  }
2524
1553
  /**
2525
- * Creates a hwdownload filter to download frames from hardware memory.
1554
+ * Adds a hwdownload filter to download frames from hardware.
2526
1555
  *
2527
- * @returns Hardware download filter string
1556
+ * @returns This instance for chaining
2528
1557
  *
2529
1558
  * @example
2530
1559
  * ```typescript
2531
- * const filter = hwPresets.hwdownload();
1560
+ * const chain = FilterPresets.chain()
1561
+ * .scale(1920, 1080)
1562
+ * .hwdownload()
1563
+ * .build();
2532
1564
  * ```
2533
- *
2534
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#hwdownload | FFmpeg hwdownload filter}
2535
1565
  */
2536
1566
  hwdownload() {
2537
- return 'hwdownload';
1567
+ this.add('hwdownload');
1568
+ return this;
2538
1569
  }
2539
1570
  /**
2540
- * Creates a hwmap filter to map frames between hardware devices.
1571
+ * Adds a hwmap filter to map frames between hardware devices.
2541
1572
  *
2542
1573
  * @param derive - Device to derive from (optional)
2543
1574
  *
2544
- * @returns Hardware map filter string
1575
+ * @returns This instance for chaining
2545
1576
  *
2546
1577
  * @example
2547
1578
  * ```typescript
2548
- * const filter = hwPresets.hwmap('cuda');
1579
+ * const chain = FilterPresets.chain()
1580
+ * .hwmap('cuda')
1581
+ * .build();
2549
1582
  * ```
2550
1583
  *
2551
1584
  * @example
2552
1585
  * ```typescript
2553
- * const filter = hwPresets.hwmap();
1586
+ * const chain = FilterPresets.chain()
1587
+ * .hwmap()
1588
+ * .build();
2554
1589
  * ```
2555
- *
2556
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#hwmap | FFmpeg hwmap filter}
2557
1590
  */
2558
1591
  hwmap(derive) {
2559
- return derive ? `hwmap=derive_device=${derive}` : 'hwmap';
1592
+ this.add(derive ? `hwmap=derive_device=${derive}` : 'hwmap');
1593
+ return this;
2560
1594
  }
2561
1595
  /**
2562
- * Gets the filter capabilities for this hardware type.
1596
+ * Adds a filter to the chain.
1597
+ *
1598
+ * @param filter - Filter string to add (ignored if null/undefined)
2563
1599
  *
2564
- * @returns Object describing which filters are supported
1600
+ * @returns This instance for chaining
2565
1601
  *
2566
1602
  * @example
2567
1603
  * ```typescript
2568
- * const caps = hw.getCapabilities();
2569
- * if (caps.scale && caps.overlay) {
2570
- * console.log('Hardware supports scaling and overlay');
2571
- * }
1604
+ * chain.add('scale=1920:1080')
2572
1605
  * ```
1606
+ *
1607
+ * @internal
2573
1608
  */
2574
- getCapabilities() {
2575
- return this.support;
1609
+ add(filter) {
1610
+ if (filter) {
1611
+ this.filters.push(filter);
1612
+ }
1613
+ return this;
2576
1614
  }
2577
1615
  /**
2578
1616
  * Determines filter support for the hardware type.
@@ -2582,7 +1620,25 @@ export class HardwareFilterPresets extends FilterPresetBase {
2582
1620
  * @internal
2583
1621
  */
2584
1622
  getSupport() {
2585
- switch (this.deviceType) {
1623
+ if (!this.hardware) {
1624
+ // Software-only - all filters supported
1625
+ return {
1626
+ scale: true,
1627
+ overlay: true,
1628
+ transpose: true,
1629
+ tonemap: true,
1630
+ deinterlace: true,
1631
+ denoise: true,
1632
+ flip: true,
1633
+ blur: true,
1634
+ sharpen: true,
1635
+ chromakey: true,
1636
+ colorspace: true,
1637
+ pad: true,
1638
+ stack: true,
1639
+ };
1640
+ }
1641
+ switch (this.hardware.deviceType) {
2586
1642
  case AV_HWDEVICE_TYPE_CUDA:
2587
1643
  return {
2588
1644
  scale: true, // scale_cuda
@@ -2757,21 +1813,4 @@ export class HardwareFilterPresets extends FilterPresetBase {
2757
1813
  }
2758
1814
  }
2759
1815
  }
2760
- /**
2761
- * Hardware filter chain builder with fluent API.
2762
- * Automatically skips unsupported filters (returns null) allowing graceful fallback.
2763
- *
2764
- * @example
2765
- * ```typescript
2766
- * const hw = new HardwareFilterPresets(AV_HWDEVICE_TYPE_CUDA, 'cuda');
2767
- * const chain = hw.chain()
2768
- * .hwupload()
2769
- * .scale(1920, 1080)
2770
- * .tonemap() // Skipped if not supported
2771
- * .hwdownload()
2772
- * .build();
2773
- * ```
2774
- */
2775
- export class HardwareFilterChainBuilder extends ChainBuilderBase {
2776
- }
2777
1816
  //# sourceMappingURL=filter-presets.js.map