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,2075 @@
1
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
2
+ if (value !== null && value !== void 0) {
3
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
4
+ var dispose, inner;
5
+ if (async) {
6
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
7
+ dispose = value[Symbol.asyncDispose];
8
+ }
9
+ if (dispose === void 0) {
10
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
11
+ dispose = value[Symbol.dispose];
12
+ if (async) inner = dispose;
13
+ }
14
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
15
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
16
+ env.stack.push({ value: value, dispose: dispose, async: async });
17
+ }
18
+ else if (async) {
19
+ env.stack.push({ async: true });
20
+ }
21
+ return value;
22
+ };
23
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
24
+ return function (env) {
25
+ function fail(e) {
26
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
27
+ env.hasError = true;
28
+ }
29
+ var r, s = 0;
30
+ function next() {
31
+ while (r = env.stack.pop()) {
32
+ try {
33
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
34
+ if (r.dispose) {
35
+ var result = r.dispose.call(r.value);
36
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
37
+ }
38
+ else s |= 1;
39
+ }
40
+ catch (e) {
41
+ fail(e);
42
+ }
43
+ }
44
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
45
+ if (env.hasError) throw env.error;
46
+ }
47
+ return next();
48
+ };
49
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
50
+ var e = new Error(message);
51
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
+ });
53
+ /* eslint-disable @stylistic/indent-binary-ops */
54
+ import { AV_BUFFERSRC_FLAG_KEEP_REF, AV_BUFFERSRC_FLAG_PUSH, AV_NOPTS_VALUE, AVERROR_EAGAIN, AVERROR_EOF, AVERROR_FILTER_NOT_FOUND, AVFILTER_FLAG_HWDEVICE, EOF, } from '../constants/constants.js';
55
+ import { FFmpegError } from '../lib/error.js';
56
+ import { FilterGraph } from '../lib/filter-graph.js';
57
+ import { FilterInOut } from '../lib/filter-inout.js';
58
+ import { Filter } from '../lib/filter.js';
59
+ import { Frame } from '../lib/frame.js';
60
+ import { Rational } from '../lib/rational.js';
61
+ import { avGetSampleFmtName, avInvQ, avRescaleQ } from '../lib/utilities.js';
62
+ import { FRAME_THREAD_QUEUE_SIZE } from './constants.js';
63
+ import { AsyncQueue } from './utilities/async-queue.js';
64
+ import { applyContextOptions } from './utilities/context-options.js';
65
+ import { Scheduler } from './utilities/scheduler.js';
66
+ /**
67
+ * High-level filter API for audio and video processing.
68
+ *
69
+ * Provides simplified interface for applying FFmpeg filters to frames.
70
+ * Handles filter graph construction, frame buffering, and command control.
71
+ * Supports both software and hardware-accelerated filtering operations.
72
+ * Essential component for effects, transformations, and format conversions.
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * import { FilterAPI } from 'node-av/api';
77
+ *
78
+ * // Create video filter - initializes on first frame
79
+ * const filter = FilterAPI.create('scale=1280:720', {
80
+ * timeBase: video.timeBase,
81
+ * });
82
+ *
83
+ * // Process frame - first frame configures filter graph
84
+ * const output = await filter.process(inputFrame);
85
+ * if (output) {
86
+ * console.log(`Filtered frame: ${output.width}x${output.height}`);
87
+ * output.free();
88
+ * }
89
+ * ```
90
+ *
91
+ * @example
92
+ * ```typescript
93
+ * // Hardware-accelerated filtering - hw context detected from frame
94
+ * const filter = FilterAPI.create('hwupload,scale_cuda=1920:1080,hwdownload', {
95
+ * timeBase: video.timeBase,
96
+ * });
97
+ * // Hardware frames context will be automatically detected from first frame
98
+ * ```
99
+ *
100
+ * @see {@link FilterGraph} For low-level filter graph API
101
+ * @see {@link Frame} For frame operations
102
+ */
103
+ export class FilterAPI {
104
+ graph;
105
+ description;
106
+ options;
107
+ buffersrcCtx = null;
108
+ buffersinkCtx = null;
109
+ frame = new Frame(); // Reusable frame for receive operations
110
+ initializePromise = null;
111
+ initialized = false;
112
+ isClosed = false;
113
+ // Auto-calculated timeBase from first frame
114
+ calculatedTimeBase = null;
115
+ // Track last frame properties for change detection (for dropOnChange/allowReinit)
116
+ lastFrameProps = null;
117
+ // Worker pattern for push-based processing
118
+ inputQueue;
119
+ outputQueue;
120
+ workerPromise = null;
121
+ nextComponent = null;
122
+ signal;
123
+ pipeToPromise = null;
124
+ /**
125
+ * @param graph - Filter graph instance
126
+ *
127
+ * @param description - Filter description string, or a function building it from a frame
128
+ *
129
+ * @param options - Filter options
130
+ *
131
+ * @internal
132
+ */
133
+ constructor(graph, description, options) {
134
+ this.graph = graph;
135
+ this.description = description;
136
+ this.options = options;
137
+ this.inputQueue = new AsyncQueue(FRAME_THREAD_QUEUE_SIZE, (f) => f.free());
138
+ this.outputQueue = new AsyncQueue(FRAME_THREAD_QUEUE_SIZE, (f) => f.free());
139
+ }
140
+ /**
141
+ * Create a filter with specified description and configuration.
142
+ *
143
+ * Direct mapping to avfilter_graph_parse_ptr() and avfilter_graph_config().
144
+ *
145
+ * @param description - Filter graph description, or a function that builds it from the
146
+ * first frame. The function runs again whenever the graph is rebuilt for a frame with
147
+ * other properties, so the graph can follow what the frames turn out to be, e.g.
148
+ * hardware frames or software frames after a decoder fell back. When its graph fails to
149
+ * configure, the function is asked once more with the error and can return another one,
150
+ * e.g. without a device filter the driver refused.
151
+ *
152
+ * @param options - Filter options
153
+ *
154
+ * @returns Configured filter instance
155
+ *
156
+ * @throws {Error} If cfr=true but framerate is not set
157
+ *
158
+ * @example
159
+ * ```typescript
160
+ * // Simple video filter (VFR mode, auto timeBase)
161
+ * const filter = FilterAPI.create('scale=640:480');
162
+ * ```
163
+ *
164
+ * @example
165
+ * ```typescript
166
+ * // CFR mode with constant framerate
167
+ * const filter = FilterAPI.create('scale=1920:1080', {
168
+ * cfr: true,
169
+ * framerate: { num: 25, den: 1 }
170
+ * });
171
+ * ```
172
+ *
173
+ * @example
174
+ * ```typescript
175
+ * // Audio filter with resampling
176
+ * const filter = FilterAPI.create('aformat=sample_fmts=s16:sample_rates=44100', {
177
+ * audioResampleOpts: 'async=1'
178
+ * });
179
+ * ```
180
+ *
181
+ * @example
182
+ * ```typescript
183
+ * // Graph chosen by the frames: download hardware frames, pass software ones
184
+ * const filter = FilterAPI.create((frame) => (frame.isHwFrame() ? 'hwdownload,format=nv12' : 'null'));
185
+ * ```
186
+ *
187
+ * @example
188
+ * ```typescript
189
+ * // Scale on the device, in software if the device filter fails
190
+ * const filter = FilterAPI.create((frame, failure) => (failure ? 'hwdownload,format=nv12,scale=640:-2' : 'scale_vaapi=640:-2'), { hardware });
191
+ * ```
192
+ *
193
+ * @see {@link process} For frame processing
194
+ * @see {@link FilterOptions} For configuration options
195
+ */
196
+ static create(description, options = {}) {
197
+ // Validate options: CFR requires framerate
198
+ if (options.cfr && !options.framerate) {
199
+ throw new Error('cfr=true requires framerate to be set');
200
+ }
201
+ // Create graph
202
+ const graph = new FilterGraph();
203
+ graph.alloc();
204
+ // Configure threading
205
+ if (options.threads !== undefined) {
206
+ graph.nbThreads = options.threads;
207
+ }
208
+ // Configure scaler options
209
+ if (options.scaleSwsOpts) {
210
+ graph.scaleSwsOpts = options.scaleSwsOpts;
211
+ }
212
+ const filter = new FilterAPI(graph, description, options);
213
+ if (options.signal) {
214
+ options.signal.throwIfAborted();
215
+ filter.signal = options.signal;
216
+ }
217
+ return filter;
218
+ }
219
+ /**
220
+ * Check if filter is open.
221
+ *
222
+ * @example
223
+ * ```typescript
224
+ * if (filter.isFilterOpen) {
225
+ * const output = await filter.process(frame);
226
+ * }
227
+ * ```
228
+ */
229
+ get isFilterOpen() {
230
+ return !this.isClosed;
231
+ }
232
+ /**
233
+ * Check if filter has been initialized.
234
+ *
235
+ * Returns true after first frame has been processed and filter graph configured.
236
+ * Useful for checking if filter has received frame properties.
237
+ *
238
+ * @returns true if filter graph has been built from first frame
239
+ *
240
+ * @example
241
+ * ```typescript
242
+ * if (!filter.isFilterInitialized) {
243
+ * console.log('Filter will initialize on first frame');
244
+ * }
245
+ * ```
246
+ */
247
+ get isFilterInitialized() {
248
+ return this.initialized;
249
+ }
250
+ /**
251
+ * Get buffersink filter context.
252
+ *
253
+ * Provides access to the buffersink filter context for advanced operations.
254
+ * Returns null if filter is not initialized.
255
+ *
256
+ * @returns Buffersink context or null
257
+ *
258
+ * @example
259
+ * ```typescript
260
+ * const sink = filter.buffersink;
261
+ * if (sink) {
262
+ * const fr = sink.buffersinkGetFrameRate();
263
+ * console.log(`Output frame rate: ${fr.num}/${fr.den}`);
264
+ * }
265
+ * ```
266
+ */
267
+ get buffersink() {
268
+ return this.buffersinkCtx;
269
+ }
270
+ /**
271
+ * Output frame rate from filter graph.
272
+ *
273
+ * Returns the frame rate determined by the filter graph output.
274
+ * Returns null if filter is not initialized or frame rate is not set.
275
+ *
276
+ * Direct mapping to av_buffersink_get_frame_rate().
277
+ *
278
+ * @returns Frame rate or null if not available
279
+ *
280
+ * @example
281
+ * ```typescript
282
+ * const frameRate = filter.frameRate;
283
+ * if (frameRate) {
284
+ * console.log(`Filter output: ${frameRate.num}/${frameRate.den} fps`);
285
+ * }
286
+ * ```
287
+ *
288
+ * @see {@link timeBase} For output timebase
289
+ */
290
+ get frameRate() {
291
+ if (!this.initialized || !this.buffersinkCtx) {
292
+ return null;
293
+ }
294
+ const fr = this.buffersinkCtx.buffersinkGetFrameRate();
295
+ // Return null if frame rate is not set (0/0 or 0/1)
296
+ if (fr.num <= 0 || fr.den <= 0) {
297
+ return null;
298
+ }
299
+ return fr;
300
+ }
301
+ /**
302
+ * Output time base from filter graph.
303
+ *
304
+ * Returns the time base of the buffersink output.
305
+ * Matches FFmpeg CLI's av_buffersink_get_time_base() behavior.
306
+ *
307
+ * Direct mapping to av_buffersink_get_time_base().
308
+ *
309
+ * @returns Time base or null if not initialized
310
+ *
311
+ * @example
312
+ * ```typescript
313
+ * const timeBase = filter.timeBase;
314
+ * if (timeBase) {
315
+ * console.log(`Filter timebase: ${timeBase.num}/${timeBase.den}`);
316
+ * }
317
+ * ```
318
+ *
319
+ * @see {@link frameRate} For output frame rate
320
+ */
321
+ get timeBase() {
322
+ if (!this.initialized || !this.buffersinkCtx) {
323
+ return null;
324
+ }
325
+ return this.buffersinkCtx.buffersinkGetTimeBase();
326
+ }
327
+ /**
328
+ * Output format from filter graph.
329
+ *
330
+ * Returns the pixel format (video) or sample format (audio) of the buffersink output.
331
+ * Matches FFmpeg CLI's av_buffersink_get_format() behavior.
332
+ *
333
+ * Direct mapping to av_buffersink_get_format().
334
+ *
335
+ * @returns Pixel format or sample format, or null if not initialized
336
+ *
337
+ * @example
338
+ * ```typescript
339
+ * const format = filter.format;
340
+ * if (format !== null) {
341
+ * console.log(`Filter output format: ${format}`);
342
+ * }
343
+ * ```
344
+ */
345
+ get format() {
346
+ if (!this.initialized || !this.buffersinkCtx) {
347
+ return null;
348
+ }
349
+ return this.buffersinkCtx.buffersinkGetFormat();
350
+ }
351
+ /**
352
+ * Output dimensions from filter graph (video only).
353
+ *
354
+ * Returns the width and height of the buffersink output.
355
+ * Matches FFmpeg CLI's av_buffersink_get_w() and av_buffersink_get_h() behavior.
356
+ * Only meaningful for video filters.
357
+ *
358
+ * Direct mapping to av_buffersink_get_w() and av_buffersink_get_h().
359
+ *
360
+ * @returns Dimensions object or null if not initialized
361
+ *
362
+ * @example
363
+ * ```typescript
364
+ * const dims = filter.dimensions;
365
+ * if (dims) {
366
+ * console.log(`Filter output: ${dims.width}x${dims.height}`);
367
+ * }
368
+ * ```
369
+ */
370
+ get dimensions() {
371
+ if (!this.initialized || !this.buffersinkCtx) {
372
+ return null;
373
+ }
374
+ return {
375
+ width: this.buffersinkCtx.buffersinkGetWidth(),
376
+ height: this.buffersinkCtx.buffersinkGetHeight(),
377
+ };
378
+ }
379
+ /**
380
+ * Output sample rate from filter graph (audio only).
381
+ *
382
+ * Returns the sample rate of the buffersink output.
383
+ * Matches FFmpeg CLI's av_buffersink_get_sample_rate() behavior.
384
+ * Only meaningful for audio filters.
385
+ *
386
+ * Direct mapping to av_buffersink_get_sample_rate().
387
+ *
388
+ * @returns Sample rate or null if not initialized
389
+ *
390
+ * @example
391
+ * ```typescript
392
+ * const sampleRate = filter.sampleRate;
393
+ * if (sampleRate) {
394
+ * console.log(`Filter output sample rate: ${sampleRate} Hz`);
395
+ * }
396
+ * ```
397
+ */
398
+ get sampleRate() {
399
+ if (!this.initialized || !this.buffersinkCtx) {
400
+ return null;
401
+ }
402
+ return this.buffersinkCtx.buffersinkGetSampleRate();
403
+ }
404
+ /**
405
+ * Output channel layout from filter graph (audio only).
406
+ *
407
+ * Returns the channel layout of the buffersink output.
408
+ * Matches FFmpeg CLI's av_buffersink_get_ch_layout() behavior.
409
+ * Only meaningful for audio filters.
410
+ *
411
+ * Direct mapping to av_buffersink_get_ch_layout().
412
+ *
413
+ * @returns Channel layout or null if not initialized
414
+ *
415
+ * @example
416
+ * ```typescript
417
+ * const layout = filter.channelLayout;
418
+ * if (layout) {
419
+ * console.log(`Filter output channels: ${layout.nbChannels}`);
420
+ * }
421
+ * ```
422
+ */
423
+ get channelLayout() {
424
+ if (!this.initialized || !this.buffersinkCtx) {
425
+ return null;
426
+ }
427
+ return this.buffersinkCtx.buffersinkGetChannelLayout();
428
+ }
429
+ /**
430
+ * Output color space from filter graph (video only).
431
+ *
432
+ * Returns the color space of the buffersink output.
433
+ * Matches FFmpeg CLI's av_buffersink_get_colorspace() behavior.
434
+ * Only meaningful for video filters.
435
+ *
436
+ * Direct mapping to av_buffersink_get_colorspace().
437
+ *
438
+ * @returns Color space or null if not initialized
439
+ *
440
+ * @example
441
+ * ```typescript
442
+ * const colorSpace = filter.colorSpace;
443
+ * if (colorSpace !== null) {
444
+ * console.log(`Filter output color space: ${colorSpace}`);
445
+ * }
446
+ * ```
447
+ */
448
+ get colorSpace() {
449
+ if (!this.initialized || !this.buffersinkCtx) {
450
+ return null;
451
+ }
452
+ return this.buffersinkCtx.buffersinkGetColorspace();
453
+ }
454
+ /**
455
+ * Output color range from filter graph (video only).
456
+ *
457
+ * Returns the color range of the buffersink output.
458
+ * Matches FFmpeg CLI's av_buffersink_get_color_range() behavior.
459
+ * Only meaningful for video filters.
460
+ *
461
+ * Direct mapping to av_buffersink_get_color_range().
462
+ *
463
+ * @returns Color range or null if not initialized
464
+ *
465
+ * @example
466
+ * ```typescript
467
+ * const colorRange = filter.colorRange;
468
+ * if (colorRange !== null) {
469
+ * console.log(`Filter output color range: ${colorRange}`);
470
+ * }
471
+ * ```
472
+ */
473
+ get colorRange() {
474
+ if (!this.initialized || !this.buffersinkCtx) {
475
+ return null;
476
+ }
477
+ return this.buffersinkCtx.buffersinkGetColorRange();
478
+ }
479
+ /**
480
+ * Output sample aspect ratio from filter graph (video only).
481
+ *
482
+ * Returns the sample aspect ratio of the buffersink output.
483
+ * Matches FFmpeg CLI's av_buffersink_get_sample_aspect_ratio() behavior.
484
+ * Only meaningful for video filters.
485
+ *
486
+ * Direct mapping to av_buffersink_get_sample_aspect_ratio().
487
+ *
488
+ * @returns Sample aspect ratio or null if not initialized
489
+ *
490
+ * @example
491
+ * ```typescript
492
+ * const sar = filter.sampleAspectRatio;
493
+ * if (sar) {
494
+ * console.log(`Filter output SAR: ${sar.num}:${sar.den}`);
495
+ * }
496
+ * ```
497
+ */
498
+ get sampleAspectRatio() {
499
+ if (!this.initialized || !this.buffersinkCtx) {
500
+ return null;
501
+ }
502
+ return this.buffersinkCtx.buffersinkGetSampleAspectRatio();
503
+ }
504
+ /**
505
+ * Check if filter is ready for processing.
506
+ *
507
+ * @returns true if initialized and ready
508
+ *
509
+ * @example
510
+ * ```typescript
511
+ * if (filter.isReady()) {
512
+ * const output = await filter.process(frame);
513
+ * }
514
+ * ```
515
+ */
516
+ isReady() {
517
+ return this.initialized && this.buffersrcCtx !== null && this.buffersinkCtx !== null && !this.isClosed;
518
+ }
519
+ /**
520
+ * Get filter graph description.
521
+ *
522
+ * Returns human-readable graph structure.
523
+ * Useful for debugging filter chains.
524
+ *
525
+ * Direct mapping to avfilter_graph_dump().
526
+ *
527
+ * @returns Graph description or null if closed
528
+ *
529
+ * @example
530
+ * ```typescript
531
+ * const description = filter.getGraphDescription();
532
+ * console.log('Filter graph:', description);
533
+ * ```
534
+ */
535
+ getGraphDescription() {
536
+ return !this.isClosed && this.initialized ? this.graph.dump() : null;
537
+ }
538
+ /**
539
+ * Send a frame to the filter.
540
+ *
541
+ * Sends a frame to the filter for processing.
542
+ * Does not return filtered frames - use {@link receive} to retrieve frames.
543
+ * On first frame, automatically builds filter graph with frame properties.
544
+ * A single input frame can produce zero, one, or multiple output frames.
545
+ *
546
+ * **Important**: This method only SENDS the frame to the filter.
547
+ * You must call {@link receive} separately (potentially multiple times) to get filtered frames.
548
+ *
549
+ * Direct mapping to av_buffersrc_add_frame().
550
+ *
551
+ * @param frame - Input frame to send to filter, or null to flush
552
+ *
553
+ * @throws {Error} If filter could not be initialized
554
+ *
555
+ * @throws {FFmpegError} If sending frame fails
556
+ *
557
+ * @example
558
+ * ```typescript
559
+ * // Send frame and receive filtered frames
560
+ * await filter.process(inputFrame);
561
+ *
562
+ * // Receive all available filtered frames
563
+ * while (true) {
564
+ * const output = await filter.receive();
565
+ * if (!output) break;
566
+ * console.log(`Got filtered frame: pts=${output.pts}`);
567
+ * output.free();
568
+ * }
569
+ * ```
570
+ *
571
+ * @example
572
+ * ```typescript
573
+ * for await (const frame of decoder.frames(input.packets())) {
574
+ * // Send frame
575
+ * await filter.process(frame);
576
+ *
577
+ * // Receive available filtered frames
578
+ * let output;
579
+ * while ((output = await filter.receive())) {
580
+ * await encoder.encode(output);
581
+ * output.free();
582
+ * }
583
+ * frame.free();
584
+ * }
585
+ * ```
586
+ *
587
+ * @see {@link receive} For receiving filtered frames
588
+ * @see {@link processAll} For combined send+receive operation
589
+ * @see {@link frames} For processing frame streams
590
+ * @see {@link flush} For end-of-stream handling
591
+ * @see {@link processSync} For synchronous version
592
+ */
593
+ async process(frame) {
594
+ this.signal?.throwIfAborted();
595
+ if (this.isClosed) {
596
+ return;
597
+ }
598
+ // Null frame = flush filter
599
+ if (frame === null) {
600
+ await this.flush();
601
+ return;
602
+ }
603
+ // Open filter if not already done
604
+ this.initializePromise ??= this.initialize(frame);
605
+ await this.initializePromise;
606
+ if (!this.buffersrcCtx || !this.buffersinkCtx) {
607
+ throw new Error('Could not initialize filter contexts');
608
+ }
609
+ // Check for frame property changes (FFmpeg: dropOnChange/allowReinit logic)
610
+ if (!this.checkFramePropertiesChanged(frame)) {
611
+ // Frame dropped due to property change
612
+ return;
613
+ }
614
+ // If reinitialized, reinitialize now
615
+ if (!this.initialized) {
616
+ this.initializePromise = this.initialize(frame);
617
+ await this.initializePromise;
618
+ }
619
+ if (!this.buffersrcCtx || !this.buffersinkCtx) {
620
+ throw new Error('Could not reinitialize filter contexts');
621
+ }
622
+ // Rescale timestamps to filter's timeBase
623
+ // AV_NOPTS_VALUE must pass through untouched: av_rescale_q(INT64_MIN) yields garbage
624
+ if (this.calculatedTimeBase) {
625
+ const originalTimeBase = frame.timeBase;
626
+ if (frame.pts !== AV_NOPTS_VALUE) {
627
+ frame.pts = avRescaleQ(frame.pts, originalTimeBase, this.calculatedTimeBase);
628
+ }
629
+ if (frame.duration !== AV_NOPTS_VALUE) {
630
+ frame.duration = avRescaleQ(frame.duration, originalTimeBase, this.calculatedTimeBase);
631
+ }
632
+ frame.timeBase = new Rational(this.calculatedTimeBase.num, this.calculatedTimeBase.den);
633
+ }
634
+ // Send frame to filter with PUSH flag for immediate processing
635
+ // KEEP_REF preserves the input frame's hw_frames_ctx for reuse across multiple filters
636
+ const addRet = await this.buffersrcCtx.buffersrcAddFrame(frame, (AV_BUFFERSRC_FLAG_PUSH | AV_BUFFERSRC_FLAG_KEEP_REF));
637
+ FFmpegError.throwIfError(addRet, 'Failed to add frame to filter');
638
+ }
639
+ /**
640
+ * Send a frame to the filter synchronously.
641
+ * Synchronous version of process.
642
+ *
643
+ * Sends a frame to the filter for processing.
644
+ * Does not return filtered frames - use {@link receiveSync} to retrieve frames.
645
+ * On first frame, automatically builds filter graph with frame properties.
646
+ * A single input frame can produce zero, one, or multiple output frames.
647
+ *
648
+ * **Important**: This method only SENDS the frame to the filter.
649
+ * You must call {@link receiveSync} separately (potentially multiple times) to get filtered frames.
650
+ *
651
+ * Direct mapping to av_buffersrc_add_frame().
652
+ *
653
+ * @param frame - Input frame to send to filter, or null to flush
654
+ *
655
+ * @throws {Error} If filter could not be initialized
656
+ *
657
+ * @throws {FFmpegError} If sending frame fails
658
+ *
659
+ * @example
660
+ * ```typescript
661
+ * // Send frame and receive filtered frames
662
+ * filter.processSync(inputFrame);
663
+ *
664
+ * // Receive all available filtered frames
665
+ * let output;
666
+ * while ((output = filter.receiveSync())) {
667
+ * console.log(`Got filtered frame: pts=${output.pts}`);
668
+ * output.free();
669
+ * }
670
+ * ```
671
+ *
672
+ * @see {@link receiveSync} For receiving filtered frames
673
+ * @see {@link processAllSync} For combined send+receive operation
674
+ * @see {@link framesSync} For processing frame streams
675
+ * @see {@link flushSync} For end-of-stream handling
676
+ * @see {@link process} For async version
677
+ */
678
+ processSync(frame) {
679
+ if (this.isClosed) {
680
+ return;
681
+ }
682
+ // Null frame = flush filter
683
+ if (frame === null) {
684
+ this.flushSync();
685
+ return;
686
+ }
687
+ // Open filter if not already done
688
+ if (!this.initialized) {
689
+ this.initializeSync(frame);
690
+ }
691
+ if (!this.buffersrcCtx || !this.buffersinkCtx) {
692
+ throw new Error('Could not initialize filter contexts');
693
+ }
694
+ // Check for frame property changes (FFmpeg: dropOnChange/allowReinit logic)
695
+ if (!this.checkFramePropertiesChanged(frame)) {
696
+ // Frame dropped due to property change
697
+ return;
698
+ }
699
+ // If reinitialized, reinitialize now
700
+ if (!this.initialized) {
701
+ this.initializeSync(frame);
702
+ }
703
+ if (!this.buffersrcCtx || !this.buffersinkCtx) {
704
+ throw new Error('Could not reinitialize filter contexts');
705
+ }
706
+ // Rescale timestamps to filter's timeBase (NOPTS passes through, see process())
707
+ if (this.calculatedTimeBase) {
708
+ const originalTimeBase = frame.timeBase;
709
+ if (frame.pts !== AV_NOPTS_VALUE) {
710
+ frame.pts = avRescaleQ(frame.pts, originalTimeBase, this.calculatedTimeBase);
711
+ }
712
+ if (frame.duration !== AV_NOPTS_VALUE) {
713
+ frame.duration = avRescaleQ(frame.duration, originalTimeBase, this.calculatedTimeBase);
714
+ }
715
+ frame.timeBase = new Rational(this.calculatedTimeBase.num, this.calculatedTimeBase.den);
716
+ }
717
+ // Send frame to filter with PUSH flag for immediate processing
718
+ // KEEP_REF preserves the input frame's hw_frames_ctx for reuse across multiple filters
719
+ const addRet = this.buffersrcCtx.buffersrcAddFrameSync(frame, (AV_BUFFERSRC_FLAG_PUSH | AV_BUFFERSRC_FLAG_KEEP_REF));
720
+ FFmpegError.throwIfError(addRet, 'Failed to add frame to filter');
721
+ }
722
+ /**
723
+ * Process a frame through the filter.
724
+ *
725
+ * Applies filter operations to input frame and receives all available output frames.
726
+ * Returns array of frames - may be empty if filter needs more input.
727
+ * On first frame, automatically builds filter graph with frame properties.
728
+ * One input frame can produce zero, one, or multiple output frames depending on filter.
729
+ * Hardware frames context is automatically detected from frame.
730
+ *
731
+ * Direct mapping to av_buffersrc_add_frame() and av_buffersink_get_frame().
732
+ *
733
+ * @param frame - Input frame to process
734
+ *
735
+ * @returns Array of filtered frames (empty if buffered or filter closed)
736
+ *
737
+ * @throws {Error} If filter could not be initialized
738
+ *
739
+ * @throws {FFmpegError} If processing fails
740
+ *
741
+ * @example
742
+ * ```typescript
743
+ * const frames = await filter.processAll(inputFrame);
744
+ * for (const output of frames) {
745
+ * console.log(`Got filtered frame: pts=${output.pts}`);
746
+ * output.free();
747
+ * }
748
+ * ```
749
+ *
750
+ * @example
751
+ * ```typescript
752
+ * // Process frame - may return multiple frames (e.g. fps filter)
753
+ * const frames = await filter.processAll(frame);
754
+ * for (const output of frames) {
755
+ * yield output;
756
+ * }
757
+ * ```
758
+ *
759
+ * @see {@link process} For single frame processing
760
+ * @see {@link frames} For processing frame streams
761
+ * @see {@link flush} For end-of-stream handling
762
+ * @see {@link processAllSync} For synchronous version
763
+ */
764
+ async processAll(frame) {
765
+ this.signal?.throwIfAborted();
766
+ await this.process(frame);
767
+ // Receive all available frames
768
+ const frames = [];
769
+ while (true) {
770
+ const outputFrame = await this.receive();
771
+ if (!outputFrame)
772
+ break; // Stop on EAGAIN or EOF
773
+ frames.push(outputFrame); // Only push actual frames
774
+ }
775
+ return frames;
776
+ }
777
+ /**
778
+ * Process a frame through the filter synchronously.
779
+ * Synchronous version of processAll.
780
+ *
781
+ * Applies filter operations to input frame and receives all available output frames.
782
+ * Returns array of frames - may be empty if filter needs more input.
783
+ * On first frame, automatically builds filter graph with frame properties.
784
+ * One input frame can produce zero, one, or multiple output frames depending on filter.
785
+ * Hardware frames context is automatically detected from frame.
786
+ *
787
+ * Direct mapping to av_buffersrc_add_frame() and av_buffersink_get_frame().
788
+ *
789
+ * @param frame - Input frame to process
790
+ *
791
+ * @returns Array of filtered frames (empty if buffered or filter closed)
792
+ *
793
+ * @throws {Error} If filter could not be initialized
794
+ *
795
+ * @throws {FFmpegError} If processing fails
796
+ *
797
+ * @example
798
+ * ```typescript
799
+ * const outputs = filter.processAllSync(inputFrame);
800
+ * for (const output of outputs) {
801
+ * console.log(`Got filtered frame: pts=${output.pts}`);
802
+ * output.free();
803
+ * }
804
+ * ```
805
+ *
806
+ * @example
807
+ * ```typescript
808
+ * // Process frame - may return multiple frames (e.g. fps filter)
809
+ * const outputs = filter.processAllSync(frame);
810
+ * for (const output of outputs) {
811
+ * yield output;
812
+ * }
813
+ * ```
814
+ *
815
+ * @see {@link processSync} For single frame processing
816
+ * @see {@link framesSync} For processing frame streams
817
+ * @see {@link flushSync} For end-of-stream handling
818
+ * @see {@link process} For async version
819
+ */
820
+ processAllSync(frame) {
821
+ this.processSync(frame);
822
+ // Receive all available frames
823
+ const frames = [];
824
+ while (true) {
825
+ const outputFrame = this.receiveSync();
826
+ if (!outputFrame)
827
+ break; // Stop on EAGAIN or EOF
828
+ frames.push(outputFrame); // Only push actual frames
829
+ }
830
+ return frames;
831
+ }
832
+ /**
833
+ * Process frame stream through filter.
834
+ *
835
+ * High-level async generator for filtering frame streams.
836
+ * Filter is only flushed when EOF (null) signal is explicitly received.
837
+ * Primary interface for stream-based filtering.
838
+ *
839
+ * **EOF Handling:**
840
+ * - Send null to flush filter and get remaining buffered frames
841
+ * - Generator yields null after flushing when null is received
842
+ * - No automatic flushing - filter stays open until EOF or close()
843
+ *
844
+ * @param frames - Async iterable of frames, single frame, or null to flush
845
+ *
846
+ * @yields {Frame | null} Filtered frames, followed by null when explicitly flushed
847
+ *
848
+ * @throws {Error} If filter not ready
849
+ *
850
+ * @throws {FFmpegError} If processing fails
851
+ *
852
+ * @example
853
+ * ```typescript
854
+ * // Stream of frames with automatic EOF propagation
855
+ * for await (const frame of filter.frames(decoder.frames(packets))) {
856
+ * if (frame === null) {
857
+ * console.log('Filter flushed');
858
+ * break;
859
+ * }
860
+ * await encoder.encode(frame);
861
+ * frame.free();
862
+ * }
863
+ * ```
864
+ *
865
+ * @example
866
+ * ```typescript
867
+ * // Single frame - no automatic flush
868
+ * for await (const frame of filter.frames(singleFrame)) {
869
+ * await encoder.encode(frame);
870
+ * frame.free();
871
+ * }
872
+ * // Filter remains open, buffered frames not flushed
873
+ * ```
874
+ *
875
+ * @example
876
+ * ```typescript
877
+ * // Explicit flush with EOF
878
+ * for await (const frame of filter.frames(null)) {
879
+ * if (frame === null) {
880
+ * console.log('All buffered frames flushed');
881
+ * break;
882
+ * }
883
+ * console.log('Buffered frame:', frame.pts);
884
+ * frame.free();
885
+ * }
886
+ * ```
887
+ *
888
+ * @see {@link process} For single frame processing
889
+ * @see {@link Decoder.frames} For frames source
890
+ * @see {@link framesSync} For sync version
891
+ */
892
+ async *frames(frames) {
893
+ const self = this;
894
+ const processFrame = async function* (frame) {
895
+ await self.process(frame);
896
+ while (true) {
897
+ const filtered = await self.receive();
898
+ if (!filtered)
899
+ break;
900
+ yield filtered;
901
+ }
902
+ }.bind(this);
903
+ const finalize = async function* () {
904
+ for await (const remaining of self.flushFrames()) {
905
+ yield remaining;
906
+ }
907
+ yield null;
908
+ }.bind(this);
909
+ if (frames === null) {
910
+ yield* finalize();
911
+ return;
912
+ }
913
+ if (frames instanceof Frame) {
914
+ yield* processFrame(frames);
915
+ return;
916
+ }
917
+ for await (const frame_1 of frames) {
918
+ const env_1 = { stack: [], error: void 0, hasError: false };
919
+ try {
920
+ const frame = __addDisposableResource(env_1, frame_1, false);
921
+ this.signal?.throwIfAborted();
922
+ if (frame === null) {
923
+ yield* finalize();
924
+ return;
925
+ }
926
+ yield* processFrame(frame);
927
+ }
928
+ catch (e_1) {
929
+ env_1.error = e_1;
930
+ env_1.hasError = true;
931
+ }
932
+ finally {
933
+ __disposeResources(env_1);
934
+ }
935
+ }
936
+ }
937
+ /**
938
+ * Process frame stream through filter synchronously.
939
+ * Synchronous version of frames.
940
+ *
941
+ * High-level sync generator for filtering frame streams.
942
+ * Filter is only flushed when EOF (null) signal is explicitly received.
943
+ * Primary interface for stream-based filtering.
944
+ *
945
+ * **EOF Handling:**
946
+ * - Send null to flush filter and get remaining buffered frames
947
+ * - Generator yields null after flushing when null is received
948
+ * - No automatic flushing - filter stays open until EOF or close()
949
+ *
950
+ * @param frames - Iterable of frames, single frame, or null to flush
951
+ *
952
+ * @yields {Frame | null} Filtered frames, followed by null when explicitly flushed
953
+ *
954
+ * @throws {Error} If filter not ready
955
+ *
956
+ * @throws {FFmpegError} If processing fails
957
+ *
958
+ * @example
959
+ * ```typescript
960
+ * // Stream of frames with automatic EOF propagation
961
+ * for (const frame of filter.framesSync(decoder.framesSync(packets))) {
962
+ * if (frame === null) {
963
+ * console.log('Filter flushed');
964
+ * break;
965
+ * }
966
+ * encoder.encodeSync(frame);
967
+ * frame.free();
968
+ * }
969
+ * ```
970
+ *
971
+ * @example
972
+ * ```typescript
973
+ * // Single frame - no automatic flush
974
+ * for (const frame of filter.framesSync(singleFrame)) {
975
+ * encoder.encodeSync(frame);
976
+ * frame.free();
977
+ * }
978
+ * // Filter remains open, buffered frames not flushed
979
+ * ```
980
+ *
981
+ * @example
982
+ * ```typescript
983
+ * // Explicit flush with EOF
984
+ * for (const frame of filter.framesSync(null)) {
985
+ * if (frame === null) {
986
+ * console.log('All buffered frames flushed');
987
+ * break;
988
+ * }
989
+ * console.log('Buffered frame:', frame.pts);
990
+ * frame.free();
991
+ * }
992
+ * ```
993
+ *
994
+ * @see {@link processSync} For single frame processing
995
+ * @see {@link Decoder.framesSync} For frames source
996
+ * @see {@link frames} For async version
997
+ */
998
+ *framesSync(frames) {
999
+ const self = this;
1000
+ // Helper: Process frame and yield all available filtered frames (filters out EAGAIN nulls)
1001
+ const processFrame = function* (frame) {
1002
+ self.processSync(frame);
1003
+ // Receive ALL filtered frames (filter out null/EAGAIN)
1004
+ while (true) {
1005
+ const filtered = self.receiveSync();
1006
+ if (!filtered)
1007
+ break; // EAGAIN or EOF - no more frames available
1008
+ yield filtered; // Only yield actual frames, not null
1009
+ }
1010
+ }.bind(this);
1011
+ // Helper: Flush filter and signal EOF
1012
+ const finalize = function* () {
1013
+ for (const remaining of self.flushFramesSync()) {
1014
+ yield remaining; // Only yield actual frames
1015
+ }
1016
+ yield null; // Signal end-of-stream
1017
+ }.bind(this);
1018
+ // Case 1: EOF input -> flush only
1019
+ if (frames === null) {
1020
+ yield* finalize();
1021
+ return;
1022
+ }
1023
+ // Case 2: Single frame
1024
+ if (frames instanceof Frame) {
1025
+ yield* processFrame(frames);
1026
+ // No automatic flush - only flush on explicit EOF
1027
+ return;
1028
+ }
1029
+ // Case 3: Iterable of frames
1030
+ for (const frame_2 of frames) {
1031
+ const env_2 = { stack: [], error: void 0, hasError: false };
1032
+ try {
1033
+ const frame = __addDisposableResource(env_2, frame_2, false);
1034
+ // Check for EOF signal from upstream
1035
+ if (frame === null) {
1036
+ yield* finalize();
1037
+ return;
1038
+ }
1039
+ yield* processFrame(frame);
1040
+ }
1041
+ catch (e_2) {
1042
+ env_2.error = e_2;
1043
+ env_2.hasError = true;
1044
+ }
1045
+ finally {
1046
+ __disposeResources(env_2);
1047
+ }
1048
+ }
1049
+ // No automatic flush - only flush on explicit EOF
1050
+ }
1051
+ /**
1052
+ * Flush filter and signal end-of-stream.
1053
+ *
1054
+ * Sends null frame to flush buffered data.
1055
+ * Must call receive() to get flushed frames.
1056
+ * Does nothing if filter is closed or was never initialized.
1057
+ *
1058
+ * Direct mapping to av_buffersrc_add_frame(NULL).
1059
+ *
1060
+ * @throws {FFmpegError} If flush fails
1061
+ *
1062
+ * @example
1063
+ * ```typescript
1064
+ * await filter.flush();
1065
+ * // Get remaining frames
1066
+ * let frame;
1067
+ * while ((frame = await filter.receive()) !== null) {
1068
+ * frame.free();
1069
+ * }
1070
+ * ```
1071
+ *
1072
+ * @see {@link flushFrames} For async iteration
1073
+ * @see {@link receive} For getting flushed frames
1074
+ * @see {@link flushSync} For synchronous version
1075
+ */
1076
+ async flush() {
1077
+ this.signal?.throwIfAborted();
1078
+ if (this.isClosed || !this.initialized || !this.buffersrcCtx) {
1079
+ return;
1080
+ }
1081
+ // Send flush frame (null)
1082
+ const ret = await this.buffersrcCtx.buffersrcAddFrame(null, AV_BUFFERSRC_FLAG_PUSH);
1083
+ if (ret < 0 && ret !== AVERROR_EOF) {
1084
+ FFmpegError.throwIfError(ret, 'Failed to flush filter');
1085
+ }
1086
+ }
1087
+ /**
1088
+ * Flush filter and signal end-of-stream synchronously.
1089
+ * Synchronous version of flush.
1090
+ *
1091
+ * Sends null frame to flush buffered data.
1092
+ * Must call receiveSync() to get flushed frames.
1093
+ * Does nothing if filter is closed or was never initialized.
1094
+ *
1095
+ * Direct mapping to av_buffersrc_add_frame(NULL).
1096
+ *
1097
+ * @throws {FFmpegError} If flush fails
1098
+ *
1099
+ * @example
1100
+ * ```typescript
1101
+ * filter.flushSync();
1102
+ * // Get remaining frames
1103
+ * let frame;
1104
+ * while ((frame = filter.receiveSync()) !== null) {
1105
+ * frame.free();
1106
+ * }
1107
+ * ```
1108
+ *
1109
+ * @see {@link flushFramesSync} For sync iteration
1110
+ * @see {@link receiveSync} For getting flushed frames
1111
+ * @see {@link flush} For async version
1112
+ */
1113
+ flushSync() {
1114
+ if (this.isClosed || !this.initialized || !this.buffersrcCtx) {
1115
+ return;
1116
+ }
1117
+ // Send flush frame (null)
1118
+ const ret = this.buffersrcCtx.buffersrcAddFrameSync(null, AV_BUFFERSRC_FLAG_PUSH);
1119
+ if (ret < 0 && ret !== AVERROR_EOF) {
1120
+ FFmpegError.throwIfError(ret, 'Failed to flush filter');
1121
+ }
1122
+ }
1123
+ /**
1124
+ * Flush filter and yield remaining frames.
1125
+ *
1126
+ * Convenient async generator for flushing.
1127
+ * Combines flush and receive operations.
1128
+ * Returns immediately if filter is closed or was never initialized.
1129
+ *
1130
+ * @yields {Frame} Remaining frames from filter
1131
+ *
1132
+ * @throws {FFmpegError} If flush fails
1133
+ *
1134
+ * @example
1135
+ * ```typescript
1136
+ * for await (const frame of filter.flushFrames()) {
1137
+ * console.log(`Flushed frame: pts=${frame.pts}`);
1138
+ * frame.free();
1139
+ * }
1140
+ * ```
1141
+ *
1142
+ * @see {@link process} For frame processing
1143
+ * @see {@link flush} For manual flush
1144
+ * @see {@link flushFramesSync} For sync version
1145
+ */
1146
+ async *flushFrames() {
1147
+ // Send flush signal
1148
+ await this.flush();
1149
+ // Yield all remaining frames (filter out null/EAGAIN and EOF)
1150
+ while (true) {
1151
+ const frame = await this.receive();
1152
+ if (!frame)
1153
+ break; // Stop on EAGAIN or EOF
1154
+ yield frame; // Only yield actual frames
1155
+ }
1156
+ }
1157
+ /**
1158
+ * Flush filter and yield remaining frames synchronously.
1159
+ * Synchronous version of flushFrames.
1160
+ *
1161
+ * Convenient sync generator for flushing.
1162
+ * Combines flush and receive operations.
1163
+ * Returns immediately if filter is closed or was never initialized.
1164
+ *
1165
+ * @yields {Frame} Remaining frames from filter
1166
+ *
1167
+ * @throws {FFmpegError} If flush fails
1168
+ *
1169
+ * @example
1170
+ * ```typescript
1171
+ * for (const frame of filter.flushFramesSync()) {
1172
+ * console.log(`Flushed frame: pts=${frame.pts}`);
1173
+ * frame.free();
1174
+ * }
1175
+ * ```
1176
+ *
1177
+ * @see {@link processSync} For frame processing
1178
+ * @see {@link flushSync} For manual flush
1179
+ * @see {@link flushFrames} For async version
1180
+ */
1181
+ *flushFramesSync() {
1182
+ // Send flush signal
1183
+ this.flushSync();
1184
+ // Yield all remaining frames (filter out null/EAGAIN and EOF)
1185
+ while (true) {
1186
+ const frame = this.receiveSync();
1187
+ if (!frame)
1188
+ break; // Stop on EAGAIN or EOF
1189
+ yield frame; // Only yield actual frames
1190
+ }
1191
+ }
1192
+ /**
1193
+ * Receive buffered frame from filter.
1194
+ *
1195
+ * Drains frames buffered by the filter.
1196
+ * Call repeatedly until null or EOF to get all buffered frames.
1197
+ * Implements FFmpeg's send/receive pattern.
1198
+ *
1199
+ * **Return Values:**
1200
+ * - `Frame` - Successfully received frame (AVERROR >= 0)
1201
+ * - `null` - Need more input data (AVERROR_EAGAIN), or filter not initialized
1202
+ * - `undefined` - End of stream reached (AVERROR_EOF), or filter is closed
1203
+ *
1204
+ * Direct mapping to av_buffersink_get_frame().
1205
+ *
1206
+ * @returns Buffered frame, null if need more data, or undefined if stream ended
1207
+ *
1208
+ * @throws {FFmpegError} If receiving fails
1209
+ *
1210
+ * @throws {Error} If frame cloning fails (out of memory)
1211
+ *
1212
+ * @example
1213
+ * ```typescript
1214
+ * // Process all buffered frames
1215
+ * while (true) {
1216
+ * const frame = await filter.receive();
1217
+ * if (!frame) break; // Stop on EAGAIN or EOF
1218
+ * console.log(`Received frame: pts=${frame.pts}`);
1219
+ * frame.free();
1220
+ * }
1221
+ * ```
1222
+ *
1223
+ * @example
1224
+ * ```typescript
1225
+ * // Handle each return value explicitly
1226
+ * const frame = await filter.receive();
1227
+ * if (frame === EOF) {
1228
+ * console.log('Filter stream ended');
1229
+ * } else if (frame === null) {
1230
+ * console.log('Need more input data');
1231
+ * } else {
1232
+ * console.log(`Got frame: pts=${frame.pts}`);
1233
+ * frame.free();
1234
+ * }
1235
+ * ```
1236
+ *
1237
+ * @see {@link process} For frame processing
1238
+ * @see {@link flush} For flushing filter
1239
+ * @see {@link receiveSync} For synchronous version
1240
+ * @see {@link EOF} For end-of-stream signal
1241
+ */
1242
+ async receive() {
1243
+ if (this.isClosed) {
1244
+ return EOF;
1245
+ }
1246
+ if (!this.initialized || !this.buffersinkCtx) {
1247
+ return null;
1248
+ }
1249
+ // Reuse frame - but alloc() instead of unref() for buffersink
1250
+ // buffersink needs a fresh allocated frame, not an unreferenced one
1251
+ this.frame.alloc();
1252
+ const ret = await this.buffersinkCtx.buffersinkGetFrame(this.frame);
1253
+ if (ret >= 0) {
1254
+ // Post-process output frame (set timeBase from buffersink, calculate duration)
1255
+ this.postProcessOutputFrame(this.frame);
1256
+ // Clone for user (keeps internal frame for reuse)
1257
+ const cloned = this.frame.clone();
1258
+ if (!cloned) {
1259
+ throw new Error('Failed to clone frame (out of memory)');
1260
+ }
1261
+ return cloned;
1262
+ }
1263
+ else if (ret === AVERROR_EAGAIN) {
1264
+ // Need more data
1265
+ return null;
1266
+ }
1267
+ else if (ret === AVERROR_EOF) {
1268
+ // End of stream
1269
+ return EOF;
1270
+ }
1271
+ else {
1272
+ FFmpegError.throwIfError(ret, 'Failed to receive frame from filter');
1273
+ return null;
1274
+ }
1275
+ }
1276
+ /**
1277
+ * Receive buffered frame from filter synchronously.
1278
+ * Synchronous version of receive.
1279
+ *
1280
+ * Drains frames buffered by the filter.
1281
+ * Call repeatedly until null or EOF to get all buffered frames.
1282
+ * Implements FFmpeg's send/receive pattern.
1283
+ *
1284
+ * **Return Values:**
1285
+ * - `Frame` - Successfully received frame (AVERROR >= 0)
1286
+ * - `null` - Need more input data (AVERROR_EAGAIN), or filter not initialized
1287
+ * - `undefined` - End of stream reached (AVERROR_EOF), or filter is closed
1288
+ *
1289
+ * Direct mapping to av_buffersink_get_frame().
1290
+ *
1291
+ * @returns Buffered frame, null if need more data, or undefined if stream ended
1292
+ *
1293
+ * @throws {FFmpegError} If receiving fails
1294
+ *
1295
+ * @throws {Error} If frame cloning fails (out of memory)
1296
+ *
1297
+ * @example
1298
+ * ```typescript
1299
+ * // Process all buffered frames
1300
+ * while (true) {
1301
+ * const frame = filter.receiveSync();
1302
+ * if (!frame) break; // Stop on EAGAIN or EOF
1303
+ * console.log(`Received frame: pts=${frame.pts}`);
1304
+ * frame.free();
1305
+ * }
1306
+ * ```
1307
+ *
1308
+ * @example
1309
+ * ```typescript
1310
+ * // Handle each return value explicitly
1311
+ * const frame = filter.receiveSync();
1312
+ * if (frame === EOF) {
1313
+ * console.log('Filter stream ended');
1314
+ * } else if (frame === null) {
1315
+ * console.log('Need more input data');
1316
+ * } else {
1317
+ * console.log(`Got frame: pts=${frame.pts}`);
1318
+ * frame.free();
1319
+ * }
1320
+ * ```
1321
+ *
1322
+ * @see {@link processSync} For frame processing
1323
+ * @see {@link flushSync} For flushing filter
1324
+ * @see {@link receive} For async version
1325
+ * @see {@link EOF} For end-of-stream signal
1326
+ */
1327
+ receiveSync() {
1328
+ if (this.isClosed) {
1329
+ return EOF;
1330
+ }
1331
+ if (!this.initialized || !this.buffersinkCtx) {
1332
+ return null;
1333
+ }
1334
+ // Reuse frame - but alloc() instead of unref() for buffersink
1335
+ // buffersink needs a fresh allocated frame, not an unreferenced one
1336
+ this.frame.alloc();
1337
+ const ret = this.buffersinkCtx.buffersinkGetFrameSync(this.frame);
1338
+ if (ret >= 0) {
1339
+ // Post-process output frame (set timeBase from buffersink, calculate duration)
1340
+ this.postProcessOutputFrame(this.frame);
1341
+ // Clone for user (keeps internal frame for reuse)
1342
+ const cloned = this.frame.clone();
1343
+ if (!cloned) {
1344
+ throw new Error('Failed to clone frame (out of memory)');
1345
+ }
1346
+ return cloned;
1347
+ }
1348
+ else if (ret === AVERROR_EAGAIN) {
1349
+ return null; // Need more data
1350
+ }
1351
+ else if (ret === AVERROR_EOF) {
1352
+ return EOF; // End of stream
1353
+ }
1354
+ else {
1355
+ FFmpegError.throwIfError(ret, 'Failed to receive frame from filter');
1356
+ return null;
1357
+ }
1358
+ }
1359
+ /**
1360
+ * Send command to filter.
1361
+ *
1362
+ * Sends runtime command to specific filter in graph.
1363
+ * Allows dynamic parameter adjustment.
1364
+ *
1365
+ * Direct mapping to avfilter_graph_send_command().
1366
+ *
1367
+ * @param target - Target filter name
1368
+ *
1369
+ * @param cmd - Command name
1370
+ *
1371
+ * @param arg - Command argument
1372
+ *
1373
+ * @param flags - Command flags
1374
+ *
1375
+ * @returns Response string from filter
1376
+ *
1377
+ * @throws {Error} If filter not ready
1378
+ *
1379
+ * @throws {FFmpegError} If command fails
1380
+ *
1381
+ * @example
1382
+ * ```typescript
1383
+ * // Change volume at runtime
1384
+ * const response = filter.sendCommand('volume', 'volume', '0.5');
1385
+ * console.log(`Volume changed: ${response}`);
1386
+ * ```
1387
+ *
1388
+ * @see {@link queueCommand} For delayed commands
1389
+ */
1390
+ sendCommand(target, cmd, arg, flags) {
1391
+ if (this.isClosed) {
1392
+ throw new Error('Filter is closed');
1393
+ }
1394
+ if (!this.initialized) {
1395
+ throw new Error('Filter not initialized');
1396
+ }
1397
+ const result = this.graph.sendCommand(target, cmd, arg, flags);
1398
+ if (typeof result === 'number') {
1399
+ FFmpegError.throwIfError(result, 'Failed to send filter command');
1400
+ return '';
1401
+ }
1402
+ return result.response ?? '';
1403
+ }
1404
+ /**
1405
+ * Queue command for later execution.
1406
+ *
1407
+ * Schedules command to execute at specific timestamp.
1408
+ * Useful for synchronized parameter changes.
1409
+ *
1410
+ * Direct mapping to avfilter_graph_queue_command().
1411
+ *
1412
+ * @param target - Target filter name
1413
+ *
1414
+ * @param cmd - Command name
1415
+ *
1416
+ * @param arg - Command argument
1417
+ *
1418
+ * @param ts - Timestamp for execution
1419
+ *
1420
+ * @param flags - Command flags
1421
+ *
1422
+ * @throws {Error} If filter not ready
1423
+ *
1424
+ * @throws {FFmpegError} If queue fails
1425
+ *
1426
+ * @example
1427
+ * ```typescript
1428
+ * // Queue volume change at 10 seconds
1429
+ * filter.queueCommand('volume', 'volume', '0.8', 10.0);
1430
+ * ```
1431
+ *
1432
+ * @see {@link sendCommand} For immediate commands
1433
+ */
1434
+ queueCommand(target, cmd, arg, ts, flags) {
1435
+ if (this.isClosed) {
1436
+ throw new Error('Filter is closed');
1437
+ }
1438
+ if (!this.initialized) {
1439
+ throw new Error('Filter not initialized');
1440
+ }
1441
+ const ret = this.graph.queueCommand(target, cmd, arg, ts, flags);
1442
+ FFmpegError.throwIfError(ret, 'Failed to queue filter command');
1443
+ }
1444
+ pipeTo(target) {
1445
+ const t = target;
1446
+ // Store reference to next component for flush propagation
1447
+ this.nextComponent = t;
1448
+ // Start worker if not already running
1449
+ this.workerPromise ??= this.runWorker();
1450
+ // Start pipe task: filter.outputQueue -> target.inputQueue (via target.send)
1451
+ this.pipeToPromise = (async () => {
1452
+ while (true) {
1453
+ const frame = await this.receiveFrame();
1454
+ if (!frame)
1455
+ break;
1456
+ await t.sendToQueue(frame);
1457
+ }
1458
+ })();
1459
+ // Return scheduler for chaining (target is now the last component)
1460
+ return new Scheduler(this, t);
1461
+ }
1462
+ /**
1463
+ * Free filter resources.
1464
+ *
1465
+ * Releases filter graph and contexts.
1466
+ * Safe to call multiple times.
1467
+ *
1468
+ * @example
1469
+ * ```typescript
1470
+ * filter.close();
1471
+ * ```
1472
+ *
1473
+ * @see {@link Symbol.dispose} For automatic cleanup
1474
+ */
1475
+ close() {
1476
+ if (this.isClosed) {
1477
+ return;
1478
+ }
1479
+ this.isClosed = true;
1480
+ this.inputQueue.close();
1481
+ this.outputQueue.close();
1482
+ this.inputQueue.clear();
1483
+ this.outputQueue.clear();
1484
+ this.buffersrcCtx?.free();
1485
+ this.buffersinkCtx?.free();
1486
+ this.buffersrcCtx = null;
1487
+ this.buffersinkCtx = null;
1488
+ this.frame.free();
1489
+ this.graph.free();
1490
+ this.initialized = false;
1491
+ this.initializePromise = null;
1492
+ }
1493
+ /**
1494
+ * Worker loop for push-based processing.
1495
+ *
1496
+ * @internal
1497
+ */
1498
+ async runWorker() {
1499
+ try {
1500
+ // Outer loop - receive frames
1501
+ while (!this.inputQueue.isClosed) {
1502
+ const env_3 = { stack: [], error: void 0, hasError: false };
1503
+ try {
1504
+ const frame = __addDisposableResource(env_3, await this.inputQueue.receive(), false);
1505
+ if (!frame)
1506
+ break;
1507
+ await this.process(frame);
1508
+ // Receive all available frames
1509
+ while (!this.outputQueue.isClosed) {
1510
+ const buffered = await this.receive();
1511
+ if (!buffered)
1512
+ break; // Stop on EAGAIN or EOF
1513
+ await this.outputQueue.send(buffered); // Only send actual frames
1514
+ }
1515
+ }
1516
+ catch (e_3) {
1517
+ env_3.error = e_3;
1518
+ env_3.hasError = true;
1519
+ }
1520
+ finally {
1521
+ __disposeResources(env_3);
1522
+ }
1523
+ }
1524
+ // Flush filter at end
1525
+ await this.flush();
1526
+ while (!this.outputQueue.isClosed) {
1527
+ const frame = await this.receive();
1528
+ if (!frame)
1529
+ break; // Stop on EAGAIN or EOF
1530
+ await this.outputQueue.send(frame); // Only send actual frames
1531
+ }
1532
+ }
1533
+ catch (error) {
1534
+ // Propagate error to both queues so upstream and downstream know
1535
+ const err = error instanceof Error ? error : new Error(String(error));
1536
+ this.inputQueue?.closeWithError(err);
1537
+ this.outputQueue?.closeWithError(err);
1538
+ }
1539
+ finally {
1540
+ // Close output queue when done (if not already closed with error)
1541
+ this.outputQueue?.close();
1542
+ }
1543
+ }
1544
+ /**
1545
+ * Send frame to input queue or flush the pipeline.
1546
+ *
1547
+ * When frame is provided, queues it for filtering.
1548
+ * When null is provided, triggers flush sequence:
1549
+ * - Closes input queue
1550
+ * - Waits for worker completion
1551
+ * - Flushes filter and sends remaining frames to output queue
1552
+ * - Closes output queue
1553
+ * - Waits for pipeTo task completion
1554
+ * - Propagates flush to next component (if any)
1555
+ *
1556
+ * Used by scheduler system for pipeline control.
1557
+ *
1558
+ * @param frame - Frame to send, or null to flush
1559
+ *
1560
+ * @internal
1561
+ */
1562
+ async sendToQueue(frame) {
1563
+ if (frame) {
1564
+ await this.inputQueue.send(frame);
1565
+ }
1566
+ else {
1567
+ // Close input queue to signal end of stream to worker
1568
+ this.inputQueue.close();
1569
+ // Wait for worker to finish processing all frames (if exists)
1570
+ if (this.workerPromise) {
1571
+ await this.workerPromise;
1572
+ }
1573
+ // Flush filter at end (like FFmpeg does)
1574
+ await this.flush();
1575
+ // Send all flushed frames to output queue
1576
+ while (true) {
1577
+ const frame = await this.receive();
1578
+ if (!frame)
1579
+ break; // Stop on EAGAIN or EOF
1580
+ await this.outputQueue.send(frame); // Only send actual frames
1581
+ }
1582
+ // Close output queue to signal end of stream to pipeTo() task
1583
+ this.outputQueue.close();
1584
+ // Wait for pipeTo() task to finish processing all frames (if exists)
1585
+ if (this.pipeToPromise) {
1586
+ await this.pipeToPromise;
1587
+ }
1588
+ // Then propagate flush to next component
1589
+ if (this.nextComponent) {
1590
+ await this.nextComponent.sendToQueue(null);
1591
+ }
1592
+ }
1593
+ }
1594
+ /**
1595
+ * Receive frame from output queue.
1596
+ *
1597
+ * @returns Frame from output queue or null if closed
1598
+ *
1599
+ * @internal
1600
+ */
1601
+ async receiveFrame() {
1602
+ return await this.outputQueue.receive();
1603
+ }
1604
+ /**
1605
+ * Initialize filter graph from first frame.
1606
+ *
1607
+ * Creates and configures filter graph components.
1608
+ * Sets buffer source parameters from frame properties.
1609
+ * Automatically configures hardware frames context if present.
1610
+ *
1611
+ * @param frame - First frame to process, provides format and hw context
1612
+ *
1613
+ * @throws {Error} If initialization fails
1614
+ *
1615
+ * @throws {FFmpegError} If configuration fails
1616
+ *
1617
+ * @internal
1618
+ */
1619
+ async initialize(frame) {
1620
+ // Calculate timeBase from first frame
1621
+ this.calculatedTimeBase = this.calculateTimeBase(frame);
1622
+ // Track initial frame properties for change detection
1623
+ this.lastFrameProps = {
1624
+ format: frame.format,
1625
+ width: frame.width,
1626
+ height: frame.height,
1627
+ sampleRate: frame.sampleRate,
1628
+ channels: frame.channelLayout?.nbChannels ?? 0,
1629
+ };
1630
+ try {
1631
+ await this.buildGraph(frame);
1632
+ }
1633
+ catch (error) {
1634
+ // A device filter can fail to configure on a driver that refuses the
1635
+ // input. A function may know another graph, so it is asked once more
1636
+ if (typeof this.description !== 'function') {
1637
+ throw error;
1638
+ }
1639
+ this.resetGraph();
1640
+ await this.buildGraph(frame, error);
1641
+ }
1642
+ this.initialized = true;
1643
+ }
1644
+ /**
1645
+ * Initialize filter graph from first frame synchronously.
1646
+ * Synchronous version of initialize.
1647
+ *
1648
+ * Creates and configures filter graph components.
1649
+ * Sets buffer source parameters from frame properties.
1650
+ * Automatically configures hardware frames context if present.
1651
+ *
1652
+ * @param frame - First frame to process, provides format and hw context
1653
+ *
1654
+ * @throws {Error} If initialization fails
1655
+ *
1656
+ * @throws {FFmpegError} If configuration fails
1657
+ *
1658
+ * @internal
1659
+ *
1660
+ * @see {@link initialize} For async version
1661
+ */
1662
+ initializeSync(frame) {
1663
+ // Calculate timeBase from first frame
1664
+ this.calculatedTimeBase = this.calculateTimeBase(frame);
1665
+ // Track initial frame properties for change detection
1666
+ this.lastFrameProps = {
1667
+ format: frame.format,
1668
+ width: frame.width,
1669
+ height: frame.height,
1670
+ sampleRate: frame.sampleRate,
1671
+ channels: frame.channelLayout?.nbChannels ?? 0,
1672
+ };
1673
+ try {
1674
+ this.buildGraphSync(frame);
1675
+ }
1676
+ catch (error) {
1677
+ if (typeof this.description !== 'function') {
1678
+ throw error;
1679
+ }
1680
+ this.resetGraph();
1681
+ this.buildGraphSync(frame, error);
1682
+ }
1683
+ this.initialized = true;
1684
+ }
1685
+ /**
1686
+ * Build and configure the graph for a frame.
1687
+ *
1688
+ * Sets the graph options, creates the buffer source and sink and parses the
1689
+ * description between them.
1690
+ *
1691
+ * @param frame - Frame the graph is built for
1692
+ *
1693
+ * @param failure - Why the previous graph for this frame failed, passed to a description function
1694
+ *
1695
+ * @throws {Error} If building fails
1696
+ *
1697
+ * @throws {FFmpegError} If configuration fails
1698
+ *
1699
+ * @internal
1700
+ */
1701
+ async buildGraph(frame, failure) {
1702
+ // Set graph options before parsing
1703
+ if (this.options.scaleSwsOpts) {
1704
+ this.graph.scaleSwsOpts = this.options.scaleSwsOpts;
1705
+ }
1706
+ if (this.options.audioResampleOpts) {
1707
+ this.graph.aresampleSwrOpts = this.options.audioResampleOpts;
1708
+ }
1709
+ // Create buffer source and sink
1710
+ this.createBufferSource(frame);
1711
+ this.createBufferSink(frame);
1712
+ // Parse filter description
1713
+ this.parseFilterDescription(frame, failure);
1714
+ applyContextOptions(this.graph, this.options.graph);
1715
+ this.options.configure?.(this.graph);
1716
+ // Configure the graph
1717
+ const ret = await this.graph.config();
1718
+ FFmpegError.throwIfError(ret, 'Failed to configure filter graph');
1719
+ }
1720
+ /**
1721
+ * Build and configure the graph for a frame synchronously.
1722
+ * Synchronous version of buildGraph.
1723
+ *
1724
+ * @param frame - Frame the graph is built for
1725
+ *
1726
+ * @param failure - Why the previous graph for this frame failed, passed to a description function
1727
+ *
1728
+ * @throws {Error} If building fails
1729
+ *
1730
+ * @throws {FFmpegError} If configuration fails
1731
+ *
1732
+ * @internal
1733
+ *
1734
+ * @see {@link buildGraph} For async version
1735
+ */
1736
+ buildGraphSync(frame, failure) {
1737
+ if (this.options.scaleSwsOpts) {
1738
+ this.graph.scaleSwsOpts = this.options.scaleSwsOpts;
1739
+ }
1740
+ if (this.options.audioResampleOpts) {
1741
+ this.graph.aresampleSwrOpts = this.options.audioResampleOpts;
1742
+ }
1743
+ this.createBufferSource(frame);
1744
+ this.createBufferSink(frame);
1745
+ this.parseFilterDescription(frame, failure);
1746
+ applyContextOptions(this.graph, this.options.graph);
1747
+ this.options.configure?.(this.graph);
1748
+ const ret = this.graph.configSync();
1749
+ FFmpegError.throwIfError(ret, 'Failed to configure filter graph');
1750
+ }
1751
+ /**
1752
+ * Replace the graph with a new, empty one.
1753
+ *
1754
+ * @internal
1755
+ */
1756
+ resetGraph() {
1757
+ this.graph.free();
1758
+ // Create new graph
1759
+ this.graph = new FilterGraph();
1760
+ this.graph.alloc();
1761
+ // Configure threading
1762
+ if (this.options.threads !== undefined) {
1763
+ this.graph.nbThreads = this.options.threads;
1764
+ }
1765
+ // Configure scaler options
1766
+ if (this.options.scaleSwsOpts) {
1767
+ this.graph.scaleSwsOpts = this.options.scaleSwsOpts;
1768
+ }
1769
+ this.buffersrcCtx = null;
1770
+ this.buffersinkCtx = null;
1771
+ }
1772
+ /**
1773
+ * Check if frame properties changed and handle according to dropOnChange/allowReinit options.
1774
+ *
1775
+ * Implements FFmpeg's IFILTER_FLAG_DROPCHANGED and IFILTER_FLAG_REINIT logic
1776
+ *
1777
+ * @param frame - Frame to check
1778
+ *
1779
+ * @returns true if frame should be processed, false if frame should be dropped
1780
+ *
1781
+ * @throws {Error} If format changed and allowReinit is false
1782
+ *
1783
+ * @internal
1784
+ */
1785
+ checkFramePropertiesChanged(frame) {
1786
+ if (!this.lastFrameProps) {
1787
+ return true; // No previous frame, allow
1788
+ }
1789
+ // Check for property changes
1790
+ const changed = frame.format !== this.lastFrameProps.format ||
1791
+ frame.width !== this.lastFrameProps.width ||
1792
+ frame.height !== this.lastFrameProps.height ||
1793
+ frame.sampleRate !== this.lastFrameProps.sampleRate ||
1794
+ (frame.channelLayout?.nbChannels ?? 0) !== this.lastFrameProps.channels;
1795
+ if (!changed) {
1796
+ return true; // No changes, process frame
1797
+ }
1798
+ // Properties changed - check dropOnChange flag
1799
+ if (this.options.dropOnChange) {
1800
+ return false; // Drop frame
1801
+ }
1802
+ // Check allowReinit flag
1803
+ // Default is true (allow reinit), only block if explicitly set to false
1804
+ const allowReinit = this.options.allowReinit !== false;
1805
+ if (!allowReinit && this.initialized) {
1806
+ throw new Error('Frame properties changed but allowReinit is false. ' +
1807
+ `Format: ${this.lastFrameProps.format}->${frame.format}, ` +
1808
+ `Size: ${this.lastFrameProps.width}x${this.lastFrameProps.height}->${frame.width}x${frame.height}`);
1809
+ }
1810
+ // Close current graph and reinitialize
1811
+ this.resetGraph();
1812
+ this.initialized = false;
1813
+ this.initializePromise = null;
1814
+ this.calculatedTimeBase = null;
1815
+ return true; // Will be reinitialized on next process
1816
+ }
1817
+ /**
1818
+ * Calculate timeBase from frame based on media type and CFR option.
1819
+ *
1820
+ * Implements FFmpeg's ifilter_parameters_from_frame logic:
1821
+ * - Audio: Always { 1, sample_rate }
1822
+ * - Video CFR: 1/framerate (inverse of framerate)
1823
+ * - Video VFR: Use frame.timeBase
1824
+ *
1825
+ * @param frame - Input frame
1826
+ *
1827
+ * @returns Calculated timeBase
1828
+ *
1829
+ * @internal
1830
+ */
1831
+ calculateTimeBase(frame) {
1832
+ if (frame.isAudio()) {
1833
+ // Audio: Always { 1, sample_rate }
1834
+ return { num: 1, den: frame.sampleRate };
1835
+ }
1836
+ else {
1837
+ // Video: Check CFR flag
1838
+ if (this.options.cfr) {
1839
+ // CFR mode: timeBase = 1/framerate = inverse(framerate)
1840
+ // Note: framerate is guaranteed to be set (validated in create())
1841
+ return avInvQ(this.options.framerate);
1842
+ }
1843
+ else {
1844
+ // VFR mode: Use frame.timeBase
1845
+ return frame.timeBase;
1846
+ }
1847
+ }
1848
+ }
1849
+ /**
1850
+ * Post-process output frame from buffersink.
1851
+ *
1852
+ * Applies FFmpeg's fg_output_step() behavior:
1853
+ * 1. Sets frame.timeBase from buffersink (filters can change timeBase, e.g., aresample)
1854
+ * 2. Calculates video frame duration from frame rate if not set
1855
+ *
1856
+ * This must be called AFTER buffersinkGetFrame() for every output frame.
1857
+ *
1858
+ * @param frame - Output frame from buffersink
1859
+ *
1860
+ * @throws {Error} If buffersink context not available
1861
+ *
1862
+ * @internal
1863
+ */
1864
+ postProcessOutputFrame(frame) {
1865
+ if (!this.buffersinkCtx) {
1866
+ throw new Error('Buffersink context not available');
1867
+ }
1868
+ // Filters can change timeBase (e.g., aresample sets output to {1, out_sample_rate})
1869
+ // Without this, frame has INPUT timeBase instead of filter's OUTPUT timeBase
1870
+ frame.timeBase = this.buffersinkCtx.buffersinkGetTimeBase();
1871
+ if (frame.isVideo() && !frame.duration) {
1872
+ const frameRate = this.buffersinkCtx.buffersinkGetFrameRate();
1873
+ if (frameRate.num > 0 && frameRate.den > 0) {
1874
+ frame.duration = avRescaleQ(1, avInvQ(frameRate), frame.timeBase);
1875
+ }
1876
+ }
1877
+ }
1878
+ /**
1879
+ * Create buffer source with frame parameters.
1880
+ *
1881
+ * Configures buffer source with frame properties including hardware context.
1882
+ * Automatically detects video/audio and sets appropriate parameters.
1883
+ *
1884
+ * @param frame - Frame providing format, dimensions, and hw_frames_ctx
1885
+ *
1886
+ * @throws {Error} If creation fails
1887
+ *
1888
+ * @throws {FFmpegError} If configuration fails
1889
+ *
1890
+ * @internal
1891
+ */
1892
+ createBufferSource(frame) {
1893
+ const filterName = frame.isVideo() ? 'buffer' : 'abuffer';
1894
+ const bufferFilter = Filter.getByName(filterName);
1895
+ if (!bufferFilter) {
1896
+ throw new FFmpegError(AVERROR_FILTER_NOT_FOUND);
1897
+ }
1898
+ // Ensure timeBase was calculated
1899
+ if (!this.calculatedTimeBase) {
1900
+ throw new Error('TimeBase not calculated - this should not happen');
1901
+ }
1902
+ // For audio, create with args. For video, use allocFilter + buffersrcParametersSet
1903
+ if (frame.isVideo()) {
1904
+ // Allocate filter without args
1905
+ this.buffersrcCtx = this.graph.allocFilter(bufferFilter, 'in');
1906
+ if (!this.buffersrcCtx) {
1907
+ throw new Error('Failed to allocate buffer source');
1908
+ }
1909
+ const ret = this.buffersrcCtx.buffersrcParametersSet({
1910
+ width: frame.width,
1911
+ height: frame.height,
1912
+ format: frame.format,
1913
+ timeBase: this.calculatedTimeBase,
1914
+ frameRate: this.options.framerate,
1915
+ sampleAspectRatio: frame.sampleAspectRatio,
1916
+ colorRange: frame.colorRange,
1917
+ colorSpace: frame.colorSpace,
1918
+ hwFramesCtx: frame.hwFramesCtx,
1919
+ });
1920
+ FFmpegError.throwIfError(ret, 'Failed to set buffer source parameters');
1921
+ // Initialize filter
1922
+ const initRet = this.buffersrcCtx.init(null);
1923
+ FFmpegError.throwIfError(initRet, 'Failed to initialize buffer source');
1924
+ }
1925
+ else {
1926
+ // For audio, create with args string
1927
+ const formatName = avGetSampleFmtName(frame.format);
1928
+ const channelLayout = frame.channelLayout.mask === 0n ? `${frame.channelLayout.nbChannels}c` : frame.channelLayout.mask.toString();
1929
+ // eslint-disable-next-line @stylistic/max-len
1930
+ const args = `time_base=${this.calculatedTimeBase.num}/${this.calculatedTimeBase.den}:sample_rate=${frame.sampleRate}:sample_fmt=${formatName}:channel_layout=${channelLayout}`;
1931
+ this.buffersrcCtx = this.graph.createFilter(bufferFilter, 'in', args);
1932
+ if (!this.buffersrcCtx) {
1933
+ throw new Error('Failed to create audio buffer source');
1934
+ }
1935
+ }
1936
+ }
1937
+ /**
1938
+ * Create buffer sink.
1939
+ *
1940
+ * @param frame - Frame
1941
+ *
1942
+ * @throws {Error} If creation fails
1943
+ *
1944
+ * @internal
1945
+ */
1946
+ createBufferSink(frame) {
1947
+ const filterName = frame.isVideo() ? 'buffersink' : 'abuffersink';
1948
+ const sinkFilter = Filter.getByName(filterName);
1949
+ if (!sinkFilter) {
1950
+ throw new FFmpegError(AVERROR_FILTER_NOT_FOUND);
1951
+ }
1952
+ this.buffersinkCtx = this.graph.createFilter(sinkFilter, 'out', null);
1953
+ if (!this.buffersinkCtx) {
1954
+ throw new Error('Failed to create buffer sink');
1955
+ }
1956
+ }
1957
+ /**
1958
+ * Parse filter description and build graph.
1959
+ *
1960
+ * Uses the Segment API to parse filters, which allows setting hw_device_ctx
1961
+ * before filter initialization when needed. Works for both hardware and software filters.
1962
+ *
1963
+ * @param frame - First frame to process, provides hw_frames_ctx if any
1964
+ *
1965
+ * @param failure - Why the previous graph for this frame failed, passed to a description function
1966
+ *
1967
+ * @throws {Error} If parsing fails
1968
+ *
1969
+ * @throws {FFmpegError} If graph construction fails
1970
+ *
1971
+ * @internal
1972
+ */
1973
+ parseFilterDescription(frame, failure) {
1974
+ if (!this.buffersrcCtx || !this.buffersinkCtx) {
1975
+ throw new Error('Buffer filters not initialized');
1976
+ }
1977
+ const description = typeof this.description === 'function' ? this.description(frame, failure) : this.description;
1978
+ // Handle empty or simple passthrough
1979
+ if (!description || description === 'null' || description === 'anull') {
1980
+ // Direct connection for null filters
1981
+ const ret = this.buffersrcCtx.link(0, this.buffersinkCtx, 0);
1982
+ FFmpegError.throwIfError(ret, 'Failed to link buffer filters');
1983
+ return;
1984
+ }
1985
+ // Step 1: Parse the filter description into a segment
1986
+ const segment = this.graph.segmentParse(description);
1987
+ if (!segment) {
1988
+ throw new Error('Failed to parse filter segment');
1989
+ }
1990
+ let inputs;
1991
+ let outputs;
1992
+ try {
1993
+ // Step 2: Create filter instances (but don't initialize yet)
1994
+ let ret = segment.createFilters();
1995
+ FFmpegError.throwIfError(ret, 'Failed to create filters in segment');
1996
+ // Step 3: Set hw_device_ctx on filters that need it BEFORE initialization (if provided)
1997
+ const filters = this.graph.filters;
1998
+ if (filters) {
1999
+ for (const filterCtx of filters) {
2000
+ const filter = filterCtx.filter;
2001
+ if (filter?.hasFlags(AVFILTER_FLAG_HWDEVICE)) {
2002
+ filterCtx.hwDeviceCtx = this.options.hardware?.deviceContext ?? frame.hwFramesCtx?.deviceRef ?? null;
2003
+ // Set extra_hw_frames if specified
2004
+ if (this.options.extraHWFrames !== undefined && this.options.extraHWFrames > 0) {
2005
+ filterCtx.extraHWFrames = this.options.extraHWFrames;
2006
+ }
2007
+ }
2008
+ }
2009
+ }
2010
+ // Step 4: Apply options to filters
2011
+ ret = segment.applyOpts();
2012
+ FFmpegError.throwIfError(ret, 'Failed to apply options to segment');
2013
+ // Step 5: Initialize and link filters in the segment
2014
+ // Create empty FilterInOut objects - segment.apply() will populate them with
2015
+ // the segment's unconnected input/output pads
2016
+ inputs = new FilterInOut();
2017
+ outputs = new FilterInOut();
2018
+ // Apply the segment - this initializes and links all filters within the segment,
2019
+ // and returns the segment's unconnected pads in inputs/outputs
2020
+ ret = segment.apply(inputs, outputs);
2021
+ FFmpegError.throwIfError(ret, 'Failed to apply segment');
2022
+ // Step 6: Manually link buffersrc/buffersink to the segment's unconnected pads
2023
+ // After segment.apply():
2024
+ // - inputs contains the segment's free INPUT pads (where buffersrc connects TO)
2025
+ // - outputs contains the segment's free OUTPUT pads (where buffersink connects FROM)
2026
+ // Link buffersrc -> first segment input (if any)
2027
+ const segmentInput = inputs.filterCtx;
2028
+ if (segmentInput) {
2029
+ ret = this.buffersrcCtx.link(0, segmentInput, inputs.padIdx);
2030
+ FFmpegError.throwIfError(ret, 'Failed to link buffersrc to segment');
2031
+ }
2032
+ else {
2033
+ // No segment inputs means the filter doesn't accept input
2034
+ throw new Error('Segment has no input pads - cannot connect buffersrc');
2035
+ }
2036
+ // Link last segment output -> buffersink (if any)
2037
+ const segmentOutput = outputs.filterCtx;
2038
+ if (segmentOutput) {
2039
+ ret = segmentOutput.link(outputs.padIdx, this.buffersinkCtx, 0);
2040
+ FFmpegError.throwIfError(ret, 'Failed to link segment to buffersink');
2041
+ }
2042
+ else {
2043
+ // No segment outputs means the filter doesn't produce output
2044
+ throw new Error('Segment has no output pads - cannot connect buffersink');
2045
+ }
2046
+ }
2047
+ finally {
2048
+ // Always free the FilterInOut structures and the segment, including on the
2049
+ // error paths above (link/apply failures) where they would otherwise leak.
2050
+ inputs?.free();
2051
+ outputs?.free();
2052
+ segment.free();
2053
+ }
2054
+ }
2055
+ /**
2056
+ * Dispose of filter.
2057
+ *
2058
+ * Implements Disposable interface for automatic cleanup.
2059
+ * Equivalent to calling close().
2060
+ *
2061
+ * @example
2062
+ * ```typescript
2063
+ * {
2064
+ * using filter = FilterAPI.create('scale=640:480', { ... });
2065
+ * // Use filter...
2066
+ * } // Automatically freed
2067
+ * ```
2068
+ *
2069
+ * @see {@link close} For manual cleanup
2070
+ */
2071
+ [Symbol.dispose]() {
2072
+ this.close();
2073
+ }
2074
+ }
2075
+ //# sourceMappingURL=filter.js.map