node-av-lgpl 0.0.0 → 7.1.0-beta.1
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/LICENSE.md +22 -0
- package/README.md +711 -0
- package/dist/api/audio-frame-buffer.d.ts +636 -0
- package/dist/api/audio-frame-buffer.js +1027 -0
- package/dist/api/audio-frame-buffer.js.map +1 -0
- package/dist/api/bitstream-filter.d.ts +938 -0
- package/dist/api/bitstream-filter.js +1427 -0
- package/dist/api/bitstream-filter.js.map +1 -0
- package/dist/api/constants.d.ts +44 -0
- package/dist/api/constants.js +45 -0
- package/dist/api/constants.js.map +1 -0
- package/dist/api/data/test_av1.ivf +0 -0
- package/dist/api/data/test_h264.h264 +0 -0
- package/dist/api/data/test_hevc.h265 +0 -0
- package/dist/api/data/test_mjpeg.mjpeg +0 -0
- package/dist/api/data/test_vp8.ivf +0 -0
- package/dist/api/data/test_vp9.ivf +0 -0
- package/dist/api/decoder.d.ts +1413 -0
- package/dist/api/decoder.js +2367 -0
- package/dist/api/decoder.js.map +1 -0
- package/dist/api/demuxer.d.ts +1295 -0
- package/dist/api/demuxer.js +2344 -0
- package/dist/api/demuxer.js.map +1 -0
- package/dist/api/device.d.ts +589 -0
- package/dist/api/device.js +964 -0
- package/dist/api/device.js.map +1 -0
- package/dist/api/encoder-pool.d.ts +262 -0
- package/dist/api/encoder-pool.js +397 -0
- package/dist/api/encoder-pool.js.map +1 -0
- package/dist/api/encoder.d.ts +1509 -0
- package/dist/api/encoder.js +2619 -0
- package/dist/api/encoder.js.map +1 -0
- package/dist/api/filter-complex.d.ts +828 -0
- package/dist/api/filter-complex.js +1661 -0
- package/dist/api/filter-complex.js.map +1 -0
- package/dist/api/filter-presets.d.ts +800 -0
- package/dist/api/filter-presets.js +1554 -0
- package/dist/api/filter-presets.js.map +1 -0
- package/dist/api/filter.d.ts +1339 -0
- package/dist/api/filter.js +2075 -0
- package/dist/api/filter.js.map +1 -0
- package/dist/api/fmp4-stream.d.ts +797 -0
- package/dist/api/fmp4-stream.js +1428 -0
- package/dist/api/fmp4-stream.js.map +1 -0
- package/dist/api/hardware.d.ts +677 -0
- package/dist/api/hardware.js +1300 -0
- package/dist/api/hardware.js.map +1 -0
- package/dist/api/index.d.ts +21 -0
- package/dist/api/index.js +38 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/io-stream.d.ts +308 -0
- package/dist/api/io-stream.js +358 -0
- package/dist/api/io-stream.js.map +1 -0
- package/dist/api/muxer.d.ts +1311 -0
- package/dist/api/muxer.js +2672 -0
- package/dist/api/muxer.js.map +1 -0
- package/dist/api/pipeline.d.ts +801 -0
- package/dist/api/pipeline.js +1344 -0
- package/dist/api/pipeline.js.map +1 -0
- package/dist/api/probe.d.ts +128 -0
- package/dist/api/probe.js +227 -0
- package/dist/api/probe.js.map +1 -0
- package/dist/api/rtp-stream.d.ts +497 -0
- package/dist/api/rtp-stream.js +1131 -0
- package/dist/api/rtp-stream.js.map +1 -0
- package/dist/api/scaler.d.ts +561 -0
- package/dist/api/scaler.js +918 -0
- package/dist/api/scaler.js.map +1 -0
- package/dist/api/types.d.ts +17 -0
- package/dist/api/types.js +2 -0
- package/dist/api/types.js.map +1 -0
- package/dist/api/utilities/async-queue.d.ts +149 -0
- package/dist/api/utilities/async-queue.js +254 -0
- package/dist/api/utilities/async-queue.js.map +1 -0
- package/dist/api/utilities/audio-sample.d.ts +117 -0
- package/dist/api/utilities/audio-sample.js +112 -0
- package/dist/api/utilities/audio-sample.js.map +1 -0
- package/dist/api/utilities/audio-transcode.d.ts +20 -0
- package/dist/api/utilities/audio-transcode.js +27 -0
- package/dist/api/utilities/audio-transcode.js.map +1 -0
- package/dist/api/utilities/channel-layout.d.ts +76 -0
- package/dist/api/utilities/channel-layout.js +80 -0
- package/dist/api/utilities/channel-layout.js.map +1 -0
- package/dist/api/utilities/codec-format.d.ts +87 -0
- package/dist/api/utilities/codec-format.js +117 -0
- package/dist/api/utilities/codec-format.js.map +1 -0
- package/dist/api/utilities/context-options.d.ts +25 -0
- package/dist/api/utilities/context-options.js +23 -0
- package/dist/api/utilities/context-options.js.map +1 -0
- package/dist/api/utilities/electron-shared-texture.d.ts +368 -0
- package/dist/api/utilities/electron-shared-texture.js +540 -0
- package/dist/api/utilities/electron-shared-texture.js.map +1 -0
- package/dist/api/utilities/image.d.ts +207 -0
- package/dist/api/utilities/image.js +213 -0
- package/dist/api/utilities/image.js.map +1 -0
- package/dist/api/utilities/index.d.ts +14 -0
- package/dist/api/utilities/index.js +27 -0
- package/dist/api/utilities/index.js.map +1 -0
- package/dist/api/utilities/live-decoding.d.ts +15 -0
- package/dist/api/utilities/live-decoding.js +32 -0
- package/dist/api/utilities/live-decoding.js.map +1 -0
- package/dist/api/utilities/media-type.d.ts +49 -0
- package/dist/api/utilities/media-type.js +53 -0
- package/dist/api/utilities/media-type.js.map +1 -0
- package/dist/api/utilities/packet-age-watchdog.d.ts +198 -0
- package/dist/api/utilities/packet-age-watchdog.js +594 -0
- package/dist/api/utilities/packet-age-watchdog.js.map +1 -0
- package/dist/api/utilities/pixel-format.d.ts +89 -0
- package/dist/api/utilities/pixel-format.js +97 -0
- package/dist/api/utilities/pixel-format.js.map +1 -0
- package/dist/api/utilities/sample-format.d.ts +129 -0
- package/dist/api/utilities/sample-format.js +141 -0
- package/dist/api/utilities/sample-format.js.map +1 -0
- package/dist/api/utilities/scale-size.d.ts +51 -0
- package/dist/api/utilities/scale-size.js +55 -0
- package/dist/api/utilities/scale-size.js.map +1 -0
- package/dist/api/utilities/scheduler.d.ts +138 -0
- package/dist/api/utilities/scheduler.js +98 -0
- package/dist/api/utilities/scheduler.js.map +1 -0
- package/dist/api/utilities/streaming.d.ts +186 -0
- package/dist/api/utilities/streaming.js +309 -0
- package/dist/api/utilities/streaming.js.map +1 -0
- package/dist/api/utilities/timestamp.d.ts +193 -0
- package/dist/api/utilities/timestamp.js +206 -0
- package/dist/api/utilities/timestamp.js.map +1 -0
- package/dist/api/utilities/video-transcode.d.ts +218 -0
- package/dist/api/utilities/video-transcode.js +624 -0
- package/dist/api/utilities/video-transcode.js.map +1 -0
- package/dist/api/utilities/whisper-model.d.ts +330 -0
- package/dist/api/utilities/whisper-model.js +587 -0
- package/dist/api/utilities/whisper-model.js.map +1 -0
- package/dist/api/utils.d.ts +19 -0
- package/dist/api/utils.js +39 -0
- package/dist/api/utils.js.map +1 -0
- package/dist/api/webrtc-stream.d.ts +294 -0
- package/dist/api/webrtc-stream.js +546 -0
- package/dist/api/webrtc-stream.js.map +1 -0
- package/dist/api/whisper.d.ts +326 -0
- package/dist/api/whisper.js +402 -0
- package/dist/api/whisper.js.map +1 -0
- package/dist/constants/bsf-options.d.ts +364 -0
- package/dist/constants/bsf-options.js +7 -0
- package/dist/constants/bsf-options.js.map +1 -0
- package/dist/constants/channel-layouts.d.ts +53 -0
- package/dist/constants/channel-layouts.js +57 -0
- package/dist/constants/channel-layouts.js.map +1 -0
- package/dist/constants/constants.d.ts +2435 -0
- package/dist/constants/constants.js +1989 -0
- package/dist/constants/constants.js.map +1 -0
- package/dist/constants/decoders.d.ts +651 -0
- package/dist/constants/decoders.js +641 -0
- package/dist/constants/decoders.js.map +1 -0
- package/dist/constants/encoders.d.ts +314 -0
- package/dist/constants/encoders.js +309 -0
- package/dist/constants/encoders.js.map +1 -0
- package/dist/constants/filter-options.d.ts +12099 -0
- package/dist/constants/filter-options.js +7 -0
- package/dist/constants/filter-options.js.map +1 -0
- package/dist/constants/format-options.d.ts +4319 -0
- package/dist/constants/format-options.js +7 -0
- package/dist/constants/format-options.js.map +1 -0
- package/dist/constants/formats.d.ts +18 -0
- package/dist/constants/formats.js +7 -0
- package/dist/constants/formats.js.map +1 -0
- package/dist/constants/hardware.d.ts +26 -0
- package/dist/constants/hardware.js +27 -0
- package/dist/constants/hardware.js.map +1 -0
- package/dist/constants/index.d.ts +10 -0
- package/dist/constants/index.js +6 -0
- package/dist/constants/index.js.map +1 -0
- package/dist/constants/options.d.ts +6217 -0
- package/dist/constants/options.js +7 -0
- package/dist/constants/options.js.map +1 -0
- package/dist/ffmpeg/index.d.ts +101 -0
- package/dist/ffmpeg/index.js +161 -0
- package/dist/ffmpeg/index.js.map +1 -0
- package/dist/ffmpeg/version.d.ts +6 -0
- package/dist/ffmpeg/version.js +7 -0
- package/dist/ffmpeg/version.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/audio-fifo.d.ts +399 -0
- package/dist/lib/audio-fifo.js +431 -0
- package/dist/lib/audio-fifo.js.map +1 -0
- package/dist/lib/binding.d.ts +242 -0
- package/dist/lib/binding.js +102 -0
- package/dist/lib/binding.js.map +1 -0
- package/dist/lib/bitstream-filter-context.d.ts +381 -0
- package/dist/lib/bitstream-filter-context.js +443 -0
- package/dist/lib/bitstream-filter-context.js.map +1 -0
- package/dist/lib/bitstream-filter.d.ts +140 -0
- package/dist/lib/bitstream-filter.js +154 -0
- package/dist/lib/bitstream-filter.js.map +1 -0
- package/dist/lib/codec-context.d.ts +1069 -0
- package/dist/lib/codec-context.js +1336 -0
- package/dist/lib/codec-context.js.map +1 -0
- package/dist/lib/codec-parameters.d.ts +664 -0
- package/dist/lib/codec-parameters.js +813 -0
- package/dist/lib/codec-parameters.js.map +1 -0
- package/dist/lib/codec-parser.d.ts +201 -0
- package/dist/lib/codec-parser.js +213 -0
- package/dist/lib/codec-parser.js.map +1 -0
- package/dist/lib/codec.d.ts +617 -0
- package/dist/lib/codec.js +746 -0
- package/dist/lib/codec.js.map +1 -0
- package/dist/lib/device.d.ts +294 -0
- package/dist/lib/device.js +326 -0
- package/dist/lib/device.js.map +1 -0
- package/dist/lib/dictionary.d.ts +333 -0
- package/dist/lib/dictionary.js +372 -0
- package/dist/lib/dictionary.js.map +1 -0
- package/dist/lib/error.d.ts +311 -0
- package/dist/lib/error.js +395 -0
- package/dist/lib/error.js.map +1 -0
- package/dist/lib/fifo.d.ts +416 -0
- package/dist/lib/fifo.js +453 -0
- package/dist/lib/fifo.js.map +1 -0
- package/dist/lib/filter-context.d.ts +714 -0
- package/dist/lib/filter-context.js +783 -0
- package/dist/lib/filter-context.js.map +1 -0
- package/dist/lib/filter-graph-segment.d.ts +161 -0
- package/dist/lib/filter-graph-segment.js +172 -0
- package/dist/lib/filter-graph-segment.js.map +1 -0
- package/dist/lib/filter-graph.d.ts +641 -0
- package/dist/lib/filter-graph.js +704 -0
- package/dist/lib/filter-graph.js.map +1 -0
- package/dist/lib/filter-inout.d.ts +202 -0
- package/dist/lib/filter-inout.js +261 -0
- package/dist/lib/filter-inout.js.map +1 -0
- package/dist/lib/filter.d.ts +243 -0
- package/dist/lib/filter.js +272 -0
- package/dist/lib/filter.js.map +1 -0
- package/dist/lib/format-context.d.ts +1335 -0
- package/dist/lib/format-context.js +1455 -0
- package/dist/lib/format-context.js.map +1 -0
- package/dist/lib/frame-utils.d.ts +116 -0
- package/dist/lib/frame-utils.js +98 -0
- package/dist/lib/frame-utils.js.map +1 -0
- package/dist/lib/frame.d.ts +1315 -0
- package/dist/lib/frame.js +1535 -0
- package/dist/lib/frame.js.map +1 -0
- package/dist/lib/hardware-device-context.d.ts +362 -0
- package/dist/lib/hardware-device-context.js +383 -0
- package/dist/lib/hardware-device-context.js.map +1 -0
- package/dist/lib/hardware-frames-context.d.ts +418 -0
- package/dist/lib/hardware-frames-context.js +471 -0
- package/dist/lib/hardware-frames-context.js.map +1 -0
- package/dist/lib/index.d.ts +35 -0
- package/dist/lib/index.js +60 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/input-format.d.ts +249 -0
- package/dist/lib/input-format.js +306 -0
- package/dist/lib/input-format.js.map +1 -0
- package/dist/lib/io-context.d.ts +698 -0
- package/dist/lib/io-context.js +771 -0
- package/dist/lib/io-context.js.map +1 -0
- package/dist/lib/log.d.ts +174 -0
- package/dist/lib/log.js +184 -0
- package/dist/lib/log.js.map +1 -0
- package/dist/lib/native-types.d.ts +1024 -0
- package/dist/lib/native-types.js +2 -0
- package/dist/lib/native-types.js.map +1 -0
- package/dist/lib/option.d.ts +953 -0
- package/dist/lib/option.js +1607 -0
- package/dist/lib/option.js.map +1 -0
- package/dist/lib/output-format.d.ts +222 -0
- package/dist/lib/output-format.js +259 -0
- package/dist/lib/output-format.js.map +1 -0
- package/dist/lib/packet.d.ts +521 -0
- package/dist/lib/packet.js +613 -0
- package/dist/lib/packet.js.map +1 -0
- package/dist/lib/rational.d.ts +251 -0
- package/dist/lib/rational.js +278 -0
- package/dist/lib/rational.js.map +1 -0
- package/dist/lib/software-resample-context.d.ts +552 -0
- package/dist/lib/software-resample-context.js +592 -0
- package/dist/lib/software-resample-context.js.map +1 -0
- package/dist/lib/software-scale-context.d.ts +344 -0
- package/dist/lib/software-scale-context.js +366 -0
- package/dist/lib/software-scale-context.js.map +1 -0
- package/dist/lib/stream.d.ts +383 -0
- package/dist/lib/stream.js +524 -0
- package/dist/lib/stream.js.map +1 -0
- package/dist/lib/sync-queue.d.ts +179 -0
- package/dist/lib/sync-queue.js +197 -0
- package/dist/lib/sync-queue.js.map +1 -0
- package/dist/lib/types.d.ts +34 -0
- package/dist/lib/types.js +2 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/lib/utilities.d.ts +1198 -0
- package/dist/lib/utilities.js +1302 -0
- package/dist/lib/utilities.js.map +1 -0
- package/dist/utils/electron.d.ts +49 -0
- package/dist/utils/electron.js +63 -0
- package/dist/utils/electron.js.map +1 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.js +7 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/libc.d.ts +11 -0
- package/dist/utils/libc.js +35 -0
- package/dist/utils/libc.js.map +1 -0
- package/dist/utils/variant.d.ts +26 -0
- package/dist/utils/variant.js +31 -0
- package/dist/utils/variant.js.map +1 -0
- package/dist/webrtc/index.d.ts +4 -0
- package/dist/webrtc/index.js +7 -0
- package/dist/webrtc/index.js.map +1 -0
- package/install/check.js +124 -0
- package/install/ffmpeg.js +66 -0
- package/package.json +165 -3
|
@@ -0,0 +1,1554 @@
|
|
|
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
|
+
import { Filter } from '../lib/filter.js';
|
|
3
|
+
import { avGetPixFmtName, avGetSampleFmtName } from '../lib/utilities.js';
|
|
4
|
+
// Escape a filter option value for the filtergraph string. Bare tokens (numbers,
|
|
5
|
+
// simple identifiers, expressions) are emitted as-is; anything with special
|
|
6
|
+
// characters is single-quoted, with embedded quotes escaped FFmpeg-style.
|
|
7
|
+
const escapeFilterValue = (value) => {
|
|
8
|
+
const s = String(value);
|
|
9
|
+
if (s !== '' && /^[A-Za-z0-9_.+\-/*]+$/.test(s))
|
|
10
|
+
return s;
|
|
11
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Filter preset builder for composing filter chains.
|
|
15
|
+
* Supports both software and hardware-accelerated filters.
|
|
16
|
+
* Automatically selects appropriate filter implementations based on hardware context.
|
|
17
|
+
* Uses fluent interface pattern for chaining multiple filters.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* // Software filter chain
|
|
22
|
+
* const filter = FilterPreset.chain()
|
|
23
|
+
* .scale(1920, 1080)
|
|
24
|
+
* .filter('fps', { fps: 30 })
|
|
25
|
+
* .filter('fade', { type: 'in', start_time: 0, duration: 2 })
|
|
26
|
+
* .build();
|
|
27
|
+
*
|
|
28
|
+
* // Hardware-accelerated filter chain
|
|
29
|
+
* const hw = HardwareContext.auto();
|
|
30
|
+
* const hwFilter = FilterPreset.chain(hw)
|
|
31
|
+
* .scale(1920, 1080)
|
|
32
|
+
* .blur('gaussian', 5)
|
|
33
|
+
* .build();
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export class FilterPreset {
|
|
37
|
+
hardware;
|
|
38
|
+
filters = [];
|
|
39
|
+
support;
|
|
40
|
+
constructor(hardware) {
|
|
41
|
+
this.hardware = hardware;
|
|
42
|
+
this.support = this.getSupport();
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Checks if a filter is hardware-accelerated.
|
|
46
|
+
*
|
|
47
|
+
* @param filterName - Name of the filter to check
|
|
48
|
+
*
|
|
49
|
+
* @returns True if the filter uses hardware acceleration
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* if (FilterPreset.isHardwareFilter('scale_cuda')) {
|
|
54
|
+
* console.log('Hardware accelerated scaling');
|
|
55
|
+
* }
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
static isHardwareFilter(filterName) {
|
|
59
|
+
const filter = Filter.getByName(filterName);
|
|
60
|
+
if (!filter) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
return filter.hasFlags(AVFILTER_FLAG_HWDEVICE);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Creates a new filter chain builder.
|
|
67
|
+
*
|
|
68
|
+
* @param hardware - Optional hardware context for hardware-accelerated filters
|
|
69
|
+
*
|
|
70
|
+
* @returns A new FilterPreset instance for chaining
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* // Software filter chain
|
|
75
|
+
* const filter = FilterPreset.chain()
|
|
76
|
+
* .scale(1280, 720)
|
|
77
|
+
* .filter('fps', { fps: 30 })
|
|
78
|
+
* .build();
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```typescript
|
|
83
|
+
* // Hardware filter chain
|
|
84
|
+
* const hw = HardwareContext.auto();
|
|
85
|
+
* const filter = FilterPreset.chain(hw)
|
|
86
|
+
* .scale(1280, 720)
|
|
87
|
+
* .deinterlace()
|
|
88
|
+
* .build();
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
static chain(hardware) {
|
|
92
|
+
const preset = new FilterPreset(hardware);
|
|
93
|
+
return preset;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Checks if an operation is available for this chain's hardware.
|
|
97
|
+
*
|
|
98
|
+
* Operations the hardware has no filter for (e.g. scaling on MediaCodec) are
|
|
99
|
+
* skipped when added, so check first where skipping would be wrong and fall
|
|
100
|
+
* back to software, e.g. by scaling in the decoder.
|
|
101
|
+
*
|
|
102
|
+
* @param operation - Operation to check
|
|
103
|
+
*
|
|
104
|
+
* @returns True if the operation is applied; always true without hardware
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* const chain = FilterPreset.chain(hw);
|
|
109
|
+
* if (chain.supports('scale')) {
|
|
110
|
+
* chain.scale(1280, 720);
|
|
111
|
+
* } else {
|
|
112
|
+
* // e.g. Decoder.create(stream, { hardware: hw, rescale: { width: 1280, height: 720 } })
|
|
113
|
+
* }
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
supports(operation) {
|
|
117
|
+
return this.support[operation];
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Adds a custom filter string to the chain.
|
|
121
|
+
*
|
|
122
|
+
* @param filter - Custom filter string
|
|
123
|
+
*
|
|
124
|
+
* @returns This instance for chaining
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```typescript
|
|
128
|
+
* chain.custom('myfilter=param1:param2')
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
custom(filter) {
|
|
132
|
+
if (!filter) {
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
return this.add(filter);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Adds any FFmpeg filter to the chain with type-safe options.
|
|
139
|
+
*
|
|
140
|
+
* Provides autocomplete for every available filter name and its options
|
|
141
|
+
* (generated from FFmpeg's AVOption metadata). Prefer the dedicated methods
|
|
142
|
+
* (e.g. {@link scale}) when hardware-aware selection or automatic mapping is
|
|
143
|
+
* needed; use {@link custom} for raw filter strings.
|
|
144
|
+
*
|
|
145
|
+
* @param name - Filter name (autocompleted from all available filters)
|
|
146
|
+
*
|
|
147
|
+
* @param options - Filter-specific options, typed to the chosen filter
|
|
148
|
+
*
|
|
149
|
+
* @returns This instance for chaining
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```typescript
|
|
153
|
+
* chain
|
|
154
|
+
* .filter('drawtext', { text: 'hello', fontsize: 24 })
|
|
155
|
+
* .filter('hue', { s: 0 });
|
|
156
|
+
* // "drawtext=text=hello:fontsize=24,hue=s=0"
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
filter(name, options) {
|
|
160
|
+
if (!options) {
|
|
161
|
+
return this.add(name);
|
|
162
|
+
}
|
|
163
|
+
const entries = Object.entries(options);
|
|
164
|
+
const args = entries
|
|
165
|
+
.filter((e) => e[1] != null)
|
|
166
|
+
.map(([k, v]) => `${k}=${escapeFilterValue(v)}`)
|
|
167
|
+
.join(':');
|
|
168
|
+
return this.add(args ? `${name}=${args}` : name);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Builds the final filter string.
|
|
172
|
+
*
|
|
173
|
+
* @param separator - Separator between filters (default: ',')
|
|
174
|
+
*
|
|
175
|
+
* @returns Combined filter string
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* ```typescript
|
|
179
|
+
* const filterString = chain.build() // "scale=1920:1080,fps=30"
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
build(separator = ',') {
|
|
183
|
+
return this.filters.join(separator);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Returns the filters as an array.
|
|
187
|
+
*
|
|
188
|
+
* @returns Array of filter strings
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* ```typescript
|
|
192
|
+
* const filters = chain.toArray() // ["scale=1920:1080", "fps=30"]
|
|
193
|
+
* ```
|
|
194
|
+
*/
|
|
195
|
+
toArray() {
|
|
196
|
+
return [...this.filters];
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Adds a scale filter to the chain.
|
|
200
|
+
* Automatically selects hardware-specific scaler if hardware context is set.
|
|
201
|
+
*
|
|
202
|
+
* @param width - Target width in pixels
|
|
203
|
+
*
|
|
204
|
+
* @param height - Target height in pixels
|
|
205
|
+
*
|
|
206
|
+
* @param options - Additional scaling options (e.g., flags for algorithm)
|
|
207
|
+
*
|
|
208
|
+
* @returns This instance for chaining (unchanged if the hardware has no scaler, see {@link supports})
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* ```typescript
|
|
212
|
+
* chain.scale(1920, 1080) // Scale to Full HD
|
|
213
|
+
* chain.scale(640, 480, { flags: 'lanczos' }) // With specific algorithm
|
|
214
|
+
* ```
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* ```typescript
|
|
218
|
+
* // Hardware crop + scale (VAAPI, CUDA, QSV, VideoToolbox)
|
|
219
|
+
* chain.crop(100, 100, 50, 50) // Sets AVFrame crop fields
|
|
220
|
+
* .scale(1920, 1080) // Hardware scaler reads crop fields
|
|
221
|
+
* ```
|
|
222
|
+
*
|
|
223
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#scale | FFmpeg scale filter}
|
|
224
|
+
*/
|
|
225
|
+
scale(width, height, options) {
|
|
226
|
+
if (!this.support.scale) {
|
|
227
|
+
return this;
|
|
228
|
+
}
|
|
229
|
+
if (this.hardware) {
|
|
230
|
+
// Special handling for different hardware scalers
|
|
231
|
+
let filterName;
|
|
232
|
+
let filter;
|
|
233
|
+
if (this.hardware.deviceType === AV_HWDEVICE_TYPE_RKMPP) {
|
|
234
|
+
filterName = 'scale_rkrga'; // RKMPP uses RGA for scaling
|
|
235
|
+
filter = `${filterName}=${width}:${height}`;
|
|
236
|
+
}
|
|
237
|
+
else if (this.hardware.deviceType === AV_HWDEVICE_TYPE_VIDEOTOOLBOX) {
|
|
238
|
+
filterName = 'scale_vt'; // VideoToolbox uses scale_vt
|
|
239
|
+
filter = `${filterName}=${width}:${height}`;
|
|
240
|
+
}
|
|
241
|
+
else if (this.hardware.deviceType === AV_HWDEVICE_TYPE_QSV) {
|
|
242
|
+
filterName = 'vpp_qsv'; // QSV uses vpp_qsv for scaling with w= and h= parameters
|
|
243
|
+
filter = `${filterName}=w=${width}:h=${height}`;
|
|
244
|
+
}
|
|
245
|
+
else if (this.hardware.deviceType === AV_HWDEVICE_TYPE_D3D11VA) {
|
|
246
|
+
// scale_d3d11 fails without an output format and takes no -1/-2 sizes
|
|
247
|
+
filterName = 'scale_d3d11';
|
|
248
|
+
filter = `${filterName}=${width}:${height}`;
|
|
249
|
+
if (options?.format === undefined) {
|
|
250
|
+
filter += ':format=nv12';
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
else if (this.hardware.deviceType === AV_HWDEVICE_TYPE_D3D12VA) {
|
|
254
|
+
filterName = 'scale_d3d12';
|
|
255
|
+
filter = `${filterName}=${width}:${height}`;
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
filterName = `scale_${this.hardware.deviceTypeName}`;
|
|
259
|
+
filter = `${filterName}=${width}:${height}`;
|
|
260
|
+
}
|
|
261
|
+
if (options) {
|
|
262
|
+
for (const [key, value] of Object.entries(options)) {
|
|
263
|
+
filter += `:${key}=${value}`;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
this.add(filter);
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
const flags = options?.flags;
|
|
270
|
+
const base = `scale=${width}:${height}`;
|
|
271
|
+
const result = flags ? `${base}:flags=${flags}` : base;
|
|
272
|
+
this.add(result);
|
|
273
|
+
}
|
|
274
|
+
return this;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Adds a GPU format conversion filter without scaling.
|
|
278
|
+
* Only available for hardware contexts. Uses hardware-specific scale filter with format parameter.
|
|
279
|
+
* Skipped without hardware; on hardware without a scaler (see {@link supports}) the
|
|
280
|
+
* graph fails to build.
|
|
281
|
+
*
|
|
282
|
+
* @param format - Target pixel format (e.g., 'nv12', 'p010')
|
|
283
|
+
*
|
|
284
|
+
* @returns This instance for chaining
|
|
285
|
+
*
|
|
286
|
+
* @example
|
|
287
|
+
* ```typescript
|
|
288
|
+
* // Convert to NV12 on GPU without scaling
|
|
289
|
+
* chain.scaleFormat('nv12')
|
|
290
|
+
* // Generates: scale_vt=format=nv12 (VideoToolbox)
|
|
291
|
+
* // or: scale_cuda=format=nv12 (CUDA)
|
|
292
|
+
* // or: scale_opencl=format=nv12 (OpenCL)
|
|
293
|
+
* ```
|
|
294
|
+
*
|
|
295
|
+
* @example
|
|
296
|
+
* ```typescript
|
|
297
|
+
* // Convert P010 → NV12 on GPU
|
|
298
|
+
* const hw = await HardwareContext.auto();
|
|
299
|
+
* const filter = FilterPreset.chain(hw)
|
|
300
|
+
* .scaleFormat('nv12')
|
|
301
|
+
* .build();
|
|
302
|
+
* ```
|
|
303
|
+
*
|
|
304
|
+
* @example
|
|
305
|
+
* ```typescript
|
|
306
|
+
* // Scale then convert format (two separate GPU operations)
|
|
307
|
+
* chain.scale(1920, 1080)
|
|
308
|
+
* .scaleFormat('p010')
|
|
309
|
+
* // Generates: scale_vt=1920:1080,scale_vt=format=p010
|
|
310
|
+
* ```
|
|
311
|
+
*/
|
|
312
|
+
scaleFormat(format) {
|
|
313
|
+
if (!this.hardware) {
|
|
314
|
+
return this;
|
|
315
|
+
}
|
|
316
|
+
// Special handling for different hardware scalers
|
|
317
|
+
let filterName;
|
|
318
|
+
if (this.hardware.deviceType === AV_HWDEVICE_TYPE_RKMPP) {
|
|
319
|
+
filterName = 'scale_rkrga';
|
|
320
|
+
}
|
|
321
|
+
else if (this.hardware.deviceType === AV_HWDEVICE_TYPE_VIDEOTOOLBOX) {
|
|
322
|
+
filterName = 'scale_vt';
|
|
323
|
+
}
|
|
324
|
+
else if (this.hardware.deviceType === AV_HWDEVICE_TYPE_QSV) {
|
|
325
|
+
filterName = 'vpp_qsv';
|
|
326
|
+
}
|
|
327
|
+
else if (this.hardware.deviceType === AV_HWDEVICE_TYPE_D3D11VA) {
|
|
328
|
+
filterName = 'scale_d3d11';
|
|
329
|
+
}
|
|
330
|
+
else if (this.hardware.deviceType === AV_HWDEVICE_TYPE_D3D12VA) {
|
|
331
|
+
filterName = 'scale_d3d12';
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
filterName = `scale_${this.hardware.deviceTypeName}`;
|
|
335
|
+
}
|
|
336
|
+
if (typeof format !== 'string') {
|
|
337
|
+
format = avGetPixFmtName(format) ?? 'nv12';
|
|
338
|
+
}
|
|
339
|
+
this.add(`${filterName}=format=${format}`);
|
|
340
|
+
return this;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Adds a crop filter to the chain.
|
|
344
|
+
*
|
|
345
|
+
* @param width - Width of the cropped area
|
|
346
|
+
*
|
|
347
|
+
* @param height - Height of the cropped area
|
|
348
|
+
*
|
|
349
|
+
* @param x - X coordinate of top-left corner (default: 0)
|
|
350
|
+
*
|
|
351
|
+
* @param y - Y coordinate of top-left corner (default: 0)
|
|
352
|
+
*
|
|
353
|
+
* @returns This instance for chaining
|
|
354
|
+
*
|
|
355
|
+
* @example
|
|
356
|
+
* ```typescript
|
|
357
|
+
* chain.crop(640, 480, 100, 100) // Crop 640x480 area starting at (100,100)
|
|
358
|
+
* chain.crop(1280, 720) // Crop from top-left corner
|
|
359
|
+
* ```
|
|
360
|
+
*
|
|
361
|
+
* @example
|
|
362
|
+
* ```typescript
|
|
363
|
+
* // Hardware crop + scale
|
|
364
|
+
* chain.crop(100, 100, 50, 50)
|
|
365
|
+
* .scale(1920, 1080)
|
|
366
|
+
* ```
|
|
367
|
+
*
|
|
368
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#crop | FFmpeg crop filter}
|
|
369
|
+
*/
|
|
370
|
+
crop(width, height, x = 0, y = 0) {
|
|
371
|
+
if (this.hardware?.deviceType === AV_HWDEVICE_TYPE_OPENCL) {
|
|
372
|
+
// crop only marks the region on hardware frames and keeps the link size, so
|
|
373
|
+
// scale_opencl needs the crop size or it scales the region back to full size
|
|
374
|
+
this.add(`crop=${width}:${height}:${x}:${y},scale_opencl=${width}:${height}`);
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
this.add(`crop=${width}:${height}:${x}:${y}`, true);
|
|
378
|
+
}
|
|
379
|
+
return this;
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Adds a blur filter to the chain (hardware-specific).
|
|
383
|
+
* Only available for hardware presets that support blur
|
|
384
|
+
*
|
|
385
|
+
* @param type - Blur type (default: 'avg')
|
|
386
|
+
*
|
|
387
|
+
* @param radius - Blur radius (optional)
|
|
388
|
+
*
|
|
389
|
+
* @returns This instance for chaining
|
|
390
|
+
*
|
|
391
|
+
* @example
|
|
392
|
+
* ```typescript
|
|
393
|
+
* const chain = FilterPresets.chain()
|
|
394
|
+
* .blur('gaussian', 5)
|
|
395
|
+
* .build();
|
|
396
|
+
* ```
|
|
397
|
+
*
|
|
398
|
+
* @example
|
|
399
|
+
* ```typescript
|
|
400
|
+
* const chain = FilterPresets.chain()
|
|
401
|
+
* .blur('box')
|
|
402
|
+
* .build();
|
|
403
|
+
* ```
|
|
404
|
+
*/
|
|
405
|
+
blur(type = 'avg', radius) {
|
|
406
|
+
if (!this.support.blur) {
|
|
407
|
+
return this;
|
|
408
|
+
}
|
|
409
|
+
if (this.hardware) {
|
|
410
|
+
let filter = null;
|
|
411
|
+
switch (this.hardware.deviceType) {
|
|
412
|
+
case AV_HWDEVICE_TYPE_CUDA:
|
|
413
|
+
filter = radius ? `bilateral_cuda=sigmaS=${radius}` : 'bilateral_cuda';
|
|
414
|
+
break;
|
|
415
|
+
case AV_HWDEVICE_TYPE_VULKAN:
|
|
416
|
+
filter = type === 'gaussian' ? (radius ? `gblur_vulkan=sigma=${radius}` : 'gblur_vulkan') : radius ? `avgblur_vulkan=sizeX=${radius}` : 'avgblur_vulkan';
|
|
417
|
+
break;
|
|
418
|
+
case AV_HWDEVICE_TYPE_OPENCL:
|
|
419
|
+
filter = type === 'box' ? (radius ? `boxblur_opencl=luma_radius=${radius}` : 'boxblur_opencl') : radius ? `avgblur_opencl=sizeX=${radius}` : 'avgblur_opencl';
|
|
420
|
+
break;
|
|
421
|
+
default:
|
|
422
|
+
filter = null;
|
|
423
|
+
}
|
|
424
|
+
if (filter) {
|
|
425
|
+
this.add(filter);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
const filter = type === 'gaussian' ? (radius ? `gblur=sigma=${radius}` : 'gblur') : radius ? `avgblur=sizeX=${radius}` : 'avgblur';
|
|
430
|
+
this.add(filter);
|
|
431
|
+
}
|
|
432
|
+
return this;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Adds a sharpen filter to the chain (hardware-specific).
|
|
436
|
+
* Only available for hardware presets that support sharpening
|
|
437
|
+
*
|
|
438
|
+
* @param amount - Sharpen amount (optional)
|
|
439
|
+
*
|
|
440
|
+
* @returns This instance for chaining
|
|
441
|
+
*
|
|
442
|
+
* @example
|
|
443
|
+
* ```typescript
|
|
444
|
+
* const chain = FilterPresets.chain()
|
|
445
|
+
* .sharpen(1.5)
|
|
446
|
+
* .build();
|
|
447
|
+
* ```
|
|
448
|
+
*
|
|
449
|
+
* @example
|
|
450
|
+
* ```typescript
|
|
451
|
+
* const chain = FilterPresets.chain()
|
|
452
|
+
* .sharpen()
|
|
453
|
+
* .build();
|
|
454
|
+
* ```
|
|
455
|
+
*/
|
|
456
|
+
sharpen(amount) {
|
|
457
|
+
if (!this.support.sharpen) {
|
|
458
|
+
return this;
|
|
459
|
+
}
|
|
460
|
+
if (this.hardware) {
|
|
461
|
+
let filter = null;
|
|
462
|
+
switch (this.hardware.deviceType) {
|
|
463
|
+
case AV_HWDEVICE_TYPE_VAAPI:
|
|
464
|
+
filter = amount ? `sharpness_vaapi=sharpness=${amount}` : 'sharpness_vaapi';
|
|
465
|
+
break;
|
|
466
|
+
case AV_HWDEVICE_TYPE_OPENCL:
|
|
467
|
+
filter = amount ? `unsharp_opencl=amount=${amount}` : 'unsharp_opencl';
|
|
468
|
+
break;
|
|
469
|
+
case AV_HWDEVICE_TYPE_QSV:
|
|
470
|
+
// vpp_qsv detail parameter: enhancement level [0-100]
|
|
471
|
+
const detail = amount ? Math.min(100, Math.max(0, Math.round(amount * 10))) : 50;
|
|
472
|
+
filter = `vpp_qsv=detail=${detail}`;
|
|
473
|
+
break;
|
|
474
|
+
case AV_HWDEVICE_TYPE_VULKAN:
|
|
475
|
+
// libplacebo contrast parameter (>1 increases sharpness, no native sharpen_vulkan available)
|
|
476
|
+
const contrast = amount ? Math.max(1, 1 + amount) : 1.5;
|
|
477
|
+
filter = `libplacebo=contrast=${contrast}`;
|
|
478
|
+
break;
|
|
479
|
+
case AV_HWDEVICE_TYPE_CUDA:
|
|
480
|
+
// CUDA sharpening not available (NPP not included)
|
|
481
|
+
filter = null;
|
|
482
|
+
break;
|
|
483
|
+
default:
|
|
484
|
+
filter = null;
|
|
485
|
+
}
|
|
486
|
+
if (filter) {
|
|
487
|
+
this.add(filter);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
const filter = amount ? `unsharp=amount=${amount}` : 'unsharp';
|
|
492
|
+
this.add(filter);
|
|
493
|
+
}
|
|
494
|
+
return this;
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Adds a Sobel edge detection filter.
|
|
498
|
+
*
|
|
499
|
+
* Applies Sobel operator to detect edges in images.
|
|
500
|
+
* Supports hardware acceleration with OpenCL.
|
|
501
|
+
* Useful for computer vision and artistic effects.
|
|
502
|
+
*
|
|
503
|
+
* @param planes - Planes to process (1-15, default: 15 for all)
|
|
504
|
+
*
|
|
505
|
+
* @param scale - Scale factor for result (default: 1.0)
|
|
506
|
+
*
|
|
507
|
+
* @returns This instance for chaining
|
|
508
|
+
*
|
|
509
|
+
* @example
|
|
510
|
+
* ```typescript
|
|
511
|
+
* const chain = FilterPresets.chain()
|
|
512
|
+
* .sobel()
|
|
513
|
+
* .build();
|
|
514
|
+
* ```
|
|
515
|
+
*
|
|
516
|
+
* @example
|
|
517
|
+
* ```typescript
|
|
518
|
+
* const chain = FilterPresets.chain()
|
|
519
|
+
* .sobel(15, 2.0) // All planes, 2x scale
|
|
520
|
+
* .build();
|
|
521
|
+
* ```
|
|
522
|
+
*/
|
|
523
|
+
sobel(planes, scale) {
|
|
524
|
+
if (this.hardware) {
|
|
525
|
+
let filter = null;
|
|
526
|
+
switch (this.hardware.deviceType) {
|
|
527
|
+
case AV_HWDEVICE_TYPE_OPENCL:
|
|
528
|
+
if (planes !== undefined || scale !== undefined) {
|
|
529
|
+
const params = [];
|
|
530
|
+
if (planes !== undefined)
|
|
531
|
+
params.push(`planes=${planes}`);
|
|
532
|
+
if (scale !== undefined)
|
|
533
|
+
params.push(`scale=${scale}`);
|
|
534
|
+
filter = `sobel_opencl=${params.join(':')}`;
|
|
535
|
+
}
|
|
536
|
+
else {
|
|
537
|
+
filter = 'sobel_opencl';
|
|
538
|
+
}
|
|
539
|
+
break;
|
|
540
|
+
}
|
|
541
|
+
if (filter) {
|
|
542
|
+
this.add(filter);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
// Software Sobel filter
|
|
547
|
+
if (planes !== undefined || scale !== undefined) {
|
|
548
|
+
const params = [];
|
|
549
|
+
if (planes !== undefined)
|
|
550
|
+
params.push(`planes=${planes}`);
|
|
551
|
+
if (scale !== undefined)
|
|
552
|
+
params.push(`scale=${scale}`);
|
|
553
|
+
this.add(`sobel=${params.join(':')}`);
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
this.add('sobel');
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
return this;
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Adds a format filter to convert pixel format.
|
|
563
|
+
*
|
|
564
|
+
* @param pixelFormat - Target pixel format(s) - AVPixelFormat enum, or array
|
|
565
|
+
*
|
|
566
|
+
* @returns This instance for chaining
|
|
567
|
+
*
|
|
568
|
+
* @example
|
|
569
|
+
* ```typescript
|
|
570
|
+
* // Single format
|
|
571
|
+
* chain.format(AV_PIX_FMT_YUV420P);
|
|
572
|
+
*
|
|
573
|
+
* // Multiple formats (tries formats in order)
|
|
574
|
+
* chain.format([AV_PIX_FMT_YUV420P, AV_PIX_FMT_RGB24]);
|
|
575
|
+
* ```
|
|
576
|
+
*
|
|
577
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#format | FFmpeg format filter}
|
|
578
|
+
*/
|
|
579
|
+
format(pixelFormat) {
|
|
580
|
+
if (Array.isArray(pixelFormat)) {
|
|
581
|
+
if (pixelFormat.length === 0) {
|
|
582
|
+
return this;
|
|
583
|
+
}
|
|
584
|
+
// Create a chain of format filters
|
|
585
|
+
const formats = pixelFormat.map((fmt) => {
|
|
586
|
+
const formatName = typeof fmt === 'string' ? fmt : (avGetPixFmtName(fmt) ?? 'yuv420p');
|
|
587
|
+
return `format=${formatName}`;
|
|
588
|
+
});
|
|
589
|
+
this.add(formats.join(','));
|
|
590
|
+
}
|
|
591
|
+
else {
|
|
592
|
+
const formatName = typeof pixelFormat === 'string' ? pixelFormat : (avGetPixFmtName(pixelFormat) ?? 'yuv420p');
|
|
593
|
+
this.add(`format=${formatName}`);
|
|
594
|
+
}
|
|
595
|
+
return this;
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Adds a flip filter to the chain (hardware-specific).
|
|
599
|
+
* Falls back to hflip/vflip if hardware flip not available
|
|
600
|
+
*
|
|
601
|
+
* @param direction - Flip direction ('h' or 'v')
|
|
602
|
+
*
|
|
603
|
+
* @returns This instance for chaining
|
|
604
|
+
*
|
|
605
|
+
* @example
|
|
606
|
+
* ```typescript
|
|
607
|
+
* const chain = FilterPresets.chain()
|
|
608
|
+
* .flip('h')
|
|
609
|
+
* .build();
|
|
610
|
+
* ```
|
|
611
|
+
*
|
|
612
|
+
* @example
|
|
613
|
+
* ```typescript
|
|
614
|
+
* const chain = FilterPresets.chain()
|
|
615
|
+
* .flip('v')
|
|
616
|
+
* .build();
|
|
617
|
+
* ```
|
|
618
|
+
*/
|
|
619
|
+
flip(direction) {
|
|
620
|
+
if (!this.support.flip) {
|
|
621
|
+
return this;
|
|
622
|
+
}
|
|
623
|
+
if (this.hardware) {
|
|
624
|
+
if (this.hardware.deviceType === AV_HWDEVICE_TYPE_VULKAN) {
|
|
625
|
+
// Use native Vulkan flip filters
|
|
626
|
+
if (direction === 'v') {
|
|
627
|
+
this.add('vflip_vulkan');
|
|
628
|
+
}
|
|
629
|
+
else {
|
|
630
|
+
this.add('hflip_vulkan');
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
else if (this.hardware.deviceType === AV_HWDEVICE_TYPE_QSV) {
|
|
634
|
+
// vpp_qsv uses transpose option for flip
|
|
635
|
+
if (direction === 'v') {
|
|
636
|
+
this.add('vpp_qsv=transpose=vflip');
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
this.add('vpp_qsv=transpose=hflip');
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
else {
|
|
644
|
+
if (direction === 'v') {
|
|
645
|
+
this.add('vflip');
|
|
646
|
+
}
|
|
647
|
+
else {
|
|
648
|
+
this.add('hflip');
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
return this;
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Adds a stack filter to the chain (hardware-specific).
|
|
655
|
+
* Only available for hardware presets that support stacking
|
|
656
|
+
*
|
|
657
|
+
* @param type - Stack type ('h' for horizontal, 'v' for vertical, 'x' for grid)
|
|
658
|
+
*
|
|
659
|
+
* @param inputs - Number of inputs (default: 2)
|
|
660
|
+
*
|
|
661
|
+
* @returns This instance for chaining
|
|
662
|
+
*
|
|
663
|
+
* @example
|
|
664
|
+
* ```typescript
|
|
665
|
+
* const chain = FilterPresets.chain()
|
|
666
|
+
* .stack('h', 2)
|
|
667
|
+
* .build();
|
|
668
|
+
* ```
|
|
669
|
+
*
|
|
670
|
+
* @example
|
|
671
|
+
* ```typescript
|
|
672
|
+
* const chain = FilterPresets.chain()
|
|
673
|
+
* .stack('x', 4)
|
|
674
|
+
* .build();
|
|
675
|
+
* ```
|
|
676
|
+
*/
|
|
677
|
+
stack(type, inputs = 2) {
|
|
678
|
+
if (!this.support.stack) {
|
|
679
|
+
return this;
|
|
680
|
+
}
|
|
681
|
+
if (this.hardware) {
|
|
682
|
+
if (this.hardware.deviceType === AV_HWDEVICE_TYPE_VAAPI || this.hardware.deviceType === AV_HWDEVICE_TYPE_QSV) {
|
|
683
|
+
const filter = `${type}stack_${this.hardware.deviceTypeName}=inputs=${inputs}`;
|
|
684
|
+
this.add(filter);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
else {
|
|
688
|
+
const filter = type === 'h' ? `hstack=inputs=${inputs}` : type === 'v' ? `vstack=inputs=${inputs}` : `xstack=inputs=${inputs}`;
|
|
689
|
+
this.add(filter);
|
|
690
|
+
}
|
|
691
|
+
return this;
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Creates a tonemap filter.
|
|
695
|
+
* Used for HDR to SDR conversion with hardware acceleration.
|
|
696
|
+
*
|
|
697
|
+
* @param alg - Tonemapping algorithm (e.g., 'hable', 'reinhard', 'mobius', etc.)
|
|
698
|
+
*
|
|
699
|
+
* @param options - Tonemapping options
|
|
700
|
+
*
|
|
701
|
+
* @returns Hardware tonemap filter string or null if not supported
|
|
702
|
+
*
|
|
703
|
+
* @example
|
|
704
|
+
* ```typescript
|
|
705
|
+
* const filter = hwPresets.tonemap();
|
|
706
|
+
* ```
|
|
707
|
+
*
|
|
708
|
+
* @example
|
|
709
|
+
* ```typescript
|
|
710
|
+
* const filter = hwPresets.tonemap({ tonemap: 'hable', desat: '0' });
|
|
711
|
+
* ```
|
|
712
|
+
*/
|
|
713
|
+
tonemap(alg, options) {
|
|
714
|
+
if (!this.support.tonemap) {
|
|
715
|
+
return this;
|
|
716
|
+
}
|
|
717
|
+
if (this.hardware) {
|
|
718
|
+
// Vulkan uses libplacebo for comprehensive HDR tone-mapping (no native tonemap_vulkan available)
|
|
719
|
+
if (this.hardware.deviceType === AV_HWDEVICE_TYPE_VULKAN) {
|
|
720
|
+
let filter = `libplacebo=tonemapping=${alg}`;
|
|
721
|
+
if (options) {
|
|
722
|
+
const opts = Object.entries(options)
|
|
723
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
724
|
+
.join(':');
|
|
725
|
+
filter += `:${opts}`;
|
|
726
|
+
}
|
|
727
|
+
this.add(filter);
|
|
728
|
+
}
|
|
729
|
+
else {
|
|
730
|
+
// VideoToolbox uses different filter name
|
|
731
|
+
const filterName = this.hardware.deviceType === AV_HWDEVICE_TYPE_VIDEOTOOLBOX ? 'tonemap_videotoolbox' : `tonemap_${this.hardware.deviceTypeName}`;
|
|
732
|
+
let filter = `${filterName}=${alg}`;
|
|
733
|
+
if (options) {
|
|
734
|
+
const opts = Object.entries(options)
|
|
735
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
736
|
+
.join(':');
|
|
737
|
+
filter += `=${opts}`;
|
|
738
|
+
}
|
|
739
|
+
this.add(filter);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
else {
|
|
743
|
+
let filter = `tonemap=${alg}`;
|
|
744
|
+
if (options) {
|
|
745
|
+
const opts = Object.entries(options)
|
|
746
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
747
|
+
.join(':');
|
|
748
|
+
filter += `=${opts}`;
|
|
749
|
+
}
|
|
750
|
+
this.add(filter);
|
|
751
|
+
}
|
|
752
|
+
return this;
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Creates an overlay filter string to composite two video streams.
|
|
756
|
+
*
|
|
757
|
+
* @param x - X position for overlay (default: 0)
|
|
758
|
+
*
|
|
759
|
+
* @param y - Y position for overlay (default: 0)
|
|
760
|
+
*
|
|
761
|
+
* @param options - Additional overlay options
|
|
762
|
+
*
|
|
763
|
+
* @returns Filter string or null if not supported
|
|
764
|
+
*
|
|
765
|
+
* @example
|
|
766
|
+
* ```typescript
|
|
767
|
+
* // Basic overlay at position
|
|
768
|
+
* presets.overlay(100, 50);
|
|
769
|
+
*
|
|
770
|
+
* // With additional options
|
|
771
|
+
* presets.overlay(0, 0, { format: 'yuv420' });
|
|
772
|
+
* ```
|
|
773
|
+
*
|
|
774
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#overlay | FFmpeg overlay filter}
|
|
775
|
+
*/
|
|
776
|
+
overlay(x = 0, y = 0, options) {
|
|
777
|
+
if (!this.support.overlay) {
|
|
778
|
+
return this;
|
|
779
|
+
}
|
|
780
|
+
if (this.hardware) {
|
|
781
|
+
// Special handling for RKMPP which uses RGA
|
|
782
|
+
const filterName = this.hardware.deviceType === AV_HWDEVICE_TYPE_RKMPP ? 'overlay_rkrga' : `overlay_${this.hardware.deviceTypeName}`;
|
|
783
|
+
// Use named options: some hardware overlays (overlay_videotoolbox) accept
|
|
784
|
+
// no positional shorthand, and x=/y= is valid for every overlay variant.
|
|
785
|
+
let filter = `${filterName}=x=${x}:y=${y}`;
|
|
786
|
+
if (options) {
|
|
787
|
+
for (const [key, value] of Object.entries(options)) {
|
|
788
|
+
filter += `:${key}=${value}`;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
this.add(filter);
|
|
792
|
+
}
|
|
793
|
+
else {
|
|
794
|
+
let filter = `overlay=x=${x}:y=${y}`;
|
|
795
|
+
if (options) {
|
|
796
|
+
for (const [key, value] of Object.entries(options)) {
|
|
797
|
+
filter += `:${key}=${value}`;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
this.add(filter);
|
|
801
|
+
}
|
|
802
|
+
return this;
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Creates an audio format filter string.
|
|
806
|
+
*
|
|
807
|
+
* @param sampleFormat - Target sample format (e.g., 's16', 'fltp')
|
|
808
|
+
*
|
|
809
|
+
* @param sampleRate - Target sample rate in Hz (optional)
|
|
810
|
+
*
|
|
811
|
+
* @param channelLayout - Target channel layout (optional)
|
|
812
|
+
*
|
|
813
|
+
* @returns Filter string or null if not supported
|
|
814
|
+
*
|
|
815
|
+
* @example
|
|
816
|
+
* ```typescript
|
|
817
|
+
* // Change sample format only
|
|
818
|
+
* presets.aformat('s16');
|
|
819
|
+
*
|
|
820
|
+
* // Change format and sample rate
|
|
821
|
+
* presets.aformat('fltp', 48000);
|
|
822
|
+
*
|
|
823
|
+
* // Full conversion
|
|
824
|
+
* presets.aformat('s16', 44100, 'stereo');
|
|
825
|
+
* ```
|
|
826
|
+
*
|
|
827
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#aformat | FFmpeg aformat filter}
|
|
828
|
+
*/
|
|
829
|
+
aformat(sampleFormat, sampleRate, channelLayout) {
|
|
830
|
+
let sampleFormats = '';
|
|
831
|
+
if (!Array.isArray(sampleFormat)) {
|
|
832
|
+
sampleFormat = [sampleFormat];
|
|
833
|
+
}
|
|
834
|
+
sampleFormats = sampleFormat.map((fmt) => (typeof fmt === 'string' ? fmt : (avGetSampleFmtName(fmt) ?? 's16'))).join('|');
|
|
835
|
+
let filter = `aformat=sample_fmts=${sampleFormats}`;
|
|
836
|
+
if (sampleRate)
|
|
837
|
+
filter += `:sample_rates=${sampleRate}`;
|
|
838
|
+
if (channelLayout)
|
|
839
|
+
filter += `:channel_layouts=${channelLayout}`;
|
|
840
|
+
this.add(filter);
|
|
841
|
+
return this;
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Adds a pad filter to add padding to video.
|
|
845
|
+
* Essential for aspect ratio adjustments and letterboxing.
|
|
846
|
+
*
|
|
847
|
+
* @param width - Output width (can use expressions like 'iw+100')
|
|
848
|
+
*
|
|
849
|
+
* @param height - Output height (can use expressions like 'ih+100')
|
|
850
|
+
*
|
|
851
|
+
* @param x - X position of input video (default: '(ow-iw)/2' for center)
|
|
852
|
+
*
|
|
853
|
+
* @param y - Y position of input video (default: '(oh-ih)/2' for center)
|
|
854
|
+
*
|
|
855
|
+
* @param color - Padding color (default: 'black')
|
|
856
|
+
*
|
|
857
|
+
* @returns This instance for chaining
|
|
858
|
+
*
|
|
859
|
+
* @example
|
|
860
|
+
* ```typescript
|
|
861
|
+
* // Add black bars for 16:9 aspect ratio
|
|
862
|
+
* chain.pad('iw', 'iw*9/16');
|
|
863
|
+
*
|
|
864
|
+
* // Add 50px padding on all sides
|
|
865
|
+
* chain.pad('iw+100', 'ih+100');
|
|
866
|
+
* ```
|
|
867
|
+
*
|
|
868
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#pad | FFmpeg pad filter}
|
|
869
|
+
*/
|
|
870
|
+
pad(width, height, x, y, color = 'black') {
|
|
871
|
+
if (this.hardware?.deviceType === AV_HWDEVICE_TYPE_VULKAN) {
|
|
872
|
+
// Vulkan uses libplacebo (no native pad_vulkan available)
|
|
873
|
+
let filter = `libplacebo=w=${width}:h=${height}`;
|
|
874
|
+
if (x !== undefined)
|
|
875
|
+
filter += `:pos_x=${x}`;
|
|
876
|
+
if (y !== undefined)
|
|
877
|
+
filter += `:pos_y=${y}`;
|
|
878
|
+
filter += `:fillcolor=${color}`;
|
|
879
|
+
this.add(filter);
|
|
880
|
+
}
|
|
881
|
+
else {
|
|
882
|
+
let filter = `pad=${width}:${height}`;
|
|
883
|
+
if (x !== undefined)
|
|
884
|
+
filter += `:${x}`;
|
|
885
|
+
if (y !== undefined)
|
|
886
|
+
filter += `:${y}`;
|
|
887
|
+
filter += `:${color}`;
|
|
888
|
+
this.add(filter);
|
|
889
|
+
}
|
|
890
|
+
return this;
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Creates a transpose filter string for rotation/flipping.
|
|
894
|
+
* More efficient than rotate for 90-degree rotations.
|
|
895
|
+
*
|
|
896
|
+
* @param mode - Transpose mode (0-3, or named constants)
|
|
897
|
+
*
|
|
898
|
+
* @returns Filter string or null if not supported
|
|
899
|
+
*
|
|
900
|
+
* @example
|
|
901
|
+
* ```typescript
|
|
902
|
+
* presets.transpose(1) // Rotate 90 degrees clockwise
|
|
903
|
+
* presets.transpose('cclock') // Rotate 90 degrees counter-clockwise
|
|
904
|
+
* ```
|
|
905
|
+
*
|
|
906
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#transpose | FFmpeg transpose filter}
|
|
907
|
+
*/
|
|
908
|
+
transpose(mode) {
|
|
909
|
+
if (!this.support.transpose) {
|
|
910
|
+
return this;
|
|
911
|
+
}
|
|
912
|
+
if (this.hardware) {
|
|
913
|
+
// Convert string modes to numbers
|
|
914
|
+
let dir;
|
|
915
|
+
if (typeof mode === 'string') {
|
|
916
|
+
switch (mode) {
|
|
917
|
+
case 'clock':
|
|
918
|
+
dir = 1;
|
|
919
|
+
break;
|
|
920
|
+
case 'cclock':
|
|
921
|
+
dir = 2;
|
|
922
|
+
break;
|
|
923
|
+
case 'clock_flip':
|
|
924
|
+
dir = 3;
|
|
925
|
+
break;
|
|
926
|
+
case 'cclock_flip':
|
|
927
|
+
dir = 0;
|
|
928
|
+
break;
|
|
929
|
+
default:
|
|
930
|
+
dir = 0;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
else {
|
|
934
|
+
dir = mode;
|
|
935
|
+
}
|
|
936
|
+
// Special handling for different hardware transpose implementations
|
|
937
|
+
let filter;
|
|
938
|
+
if (this.hardware.deviceType === AV_HWDEVICE_TYPE_CUDA) {
|
|
939
|
+
filter = `transpose_cuda=dir=${dir}`; // Uses custom CUDA kernel (patch 0054)
|
|
940
|
+
}
|
|
941
|
+
else if (this.hardware.deviceType === AV_HWDEVICE_TYPE_VIDEOTOOLBOX) {
|
|
942
|
+
filter = `transpose_vt=dir=${dir}`; // CoreImage-based transpose
|
|
943
|
+
}
|
|
944
|
+
else if (this.hardware.deviceType === AV_HWDEVICE_TYPE_QSV) {
|
|
945
|
+
// vpp_qsv transpose parameter maps: clock=1, cclock=2, cclock_hflip=0, clock_hflip=3, reversal=4, hflip=5, vflip=6
|
|
946
|
+
filter = `vpp_qsv=transpose=${dir}`;
|
|
947
|
+
}
|
|
948
|
+
else {
|
|
949
|
+
filter = `transpose_${this.hardware.deviceTypeName}=dir=${dir}`;
|
|
950
|
+
}
|
|
951
|
+
this.add(filter);
|
|
952
|
+
}
|
|
953
|
+
else {
|
|
954
|
+
this.add(`transpose=${mode}`);
|
|
955
|
+
}
|
|
956
|
+
return this;
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* Creates a deinterlace filter string.
|
|
960
|
+
* Essential for processing interlaced content.
|
|
961
|
+
*
|
|
962
|
+
* @param mode - Deinterlace mode (default: 'yadif')
|
|
963
|
+
*
|
|
964
|
+
* @param options - Additional options for the filter
|
|
965
|
+
*
|
|
966
|
+
* @returns Filter string or null if not supported
|
|
967
|
+
*
|
|
968
|
+
* @example
|
|
969
|
+
* ```typescript
|
|
970
|
+
* presets.deinterlace('yadif') // Standard deinterlacing
|
|
971
|
+
* presets.deinterlace('bwdif') // Bob Weaver deinterlacing
|
|
972
|
+
* ```
|
|
973
|
+
*
|
|
974
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#yadif | FFmpeg yadif filter}
|
|
975
|
+
*/
|
|
976
|
+
deinterlace(mode = 'yadif', options) {
|
|
977
|
+
if (!this.support.deinterlace) {
|
|
978
|
+
return this;
|
|
979
|
+
}
|
|
980
|
+
if (this.hardware) {
|
|
981
|
+
let filter = null;
|
|
982
|
+
switch (this.hardware.deviceType) {
|
|
983
|
+
case AV_HWDEVICE_TYPE_CUDA:
|
|
984
|
+
filter = mode ? `yadif_cuda=mode=${mode}` : 'yadif_cuda';
|
|
985
|
+
break;
|
|
986
|
+
case AV_HWDEVICE_TYPE_VAAPI:
|
|
987
|
+
filter = mode ? `deinterlace_vaapi=mode=${mode}` : 'deinterlace_vaapi';
|
|
988
|
+
break;
|
|
989
|
+
case AV_HWDEVICE_TYPE_QSV:
|
|
990
|
+
// vpp_qsv deinterlace: 0=off, 1=bob, 2=advanced
|
|
991
|
+
// Map yadif/bwdif mode to vpp_qsv: default to bob(1)
|
|
992
|
+
filter = mode ? 'vpp_qsv=deinterlace=1' : 'vpp_qsv=deinterlace=1';
|
|
993
|
+
break;
|
|
994
|
+
case AV_HWDEVICE_TYPE_VULKAN:
|
|
995
|
+
filter = mode ? `bwdif_vulkan=mode=${mode}` : 'bwdif_vulkan';
|
|
996
|
+
break;
|
|
997
|
+
case AV_HWDEVICE_TYPE_VIDEOTOOLBOX:
|
|
998
|
+
filter = mode ? `yadif_videotoolbox=mode=${mode}` : 'yadif_videotoolbox';
|
|
999
|
+
break;
|
|
1000
|
+
default:
|
|
1001
|
+
filter = null;
|
|
1002
|
+
}
|
|
1003
|
+
if (filter) {
|
|
1004
|
+
if (options) {
|
|
1005
|
+
const params = [];
|
|
1006
|
+
for (const [key, value] of Object.entries(options)) {
|
|
1007
|
+
params.push(`${key}=${value}`);
|
|
1008
|
+
}
|
|
1009
|
+
filter += '=' + params.join(':');
|
|
1010
|
+
}
|
|
1011
|
+
this.add(filter);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
else {
|
|
1015
|
+
let filter = mode;
|
|
1016
|
+
if (options) {
|
|
1017
|
+
const params = [];
|
|
1018
|
+
for (const [key, value] of Object.entries(options)) {
|
|
1019
|
+
params.push(`${key}=${value}`);
|
|
1020
|
+
}
|
|
1021
|
+
filter += '=' + params.join(':');
|
|
1022
|
+
}
|
|
1023
|
+
this.add(filter);
|
|
1024
|
+
}
|
|
1025
|
+
return this;
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* Adds a whisper filter for audio transcription using whisper.cpp.
|
|
1029
|
+
* Transcribes audio and adds metadata to frames (lavfi.whisper.text, lavfi.whisper.duration).
|
|
1030
|
+
*
|
|
1031
|
+
* @param options - Whisper transcription options
|
|
1032
|
+
*
|
|
1033
|
+
* @param options.model - Path to whisper.cpp model file
|
|
1034
|
+
*
|
|
1035
|
+
* @param options.language - Language for transcription (default: 'auto')
|
|
1036
|
+
*
|
|
1037
|
+
* @param options.queue - Audio queue size in seconds (default: 3)
|
|
1038
|
+
*
|
|
1039
|
+
* @param options.useGpu - Use GPU for processing (default: true)
|
|
1040
|
+
*
|
|
1041
|
+
* @param options.gpuDevice - GPU device to use (default: 0)
|
|
1042
|
+
*
|
|
1043
|
+
* @param options.destination - Output destination for transcripts
|
|
1044
|
+
*
|
|
1045
|
+
* @param options.format - Output format: text|srt|json (default: 'text')
|
|
1046
|
+
*
|
|
1047
|
+
* @param options.vadModel - Path to VAD model file (optional)
|
|
1048
|
+
*
|
|
1049
|
+
* @param options.vadThreshold - VAD threshold 0.0-1.0 (default: 0.5)
|
|
1050
|
+
*
|
|
1051
|
+
* @param options.vadMinSpeechDuration - Minimum speech duration for VAD in seconds (default: 0.1)
|
|
1052
|
+
*
|
|
1053
|
+
* @param options.vadMinSilenceDuration - Minimum silence duration for VAD in seconds (default: 0.5)
|
|
1054
|
+
*
|
|
1055
|
+
* @returns This instance for chaining
|
|
1056
|
+
*
|
|
1057
|
+
* @example
|
|
1058
|
+
* ```typescript
|
|
1059
|
+
* // Basic transcription
|
|
1060
|
+
* chain.whisper({
|
|
1061
|
+
* model: '/path/to/ggml-base.bin'
|
|
1062
|
+
* });
|
|
1063
|
+
*
|
|
1064
|
+
* // With language and output
|
|
1065
|
+
* chain.whisper({
|
|
1066
|
+
* model: '/path/to/ggml-base.bin',
|
|
1067
|
+
* language: 'en',
|
|
1068
|
+
* destination: 'output.srt',
|
|
1069
|
+
* format: 'srt'
|
|
1070
|
+
* });
|
|
1071
|
+
*
|
|
1072
|
+
* // With VAD model
|
|
1073
|
+
* chain.whisper({
|
|
1074
|
+
* model: '/path/to/ggml-base.bin',
|
|
1075
|
+
* vadModel: '/path/to/ggml-silero-v5.1.2.bin',
|
|
1076
|
+
* vadThreshold: 0.5
|
|
1077
|
+
* });
|
|
1078
|
+
* ```
|
|
1079
|
+
*
|
|
1080
|
+
* @see {@link https://ffmpeg.org/ffmpeg-filters.html#whisper | FFmpeg whisper filter}
|
|
1081
|
+
*/
|
|
1082
|
+
whisper(options) {
|
|
1083
|
+
// Normalize path for FFmpeg filter syntax:
|
|
1084
|
+
// 1. Convert backslashes to forward slashes (Windows compatibility)
|
|
1085
|
+
// 2. Escape colons with double backslash (for multi-level parsing: JS → C++ → FFmpeg)
|
|
1086
|
+
const normalizedModel = options.model.replace(/\\/g, '/').replace(/:/g, '\\\\:');
|
|
1087
|
+
const params = [`model=${normalizedModel}`];
|
|
1088
|
+
if (options.language !== undefined) {
|
|
1089
|
+
params.push(`language=${options.language}`);
|
|
1090
|
+
}
|
|
1091
|
+
if (options.queue !== undefined) {
|
|
1092
|
+
params.push(`queue=${options.queue}`);
|
|
1093
|
+
}
|
|
1094
|
+
if (options.useGpu !== undefined) {
|
|
1095
|
+
params.push(`use_gpu=${options.useGpu ? 1 : 0}`);
|
|
1096
|
+
}
|
|
1097
|
+
if (options.gpuDevice !== undefined) {
|
|
1098
|
+
params.push(`gpu_device=${options.gpuDevice}`);
|
|
1099
|
+
}
|
|
1100
|
+
if (options.destination !== undefined) {
|
|
1101
|
+
const normalizedDest = options.destination.replace(/\\/g, '/').replace(/:/g, '\\\\:');
|
|
1102
|
+
params.push(`destination=${normalizedDest}`);
|
|
1103
|
+
}
|
|
1104
|
+
if (options.format !== undefined) {
|
|
1105
|
+
params.push(`format=${options.format}`);
|
|
1106
|
+
}
|
|
1107
|
+
if (options.vadModel !== undefined) {
|
|
1108
|
+
const normalizedVadModel = options.vadModel.replace(/\\/g, '/').replace(/:/g, '\\\\:');
|
|
1109
|
+
params.push(`vad_model=${normalizedVadModel}`);
|
|
1110
|
+
}
|
|
1111
|
+
if (options.vadThreshold !== undefined) {
|
|
1112
|
+
params.push(`vad_threshold=${options.vadThreshold}`);
|
|
1113
|
+
}
|
|
1114
|
+
if (options.vadMinSpeechDuration !== undefined) {
|
|
1115
|
+
params.push(`vad_min_speech_duration=${options.vadMinSpeechDuration}`);
|
|
1116
|
+
}
|
|
1117
|
+
if (options.vadMinSilenceDuration !== undefined) {
|
|
1118
|
+
params.push(`vad_min_silence_duration=${options.vadMinSilenceDuration}`);
|
|
1119
|
+
}
|
|
1120
|
+
this.add(`whisper=${params.join(':')}`);
|
|
1121
|
+
return this;
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Adds a hwupload filter to upload frames to hardware.
|
|
1125
|
+
* Only applied if hardware acceleration is configured.
|
|
1126
|
+
*
|
|
1127
|
+
* @returns This instance for chaining
|
|
1128
|
+
*
|
|
1129
|
+
* @example
|
|
1130
|
+
* ```typescript
|
|
1131
|
+
* const chain = FilterPresets.chain()
|
|
1132
|
+
* .hwupload()
|
|
1133
|
+
* .scale(1920, 1080)
|
|
1134
|
+
* .build();
|
|
1135
|
+
* ```
|
|
1136
|
+
*/
|
|
1137
|
+
hwupload() {
|
|
1138
|
+
if (!this.hardware) {
|
|
1139
|
+
return this;
|
|
1140
|
+
}
|
|
1141
|
+
if (this.hardware.deviceType === AV_HWDEVICE_TYPE_CUDA) {
|
|
1142
|
+
this.add('hwupload_cuda');
|
|
1143
|
+
}
|
|
1144
|
+
else {
|
|
1145
|
+
this.add('hwupload');
|
|
1146
|
+
}
|
|
1147
|
+
return this;
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Adds a hwdownload filter to download frames from hardware.
|
|
1151
|
+
* Only applied if hardware acceleration is configured.
|
|
1152
|
+
*
|
|
1153
|
+
* @returns This instance for chaining
|
|
1154
|
+
*
|
|
1155
|
+
* @example
|
|
1156
|
+
* ```typescript
|
|
1157
|
+
* const chain = FilterPresets.chain()
|
|
1158
|
+
* .scale(1920, 1080)
|
|
1159
|
+
* .hwdownload()
|
|
1160
|
+
* .build();
|
|
1161
|
+
* ```
|
|
1162
|
+
*/
|
|
1163
|
+
hwdownload() {
|
|
1164
|
+
if (!this.hardware) {
|
|
1165
|
+
return this;
|
|
1166
|
+
}
|
|
1167
|
+
this.add('hwdownload');
|
|
1168
|
+
return this;
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Adds a filter to the chain.
|
|
1172
|
+
*
|
|
1173
|
+
* @param filter - Filter string to add (ignored if null/undefined)
|
|
1174
|
+
*
|
|
1175
|
+
* @param unshift - If true, adds to the start instead of the end (default: false)
|
|
1176
|
+
*
|
|
1177
|
+
* @returns This instance for chaining
|
|
1178
|
+
*
|
|
1179
|
+
* @example
|
|
1180
|
+
* ```typescript
|
|
1181
|
+
* chain.add('scale=1920:1080')
|
|
1182
|
+
* ```
|
|
1183
|
+
*
|
|
1184
|
+
* @internal
|
|
1185
|
+
*/
|
|
1186
|
+
add(filter, unshift) {
|
|
1187
|
+
if (filter) {
|
|
1188
|
+
if (unshift) {
|
|
1189
|
+
this.filters.unshift(filter);
|
|
1190
|
+
}
|
|
1191
|
+
else {
|
|
1192
|
+
this.filters.push(filter);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
return this;
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* Determines filter support for the hardware type.
|
|
1199
|
+
*
|
|
1200
|
+
* @returns Hardware filter support configuration
|
|
1201
|
+
*
|
|
1202
|
+
* @internal
|
|
1203
|
+
*/
|
|
1204
|
+
getSupport() {
|
|
1205
|
+
if (!this.hardware) {
|
|
1206
|
+
// Software-only - all filters supported
|
|
1207
|
+
return {
|
|
1208
|
+
scale: true,
|
|
1209
|
+
overlay: true,
|
|
1210
|
+
transpose: true,
|
|
1211
|
+
tonemap: true,
|
|
1212
|
+
deinterlace: true,
|
|
1213
|
+
denoise: true,
|
|
1214
|
+
flip: true,
|
|
1215
|
+
blur: true,
|
|
1216
|
+
sharpen: true,
|
|
1217
|
+
sobel: true,
|
|
1218
|
+
chromakey: true,
|
|
1219
|
+
colorspace: true,
|
|
1220
|
+
pad: true,
|
|
1221
|
+
stack: true,
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
switch (this.hardware.deviceType) {
|
|
1225
|
+
case AV_HWDEVICE_TYPE_CUDA:
|
|
1226
|
+
return {
|
|
1227
|
+
scale: true, // scale_cuda with crop support (patch 1002)
|
|
1228
|
+
overlay: true, // overlay_cuda
|
|
1229
|
+
transpose: true, // transpose_cuda (patch 0054)
|
|
1230
|
+
tonemap: true, // tonemap_cuda (patch 0004)
|
|
1231
|
+
deinterlace: true, // bwdif_cuda, yadif_cuda
|
|
1232
|
+
denoise: false,
|
|
1233
|
+
flip: false,
|
|
1234
|
+
blur: true, // bilateral_cuda
|
|
1235
|
+
sharpen: false, // Not available (NPP not included in build)
|
|
1236
|
+
sobel: false,
|
|
1237
|
+
chromakey: true, // chromakey_cuda
|
|
1238
|
+
colorspace: true, // colorspace_cuda
|
|
1239
|
+
pad: false,
|
|
1240
|
+
stack: false,
|
|
1241
|
+
};
|
|
1242
|
+
case AV_HWDEVICE_TYPE_VAAPI:
|
|
1243
|
+
return {
|
|
1244
|
+
scale: true, // scale_vaapi
|
|
1245
|
+
overlay: true, // overlay_vaapi
|
|
1246
|
+
transpose: true, // transpose_vaapi
|
|
1247
|
+
tonemap: true, // tonemap_vaapi
|
|
1248
|
+
deinterlace: true, // deinterlace_vaapi
|
|
1249
|
+
denoise: true, // denoise_vaapi
|
|
1250
|
+
flip: false,
|
|
1251
|
+
blur: false,
|
|
1252
|
+
sharpen: true, // sharpness_vaapi
|
|
1253
|
+
sobel: false,
|
|
1254
|
+
chromakey: false,
|
|
1255
|
+
colorspace: false,
|
|
1256
|
+
pad: true, // pad_vaapi
|
|
1257
|
+
stack: true, // hstack_vaapi, vstack_vaapi, xstack_vaapi
|
|
1258
|
+
};
|
|
1259
|
+
case AV_HWDEVICE_TYPE_QSV:
|
|
1260
|
+
return {
|
|
1261
|
+
scale: true, // vpp_qsv (w/h params) with crop support (cw/ch/cx/cy)
|
|
1262
|
+
overlay: true, // overlay_qsv
|
|
1263
|
+
transpose: true, // vpp_qsv transpose option
|
|
1264
|
+
tonemap: true, // vpp_qsv tonemap option
|
|
1265
|
+
deinterlace: true, // vpp_qsv deinterlace option (bob, advanced)
|
|
1266
|
+
denoise: true, // vpp_qsv denoise option [0-100]
|
|
1267
|
+
flip: true, // vpp_qsv transpose=hflip/vflip
|
|
1268
|
+
blur: false,
|
|
1269
|
+
sharpen: true, // vpp_qsv detail option [0-100]
|
|
1270
|
+
sobel: false,
|
|
1271
|
+
chromakey: false,
|
|
1272
|
+
colorspace: true, // vpp_qsv out_color_matrix/primaries/transfer
|
|
1273
|
+
pad: false,
|
|
1274
|
+
stack: true, // hstack_qsv, vstack_qsv, xstack_qsv
|
|
1275
|
+
};
|
|
1276
|
+
case AV_HWDEVICE_TYPE_VULKAN:
|
|
1277
|
+
return {
|
|
1278
|
+
scale: true, // scale_vulkan (native Vulkan scaler)
|
|
1279
|
+
overlay: true, // overlay_vulkan (native Vulkan overlay)
|
|
1280
|
+
transpose: true, // transpose_vulkan (native Vulkan transpose)
|
|
1281
|
+
tonemap: true, // libplacebo (comprehensive HDR tone-mapping, no native alternative)
|
|
1282
|
+
deinterlace: true, // bwdif_vulkan (native Vulkan deinterlacer)
|
|
1283
|
+
denoise: true, // nlmeans_vulkan (native Vulkan denoiser)
|
|
1284
|
+
flip: true, // hflip_vulkan/vflip_vulkan (native Vulkan flip)
|
|
1285
|
+
blur: true, // avgblur_vulkan/gblur_vulkan (native Vulkan blur)
|
|
1286
|
+
sharpen: true, // libplacebo (contrast/sharpness adjustments, no native alternative)
|
|
1287
|
+
sobel: false, // Not available
|
|
1288
|
+
chromakey: false, // chromaber_vulkan exists but different purpose (chromatic aberration)
|
|
1289
|
+
colorspace: true, // libplacebo (comprehensive color management, no native alternative)
|
|
1290
|
+
pad: true, // libplacebo (via pos_x/y/w/h and fillcolor, no native alternative)
|
|
1291
|
+
stack: false, // Not available
|
|
1292
|
+
};
|
|
1293
|
+
case AV_HWDEVICE_TYPE_OPENCL:
|
|
1294
|
+
return {
|
|
1295
|
+
scale: true, // scale_opencl, applies the frame crop (patch 0006)
|
|
1296
|
+
overlay: true, // overlay_opencl (+ PGS support patch 0008)
|
|
1297
|
+
transpose: true, // transpose_opencl
|
|
1298
|
+
tonemap: true, // tonemap_opencl (enhanced in patch 0007)
|
|
1299
|
+
deinterlace: false,
|
|
1300
|
+
denoise: false,
|
|
1301
|
+
flip: false,
|
|
1302
|
+
blur: true, // avgblur_opencl, boxblur_opencl
|
|
1303
|
+
sharpen: true, // unsharp_opencl
|
|
1304
|
+
sobel: true, // sobel_opencl
|
|
1305
|
+
chromakey: true, // colorkey_opencl
|
|
1306
|
+
colorspace: false,
|
|
1307
|
+
pad: true, // pad_opencl
|
|
1308
|
+
stack: false,
|
|
1309
|
+
};
|
|
1310
|
+
case AV_HWDEVICE_TYPE_VIDEOTOOLBOX:
|
|
1311
|
+
return {
|
|
1312
|
+
scale: true, // scale_vt with crop support (patches 0047, 1001)
|
|
1313
|
+
overlay: true, // overlay_videotoolbox (patch 0048)
|
|
1314
|
+
transpose: true, // transpose_vt (patch 0049, CoreImage based)
|
|
1315
|
+
tonemap: true, // tonemap_videotoolbox (patch 0050)
|
|
1316
|
+
deinterlace: true, // yadif_videotoolbox
|
|
1317
|
+
denoise: false,
|
|
1318
|
+
flip: false,
|
|
1319
|
+
blur: false,
|
|
1320
|
+
sharpen: false,
|
|
1321
|
+
sobel: false,
|
|
1322
|
+
chromakey: false,
|
|
1323
|
+
colorspace: false,
|
|
1324
|
+
pad: false,
|
|
1325
|
+
stack: false,
|
|
1326
|
+
};
|
|
1327
|
+
case AV_HWDEVICE_TYPE_MEDIACODEC:
|
|
1328
|
+
// MediaCodec is Android's hardware acceleration - mainly for decode/encode
|
|
1329
|
+
return {
|
|
1330
|
+
scale: false,
|
|
1331
|
+
overlay: false,
|
|
1332
|
+
transpose: false,
|
|
1333
|
+
tonemap: false,
|
|
1334
|
+
deinterlace: false,
|
|
1335
|
+
denoise: false,
|
|
1336
|
+
flip: false,
|
|
1337
|
+
blur: false,
|
|
1338
|
+
sharpen: false,
|
|
1339
|
+
sobel: false,
|
|
1340
|
+
chromakey: false,
|
|
1341
|
+
colorspace: false,
|
|
1342
|
+
pad: false,
|
|
1343
|
+
stack: false,
|
|
1344
|
+
};
|
|
1345
|
+
case AV_HWDEVICE_TYPE_RKMPP: // Rockchip - has RGA filters via patch 0046
|
|
1346
|
+
// Note: RKMPP uses separate RKRGA (Rockchip 2D Raster Graphic Acceleration)
|
|
1347
|
+
// for filtering operations, configured with --enable-rkrga
|
|
1348
|
+
return {
|
|
1349
|
+
scale: true, // scale_rkrga (patch 0046)
|
|
1350
|
+
overlay: true, // overlay_rkrga (patch 0046)
|
|
1351
|
+
transpose: false,
|
|
1352
|
+
tonemap: false,
|
|
1353
|
+
deinterlace: false,
|
|
1354
|
+
denoise: false,
|
|
1355
|
+
flip: false,
|
|
1356
|
+
blur: false,
|
|
1357
|
+
sharpen: false,
|
|
1358
|
+
sobel: false,
|
|
1359
|
+
chromakey: false,
|
|
1360
|
+
colorspace: false,
|
|
1361
|
+
pad: false,
|
|
1362
|
+
stack: false,
|
|
1363
|
+
};
|
|
1364
|
+
case AV_HWDEVICE_TYPE_D3D11VA: // Windows video processor
|
|
1365
|
+
case AV_HWDEVICE_TYPE_D3D12VA:
|
|
1366
|
+
return {
|
|
1367
|
+
scale: true, // scale_d3d11 / scale_d3d12
|
|
1368
|
+
overlay: false,
|
|
1369
|
+
transpose: false,
|
|
1370
|
+
tonemap: false,
|
|
1371
|
+
deinterlace: false,
|
|
1372
|
+
denoise: false,
|
|
1373
|
+
flip: false,
|
|
1374
|
+
blur: false,
|
|
1375
|
+
sharpen: false,
|
|
1376
|
+
sobel: false,
|
|
1377
|
+
chromakey: false,
|
|
1378
|
+
colorspace: false,
|
|
1379
|
+
pad: false,
|
|
1380
|
+
stack: false,
|
|
1381
|
+
};
|
|
1382
|
+
// These hardware types don't have dedicated filters - they're mainly for decode/encode
|
|
1383
|
+
case AV_HWDEVICE_TYPE_VDPAU: // Decode-only, deprecated in favor of VAAPI
|
|
1384
|
+
case AV_HWDEVICE_TYPE_DXVA2: // Windows decode-only
|
|
1385
|
+
case AV_HWDEVICE_TYPE_DRM: // Linux DRM buffer sharing, not processing
|
|
1386
|
+
return {
|
|
1387
|
+
scale: false,
|
|
1388
|
+
overlay: false,
|
|
1389
|
+
transpose: false,
|
|
1390
|
+
tonemap: false,
|
|
1391
|
+
deinterlace: false,
|
|
1392
|
+
denoise: false,
|
|
1393
|
+
flip: false,
|
|
1394
|
+
blur: false,
|
|
1395
|
+
sharpen: false,
|
|
1396
|
+
sobel: false,
|
|
1397
|
+
chromakey: false,
|
|
1398
|
+
colorspace: false,
|
|
1399
|
+
pad: false,
|
|
1400
|
+
stack: false,
|
|
1401
|
+
};
|
|
1402
|
+
default:
|
|
1403
|
+
// Unknown hardware - no filter support
|
|
1404
|
+
return {
|
|
1405
|
+
scale: false,
|
|
1406
|
+
overlay: false,
|
|
1407
|
+
transpose: false,
|
|
1408
|
+
tonemap: false,
|
|
1409
|
+
deinterlace: false,
|
|
1410
|
+
denoise: false,
|
|
1411
|
+
flip: false,
|
|
1412
|
+
blur: false,
|
|
1413
|
+
sharpen: false,
|
|
1414
|
+
sobel: false,
|
|
1415
|
+
chromakey: false,
|
|
1416
|
+
colorspace: false,
|
|
1417
|
+
pad: false,
|
|
1418
|
+
stack: false,
|
|
1419
|
+
};
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
/**
|
|
1424
|
+
* Type-safe builder for `filter_complex` graph strings.
|
|
1425
|
+
*
|
|
1426
|
+
* Composes one or more labeled filter chains - each with optional input/output
|
|
1427
|
+
* pad labels and a sequence of type-safe {@link FilterPreset.filter} calls - and
|
|
1428
|
+
* renders them to the description string consumed by
|
|
1429
|
+
* {@link FilterComplexAPI.create}. Provides the same autocomplete and enum
|
|
1430
|
+
* validation as {@link FilterPreset}, but for multi-input/output graphs.
|
|
1431
|
+
*
|
|
1432
|
+
* @example
|
|
1433
|
+
* ```typescript
|
|
1434
|
+
* const graph = FilterComplexGraph.create()
|
|
1435
|
+
* .chain({ inputs: ['0:v', '1:v'], outputs: 'tmp' }, (c) => c.filter('overlay', { x: 100, y: 50 }))
|
|
1436
|
+
* .chain({ inputs: 'tmp', outputs: 'out' }, (c) => c.filter('hue', { s: 0 }))
|
|
1437
|
+
* .build();
|
|
1438
|
+
* // "[0:v][1:v]overlay=x=100:y=50[tmp];[tmp]hue=s=0[out]"
|
|
1439
|
+
*
|
|
1440
|
+
* using complex = FilterComplexAPI.create(graph, {
|
|
1441
|
+
* inputs: [{ label: '0:v' }, { label: '1:v' }],
|
|
1442
|
+
* outputs: [{ label: 'out' }],
|
|
1443
|
+
* });
|
|
1444
|
+
* ```
|
|
1445
|
+
*/
|
|
1446
|
+
export class FilterComplexGraph {
|
|
1447
|
+
chains = [];
|
|
1448
|
+
hardware;
|
|
1449
|
+
/**
|
|
1450
|
+
* Create a new filter-complex graph builder.
|
|
1451
|
+
*
|
|
1452
|
+
* @param hardware - Optional hardware context. Each {@link chain} then builds
|
|
1453
|
+
* with hardware-aware filter selection (e.g. `scale` → `scale_vaapi`,
|
|
1454
|
+
* `overlay` → `overlay_vaapi`), like {@link FilterPreset.chain}.
|
|
1455
|
+
*
|
|
1456
|
+
* @returns A new builder instance
|
|
1457
|
+
*
|
|
1458
|
+
* @example
|
|
1459
|
+
* ```typescript
|
|
1460
|
+
* const graph = FilterComplexGraph.create();
|
|
1461
|
+
* ```
|
|
1462
|
+
*
|
|
1463
|
+
* @example
|
|
1464
|
+
* ```typescript
|
|
1465
|
+
* // Hardware-aware graph (scale_vaapi / overlay_vaapi etc.)
|
|
1466
|
+
* const hw = HardwareContext.auto();
|
|
1467
|
+
* const graph = FilterComplexGraph.create(hw)
|
|
1468
|
+
* .chain({ inputs: '1:v', outputs: 'pip' }, (c) => c.scale(320, 240))
|
|
1469
|
+
* .chain({ inputs: ['0:v', 'pip'], outputs: 'out' }, (c) => c.overlay(10, 10));
|
|
1470
|
+
* ```
|
|
1471
|
+
*/
|
|
1472
|
+
static create(hardware) {
|
|
1473
|
+
const graph = new FilterComplexGraph();
|
|
1474
|
+
graph.hardware = hardware;
|
|
1475
|
+
return graph;
|
|
1476
|
+
}
|
|
1477
|
+
/**
|
|
1478
|
+
* Add a labeled filter chain to the graph.
|
|
1479
|
+
*
|
|
1480
|
+
* The chain's filters are built via a callback that receives a
|
|
1481
|
+
* {@link FilterPreset} for type-safe `filter()` composition.
|
|
1482
|
+
*
|
|
1483
|
+
* @param labels - Optional input and output pad labels
|
|
1484
|
+
*
|
|
1485
|
+
* @param labels.inputs - Input pad label(s) prepended to the chain
|
|
1486
|
+
*
|
|
1487
|
+
* @param labels.outputs - Output pad label(s) appended to the chain
|
|
1488
|
+
*
|
|
1489
|
+
* @param build - Callback composing the chain's filters
|
|
1490
|
+
*
|
|
1491
|
+
* @returns This instance for chaining
|
|
1492
|
+
*
|
|
1493
|
+
* @example
|
|
1494
|
+
* ```typescript
|
|
1495
|
+
* graph.chain({ inputs: ['0:v', '1:v'], outputs: 'out' }, (c) =>
|
|
1496
|
+
* c.filter('overlay', { x: 0, y: 0 }).filter('hue', { s: 0 }),
|
|
1497
|
+
* );
|
|
1498
|
+
* ```
|
|
1499
|
+
*/
|
|
1500
|
+
chain(labels, build) {
|
|
1501
|
+
const preset = FilterPreset.chain(this.hardware);
|
|
1502
|
+
const filterString = build(preset).build();
|
|
1503
|
+
this.chains.push(`${this.renderLabels(labels.inputs)}${filterString}${this.renderLabels(labels.outputs)}`);
|
|
1504
|
+
return this;
|
|
1505
|
+
}
|
|
1506
|
+
/**
|
|
1507
|
+
* Add a raw chain segment to the graph.
|
|
1508
|
+
*
|
|
1509
|
+
* Escape hatch for graph syntax not expressible through {@link chain}.
|
|
1510
|
+
*
|
|
1511
|
+
* @param segment - Raw chain string (e.g. `'[0:v]split[a][b]'`)
|
|
1512
|
+
*
|
|
1513
|
+
* @returns This instance for chaining
|
|
1514
|
+
*
|
|
1515
|
+
* @example
|
|
1516
|
+
* ```typescript
|
|
1517
|
+
* graph.custom('[0:v]split[a][b]');
|
|
1518
|
+
* ```
|
|
1519
|
+
*/
|
|
1520
|
+
custom(segment) {
|
|
1521
|
+
this.chains.push(segment);
|
|
1522
|
+
return this;
|
|
1523
|
+
}
|
|
1524
|
+
/**
|
|
1525
|
+
* Build the final filter-complex description string.
|
|
1526
|
+
*
|
|
1527
|
+
* @returns The graph description (chains joined by `;`)
|
|
1528
|
+
*
|
|
1529
|
+
* @example
|
|
1530
|
+
* ```typescript
|
|
1531
|
+
* const description = graph.build();
|
|
1532
|
+
* ```
|
|
1533
|
+
*/
|
|
1534
|
+
build() {
|
|
1535
|
+
return this.chains.join(';');
|
|
1536
|
+
}
|
|
1537
|
+
/**
|
|
1538
|
+
* Wrap pad labels in `[...]`.
|
|
1539
|
+
*
|
|
1540
|
+
* @param labels - A single label, an array of labels, or undefined
|
|
1541
|
+
*
|
|
1542
|
+
* @returns The bracketed label string (empty when no labels)
|
|
1543
|
+
*
|
|
1544
|
+
* @internal
|
|
1545
|
+
*/
|
|
1546
|
+
renderLabels(labels) {
|
|
1547
|
+
if (!labels) {
|
|
1548
|
+
return '';
|
|
1549
|
+
}
|
|
1550
|
+
const list = Array.isArray(labels) ? labels : [labels];
|
|
1551
|
+
return list.map((label) => `[${label}]`).join('');
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
//# sourceMappingURL=filter-presets.js.map
|