node-av 5.2.4 → 6.0.0-beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/README.md +15 -1
  2. package/dist/api/bitstream-filter.d.ts +110 -87
  3. package/dist/api/bitstream-filter.js +161 -103
  4. package/dist/api/bitstream-filter.js.map +1 -1
  5. package/dist/api/decoder.d.ts +177 -15
  6. package/dist/api/decoder.js +335 -28
  7. package/dist/api/decoder.js.map +1 -1
  8. package/dist/api/demuxer.d.ts +30 -24
  9. package/dist/api/demuxer.js +4 -5
  10. package/dist/api/demuxer.js.map +1 -1
  11. package/dist/api/device.js.map +1 -1
  12. package/dist/api/encoder-pool.d.ts +220 -0
  13. package/dist/api/encoder-pool.js +285 -0
  14. package/dist/api/encoder-pool.js.map +1 -0
  15. package/dist/api/encoder.d.ts +194 -7
  16. package/dist/api/encoder.js +431 -71
  17. package/dist/api/encoder.js.map +1 -1
  18. package/dist/api/filter-complex.d.ts +2 -1
  19. package/dist/api/filter-complex.js +11 -7
  20. package/dist/api/filter-complex.js.map +1 -1
  21. package/dist/api/filter-presets.d.ts +130 -654
  22. package/dist/api/filter-presets.js +180 -858
  23. package/dist/api/filter-presets.js.map +1 -1
  24. package/dist/api/filter.js +12 -9
  25. package/dist/api/filter.js.map +1 -1
  26. package/dist/api/fmp4-stream.js +1 -1
  27. package/dist/api/fmp4-stream.js.map +1 -1
  28. package/dist/api/index.d.ts +6 -6
  29. package/dist/api/index.js +8 -8
  30. package/dist/api/index.js.map +1 -1
  31. package/dist/api/muxer.d.ts +43 -15
  32. package/dist/api/muxer.js +79 -27
  33. package/dist/api/muxer.js.map +1 -1
  34. package/dist/api/pipeline.d.ts +50 -0
  35. package/dist/api/pipeline.js +138 -22
  36. package/dist/api/pipeline.js.map +1 -1
  37. package/dist/api/probe.d.ts +128 -0
  38. package/dist/api/probe.js +227 -0
  39. package/dist/api/probe.js.map +1 -0
  40. package/dist/api/rtp-stream.d.ts +14 -11
  41. package/dist/api/rtp-stream.js +23 -48
  42. package/dist/api/rtp-stream.js.map +1 -1
  43. package/dist/api/scaler.d.ts +431 -0
  44. package/dist/api/scaler.js +620 -0
  45. package/dist/api/scaler.js.map +1 -0
  46. package/dist/api/utilities/async-queue.d.ts +27 -1
  47. package/dist/api/utilities/async-queue.js +38 -3
  48. package/dist/api/utilities/async-queue.js.map +1 -1
  49. package/dist/api/utilities/codec-format.d.ts +87 -0
  50. package/dist/api/utilities/codec-format.js +117 -0
  51. package/dist/api/utilities/codec-format.js.map +1 -0
  52. package/dist/api/utilities/electron-shared-texture.d.ts +41 -1
  53. package/dist/api/utilities/electron-shared-texture.js +41 -4
  54. package/dist/api/utilities/electron-shared-texture.js.map +1 -1
  55. package/dist/api/utilities/index.d.ts +2 -1
  56. package/dist/api/utilities/index.js +2 -0
  57. package/dist/api/utilities/index.js.map +1 -1
  58. package/dist/api/webrtc-stream.d.ts +0 -1
  59. package/dist/api/webrtc-stream.js +0 -1
  60. package/dist/api/webrtc-stream.js.map +1 -1
  61. package/dist/constants/bsf-options.d.ts +333 -0
  62. package/dist/constants/bsf-options.js +7 -0
  63. package/dist/constants/bsf-options.js.map +1 -0
  64. package/dist/constants/constants.d.ts +109 -0
  65. package/dist/constants/constants.js +110 -0
  66. package/dist/constants/constants.js.map +1 -1
  67. package/dist/constants/decoders.d.ts +636 -618
  68. package/dist/constants/decoders.js +1 -3
  69. package/dist/constants/decoders.js.map +1 -1
  70. package/dist/constants/encoders.d.ts +300 -282
  71. package/dist/constants/encoders.js +0 -2
  72. package/dist/constants/encoders.js.map +1 -1
  73. package/dist/constants/filter-options.d.ts +10915 -0
  74. package/dist/constants/filter-options.js +7 -0
  75. package/dist/constants/filter-options.js.map +1 -0
  76. package/dist/constants/format-options.d.ts +3056 -0
  77. package/dist/constants/format-options.js +7 -0
  78. package/dist/constants/format-options.js.map +1 -0
  79. package/dist/constants/formats.d.ts +18 -0
  80. package/dist/constants/formats.js +7 -0
  81. package/dist/constants/formats.js.map +1 -0
  82. package/dist/constants/index.d.ts +5 -0
  83. package/dist/constants/options.d.ts +4073 -0
  84. package/dist/constants/options.js +7 -0
  85. package/dist/constants/options.js.map +1 -0
  86. package/dist/lib/binding.d.ts +5 -1
  87. package/dist/lib/binding.js.map +1 -1
  88. package/dist/lib/codec.d.ts +36 -5
  89. package/dist/lib/codec.js +37 -4
  90. package/dist/lib/codec.js.map +1 -1
  91. package/dist/lib/dictionary.d.ts +1 -1
  92. package/dist/lib/dictionary.js.map +1 -1
  93. package/dist/lib/error.d.ts +69 -0
  94. package/dist/lib/error.js +92 -0
  95. package/dist/lib/error.js.map +1 -1
  96. package/dist/lib/frame.d.ts +55 -3
  97. package/dist/lib/frame.js +59 -3
  98. package/dist/lib/frame.js.map +1 -1
  99. package/dist/lib/index.d.ts +1 -1
  100. package/dist/lib/index.js.map +1 -1
  101. package/dist/lib/native-types.d.ts +68 -0
  102. package/dist/lib/packet.d.ts +22 -3
  103. package/dist/lib/packet.js +24 -3
  104. package/dist/lib/packet.js.map +1 -1
  105. package/dist/lib/utilities.d.ts +45 -0
  106. package/dist/lib/utilities.js +49 -0
  107. package/dist/lib/utilities.js.map +1 -1
  108. package/dist/webrtc/index.d.ts +3 -0
  109. package/dist/webrtc/index.js +7 -0
  110. package/dist/webrtc/index.js.map +1 -0
  111. package/package.json +34 -23
@@ -1,6 +1,15 @@
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
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
+ };
4
13
  /**
5
14
  * Filter preset builder for composing filter chains.
6
15
  * Supports both software and hardware-accelerated filters.
@@ -12,8 +21,8 @@ import { avGetPixFmtName, avGetSampleFmtName } from '../lib/utilities.js';
12
21
  * // Software filter chain
13
22
  * const filter = FilterPreset.chain()
14
23
  * .scale(1920, 1080)
15
- * .fps(30)
16
- * .fade('in', 0, 2)
24
+ * .filter('fps', { fps: 30 })
25
+ * .filter('fade', { type: 'in', start_time: 0, duration: 2 })
17
26
  * .build();
18
27
  *
19
28
  * // Hardware-accelerated filter chain
@@ -65,7 +74,7 @@ export class FilterPreset {
65
74
  * // Software filter chain
66
75
  * const filter = FilterPreset.chain()
67
76
  * .scale(1280, 720)
68
- * .fps(30)
77
+ * .filter('fps', { fps: 30 })
69
78
  * .build();
70
79
  * ```
71
80
  *
@@ -101,6 +110,39 @@ export class FilterPreset {
101
110
  }
102
111
  return this.add(filter);
103
112
  }
113
+ /**
114
+ * Adds any FFmpeg filter to the chain with type-safe options.
115
+ *
116
+ * Provides autocomplete for every available filter name and its options
117
+ * (generated from FFmpeg's AVOption metadata). Prefer the dedicated methods
118
+ * (e.g. {@link scale}) when hardware-aware selection or automatic mapping is
119
+ * needed; use {@link custom} for raw filter strings.
120
+ *
121
+ * @param name - Filter name (autocompleted from all available filters)
122
+ *
123
+ * @param options - Filter-specific options, typed to the chosen filter
124
+ *
125
+ * @returns This instance for chaining
126
+ *
127
+ * @example
128
+ * ```typescript
129
+ * chain
130
+ * .filter('drawtext', { text: 'hello', fontsize: 24 })
131
+ * .filter('hue', { s: 0 });
132
+ * // "drawtext=text=hello:fontsize=24,hue=s=0"
133
+ * ```
134
+ */
135
+ filter(name, options) {
136
+ if (!options) {
137
+ return this.add(name);
138
+ }
139
+ const entries = Object.entries(options);
140
+ const args = entries
141
+ .filter((e) => e[1] != null)
142
+ .map(([k, v]) => `${k}=${escapeFilterValue(v)}`)
143
+ .join(':');
144
+ return this.add(args ? `${name}=${args}` : name);
145
+ }
104
146
  /**
105
147
  * Builds the final filter string.
106
148
  *
@@ -470,28 +512,6 @@ export class FilterPreset {
470
512
  }
471
513
  return this;
472
514
  }
473
- /**
474
- * Adds an FPS filter to change frame rate.
475
- *
476
- * @param fps - Target frames per second
477
- *
478
- * @returns This instance for chaining
479
- *
480
- * @example
481
- * ```typescript
482
- * chain.fps(30) // Convert to 30 FPS
483
- * chain.fps(23.976) // Film frame rate
484
- * ```
485
- *
486
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#fps | FFmpeg fps filter}
487
- */
488
- fps(fps) {
489
- if (fps <= 0) {
490
- return this;
491
- }
492
- this.add(`fps=fps=${fps}`);
493
- return this;
494
- }
495
515
  /**
496
516
  * Adds a format filter to convert pixel format.
497
517
  *
@@ -528,25 +548,6 @@ export class FilterPreset {
528
548
  }
529
549
  return this;
530
550
  }
531
- /**
532
- * Adds a rotate filter to the chain.
533
- *
534
- * @param angle - Rotation angle in degrees
535
- *
536
- * @returns This instance for chaining
537
- *
538
- * @example
539
- * ```typescript
540
- * chain.rotate(90) // Rotate 90 degrees clockwise
541
- * chain.rotate(-45) // Rotate 45 degrees counter-clockwise
542
- * ```
543
- *
544
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#rotate | FFmpeg rotate filter}
545
- */
546
- rotate(angle) {
547
- this.add(`rotate=${angle}*PI/180`);
548
- return this;
549
- }
550
551
  /**
551
552
  * Adds a flip filter to the chain (hardware-specific).
552
553
  * Falls back to hflip/vflip if hardware flip not available
@@ -704,29 +705,6 @@ export class FilterPreset {
704
705
  }
705
706
  return this;
706
707
  }
707
- /**
708
- * Creates a fade filter string for video.
709
- *
710
- * @param type - Fade type ('in' or 'out')
711
- *
712
- * @param start - Start time in seconds
713
- *
714
- * @param duration - Fade duration in seconds
715
- *
716
- * @returns Filter string or null if not supported
717
- *
718
- * @example
719
- * ```typescript
720
- * presets.fade('in', 0, 2) // 2-second fade in from start
721
- * presets.fade('out', 10, 1) // 1-second fade out at 10 seconds
722
- * ```
723
- *
724
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#fade | FFmpeg fade filter}
725
- */
726
- fade(type, start, duration) {
727
- this.add(`fade=t=${type}:st=${start}:d=${duration}`);
728
- return this;
729
- }
730
708
  /**
731
709
  * Creates an overlay filter string to composite two video streams.
732
710
  *
@@ -775,25 +753,6 @@ export class FilterPreset {
775
753
  }
776
754
  return this;
777
755
  }
778
- /**
779
- * Creates a volume filter string for audio.
780
- *
781
- * @param factor - Volume multiplication factor (1.0 = unchanged, 2.0 = double)
782
- *
783
- * @returns Filter string or null if not supported
784
- *
785
- * @example
786
- * ```typescript
787
- * presets.volume(0.5) // Reduce volume by 50%
788
- * presets.volume(1.5) // Increase volume by 50%
789
- * ```
790
- *
791
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#volume | FFmpeg volume filter}
792
- */
793
- volume(factor) {
794
- this.add(`volume=${factor}`);
795
- return this;
796
- }
797
756
  /**
798
757
  * Creates an audio format filter string.
799
758
  *
@@ -833,130 +792,6 @@ export class FilterPreset {
833
792
  this.add(filter);
834
793
  return this;
835
794
  }
836
- /**
837
- * Adds an asetnsamples filter to set the number of samples per frame.
838
- * This is crucial for encoders like Opus that require specific frame sizes.
839
- *
840
- * @param samples - Number of samples per frame
841
- *
842
- * @param padding - Whether to pad or drop samples (default: true)
843
- *
844
- * @returns This instance for chaining
845
- *
846
- * @example
847
- * ```typescript
848
- * // For Opus encoder (requires 960 samples)
849
- * chain.asetnsamples(960);
850
- *
851
- * // Drop samples instead of padding
852
- * chain.asetnsamples(1024, false);
853
- * ```
854
- *
855
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#asetnsamples | FFmpeg asetnsamples filter}
856
- */
857
- asetnsamples(samples, padding = true) {
858
- const p = padding ? 1 : 0;
859
- this.add(`asetnsamples=n=${samples}:p=${p}`);
860
- return this;
861
- }
862
- /**
863
- * Adds an aresample filter to change audio sample rate, format, and channel layout.
864
- *
865
- * Uses libswresample for high-quality audio resampling and format conversion.
866
- * Can also perform timestamp compensation (stretch/squeeze/fill/trim).
867
- *
868
- * @param rate - Target sample rate in Hz
869
- *
870
- * @param format - Optional target sample format (e.g., 's16', 'flt', 'fltp')
871
- *
872
- * @param channelLayout - Optional target channel layout (e.g., 'mono', 'stereo')
873
- *
874
- * @returns This instance for chaining
875
- *
876
- * @example
877
- * ```typescript
878
- * chain.aresample(44100) // Convert to 44.1 kHz only
879
- * chain.aresample(48000, 's16', 'stereo') // Full conversion
880
- * ```
881
- *
882
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#aresample | FFmpeg aresample filter}
883
- */
884
- aresample(rate, format, channelLayout) {
885
- const params = [`${rate}`];
886
- if (format !== undefined) {
887
- const formatStr = typeof format === 'number' ? `${format}` : format;
888
- params.push(`osf=${formatStr}`);
889
- }
890
- if (channelLayout !== undefined) {
891
- params.push(`ochl=${channelLayout}`);
892
- }
893
- this.add(`aresample=${params.join(':')}`);
894
- return this;
895
- }
896
- /**
897
- * Adds an atempo filter to change audio playback speed.
898
- * Factor must be between 0.5 and 2.0. For larger changes, chain multiple atempo filters.
899
- *
900
- * @param factor - Tempo factor (0.5 = half speed, 2.0 = double speed)
901
- *
902
- * @returns This instance for chaining
903
- *
904
- * @example
905
- * ```typescript
906
- * chain.atempo(1.5) // 1.5x speed
907
- * chain.atempo(0.8) // Slow down to 80% speed
908
- * ```
909
- *
910
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#atempo | FFmpeg atempo filter}
911
- */
912
- atempo(factor) {
913
- this.add(`atempo=${factor}`);
914
- return this;
915
- }
916
- /**
917
- * Adds an audio fade filter.
918
- *
919
- * @param type - Fade type ('in' or 'out')
920
- *
921
- * @param start - Start time in seconds
922
- *
923
- * @param duration - Fade duration in seconds
924
- *
925
- * @returns This instance for chaining
926
- *
927
- * @example
928
- * ```typescript
929
- * chain.afade('in', 0, 3) // 3-second audio fade in
930
- * chain.afade('out', 20, 2) // 2-second fade out at 20s
931
- * ```
932
- *
933
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#afade | FFmpeg afade filter}
934
- */
935
- afade(type, start, duration) {
936
- this.add(`afade=t=${type}:st=${start}:d=${duration}`);
937
- return this;
938
- }
939
- /**
940
- * Adds an amix filter to mix multiple audio streams.
941
- *
942
- * @param inputs - Number of input streams to mix (default: 2)
943
- *
944
- * @param duration - How to determine output duration (default: 'longest')
945
- *
946
- * @returns This instance for chaining
947
- *
948
- * @example
949
- * ```typescript
950
- * chain.amix(3, 'longest') // Mix 3 audio streams
951
- * chain.amix(2, 'first') // Mix 2 streams, use first's duration
952
- * ```
953
- *
954
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#amix | FFmpeg amix filter}
955
- */
956
- amix(inputs = 2, duration = 'longest') {
957
- this.add(`amix=inputs=${inputs}:duration=${duration}`);
958
- return this;
959
- }
960
795
  /**
961
796
  * Adds a pad filter to add padding to video.
962
797
  * Essential for aspect ratio adjustments and letterboxing.
@@ -1006,79 +841,6 @@ export class FilterPreset {
1006
841
  }
1007
842
  return this;
1008
843
  }
1009
- /**
1010
- * Adds a trim filter to cut a portion of the stream.
1011
- * Crucial for cutting segments from media.
1012
- *
1013
- * @param start - Start time in seconds
1014
- *
1015
- * @param end - End time in seconds (optional)
1016
- *
1017
- * @param duration - Duration in seconds (optional, alternative to end)
1018
- *
1019
- * @returns This instance for chaining
1020
- *
1021
- * @example
1022
- * ```typescript
1023
- * chain.trim(10, 30) // Extract from 10s to 30s
1024
- * chain.trim(5, undefined, 10) // Extract 10s starting at 5s
1025
- * ```
1026
- *
1027
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#trim | FFmpeg trim filter}
1028
- */
1029
- trim(start, end, duration) {
1030
- let filter = `trim=start=${start}`;
1031
- if (end !== undefined)
1032
- filter += `:end=${end}`;
1033
- if (duration !== undefined)
1034
- filter += `:duration=${duration}`;
1035
- this.add(filter);
1036
- return this;
1037
- }
1038
- /**
1039
- * Creates a setpts filter string to change presentation timestamps.
1040
- * Essential for speed changes and timestamp manipulation.
1041
- *
1042
- * @param expression - PTS expression (e.g., 'PTS*2' for half speed, 'PTS/2' for double speed)
1043
- *
1044
- * @returns Filter string or null if not supported
1045
- *
1046
- * @example
1047
- * ```typescript
1048
- * // Double speed
1049
- * presets.setpts('PTS/2');
1050
- *
1051
- * // Half speed
1052
- * presets.setpts('PTS*2');
1053
- *
1054
- * // Reset timestamps
1055
- * presets.setpts('PTS-STARTPTS');
1056
- * ```
1057
- *
1058
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#setpts | FFmpeg setpts filter}
1059
- */
1060
- setpts(expression) {
1061
- this.add(`setpts=${expression}`);
1062
- return this;
1063
- }
1064
- /**
1065
- * Creates an asetpts filter string for audio timestamp manipulation.
1066
- *
1067
- * @param expression - PTS expression
1068
- *
1069
- * @returns Filter string or null if not supported
1070
- *
1071
- * @example
1072
- * ```typescript
1073
- * presets.asetpts('PTS-STARTPTS') // Reset timestamps to start from 0
1074
- * ```
1075
- *
1076
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#asetpts | FFmpeg asetpts filter}
1077
- */
1078
- asetpts(expression) {
1079
- this.add(`asetpts=${expression}`);
1080
- return this;
1081
- }
1082
844
  /**
1083
845
  * Creates a transpose filter string for rotation/flipping.
1084
846
  * More efficient than rotate for 90-degree rotations.
@@ -1145,74 +907,6 @@ export class FilterPreset {
1145
907
  }
1146
908
  return this;
1147
909
  }
1148
- /**
1149
- * Creates a setsar filter string to set sample aspect ratio.
1150
- * Important for correcting aspect ratio issues.
1151
- *
1152
- * @param ratio - Aspect ratio (e.g., '1:1', '16:9', or number)
1153
- *
1154
- * @returns Filter string or null if not supported
1155
- *
1156
- * @example
1157
- * ```typescript
1158
- * presets.setsar('1:1') // Square pixels
1159
- * presets.setsar(1.333) // 4:3 aspect ratio
1160
- * ```
1161
- *
1162
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#setsar | FFmpeg setsar/setdar filter}
1163
- */
1164
- setsar(ratio) {
1165
- this.add(`setsar=${ratio}`);
1166
- return this;
1167
- }
1168
- /**
1169
- * Creates a setdar filter string to set display aspect ratio.
1170
- *
1171
- * @param ratio - Aspect ratio (e.g., '16:9', '4:3')
1172
- *
1173
- * @returns Filter string or null if not supported
1174
- *
1175
- * @example
1176
- * ```typescript
1177
- * presets.setdar('16:9') // Widescreen
1178
- * presets.setdar('4:3') // Traditional TV aspect
1179
- * ```
1180
- *
1181
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#setsar | FFmpeg setsar/setdar filter}
1182
- */
1183
- setdar(ratio) {
1184
- this.add(`setdar=${ratio}`);
1185
- return this;
1186
- }
1187
- /**
1188
- * Adds an apad filter to add audio padding.
1189
- * Useful for ensuring minimum audio duration.
1190
- *
1191
- * @param wholeDuration - Minimum duration in seconds (optional)
1192
- *
1193
- * @param padDuration - Amount of padding to add in seconds (optional)
1194
- *
1195
- * @returns This instance for chaining
1196
- *
1197
- * @example
1198
- * ```typescript
1199
- * chain.apad(30) // Ensure at least 30 seconds total
1200
- * chain.apad(undefined, 5) // Add 5 seconds of padding
1201
- * ```
1202
- *
1203
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#apad | FFmpeg apad filter}
1204
- */
1205
- apad(wholeDuration, padDuration) {
1206
- if (!wholeDuration && !padDuration)
1207
- return this;
1208
- let filter = 'apad';
1209
- if (wholeDuration)
1210
- filter += `=whole_dur=${wholeDuration}`;
1211
- if (padDuration)
1212
- filter += wholeDuration ? `:pad_dur=${padDuration}` : `=pad_dur=${padDuration}`;
1213
- this.add(filter);
1214
- return this;
1215
- }
1216
910
  /**
1217
911
  * Creates a deinterlace filter string.
1218
912
  * Essential for processing interlaced content.
@@ -1283,503 +977,41 @@ export class FilterPreset {
1283
977
  return this;
1284
978
  }
1285
979
  /**
1286
- * Creates a select filter string to select specific frames.
1287
- * Powerful for extracting keyframes, specific frame types, etc.
980
+ * Adds a whisper filter for audio transcription using whisper.cpp.
981
+ * Transcribes audio and adds metadata to frames (lavfi.whisper.text, lavfi.whisper.duration).
1288
982
  *
1289
- * @param expression - Selection expression
983
+ * @param options - Whisper transcription options
1290
984
  *
1291
- * @returns Filter string or null if not supported
985
+ * @param options.model - Path to whisper.cpp model file
1292
986
  *
1293
- * @example
1294
- * ```typescript
1295
- * presets.select('eq(pict_type,I)') // Select only keyframes
1296
- * presets.select('not(mod(n,10))') // Select every 10th frame
1297
- * ```
987
+ * @param options.language - Language for transcription (default: 'auto')
1298
988
  *
1299
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#select | FFmpeg select filter}
1300
- */
1301
- select(expression) {
1302
- this.add(`select='${expression}'`);
1303
- return this;
1304
- }
1305
- /**
1306
- * Creates an aselect filter string for audio selection.
989
+ * @param options.queue - Audio queue size in seconds (default: 3)
1307
990
  *
1308
- * @param expression - Selection expression
991
+ * @param options.useGpu - Use GPU for processing (default: true)
1309
992
  *
1310
- * @returns Filter string or null if not supported
993
+ * @param options.gpuDevice - GPU device to use (default: 0)
1311
994
  *
1312
- * @example
1313
- * ```typescript
1314
- * presets.aselect('between(t,10,20)') // Select audio between 10-20 seconds
1315
- * ```
995
+ * @param options.destination - Output destination for transcripts
1316
996
  *
1317
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#aselect | FFmpeg aselect filter}
1318
- */
1319
- aselect(expression) {
1320
- this.add(`aselect='${expression}'`);
1321
- return this;
1322
- }
1323
- /**
1324
- * Creates a concat filter string to concatenate multiple inputs.
1325
- * Essential for joining multiple video/audio segments.
997
+ * @param options.format - Output format: text|srt|json (default: 'text')
1326
998
  *
1327
- * @param n - Number of input segments
999
+ * @param options.vadModel - Path to VAD model file (optional)
1328
1000
  *
1329
- * @param v - Number of output video streams (0 or 1)
1001
+ * @param options.vadThreshold - VAD threshold 0.0-1.0 (default: 0.5)
1330
1002
  *
1331
- * @param a - Number of output audio streams (0 or 1)
1003
+ * @param options.vadMinSpeechDuration - Minimum speech duration for VAD in seconds (default: 0.1)
1332
1004
  *
1333
- * @returns Filter string or null if not supported
1005
+ * @param options.vadMinSilenceDuration - Minimum silence duration for VAD in seconds (default: 0.5)
1006
+ *
1007
+ * @returns This instance for chaining
1334
1008
  *
1335
1009
  * @example
1336
1010
  * ```typescript
1337
- * presets.concat(3, 1, 1) // Join 3 segments with video and audio
1338
- * presets.concat(2, 1, 0) // Join 2 video-only segments
1339
- * ```
1340
- *
1341
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#concat | FFmpeg concat filter}
1342
- */
1343
- concat(n, v = 1, a = 1) {
1344
- this.add(`concat=n=${n}:v=${v}:a=${a}`);
1345
- return this;
1346
- }
1347
- /**
1348
- * Creates an amerge filter string to merge multiple audio streams into one.
1349
- * Different from amix - this creates multi-channel output.
1350
- *
1351
- * @param inputs - Number of input streams
1352
- *
1353
- * @returns Filter string or null if not supported
1354
- *
1355
- * @example
1356
- * ```typescript
1357
- * presets.amerge(2) // Merge 2 mono streams to stereo
1358
- * presets.amerge(6) // Merge 6 channels for 5.1 surround
1359
- * ```
1360
- *
1361
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#amerge | FFmpeg amerge filter}
1362
- */
1363
- amerge(inputs = 2) {
1364
- this.add(`amerge=inputs=${inputs}`);
1365
- return this;
1366
- }
1367
- /**
1368
- * Creates a channelmap filter string to remap audio channels.
1369
- * Critical for audio channel manipulation.
1370
- *
1371
- * @param map - Channel mapping (e.g., '0-0|1-1' or 'FL-FR|FR-FL' to swap stereo)
1372
- *
1373
- * @returns Filter string or null if not supported
1374
- *
1375
- * @example
1376
- * ```typescript
1377
- * presets.channelmap('FL-FR|FR-FL') // Swap left and right channels
1378
- * presets.channelmap('0-0|0-1') // Duplicate mono to stereo
1379
- * ```
1380
- *
1381
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#channelmap | FFmpeg channelmap filter}
1382
- */
1383
- channelmap(map) {
1384
- this.add(`channelmap=${map}`);
1385
- return this;
1386
- }
1387
- /**
1388
- * Creates a channelsplit filter string to split audio channels.
1389
- *
1390
- * @param channelLayout - Channel layout to split (optional)
1391
- *
1392
- * @returns Filter string or null if not supported
1393
- *
1394
- * @example
1395
- * ```typescript
1396
- * presets.channelsplit('stereo') // Split stereo to 2 mono
1397
- * presets.channelsplit('5.1') // Split 5.1 to individual channels
1398
- * ```
1399
- *
1400
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#channelsplit | FFmpeg channelsplit filter}
1401
- */
1402
- channelsplit(channelLayout) {
1403
- this.add(channelLayout ? `channelsplit=channel_layout=${channelLayout}` : 'channelsplit');
1404
- return this;
1405
- }
1406
- /**
1407
- * Creates a loudnorm filter string for loudness normalization.
1408
- * Essential for broadcast compliance and consistent audio levels.
1409
- *
1410
- * @param I - Integrated loudness target (default: -24 LUFS)
1411
- *
1412
- * @param TP - True peak (default: -2 dBTP)
1413
- *
1414
- * @param LRA - Loudness range (default: 7 LU)
1415
- *
1416
- * @returns Filter string or null if not supported
1417
- *
1418
- * @example
1419
- * ```typescript
1420
- * presets.loudnorm(-23, -1, 7) // EBU R128 broadcast standard
1421
- * presets.loudnorm(-16, -1.5, 11) // Streaming platforms standard
1422
- * ```
1423
- *
1424
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#loudnorm | FFmpeg loudnorm filter}
1425
- */
1426
- loudnorm(I = -24, TP = -2, LRA = 7) {
1427
- this.add(`loudnorm=I=${I}:TP=${TP}:LRA=${LRA}`);
1428
- return this;
1429
- }
1430
- /**
1431
- * Creates a compand filter string for audio compression/expansion.
1432
- * Important for dynamic range control.
1433
- *
1434
- * @param attacks - Attack times
1435
- *
1436
- * @param decays - Decay times
1437
- *
1438
- * @param points - Transfer function points
1439
- *
1440
- * @param gain - Output gain
1441
- *
1442
- * @returns Filter string or null if not supported
1443
- *
1444
- * @example
1445
- * ```typescript
1446
- * presets.compand('0.3|0.3', '1|1', '-90/-60|-60/-40|-40/-30|-20/-20', 6)
1447
- * ```
1448
- *
1449
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#compand | FFmpeg compand filter}
1450
- */
1451
- compand(attacks, decays, points, gain) {
1452
- let filter = `compand=attacks=${attacks}:decays=${decays}:points=${points}`;
1453
- if (gain !== undefined)
1454
- filter += `:gain=${gain}`;
1455
- this.add(filter);
1456
- return this;
1457
- }
1458
- /**
1459
- * Adds a drawtext filter to overlay text on video.
1460
- *
1461
- * @param text - Text to display
1462
- *
1463
- * @param options - Text rendering options
1464
- *
1465
- * @returns This instance for chaining
1466
- *
1467
- * @example
1468
- * ```typescript
1469
- * chain.drawtext('Hello World', { x: 10, y: 10, fontsize: 24 })
1470
- * chain.drawtext('Timestamp', {
1471
- * x: 10,
1472
- * y: 10,
1473
- * fontsize: 24,
1474
- * fontcolor: 'white',
1475
- * fontfile: '/path/to/font.ttf'
1476
- * })
1477
- * ```
1478
- *
1479
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#drawtext | FFmpeg drawtext filter}
1480
- */
1481
- drawtext(text, options) {
1482
- let filter = `drawtext=text='${text.replace(/'/g, "\\'").replace(/"/g, '\\"')}'`;
1483
- for (const [key, value] of Object.entries(options)) {
1484
- if (key === 'fontfile' && typeof value === 'string') {
1485
- filter += `:${key}='${value}'`;
1486
- }
1487
- else {
1488
- filter += `:${key}=${value}`;
1489
- }
1490
- }
1491
- this.add(filter);
1492
- return this;
1493
- }
1494
- /**
1495
- * Adds a split filter to duplicate a video stream.
1496
- *
1497
- * @param outputs - Number of output streams (default: 2)
1498
- *
1499
- * @returns This instance for chaining
1500
- *
1501
- * @example
1502
- * ```typescript
1503
- * chain.split() // Split into 2 outputs
1504
- * chain.split(3) // Split into 3 outputs
1505
- * ```
1506
- *
1507
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#split | FFmpeg split filter}
1508
- */
1509
- split(outputs = 2) {
1510
- this.add(`split=${outputs}`);
1511
- return this;
1512
- }
1513
- /**
1514
- * Adds an asplit filter to duplicate an audio stream.
1515
- *
1516
- * @param outputs - Number of output streams (default: 2)
1517
- *
1518
- * @returns This instance for chaining
1519
- *
1520
- * @example
1521
- * ```typescript
1522
- * chain.asplit() // Split into 2 outputs
1523
- * chain.asplit(3) // Split into 3 outputs
1524
- * ```
1525
- *
1526
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#asplit | FFmpeg asplit filter}
1527
- */
1528
- asplit(outputs = 2) {
1529
- this.add(`asplit=${outputs}`);
1530
- return this;
1531
- }
1532
- /**
1533
- * Adds an adelay filter to delay audio by specified milliseconds.
1534
- *
1535
- * @param delays - Delay in milliseconds (single value or array for multiple channels)
1536
- *
1537
- * @returns This instance for chaining
1538
- *
1539
- * @example
1540
- * ```typescript
1541
- * chain.adelay(100) // Delay all channels by 100ms
1542
- * chain.adelay([100, 200]) // Delay first channel by 100ms, second by 200ms
1543
- * ```
1544
- *
1545
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#adelay | FFmpeg adelay filter}
1546
- */
1547
- adelay(delays) {
1548
- const delayStr = Array.isArray(delays) ? delays.join('|') : delays.toString();
1549
- this.add(`adelay=${delayStr}`);
1550
- return this;
1551
- }
1552
- /**
1553
- * Adds an aecho filter for audio echo effect.
1554
- *
1555
- * @param in_gain - Input gain (0-1)
1556
- *
1557
- * @param out_gain - Output gain (0-1)
1558
- *
1559
- * @param delays - Delay in milliseconds
1560
- *
1561
- * @param decays - Decay factor (0-1)
1562
- *
1563
- * @returns This instance for chaining
1564
- *
1565
- * @example
1566
- * ```typescript
1567
- * chain.aecho(0.8, 0.9, 1000, 0.3) // Echo with 1 second delay
1568
- * ```
1569
- *
1570
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#aecho | FFmpeg aecho filter}
1571
- */
1572
- aecho(in_gain, out_gain, delays, decays) {
1573
- this.add(`aecho=${in_gain}:${out_gain}:${delays}:${decays}`);
1574
- return this;
1575
- }
1576
- /**
1577
- * Adds a highpass filter to remove low frequencies.
1578
- *
1579
- * @param frequency - Cutoff frequency in Hz
1580
- *
1581
- * @param options - Additional filter options
1582
- *
1583
- * @returns This instance for chaining
1584
- *
1585
- * @example
1586
- * ```typescript
1587
- * chain.highpass(200) // Remove frequencies below 200Hz
1588
- * chain.highpass(200, { width_type: 'q', width: 1 })
1589
- * ```
1590
- *
1591
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#highpass | FFmpeg highpass filter}
1592
- */
1593
- highpass(frequency, options) {
1594
- let filter = `highpass=f=${frequency}`;
1595
- if (options) {
1596
- for (const [key, value] of Object.entries(options)) {
1597
- filter += `:${key}=${value}`;
1598
- }
1599
- }
1600
- this.add(filter);
1601
- return this;
1602
- }
1603
- /**
1604
- * Adds a lowpass filter to remove high frequencies.
1605
- *
1606
- * @param frequency - Cutoff frequency in Hz
1607
- *
1608
- * @param options - Additional filter options
1609
- *
1610
- * @returns This instance for chaining
1611
- *
1612
- * @example
1613
- * ```typescript
1614
- * chain.lowpass(5000) // Remove frequencies above 5000Hz
1615
- * ```
1616
- *
1617
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#lowpass | FFmpeg lowpass filter}
1618
- */
1619
- lowpass(frequency, options) {
1620
- let filter = `lowpass=f=${frequency}`;
1621
- if (options) {
1622
- for (const [key, value] of Object.entries(options)) {
1623
- filter += `:${key}=${value}`;
1624
- }
1625
- }
1626
- this.add(filter);
1627
- return this;
1628
- }
1629
- /**
1630
- * Adds a bandpass filter to keep only a frequency band.
1631
- *
1632
- * @param frequency - Center frequency in Hz
1633
- *
1634
- * @param options - Additional filter options
1635
- *
1636
- * @returns This instance for chaining
1637
- *
1638
- * @example
1639
- * ```typescript
1640
- * chain.bandpass(1000) // Keep frequencies around 1000Hz
1641
- * ```
1642
- *
1643
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#bandpass | FFmpeg bandpass filter}
1644
- */
1645
- bandpass(frequency, options) {
1646
- let filter = `bandpass=f=${frequency}`;
1647
- if (options) {
1648
- for (const [key, value] of Object.entries(options)) {
1649
- filter += `:${key}=${value}`;
1650
- }
1651
- }
1652
- this.add(filter);
1653
- return this;
1654
- }
1655
- /**
1656
- * Adds an equalizer filter for frequency band adjustment.
1657
- *
1658
- * @param frequency - Center frequency in Hz
1659
- *
1660
- * @param width - Band width
1661
- *
1662
- * @param gain - Gain in dB
1663
- *
1664
- * @param width_type - Width type (optional)
1665
- *
1666
- * @returns This instance for chaining
1667
- *
1668
- * @example
1669
- * ```typescript
1670
- * chain.equalizer(1000, 2, 5) // Boost 1000Hz by 5dB
1671
- * chain.equalizer(1000, 2, 5, 'q') // Use Q factor for width
1672
- * ```
1673
- *
1674
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#equalizer | FFmpeg equalizer filter}
1675
- */
1676
- equalizer(frequency, width, gain, width_type) {
1677
- let filter = `equalizer=f=${frequency}`;
1678
- if (width_type) {
1679
- filter += `:width_type=${width_type}`;
1680
- }
1681
- filter += `:width=${width}:gain=${gain}`;
1682
- this.add(filter);
1683
- return this;
1684
- }
1685
- /**
1686
- * Adds a compressor filter for dynamic range compression.
1687
- *
1688
- * @param options - Compressor parameters
1689
- *
1690
- * @returns This instance for chaining
1691
- *
1692
- * @example
1693
- * ```typescript
1694
- * chain.compressor() // Default compression
1695
- * chain.compressor({
1696
- * threshold: 0.5,
1697
- * ratio: 4,
1698
- * attack: 5,
1699
- * release: 50
1700
- * })
1701
- * ```
1702
- *
1703
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#acompressor | FFmpeg acompressor filter}
1704
- */
1705
- compressor(options) {
1706
- if (!options || Object.keys(options).length === 0) {
1707
- this.add('acompressor');
1708
- }
1709
- else {
1710
- let filter = 'acompressor';
1711
- const params = [];
1712
- for (const [key, value] of Object.entries(options)) {
1713
- params.push(`${key}=${value}`);
1714
- }
1715
- filter += '=' + params.join(':');
1716
- this.add(filter);
1717
- }
1718
- return this;
1719
- }
1720
- /**
1721
- * Adds an atrim filter to trim audio.
1722
- *
1723
- * @param start - Start time in seconds
1724
- *
1725
- * @param end - End time in seconds (optional)
1726
- *
1727
- * @param duration - Duration in seconds (optional)
1728
- *
1729
- * @returns This instance for chaining
1730
- *
1731
- * @example
1732
- * ```typescript
1733
- * chain.atrim(10, 20) // Extract audio from 10s to 20s
1734
- * ```
1735
- *
1736
- * @see {@link https://ffmpeg.org/ffmpeg-filters.html#atrim | FFmpeg atrim filter}
1737
- */
1738
- atrim(start, end, duration) {
1739
- let filter = `atrim=start=${start}`;
1740
- if (end !== undefined)
1741
- filter += `:end=${end}`;
1742
- if (duration !== undefined)
1743
- filter += `:duration=${duration}`;
1744
- this.add(filter);
1745
- return this;
1746
- }
1747
- /**
1748
- * Adds a whisper filter for audio transcription using whisper.cpp.
1749
- * Transcribes audio and adds metadata to frames (lavfi.whisper.text, lavfi.whisper.duration).
1750
- *
1751
- * @param options - Whisper transcription options
1752
- *
1753
- * @param options.model - Path to whisper.cpp model file
1754
- *
1755
- * @param options.language - Language for transcription (default: 'auto')
1756
- *
1757
- * @param options.queue - Audio queue size in seconds (default: 3)
1758
- *
1759
- * @param options.useGpu - Use GPU for processing (default: true)
1760
- *
1761
- * @param options.gpuDevice - GPU device to use (default: 0)
1762
- *
1763
- * @param options.destination - Output destination for transcripts
1764
- *
1765
- * @param options.format - Output format: text|srt|json (default: 'text')
1766
- *
1767
- * @param options.vadModel - Path to VAD model file (optional)
1768
- *
1769
- * @param options.vadThreshold - VAD threshold 0.0-1.0 (default: 0.5)
1770
- *
1771
- * @param options.vadMinSpeechDuration - Minimum speech duration for VAD in seconds (default: 0.1)
1772
- *
1773
- * @param options.vadMinSilenceDuration - Minimum silence duration for VAD in seconds (default: 0.5)
1774
- *
1775
- * @returns This instance for chaining
1776
- *
1777
- * @example
1778
- * ```typescript
1779
- * // Basic transcription
1780
- * chain.whisper({
1781
- * model: '/path/to/ggml-base.bin'
1782
- * });
1011
+ * // Basic transcription
1012
+ * chain.whisper({
1013
+ * model: '/path/to/ggml-base.bin'
1014
+ * });
1783
1015
  *
1784
1016
  * // With language and output
1785
1017
  * chain.whisper({
@@ -1887,31 +1119,6 @@ export class FilterPreset {
1887
1119
  this.add('hwdownload');
1888
1120
  return this;
1889
1121
  }
1890
- /**
1891
- * Adds a hwmap filter to map frames between hardware devices.
1892
- *
1893
- * @param derive - Device to derive from (optional)
1894
- *
1895
- * @returns This instance for chaining
1896
- *
1897
- * @example
1898
- * ```typescript
1899
- * const chain = FilterPresets.chain()
1900
- * .hwmap('cuda')
1901
- * .build();
1902
- * ```
1903
- *
1904
- * @example
1905
- * ```typescript
1906
- * const chain = FilterPresets.chain()
1907
- * .hwmap()
1908
- * .build();
1909
- * ```
1910
- */
1911
- hwmap(derive) {
1912
- this.add(derive ? `hwmap=derive_device=${derive}` : 'hwmap');
1913
- return this;
1914
- }
1915
1122
  /**
1916
1123
  * Adds a filter to the chain.
1917
1124
  *
@@ -2149,4 +1356,119 @@ export class FilterPreset {
2149
1356
  }
2150
1357
  }
2151
1358
  }
1359
+ /**
1360
+ * Type-safe builder for `filter_complex` graph strings.
1361
+ *
1362
+ * Composes one or more labeled filter chains - each with optional input/output
1363
+ * pad labels and a sequence of type-safe {@link FilterPreset.filter} calls - and
1364
+ * renders them to the description string consumed by
1365
+ * {@link FilterComplexAPI.create}. Provides the same autocomplete and enum
1366
+ * validation as {@link FilterPreset}, but for multi-input/output graphs.
1367
+ *
1368
+ * @example
1369
+ * ```typescript
1370
+ * const graph = FilterComplexGraph.create()
1371
+ * .chain({ inputs: ['0:v', '1:v'], outputs: 'tmp' }, (c) => c.filter('overlay', { x: 100, y: 50 }))
1372
+ * .chain({ inputs: 'tmp', outputs: 'out' }, (c) => c.filter('hue', { s: 0 }))
1373
+ * .build();
1374
+ * // "[0:v][1:v]overlay=x=100:y=50[tmp];[tmp]hue=s=0[out]"
1375
+ *
1376
+ * using complex = FilterComplexAPI.create(graph, {
1377
+ * inputs: [{ label: '0:v' }, { label: '1:v' }],
1378
+ * outputs: [{ label: 'out' }],
1379
+ * });
1380
+ * ```
1381
+ */
1382
+ export class FilterComplexGraph {
1383
+ chains = [];
1384
+ /**
1385
+ * Create a new filter-complex graph builder.
1386
+ *
1387
+ * @returns A new builder instance
1388
+ *
1389
+ * @example
1390
+ * ```typescript
1391
+ * const graph = FilterComplexGraph.create();
1392
+ * ```
1393
+ */
1394
+ static create() {
1395
+ return new FilterComplexGraph();
1396
+ }
1397
+ /**
1398
+ * Add a labeled filter chain to the graph.
1399
+ *
1400
+ * The chain's filters are built via a callback that receives a
1401
+ * {@link FilterPreset} for type-safe `filter()` composition.
1402
+ *
1403
+ * @param labels - Optional input and output pad labels
1404
+ *
1405
+ * @param labels.inputs - Input pad label(s) prepended to the chain
1406
+ *
1407
+ * @param labels.outputs - Output pad label(s) appended to the chain
1408
+ *
1409
+ * @param build - Callback composing the chain's filters
1410
+ *
1411
+ * @returns This instance for chaining
1412
+ *
1413
+ * @example
1414
+ * ```typescript
1415
+ * graph.chain({ inputs: ['0:v', '1:v'], outputs: 'out' }, (c) =>
1416
+ * c.filter('overlay', { x: 0, y: 0 }).filter('hue', { s: 0 }),
1417
+ * );
1418
+ * ```
1419
+ */
1420
+ chain(labels, build) {
1421
+ const preset = FilterPreset.chain();
1422
+ const filterString = build(preset).build();
1423
+ this.chains.push(`${this.renderLabels(labels.inputs)}${filterString}${this.renderLabels(labels.outputs)}`);
1424
+ return this;
1425
+ }
1426
+ /**
1427
+ * Add a raw chain segment to the graph.
1428
+ *
1429
+ * Escape hatch for graph syntax not expressible through {@link chain}.
1430
+ *
1431
+ * @param segment - Raw chain string (e.g. `'[0:v]split[a][b]'`)
1432
+ *
1433
+ * @returns This instance for chaining
1434
+ *
1435
+ * @example
1436
+ * ```typescript
1437
+ * graph.custom('[0:v]split[a][b]');
1438
+ * ```
1439
+ */
1440
+ custom(segment) {
1441
+ this.chains.push(segment);
1442
+ return this;
1443
+ }
1444
+ /**
1445
+ * Build the final filter-complex description string.
1446
+ *
1447
+ * @returns The graph description (chains joined by `;`)
1448
+ *
1449
+ * @example
1450
+ * ```typescript
1451
+ * const description = graph.build();
1452
+ * ```
1453
+ */
1454
+ build() {
1455
+ return this.chains.join(';');
1456
+ }
1457
+ /**
1458
+ * Wrap pad labels in `[...]`.
1459
+ *
1460
+ * @param labels - A single label, an array of labels, or undefined
1461
+ *
1462
+ * @returns The bracketed label string (empty when no labels)
1463
+ *
1464
+ * @internal
1465
+ */
1466
+ renderLabels(labels) {
1467
+ if (!labels) {
1468
+ return '';
1469
+ }
1470
+ const list = Array.isArray(labels) ? labels : [labels];
1471
+ return list.map((label) => `[${label}]`).join('');
1472
+ }
1473
+ }
2152
1474
  //# sourceMappingURL=filter-presets.js.map