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.
- package/README.md +37 -38
- package/dist/api/bitstream-filter.d.ts +2 -2
- package/dist/api/bitstream-filter.js +2 -2
- package/dist/api/decoder.d.ts +131 -120
- package/dist/api/decoder.js +191 -203
- package/dist/api/decoder.js.map +1 -1
- package/dist/api/encoder.d.ts +135 -77
- package/dist/api/encoder.js +235 -192
- package/dist/api/encoder.js.map +1 -1
- package/dist/api/filter-presets.d.ts +408 -1534
- package/dist/api/filter-presets.js +1005 -2058
- package/dist/api/filter-presets.js.map +1 -1
- package/dist/api/filter.d.ts +160 -165
- package/dist/api/filter.js +294 -374
- package/dist/api/filter.js.map +1 -1
- package/dist/api/hardware.d.ts +8 -31
- package/dist/api/hardware.js +19 -70
- package/dist/api/hardware.js.map +1 -1
- package/dist/api/index.d.ts +1 -1
- package/dist/api/index.js +1 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/media-input.d.ts +1 -1
- package/dist/api/media-input.js +3 -8
- package/dist/api/media-input.js.map +1 -1
- package/dist/api/media-output.d.ts +35 -128
- package/dist/api/media-output.js +136 -208
- package/dist/api/media-output.js.map +1 -1
- package/dist/api/pipeline.d.ts +17 -17
- package/dist/api/pipeline.js +19 -42
- package/dist/api/pipeline.js.map +1 -1
- package/dist/api/types.d.ts +17 -57
- package/dist/lib/dictionary.d.ts +2 -2
- package/dist/lib/dictionary.js +2 -2
- package/dist/lib/dictionary.js.map +1 -1
- package/dist/lib/filter-context.d.ts +19 -2
- package/dist/lib/filter-context.js +15 -0
- package/dist/lib/filter-context.js.map +1 -1
- package/dist/lib/format-context.d.ts +18 -18
- package/dist/lib/format-context.js +20 -20
- package/dist/lib/format-context.js.map +1 -1
- package/dist/lib/frame.d.ts +43 -1
- package/dist/lib/frame.js +53 -0
- package/dist/lib/frame.js.map +1 -1
- package/package.json +17 -17
- 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
|
-
*
|
|
7
|
-
*
|
|
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
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
43
|
+
* if (FilterPreset.isHardwareFilter('scale_cuda')) {
|
|
44
|
+
* console.log('Hardware accelerated scaling');
|
|
45
|
+
* }
|
|
1197
46
|
* ```
|
|
1198
|
-
*
|
|
1199
|
-
* @see {@link FilterPresetBase.trim}
|
|
1200
47
|
*/
|
|
1201
|
-
|
|
1202
|
-
|
|
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
|
-
*
|
|
1206
|
-
*
|
|
1207
|
-
* @param expression - PTS expression
|
|
57
|
+
* Creates a new filter chain builder.
|
|
1208
58
|
*
|
|
1209
|
-
* @
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
1236
|
-
|
|
81
|
+
static chain(hardware) {
|
|
82
|
+
const preset = new FilterPreset(hardware);
|
|
83
|
+
return preset;
|
|
1237
84
|
}
|
|
1238
85
|
/**
|
|
1239
|
-
* Adds a
|
|
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.
|
|
93
|
+
* chain.custom('myfilter=param1:param2')
|
|
1248
94
|
* ```
|
|
1249
|
-
*
|
|
1250
|
-
* @see {@link FilterPresetBase.setsar}
|
|
1251
95
|
*/
|
|
1252
|
-
|
|
1253
|
-
return this.add(
|
|
96
|
+
custom(filter) {
|
|
97
|
+
return this.add(filter);
|
|
1254
98
|
}
|
|
1255
99
|
/**
|
|
1256
|
-
*
|
|
1257
|
-
*
|
|
1258
|
-
* @param ratio - Display aspect ratio
|
|
100
|
+
* Builds the final filter string.
|
|
1259
101
|
*
|
|
1260
|
-
* @
|
|
102
|
+
* @param separator - Separator between filters (default: ',')
|
|
103
|
+
* @returns Combined filter string
|
|
1261
104
|
*
|
|
1262
105
|
* @example
|
|
1263
106
|
* ```typescript
|
|
1264
|
-
* chain.
|
|
107
|
+
* const filterString = chain.build() // "scale=1920:1080,fps=30"
|
|
1265
108
|
* ```
|
|
1266
|
-
*
|
|
1267
|
-
* @see {@link FilterPresetBase.setdar}
|
|
1268
109
|
*/
|
|
1269
|
-
|
|
1270
|
-
return this.
|
|
110
|
+
build(separator = ',') {
|
|
111
|
+
return this.filters.join(separator);
|
|
1271
112
|
}
|
|
1272
113
|
/**
|
|
1273
|
-
*
|
|
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
|
|
116
|
+
* @returns Array of filter strings
|
|
1279
117
|
*
|
|
1280
118
|
* @example
|
|
1281
119
|
* ```typescript
|
|
1282
|
-
* chain.
|
|
120
|
+
* const filters = chain.toArray() // ["scale=1920:1080", "fps=30"]
|
|
1283
121
|
* ```
|
|
1284
|
-
*
|
|
1285
|
-
* @see {@link FilterPresetBase.apad}
|
|
1286
122
|
*/
|
|
1287
|
-
|
|
1288
|
-
return this.
|
|
123
|
+
toArray() {
|
|
124
|
+
return [...this.filters];
|
|
1289
125
|
}
|
|
1290
126
|
/**
|
|
1291
|
-
* Adds a
|
|
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.
|
|
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
|
|
142
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#scale | FFmpeg scale filter}
|
|
1303
143
|
*/
|
|
1304
|
-
|
|
1305
|
-
|
|
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
|
|
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.
|
|
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
|
|
197
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#crop | FFmpeg crop filter}
|
|
1320
198
|
*/
|
|
1321
|
-
|
|
1322
|
-
|
|
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
|
|
204
|
+
* Adds a blur filter to the chain (hardware-specific).
|
|
205
|
+
* Only available for hardware presets that support blur
|
|
1326
206
|
*
|
|
1327
|
-
* @param
|
|
1328
|
-
* @param
|
|
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.
|
|
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.
|
|
221
|
+
* const chain = FilterPresets.chain()
|
|
222
|
+
* .blur('box')
|
|
223
|
+
* .build();
|
|
1353
224
|
* ```
|
|
1354
|
-
*
|
|
1355
|
-
* @see {@link FilterPresetBase.amerge}
|
|
1356
225
|
*/
|
|
1357
|
-
|
|
1358
|
-
|
|
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
|
|
256
|
+
* Adds a sharpen filter to the chain (hardware-specific).
|
|
257
|
+
* Only available for hardware presets that support sharpening
|
|
1362
258
|
*
|
|
1363
|
-
* @param
|
|
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.
|
|
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.
|
|
272
|
+
* const chain = FilterPresets.chain()
|
|
273
|
+
* .sharpen()
|
|
274
|
+
* .build();
|
|
1387
275
|
* ```
|
|
1388
|
-
*
|
|
1389
|
-
* @see {@link FilterPresetBase.channelsplit}
|
|
1390
276
|
*/
|
|
1391
|
-
|
|
1392
|
-
|
|
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
|
|
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.
|
|
315
|
+
* chain.fps(30) // Convert to 30 FPS
|
|
316
|
+
* chain.fps(23.976) // Film frame rate
|
|
1406
317
|
* ```
|
|
1407
318
|
*
|
|
1408
|
-
* @see {@link
|
|
319
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#fps | FFmpeg fps filter}
|
|
1409
320
|
*/
|
|
1410
|
-
|
|
1411
|
-
|
|
321
|
+
fps(fps) {
|
|
322
|
+
this.add(`fps=${fps}`);
|
|
323
|
+
return this;
|
|
1412
324
|
}
|
|
1413
325
|
/**
|
|
1414
|
-
* Adds a
|
|
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
|
-
*
|
|
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
|
-
*
|
|
1442
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
|
|
1456
|
-
if (
|
|
1457
|
-
|
|
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
|
|
355
|
+
return this;
|
|
1460
356
|
}
|
|
1461
357
|
/**
|
|
1462
|
-
* Adds a
|
|
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
|
-
*
|
|
1472
|
-
*
|
|
1473
|
-
* .build();
|
|
365
|
+
* chain.rotate(90) // Rotate 90 degrees clockwise
|
|
366
|
+
* chain.rotate(-45) // Rotate 45 degrees counter-clockwise
|
|
1474
367
|
* ```
|
|
1475
368
|
*
|
|
1476
|
-
* @
|
|
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
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
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
|
|
1491
|
-
*
|
|
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
|
|
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
|
-
* .
|
|
386
|
+
* .flip('h')
|
|
1501
387
|
* .build();
|
|
1502
388
|
* ```
|
|
1503
389
|
*
|
|
1504
390
|
* @example
|
|
1505
391
|
* ```typescript
|
|
1506
392
|
* const chain = FilterPresets.chain()
|
|
1507
|
-
* .
|
|
393
|
+
* .flip('v')
|
|
1508
394
|
* .build();
|
|
1509
395
|
* ```
|
|
1510
396
|
*/
|
|
1511
|
-
|
|
1512
|
-
if (
|
|
1513
|
-
return this
|
|
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
|
|
419
|
+
return this;
|
|
1516
420
|
}
|
|
1517
421
|
/**
|
|
1518
|
-
* Adds a
|
|
1519
|
-
*
|
|
422
|
+
* Adds a stack filter to the chain (hardware-specific).
|
|
423
|
+
* Only available for hardware presets that support stacking
|
|
1520
424
|
*
|
|
1521
|
-
* @param
|
|
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
|
-
* .
|
|
433
|
+
* .stack('h', 2)
|
|
1529
434
|
* .build();
|
|
1530
435
|
* ```
|
|
1531
436
|
*
|
|
1532
437
|
* @example
|
|
1533
438
|
* ```typescript
|
|
1534
439
|
* const chain = FilterPresets.chain()
|
|
1535
|
-
* .
|
|
440
|
+
* .stack('x', 4)
|
|
1536
441
|
* .build();
|
|
1537
442
|
* ```
|
|
1538
443
|
*/
|
|
1539
|
-
|
|
1540
|
-
if (
|
|
1541
|
-
return this
|
|
444
|
+
stack(type, inputs = 2) {
|
|
445
|
+
if (!this.support.stack) {
|
|
446
|
+
return this;
|
|
1542
447
|
}
|
|
1543
|
-
|
|
1544
|
-
|
|
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
|
-
*
|
|
1548
|
-
*
|
|
461
|
+
* Creates a tonemap filter.
|
|
462
|
+
* Used for HDR to SDR conversion with hardware acceleration.
|
|
1549
463
|
*
|
|
1550
|
-
* @param
|
|
1551
|
-
* @param
|
|
464
|
+
* @param alg - Tonemapping algorithm (e.g., 'hable', 'reinhard', 'mobius', etc.)
|
|
465
|
+
* @param options - Tonemapping options
|
|
1552
466
|
*
|
|
1553
|
-
* @returns
|
|
467
|
+
* @returns Hardware tonemap filter string or null if not supported
|
|
1554
468
|
*
|
|
1555
469
|
* @example
|
|
1556
470
|
* ```typescript
|
|
1557
|
-
* const
|
|
1558
|
-
* .blur('gaussian', 5)
|
|
1559
|
-
* .build();
|
|
471
|
+
* const filter = hwPresets.tonemap();
|
|
1560
472
|
* ```
|
|
1561
473
|
*
|
|
1562
474
|
* @example
|
|
1563
475
|
* ```typescript
|
|
1564
|
-
* const
|
|
1565
|
-
* .blur('box')
|
|
1566
|
-
* .build();
|
|
476
|
+
* const filter = hwPresets.tonemap({ tonemap: 'hable', desat: '0' });
|
|
1567
477
|
* ```
|
|
1568
478
|
*/
|
|
1569
|
-
|
|
1570
|
-
if (
|
|
1571
|
-
return this
|
|
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
|
|
505
|
+
return this;
|
|
1574
506
|
}
|
|
1575
507
|
/**
|
|
1576
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
1586
|
-
*
|
|
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
|
-
*
|
|
1593
|
-
*
|
|
1594
|
-
*
|
|
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
|
-
|
|
1598
|
-
if (
|
|
1599
|
-
return this
|
|
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
|
|
570
|
+
return this;
|
|
1602
571
|
}
|
|
1603
572
|
/**
|
|
1604
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
1615
|
-
*
|
|
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
|
-
*
|
|
1622
|
-
*
|
|
1623
|
-
*
|
|
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
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
612
|
+
aformat(sampleFormat, sampleRate, channelLayout) {
|
|
613
|
+
let sampleFormats = '';
|
|
614
|
+
if (!Array.isArray(sampleFormat)) {
|
|
615
|
+
sampleFormat = [sampleFormat];
|
|
1629
616
|
}
|
|
1630
|
-
|
|
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
|
|
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
|
-
*
|
|
1640
|
-
*
|
|
1641
|
-
*
|
|
1642
|
-
*
|
|
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
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
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
|
|
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
|
-
*
|
|
1659
|
-
*
|
|
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
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
}
|
|
1668
|
-
return this.add('hwdownload');
|
|
664
|
+
aresample(rate) {
|
|
665
|
+
this.add(`aresample=${rate}`);
|
|
666
|
+
return this;
|
|
1669
667
|
}
|
|
1670
668
|
/**
|
|
1671
|
-
* Adds
|
|
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
|
-
*
|
|
1680
|
-
*
|
|
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
|
-
*
|
|
1687
|
-
*
|
|
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
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
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
|
-
*
|
|
708
|
+
* Adds an amix filter to mix multiple audio streams.
|
|
1737
709
|
*
|
|
1738
|
-
* @
|
|
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
|
-
*
|
|
1743
|
-
*
|
|
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
|
-
|
|
1749
|
-
|
|
722
|
+
amix(inputs = 2, duration = 'longest') {
|
|
723
|
+
this.add(`amix=inputs=${inputs}:duration=${duration}`);
|
|
724
|
+
return this;
|
|
1750
725
|
}
|
|
1751
726
|
/**
|
|
1752
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
1763
|
-
*
|
|
739
|
+
* // Add black bars for 16:9 aspect ratio
|
|
740
|
+
* chain.pad('iw', 'iw*9/16');
|
|
1764
741
|
*
|
|
1765
|
-
*
|
|
1766
|
-
*
|
|
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
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
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
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
1808
|
-
*
|
|
793
|
+
* // Double speed
|
|
794
|
+
* presets.setpts('PTS/2');
|
|
1809
795
|
*
|
|
1810
|
-
*
|
|
1811
|
-
*
|
|
1812
|
-
*
|
|
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
|
-
|
|
1816
|
-
|
|
1817
|
-
return
|
|
805
|
+
setpts(expression) {
|
|
806
|
+
this.add(`setpts=${expression}`);
|
|
807
|
+
return this;
|
|
1818
808
|
}
|
|
1819
809
|
/**
|
|
1820
|
-
* Creates
|
|
1821
|
-
*
|
|
1822
|
-
* @param pixelFormat - Target pixel format(s)
|
|
810
|
+
* Creates an asetpts filter string for audio timestamp manipulation.
|
|
1823
811
|
*
|
|
1824
|
-
* @
|
|
812
|
+
* @param expression - PTS expression
|
|
813
|
+
* @returns Filter string or null if not supported
|
|
1825
814
|
*
|
|
1826
815
|
* @example
|
|
1827
816
|
* ```typescript
|
|
1828
|
-
*
|
|
817
|
+
* presets.asetpts('PTS-STARTPTS') // Reset timestamps to start from 0
|
|
1829
818
|
* ```
|
|
1830
819
|
*
|
|
1831
|
-
* @
|
|
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
|
-
|
|
1837
|
-
|
|
1838
|
-
return
|
|
822
|
+
asetpts(expression) {
|
|
823
|
+
this.add(`asetpts=${expression}`);
|
|
824
|
+
return this;
|
|
1839
825
|
}
|
|
1840
826
|
/**
|
|
1841
|
-
* Creates a
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
835
|
+
* presets.transpose(1) // Rotate 90 degrees clockwise
|
|
836
|
+
* presets.transpose('cclock') // Rotate 90 degrees counter-clockwise
|
|
1850
837
|
* ```
|
|
1851
838
|
*
|
|
1852
|
-
* @
|
|
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
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
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
|
|
888
|
+
* Creates a setsar filter string to set sample aspect ratio.
|
|
889
|
+
* Important for correcting aspect ratio issues.
|
|
1863
890
|
*
|
|
1864
|
-
* @
|
|
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
|
-
*
|
|
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
|
-
|
|
1872
|
-
|
|
1873
|
-
return
|
|
902
|
+
setsar(ratio) {
|
|
903
|
+
this.add(`setsar=${ratio}`);
|
|
904
|
+
return this;
|
|
1874
905
|
}
|
|
1875
906
|
/**
|
|
1876
|
-
* Creates a
|
|
907
|
+
* Creates a setdar filter string to set display aspect ratio.
|
|
1877
908
|
*
|
|
1878
|
-
* @
|
|
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
|
-
*
|
|
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
|
-
|
|
1886
|
-
|
|
1887
|
-
return
|
|
920
|
+
setdar(ratio) {
|
|
921
|
+
this.add(`setdar=${ratio}`);
|
|
922
|
+
return this;
|
|
1888
923
|
}
|
|
1889
924
|
/**
|
|
1890
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
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
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
961
|
+
* presets.deinterlace('yadif') // Standard deinterlacing
|
|
962
|
+
* presets.deinterlace('bwdif') // Bob Weaver deinterlacing
|
|
1923
963
|
* ```
|
|
1924
964
|
*
|
|
1925
|
-
* @
|
|
1926
|
-
* ```typescript
|
|
1927
|
-
* const filter = FilterPresets.overlay();
|
|
1928
|
-
* ```
|
|
965
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#yadif | FFmpeg yadif filter}
|
|
1929
966
|
*/
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
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
|
|
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
|
-
* @
|
|
1020
|
+
* @param expression - Selection expression
|
|
1021
|
+
* @returns Filter string or null if not supported
|
|
1940
1022
|
*
|
|
1941
1023
|
* @example
|
|
1942
1024
|
* ```typescript
|
|
1943
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
|
|
1952
|
-
|
|
1953
|
-
return
|
|
1031
|
+
select(expression) {
|
|
1032
|
+
this.add(`select='${expression}'`);
|
|
1033
|
+
return this;
|
|
1954
1034
|
}
|
|
1955
1035
|
/**
|
|
1956
|
-
* Creates an
|
|
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
|
-
* @
|
|
1038
|
+
* @param expression - Selection expression
|
|
1039
|
+
* @returns Filter string or null if not supported
|
|
1963
1040
|
*
|
|
1964
1041
|
* @example
|
|
1965
1042
|
* ```typescript
|
|
1966
|
-
*
|
|
1043
|
+
* presets.aselect('between(t,10,20)') // Select audio between 10-20 seconds
|
|
1967
1044
|
* ```
|
|
1968
1045
|
*
|
|
1969
|
-
* @
|
|
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
|
-
|
|
1975
|
-
|
|
1976
|
-
return
|
|
1048
|
+
aselect(expression) {
|
|
1049
|
+
this.add(`aselect='${expression}'`);
|
|
1050
|
+
return this;
|
|
1977
1051
|
}
|
|
1978
1052
|
/**
|
|
1979
|
-
* Creates
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
|
|
1997
|
-
|
|
1998
|
-
return
|
|
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
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
|
|
2018
|
-
|
|
2019
|
-
return
|
|
1088
|
+
amerge(inputs = 2) {
|
|
1089
|
+
this.add(`amerge=inputs=${inputs}`);
|
|
1090
|
+
return this;
|
|
2020
1091
|
}
|
|
2021
1092
|
/**
|
|
2022
|
-
* Creates
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
|
|
2041
|
-
|
|
2042
|
-
return
|
|
1107
|
+
channelmap(map) {
|
|
1108
|
+
this.add(`channelmap=${map}`);
|
|
1109
|
+
return this;
|
|
2043
1110
|
}
|
|
2044
1111
|
/**
|
|
2045
|
-
* Creates
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
|
|
2100
|
-
|
|
2101
|
-
this
|
|
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
|
-
*
|
|
1130
|
+
* Creates a loudnorm filter string for loudness normalization.
|
|
1131
|
+
* Essential for broadcast compliance and consistent audio levels.
|
|
2107
1132
|
*
|
|
2108
|
-
* @param
|
|
2109
|
-
* @
|
|
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
|
-
*
|
|
2114
|
-
*
|
|
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
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
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
|
|
1151
|
+
* Creates a compand filter string for audio compression/expansion.
|
|
1152
|
+
* Important for dynamic range control.
|
|
2128
1153
|
*
|
|
2129
|
-
* @
|
|
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
|
-
*
|
|
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
|
-
|
|
2141
|
-
|
|
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
|
-
*
|
|
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
|
|
2154
|
-
* @param
|
|
2155
|
-
* @
|
|
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
|
-
*
|
|
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#
|
|
1193
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#drawtext | FFmpeg drawtext filter}
|
|
2170
1194
|
*/
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
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
|
-
|
|
1205
|
+
this.add(filter);
|
|
1206
|
+
return this;
|
|
2199
1207
|
}
|
|
2200
1208
|
/**
|
|
2201
|
-
*
|
|
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
|
-
* @
|
|
1211
|
+
* @param outputs - Number of output streams (default: 2)
|
|
1212
|
+
* @returns This instance for chaining
|
|
2208
1213
|
*
|
|
2209
1214
|
* @example
|
|
2210
1215
|
* ```typescript
|
|
2211
|
-
*
|
|
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
|
-
*
|
|
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#
|
|
1238
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#asplit | FFmpeg asplit filter}
|
|
2220
1239
|
*/
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
2317
|
-
|
|
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
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
filter += `=${opts}`;
|
|
1301
|
+
for (const [key, value] of Object.entries(options)) {
|
|
1302
|
+
filter += `:${key}=${value}`;
|
|
1303
|
+
}
|
|
2328
1304
|
}
|
|
2329
|
-
|
|
1305
|
+
this.add(filter);
|
|
1306
|
+
return this;
|
|
2330
1307
|
}
|
|
2331
1308
|
/**
|
|
2332
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
1317
|
+
* chain.lowpass(5000) // Remove frequencies above 5000Hz
|
|
2348
1318
|
* ```
|
|
2349
1319
|
*
|
|
2350
|
-
* @
|
|
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
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
1341
|
+
* chain.bandpass(1000) // Keep frequencies around 1000Hz
|
|
2385
1342
|
* ```
|
|
2386
1343
|
*
|
|
2387
|
-
* @
|
|
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
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
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
|
-
|
|
1353
|
+
this.add(filter);
|
|
1354
|
+
return this;
|
|
2400
1355
|
}
|
|
2401
1356
|
/**
|
|
2402
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
2457
|
-
* ```typescript
|
|
2458
|
-
* const filter = hwPresets.sharpen();
|
|
2459
|
-
* ```
|
|
1399
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#acompressor | FFmpeg acompressor filter}
|
|
2460
1400
|
*/
|
|
2461
|
-
|
|
2462
|
-
if (!
|
|
2463
|
-
|
|
1401
|
+
compressor(options) {
|
|
1402
|
+
if (!options || Object.keys(options).length === 0) {
|
|
1403
|
+
this.add('acompressor');
|
|
2464
1404
|
}
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
1426
|
+
* chain.atrim(10, 20) // Extract audio from 10s to 20s
|
|
2489
1427
|
* ```
|
|
2490
1428
|
*
|
|
2491
|
-
* @
|
|
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
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
if (
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
return
|
|
1431
|
+
atrim(start, end, duration) {
|
|
1432
|
+
let filter = `atrim=start=${start}`;
|
|
1433
|
+
if (end !== undefined)
|
|
1434
|
+
filter += `:end=${end}`;
|
|
1435
|
+
if (duration !== undefined)
|
|
1436
|
+
filter += `:duration=${duration}`;
|
|
1437
|
+
this.add(filter);
|
|
1438
|
+
return this;
|
|
2504
1439
|
}
|
|
2505
1440
|
/**
|
|
2506
|
-
*
|
|
2507
|
-
* CUDA uses hwupload_cuda, others use generic hwupload.
|
|
1441
|
+
* Adds a hwupload filter to upload frames to hardware.
|
|
2508
1442
|
*
|
|
2509
|
-
* @returns
|
|
1443
|
+
* @returns This instance for chaining
|
|
2510
1444
|
*
|
|
2511
1445
|
* @example
|
|
2512
1446
|
* ```typescript
|
|
2513
|
-
* const
|
|
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
|
-
|
|
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
|
|
1460
|
+
return this;
|
|
2523
1461
|
}
|
|
2524
1462
|
/**
|
|
2525
|
-
*
|
|
1463
|
+
* Adds a hwdownload filter to download frames from hardware.
|
|
2526
1464
|
*
|
|
2527
|
-
* @returns
|
|
1465
|
+
* @returns This instance for chaining
|
|
2528
1466
|
*
|
|
2529
1467
|
* @example
|
|
2530
1468
|
* ```typescript
|
|
2531
|
-
* const
|
|
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
|
-
|
|
1476
|
+
this.add('hwdownload');
|
|
1477
|
+
return this;
|
|
2538
1478
|
}
|
|
2539
1479
|
/**
|
|
2540
|
-
*
|
|
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
|
|
1484
|
+
* @returns This instance for chaining
|
|
2545
1485
|
*
|
|
2546
1486
|
* @example
|
|
2547
1487
|
* ```typescript
|
|
2548
|
-
* const
|
|
1488
|
+
* const chain = FilterPresets.chain()
|
|
1489
|
+
* .hwmap('cuda')
|
|
1490
|
+
* .build();
|
|
2549
1491
|
* ```
|
|
2550
1492
|
*
|
|
2551
1493
|
* @example
|
|
2552
1494
|
* ```typescript
|
|
2553
|
-
* const
|
|
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
|
-
|
|
1501
|
+
this.add(derive ? `hwmap=derive_device=${derive}` : 'hwmap');
|
|
1502
|
+
return this;
|
|
2560
1503
|
}
|
|
2561
1504
|
/**
|
|
2562
|
-
*
|
|
1505
|
+
* Adds a filter to the chain.
|
|
2563
1506
|
*
|
|
2564
|
-
* @
|
|
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
|
-
*
|
|
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
|
-
|
|
2575
|
-
|
|
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
|
-
|
|
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
|