node-av 1.3.0 → 2.0.0

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