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.
Files changed (311) hide show
  1. package/LICENSE.md +22 -0
  2. package/README.md +711 -0
  3. package/dist/api/audio-frame-buffer.d.ts +636 -0
  4. package/dist/api/audio-frame-buffer.js +1027 -0
  5. package/dist/api/audio-frame-buffer.js.map +1 -0
  6. package/dist/api/bitstream-filter.d.ts +938 -0
  7. package/dist/api/bitstream-filter.js +1427 -0
  8. package/dist/api/bitstream-filter.js.map +1 -0
  9. package/dist/api/constants.d.ts +44 -0
  10. package/dist/api/constants.js +45 -0
  11. package/dist/api/constants.js.map +1 -0
  12. package/dist/api/data/test_av1.ivf +0 -0
  13. package/dist/api/data/test_h264.h264 +0 -0
  14. package/dist/api/data/test_hevc.h265 +0 -0
  15. package/dist/api/data/test_mjpeg.mjpeg +0 -0
  16. package/dist/api/data/test_vp8.ivf +0 -0
  17. package/dist/api/data/test_vp9.ivf +0 -0
  18. package/dist/api/decoder.d.ts +1413 -0
  19. package/dist/api/decoder.js +2367 -0
  20. package/dist/api/decoder.js.map +1 -0
  21. package/dist/api/demuxer.d.ts +1295 -0
  22. package/dist/api/demuxer.js +2344 -0
  23. package/dist/api/demuxer.js.map +1 -0
  24. package/dist/api/device.d.ts +589 -0
  25. package/dist/api/device.js +964 -0
  26. package/dist/api/device.js.map +1 -0
  27. package/dist/api/encoder-pool.d.ts +262 -0
  28. package/dist/api/encoder-pool.js +397 -0
  29. package/dist/api/encoder-pool.js.map +1 -0
  30. package/dist/api/encoder.d.ts +1509 -0
  31. package/dist/api/encoder.js +2619 -0
  32. package/dist/api/encoder.js.map +1 -0
  33. package/dist/api/filter-complex.d.ts +828 -0
  34. package/dist/api/filter-complex.js +1661 -0
  35. package/dist/api/filter-complex.js.map +1 -0
  36. package/dist/api/filter-presets.d.ts +800 -0
  37. package/dist/api/filter-presets.js +1554 -0
  38. package/dist/api/filter-presets.js.map +1 -0
  39. package/dist/api/filter.d.ts +1339 -0
  40. package/dist/api/filter.js +2075 -0
  41. package/dist/api/filter.js.map +1 -0
  42. package/dist/api/fmp4-stream.d.ts +797 -0
  43. package/dist/api/fmp4-stream.js +1428 -0
  44. package/dist/api/fmp4-stream.js.map +1 -0
  45. package/dist/api/hardware.d.ts +677 -0
  46. package/dist/api/hardware.js +1300 -0
  47. package/dist/api/hardware.js.map +1 -0
  48. package/dist/api/index.d.ts +21 -0
  49. package/dist/api/index.js +38 -0
  50. package/dist/api/index.js.map +1 -0
  51. package/dist/api/io-stream.d.ts +308 -0
  52. package/dist/api/io-stream.js +358 -0
  53. package/dist/api/io-stream.js.map +1 -0
  54. package/dist/api/muxer.d.ts +1311 -0
  55. package/dist/api/muxer.js +2672 -0
  56. package/dist/api/muxer.js.map +1 -0
  57. package/dist/api/pipeline.d.ts +801 -0
  58. package/dist/api/pipeline.js +1344 -0
  59. package/dist/api/pipeline.js.map +1 -0
  60. package/dist/api/probe.d.ts +128 -0
  61. package/dist/api/probe.js +227 -0
  62. package/dist/api/probe.js.map +1 -0
  63. package/dist/api/rtp-stream.d.ts +497 -0
  64. package/dist/api/rtp-stream.js +1131 -0
  65. package/dist/api/rtp-stream.js.map +1 -0
  66. package/dist/api/scaler.d.ts +561 -0
  67. package/dist/api/scaler.js +918 -0
  68. package/dist/api/scaler.js.map +1 -0
  69. package/dist/api/types.d.ts +17 -0
  70. package/dist/api/types.js +2 -0
  71. package/dist/api/types.js.map +1 -0
  72. package/dist/api/utilities/async-queue.d.ts +149 -0
  73. package/dist/api/utilities/async-queue.js +254 -0
  74. package/dist/api/utilities/async-queue.js.map +1 -0
  75. package/dist/api/utilities/audio-sample.d.ts +117 -0
  76. package/dist/api/utilities/audio-sample.js +112 -0
  77. package/dist/api/utilities/audio-sample.js.map +1 -0
  78. package/dist/api/utilities/audio-transcode.d.ts +20 -0
  79. package/dist/api/utilities/audio-transcode.js +27 -0
  80. package/dist/api/utilities/audio-transcode.js.map +1 -0
  81. package/dist/api/utilities/channel-layout.d.ts +76 -0
  82. package/dist/api/utilities/channel-layout.js +80 -0
  83. package/dist/api/utilities/channel-layout.js.map +1 -0
  84. package/dist/api/utilities/codec-format.d.ts +87 -0
  85. package/dist/api/utilities/codec-format.js +117 -0
  86. package/dist/api/utilities/codec-format.js.map +1 -0
  87. package/dist/api/utilities/context-options.d.ts +25 -0
  88. package/dist/api/utilities/context-options.js +23 -0
  89. package/dist/api/utilities/context-options.js.map +1 -0
  90. package/dist/api/utilities/electron-shared-texture.d.ts +368 -0
  91. package/dist/api/utilities/electron-shared-texture.js +540 -0
  92. package/dist/api/utilities/electron-shared-texture.js.map +1 -0
  93. package/dist/api/utilities/image.d.ts +207 -0
  94. package/dist/api/utilities/image.js +213 -0
  95. package/dist/api/utilities/image.js.map +1 -0
  96. package/dist/api/utilities/index.d.ts +14 -0
  97. package/dist/api/utilities/index.js +27 -0
  98. package/dist/api/utilities/index.js.map +1 -0
  99. package/dist/api/utilities/live-decoding.d.ts +15 -0
  100. package/dist/api/utilities/live-decoding.js +32 -0
  101. package/dist/api/utilities/live-decoding.js.map +1 -0
  102. package/dist/api/utilities/media-type.d.ts +49 -0
  103. package/dist/api/utilities/media-type.js +53 -0
  104. package/dist/api/utilities/media-type.js.map +1 -0
  105. package/dist/api/utilities/packet-age-watchdog.d.ts +198 -0
  106. package/dist/api/utilities/packet-age-watchdog.js +594 -0
  107. package/dist/api/utilities/packet-age-watchdog.js.map +1 -0
  108. package/dist/api/utilities/pixel-format.d.ts +89 -0
  109. package/dist/api/utilities/pixel-format.js +97 -0
  110. package/dist/api/utilities/pixel-format.js.map +1 -0
  111. package/dist/api/utilities/sample-format.d.ts +129 -0
  112. package/dist/api/utilities/sample-format.js +141 -0
  113. package/dist/api/utilities/sample-format.js.map +1 -0
  114. package/dist/api/utilities/scale-size.d.ts +51 -0
  115. package/dist/api/utilities/scale-size.js +55 -0
  116. package/dist/api/utilities/scale-size.js.map +1 -0
  117. package/dist/api/utilities/scheduler.d.ts +138 -0
  118. package/dist/api/utilities/scheduler.js +98 -0
  119. package/dist/api/utilities/scheduler.js.map +1 -0
  120. package/dist/api/utilities/streaming.d.ts +186 -0
  121. package/dist/api/utilities/streaming.js +309 -0
  122. package/dist/api/utilities/streaming.js.map +1 -0
  123. package/dist/api/utilities/timestamp.d.ts +193 -0
  124. package/dist/api/utilities/timestamp.js +206 -0
  125. package/dist/api/utilities/timestamp.js.map +1 -0
  126. package/dist/api/utilities/video-transcode.d.ts +218 -0
  127. package/dist/api/utilities/video-transcode.js +624 -0
  128. package/dist/api/utilities/video-transcode.js.map +1 -0
  129. package/dist/api/utilities/whisper-model.d.ts +330 -0
  130. package/dist/api/utilities/whisper-model.js +587 -0
  131. package/dist/api/utilities/whisper-model.js.map +1 -0
  132. package/dist/api/utils.d.ts +19 -0
  133. package/dist/api/utils.js +39 -0
  134. package/dist/api/utils.js.map +1 -0
  135. package/dist/api/webrtc-stream.d.ts +294 -0
  136. package/dist/api/webrtc-stream.js +546 -0
  137. package/dist/api/webrtc-stream.js.map +1 -0
  138. package/dist/api/whisper.d.ts +326 -0
  139. package/dist/api/whisper.js +402 -0
  140. package/dist/api/whisper.js.map +1 -0
  141. package/dist/constants/bsf-options.d.ts +364 -0
  142. package/dist/constants/bsf-options.js +7 -0
  143. package/dist/constants/bsf-options.js.map +1 -0
  144. package/dist/constants/channel-layouts.d.ts +53 -0
  145. package/dist/constants/channel-layouts.js +57 -0
  146. package/dist/constants/channel-layouts.js.map +1 -0
  147. package/dist/constants/constants.d.ts +2435 -0
  148. package/dist/constants/constants.js +1989 -0
  149. package/dist/constants/constants.js.map +1 -0
  150. package/dist/constants/decoders.d.ts +651 -0
  151. package/dist/constants/decoders.js +641 -0
  152. package/dist/constants/decoders.js.map +1 -0
  153. package/dist/constants/encoders.d.ts +314 -0
  154. package/dist/constants/encoders.js +309 -0
  155. package/dist/constants/encoders.js.map +1 -0
  156. package/dist/constants/filter-options.d.ts +12099 -0
  157. package/dist/constants/filter-options.js +7 -0
  158. package/dist/constants/filter-options.js.map +1 -0
  159. package/dist/constants/format-options.d.ts +4319 -0
  160. package/dist/constants/format-options.js +7 -0
  161. package/dist/constants/format-options.js.map +1 -0
  162. package/dist/constants/formats.d.ts +18 -0
  163. package/dist/constants/formats.js +7 -0
  164. package/dist/constants/formats.js.map +1 -0
  165. package/dist/constants/hardware.d.ts +26 -0
  166. package/dist/constants/hardware.js +27 -0
  167. package/dist/constants/hardware.js.map +1 -0
  168. package/dist/constants/index.d.ts +10 -0
  169. package/dist/constants/index.js +6 -0
  170. package/dist/constants/index.js.map +1 -0
  171. package/dist/constants/options.d.ts +6217 -0
  172. package/dist/constants/options.js +7 -0
  173. package/dist/constants/options.js.map +1 -0
  174. package/dist/ffmpeg/index.d.ts +101 -0
  175. package/dist/ffmpeg/index.js +161 -0
  176. package/dist/ffmpeg/index.js.map +1 -0
  177. package/dist/ffmpeg/version.d.ts +6 -0
  178. package/dist/ffmpeg/version.js +7 -0
  179. package/dist/ffmpeg/version.js.map +1 -0
  180. package/dist/index.d.ts +4 -0
  181. package/dist/index.js +9 -0
  182. package/dist/index.js.map +1 -0
  183. package/dist/lib/audio-fifo.d.ts +399 -0
  184. package/dist/lib/audio-fifo.js +431 -0
  185. package/dist/lib/audio-fifo.js.map +1 -0
  186. package/dist/lib/binding.d.ts +242 -0
  187. package/dist/lib/binding.js +102 -0
  188. package/dist/lib/binding.js.map +1 -0
  189. package/dist/lib/bitstream-filter-context.d.ts +381 -0
  190. package/dist/lib/bitstream-filter-context.js +443 -0
  191. package/dist/lib/bitstream-filter-context.js.map +1 -0
  192. package/dist/lib/bitstream-filter.d.ts +140 -0
  193. package/dist/lib/bitstream-filter.js +154 -0
  194. package/dist/lib/bitstream-filter.js.map +1 -0
  195. package/dist/lib/codec-context.d.ts +1069 -0
  196. package/dist/lib/codec-context.js +1336 -0
  197. package/dist/lib/codec-context.js.map +1 -0
  198. package/dist/lib/codec-parameters.d.ts +664 -0
  199. package/dist/lib/codec-parameters.js +813 -0
  200. package/dist/lib/codec-parameters.js.map +1 -0
  201. package/dist/lib/codec-parser.d.ts +201 -0
  202. package/dist/lib/codec-parser.js +213 -0
  203. package/dist/lib/codec-parser.js.map +1 -0
  204. package/dist/lib/codec.d.ts +617 -0
  205. package/dist/lib/codec.js +746 -0
  206. package/dist/lib/codec.js.map +1 -0
  207. package/dist/lib/device.d.ts +294 -0
  208. package/dist/lib/device.js +326 -0
  209. package/dist/lib/device.js.map +1 -0
  210. package/dist/lib/dictionary.d.ts +333 -0
  211. package/dist/lib/dictionary.js +372 -0
  212. package/dist/lib/dictionary.js.map +1 -0
  213. package/dist/lib/error.d.ts +311 -0
  214. package/dist/lib/error.js +395 -0
  215. package/dist/lib/error.js.map +1 -0
  216. package/dist/lib/fifo.d.ts +416 -0
  217. package/dist/lib/fifo.js +453 -0
  218. package/dist/lib/fifo.js.map +1 -0
  219. package/dist/lib/filter-context.d.ts +714 -0
  220. package/dist/lib/filter-context.js +783 -0
  221. package/dist/lib/filter-context.js.map +1 -0
  222. package/dist/lib/filter-graph-segment.d.ts +161 -0
  223. package/dist/lib/filter-graph-segment.js +172 -0
  224. package/dist/lib/filter-graph-segment.js.map +1 -0
  225. package/dist/lib/filter-graph.d.ts +641 -0
  226. package/dist/lib/filter-graph.js +704 -0
  227. package/dist/lib/filter-graph.js.map +1 -0
  228. package/dist/lib/filter-inout.d.ts +202 -0
  229. package/dist/lib/filter-inout.js +261 -0
  230. package/dist/lib/filter-inout.js.map +1 -0
  231. package/dist/lib/filter.d.ts +243 -0
  232. package/dist/lib/filter.js +272 -0
  233. package/dist/lib/filter.js.map +1 -0
  234. package/dist/lib/format-context.d.ts +1335 -0
  235. package/dist/lib/format-context.js +1455 -0
  236. package/dist/lib/format-context.js.map +1 -0
  237. package/dist/lib/frame-utils.d.ts +116 -0
  238. package/dist/lib/frame-utils.js +98 -0
  239. package/dist/lib/frame-utils.js.map +1 -0
  240. package/dist/lib/frame.d.ts +1315 -0
  241. package/dist/lib/frame.js +1535 -0
  242. package/dist/lib/frame.js.map +1 -0
  243. package/dist/lib/hardware-device-context.d.ts +362 -0
  244. package/dist/lib/hardware-device-context.js +383 -0
  245. package/dist/lib/hardware-device-context.js.map +1 -0
  246. package/dist/lib/hardware-frames-context.d.ts +418 -0
  247. package/dist/lib/hardware-frames-context.js +471 -0
  248. package/dist/lib/hardware-frames-context.js.map +1 -0
  249. package/dist/lib/index.d.ts +35 -0
  250. package/dist/lib/index.js +60 -0
  251. package/dist/lib/index.js.map +1 -0
  252. package/dist/lib/input-format.d.ts +249 -0
  253. package/dist/lib/input-format.js +306 -0
  254. package/dist/lib/input-format.js.map +1 -0
  255. package/dist/lib/io-context.d.ts +698 -0
  256. package/dist/lib/io-context.js +771 -0
  257. package/dist/lib/io-context.js.map +1 -0
  258. package/dist/lib/log.d.ts +174 -0
  259. package/dist/lib/log.js +184 -0
  260. package/dist/lib/log.js.map +1 -0
  261. package/dist/lib/native-types.d.ts +1024 -0
  262. package/dist/lib/native-types.js +2 -0
  263. package/dist/lib/native-types.js.map +1 -0
  264. package/dist/lib/option.d.ts +953 -0
  265. package/dist/lib/option.js +1607 -0
  266. package/dist/lib/option.js.map +1 -0
  267. package/dist/lib/output-format.d.ts +222 -0
  268. package/dist/lib/output-format.js +259 -0
  269. package/dist/lib/output-format.js.map +1 -0
  270. package/dist/lib/packet.d.ts +521 -0
  271. package/dist/lib/packet.js +613 -0
  272. package/dist/lib/packet.js.map +1 -0
  273. package/dist/lib/rational.d.ts +251 -0
  274. package/dist/lib/rational.js +278 -0
  275. package/dist/lib/rational.js.map +1 -0
  276. package/dist/lib/software-resample-context.d.ts +552 -0
  277. package/dist/lib/software-resample-context.js +592 -0
  278. package/dist/lib/software-resample-context.js.map +1 -0
  279. package/dist/lib/software-scale-context.d.ts +344 -0
  280. package/dist/lib/software-scale-context.js +366 -0
  281. package/dist/lib/software-scale-context.js.map +1 -0
  282. package/dist/lib/stream.d.ts +383 -0
  283. package/dist/lib/stream.js +524 -0
  284. package/dist/lib/stream.js.map +1 -0
  285. package/dist/lib/sync-queue.d.ts +179 -0
  286. package/dist/lib/sync-queue.js +197 -0
  287. package/dist/lib/sync-queue.js.map +1 -0
  288. package/dist/lib/types.d.ts +34 -0
  289. package/dist/lib/types.js +2 -0
  290. package/dist/lib/types.js.map +1 -0
  291. package/dist/lib/utilities.d.ts +1198 -0
  292. package/dist/lib/utilities.js +1302 -0
  293. package/dist/lib/utilities.js.map +1 -0
  294. package/dist/utils/electron.d.ts +49 -0
  295. package/dist/utils/electron.js +63 -0
  296. package/dist/utils/electron.js.map +1 -0
  297. package/dist/utils/index.d.ts +6 -0
  298. package/dist/utils/index.js +7 -0
  299. package/dist/utils/index.js.map +1 -0
  300. package/dist/utils/libc.d.ts +11 -0
  301. package/dist/utils/libc.js +35 -0
  302. package/dist/utils/libc.js.map +1 -0
  303. package/dist/utils/variant.d.ts +26 -0
  304. package/dist/utils/variant.js +31 -0
  305. package/dist/utils/variant.js.map +1 -0
  306. package/dist/webrtc/index.d.ts +4 -0
  307. package/dist/webrtc/index.js +7 -0
  308. package/dist/webrtc/index.js.map +1 -0
  309. package/install/check.js +124 -0
  310. package/install/ffmpeg.js +66 -0
  311. 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