node-av 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/README.md +51 -38
  2. package/dist/api/bitstream-filter.d.ts +180 -123
  3. package/dist/api/bitstream-filter.js +180 -125
  4. package/dist/api/bitstream-filter.js.map +1 -1
  5. package/dist/api/decoder.d.ts +279 -132
  6. package/dist/api/decoder.js +285 -142
  7. package/dist/api/decoder.js.map +1 -1
  8. package/dist/api/encoder.d.ts +246 -162
  9. package/dist/api/encoder.js +272 -208
  10. package/dist/api/encoder.js.map +1 -1
  11. package/dist/api/filter-presets.d.ts +690 -94
  12. package/dist/api/filter-presets.js +686 -102
  13. package/dist/api/filter-presets.js.map +1 -1
  14. package/dist/api/filter.d.ts +249 -213
  15. package/dist/api/filter.js +252 -242
  16. package/dist/api/filter.js.map +1 -1
  17. package/dist/api/hardware.d.ts +224 -117
  18. package/dist/api/hardware.js +380 -214
  19. package/dist/api/hardware.js.map +1 -1
  20. package/dist/api/index.d.ts +3 -3
  21. package/dist/api/index.js +1 -1
  22. package/dist/api/index.js.map +1 -1
  23. package/dist/api/io-stream.d.ts +65 -61
  24. package/dist/api/io-stream.js +43 -46
  25. package/dist/api/io-stream.js.map +1 -1
  26. package/dist/api/media-input.d.ts +242 -140
  27. package/dist/api/media-input.js +205 -103
  28. package/dist/api/media-input.js.map +1 -1
  29. package/dist/api/media-output.d.ts +206 -128
  30. package/dist/api/media-output.js +210 -128
  31. package/dist/api/media-output.js.map +1 -1
  32. package/dist/api/pipeline.d.ts +168 -38
  33. package/dist/api/pipeline.js +238 -14
  34. package/dist/api/pipeline.js.map +1 -1
  35. package/dist/api/types.d.ts +21 -187
  36. package/dist/api/utils.d.ts +1 -2
  37. package/dist/api/utils.js +9 -0
  38. package/dist/api/utils.js.map +1 -1
  39. package/dist/lib/audio-fifo.d.ts +127 -170
  40. package/dist/lib/audio-fifo.js +130 -173
  41. package/dist/lib/audio-fifo.js.map +1 -1
  42. package/dist/lib/binding.js +5 -0
  43. package/dist/lib/binding.js.map +1 -1
  44. package/dist/lib/bitstream-filter-context.d.ts +139 -184
  45. package/dist/lib/bitstream-filter-context.js +139 -188
  46. package/dist/lib/bitstream-filter-context.js.map +1 -1
  47. package/dist/lib/bitstream-filter.d.ts +68 -54
  48. package/dist/lib/bitstream-filter.js +68 -54
  49. package/dist/lib/bitstream-filter.js.map +1 -1
  50. package/dist/lib/codec-context.d.ts +316 -380
  51. package/dist/lib/codec-context.js +316 -381
  52. package/dist/lib/codec-context.js.map +1 -1
  53. package/dist/lib/codec-parameters.d.ts +160 -170
  54. package/dist/lib/codec-parameters.js +162 -172
  55. package/dist/lib/codec-parameters.js.map +1 -1
  56. package/dist/lib/codec-parser.d.ts +91 -104
  57. package/dist/lib/codec-parser.js +92 -103
  58. package/dist/lib/codec-parser.js.map +1 -1
  59. package/dist/lib/codec.d.ts +264 -281
  60. package/dist/lib/codec.js +268 -285
  61. package/dist/lib/codec.js.map +1 -1
  62. package/dist/lib/dictionary.d.ts +149 -203
  63. package/dist/lib/dictionary.js +158 -212
  64. package/dist/lib/dictionary.js.map +1 -1
  65. package/dist/lib/error.d.ts +96 -130
  66. package/dist/lib/error.js +98 -128
  67. package/dist/lib/error.js.map +1 -1
  68. package/dist/lib/filter-context.d.ts +284 -218
  69. package/dist/lib/filter-context.js +290 -227
  70. package/dist/lib/filter-context.js.map +1 -1
  71. package/dist/lib/filter-graph.d.ts +251 -292
  72. package/dist/lib/filter-graph.js +253 -294
  73. package/dist/lib/filter-graph.js.map +1 -1
  74. package/dist/lib/filter-inout.d.ts +87 -95
  75. package/dist/lib/filter-inout.js +87 -95
  76. package/dist/lib/filter-inout.js.map +1 -1
  77. package/dist/lib/filter.d.ts +93 -111
  78. package/dist/lib/filter.js +93 -111
  79. package/dist/lib/filter.js.map +1 -1
  80. package/dist/lib/format-context.d.ts +320 -428
  81. package/dist/lib/format-context.js +313 -385
  82. package/dist/lib/format-context.js.map +1 -1
  83. package/dist/lib/frame.d.ts +262 -405
  84. package/dist/lib/frame.js +263 -408
  85. package/dist/lib/frame.js.map +1 -1
  86. package/dist/lib/hardware-device-context.d.ts +149 -203
  87. package/dist/lib/hardware-device-context.js +149 -203
  88. package/dist/lib/hardware-device-context.js.map +1 -1
  89. package/dist/lib/hardware-frames-context.d.ts +170 -180
  90. package/dist/lib/hardware-frames-context.js +171 -181
  91. package/dist/lib/hardware-frames-context.js.map +1 -1
  92. package/dist/lib/index.d.ts +2 -1
  93. package/dist/lib/index.js +2 -2
  94. package/dist/lib/index.js.map +1 -1
  95. package/dist/lib/input-format.d.ts +89 -117
  96. package/dist/lib/input-format.js +89 -117
  97. package/dist/lib/input-format.js.map +1 -1
  98. package/dist/lib/io-context.d.ts +209 -241
  99. package/dist/lib/io-context.js +220 -252
  100. package/dist/lib/io-context.js.map +1 -1
  101. package/dist/lib/log.d.ts +85 -119
  102. package/dist/lib/log.js +85 -122
  103. package/dist/lib/log.js.map +1 -1
  104. package/dist/lib/native-types.d.ts +117 -106
  105. package/dist/lib/native-types.js +0 -7
  106. package/dist/lib/native-types.js.map +1 -1
  107. package/dist/lib/option.d.ts +284 -241
  108. package/dist/lib/option.js +309 -249
  109. package/dist/lib/option.js.map +1 -1
  110. package/dist/lib/output-format.d.ts +77 -101
  111. package/dist/lib/output-format.js +77 -101
  112. package/dist/lib/output-format.js.map +1 -1
  113. package/dist/lib/packet.d.ts +172 -240
  114. package/dist/lib/packet.js +172 -241
  115. package/dist/lib/packet.js.map +1 -1
  116. package/dist/lib/rational.d.ts +0 -2
  117. package/dist/lib/rational.js +0 -2
  118. package/dist/lib/rational.js.map +1 -1
  119. package/dist/lib/software-resample-context.d.ts +241 -325
  120. package/dist/lib/software-resample-context.js +242 -326
  121. package/dist/lib/software-resample-context.js.map +1 -1
  122. package/dist/lib/software-scale-context.d.ts +129 -173
  123. package/dist/lib/software-scale-context.js +131 -175
  124. package/dist/lib/software-scale-context.js.map +1 -1
  125. package/dist/lib/stream.d.ts +87 -197
  126. package/dist/lib/stream.js +87 -197
  127. package/dist/lib/stream.js.map +1 -1
  128. package/dist/lib/utilities.d.ts +372 -181
  129. package/dist/lib/utilities.js +373 -182
  130. package/dist/lib/utilities.js.map +1 -1
  131. package/install/check.js +0 -1
  132. package/package.json +21 -12
  133. package/release_notes.md +43 -59
  134. package/CHANGELOG.md +0 -8
@@ -1,67 +1,42 @@
1
- /**
2
- * Filter - High-level wrapper for media filtering
3
- *
4
- * Implements FFmpeg CLI's filter graph behavior with proper hardware context handling.
5
- * Uses lazy initialization for hardware inputs: graph is built when first frame arrives
6
- * with hw_frames_ctx. For software inputs, initializes immediately.
7
- *
8
- * Handles filter graph creation, frame processing, and format conversion.
9
- * Supports complex filter chains and hardware-accelerated filters.
10
- *
11
- * @module api/filter
12
- */
13
1
  import { AVERROR_EOF, AVFILTER_FLAG_HWDEVICE, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_VIDEO } from '../constants/constants.js';
14
2
  import { AVERROR_EAGAIN, avGetSampleFmtName, avIsHardwarePixelFormat, FFmpegError, Filter, FilterGraph, FilterInOut, Frame } from '../lib/index.js';
15
3
  /**
16
- * High-level filter API for media processing.
4
+ * High-level filter API for audio and video processing.
17
5
  *
18
- * Provides a simplified interface for FFmpeg's filter system.
19
- * Supports both simple filter chains and complex filter graphs.
20
- * Handles automatic format negotiation and buffer management.
21
- *
22
- * The filter graph uses lazy initialization for hardware inputs - it's built when
23
- * the first frame arrives with hw_frames_ctx. This matches FFmpeg CLI behavior
24
- * for proper hardware context propagation.
6
+ * Provides simplified interface for applying FFmpeg filters to frames.
7
+ * Handles filter graph construction, frame buffering, and command control.
8
+ * Supports both software and hardware-accelerated filtering operations.
9
+ * Essential component for effects, transformations, and format conversions.
25
10
  *
26
11
  * @example
27
12
  * ```typescript
28
- * import { FilterAPI, Frame } from '@seydx/av/api';
29
- *
30
- * // Simple video filter from a stream
31
- * const videoStream = media.video();
32
- * const filter = await FilterAPI.create('scale=1280:720,format=yuv420p', videoStream);
13
+ * import { FilterAPI } from 'node-av/api';
33
14
  *
34
- * // Process frames
35
- * const outputFrame = await filter.process(inputFrame);
36
- * ```
15
+ * // Create video filter
16
+ * const filter = await FilterAPI.create('scale=1280:720', videoInfo);
37
17
  *
38
- * @example
39
- * ```typescript
40
- * // Hardware acceleration (decoder -> hw filter -> encoder)
41
- * const hw = await HardwareContext.auto();
42
- * const decoder = await Decoder.create(stream, { hardware: hw });
43
- * const filter = await FilterAPI.create('scale_vt=640:480', decoder.getOutputStreamInfo(), {
44
- * hardware: hw
45
- * });
18
+ * // Process frame
19
+ * const output = await filter.process(inputFrame);
20
+ * if (output) {
21
+ * console.log(`Filtered frame: ${output.width}x${output.height}`);
22
+ * output.free();
23
+ * }
46
24
  * ```
47
25
  *
48
26
  * @example
49
27
  * ```typescript
50
- * // Software decode -> hardware encode pipeline with hwupload
51
- * const decoder = await Decoder.create(stream);
52
- * const hw = await HardwareContext.auto();
53
- * const filter = await FilterAPI.create('format=nv12,hwupload', decoder.getOutputStreamInfo(), {
54
- * hardware: hw // Required for hwupload to create hw_frames_ctx
55
- * });
28
+ * // Hardware-accelerated filtering
29
+ * const hw = HardwareContext.auto();
30
+ * const filter = await FilterAPI.create(
31
+ * 'hwupload,scale_cuda=1920:1080,hwdownload',
32
+ * videoInfo,
33
+ * { hardware: hw }
34
+ * );
56
35
  * ```
57
36
  *
58
- * @example
59
- * ```typescript
60
- * // Hardware decode -> software encode pipeline with hwdownload
61
- * const hw = await HardwareContext.auto();
62
- * const decoder = await Decoder.create(stream, { hardware: hw });
63
- * const filter = await FilterAPI.create('hwdownload,format=yuv420p', decoder.getOutputStreamInfo());
64
- * ```
37
+ * @see {@link FilterGraph} For low-level filter graph API
38
+ * @see {@link HardwareContext} For hardware acceleration
39
+ * @see {@link Frame} For frame operations
65
40
  */
66
41
  export class FilterAPI {
67
42
  graph = null;
@@ -74,11 +49,9 @@ export class FilterAPI {
74
49
  description;
75
50
  options;
76
51
  /**
77
- * Create a new Filter instance.
78
- *
79
- * @param config - Stream information from input stream
80
- * @param description - Filter graph description
81
- * @param options - Filter options including hardware context
52
+ * @param config - Stream configuration
53
+ * @param description - Filter description string
54
+ * @param options - Filter options
82
55
  * @internal
83
56
  */
84
57
  constructor(config, description, options) {
@@ -89,52 +62,48 @@ export class FilterAPI {
89
62
  this.mediaType = config.type === 'video' ? AVMEDIA_TYPE_VIDEO : AVMEDIA_TYPE_AUDIO;
90
63
  }
91
64
  /**
92
- * Create a filter from a filter description string.
65
+ * Create a filter with specified description and configuration.
93
66
  *
94
- * Accepts either a Stream (from MediaInput/Decoder) or StreamInfo (for raw data).
95
- * Automatically sets up buffer source and sink filters.
67
+ * Constructs filter graph from description string.
68
+ * Configures input/output buffers and threading.
69
+ * For video filters, uses lazy initialization to detect hardware frames.
96
70
  *
97
- * For hardware input formats: Uses lazy initialization, waits for first frame
98
- * with hw_frames_ctx before configuring the filter graph.
99
- * For software formats: Initializes immediately.
71
+ * Direct mapping to avfilter_graph_parse_ptr() and avfilter_graph_config().
100
72
  *
101
- * Hardware context handling:
102
- * - hwupload: Requires hardware context, creates its own hw_frames_ctx
103
- * - hwdownload: Uses hw_frames_ctx propagated from previous filters
104
- * - Other HW filters: Use propagated hw_frames_ctx or hwupload's output
105
- *
106
- * @param description - Filter graph description (e.g., "scale=1280:720" or complex chains)
107
- * @param input - Stream or StreamInfo describing the input
108
- * @param options - Optional filter options including hardware context
109
- *
110
- * @returns Promise resolving to configured Filter instance
73
+ * @param description - Filter graph description
74
+ * @param input - Input stream configuration
75
+ * @param options - Filter options
76
+ * @returns Configured filter instance
111
77
  *
112
- * @throws {FFmpegError} If filter creation or configuration fails
113
- * @throws {Error} If hardware filter requires hardware context but none provided
78
+ * @throws {Error} If filter creation or configuration fails
79
+ * @throws {FFmpegError} If graph parsing or config fails
114
80
  *
115
81
  * @example
116
82
  * ```typescript
117
- * // Simple filter
118
- * const filter = await FilterAPI.create('scale=640:480', videoStream);
83
+ * // Simple video filter
84
+ * const filter = await FilterAPI.create('scale=640:480', videoInfo);
85
+ * ```
119
86
  *
120
- * // Complex filter chain with hardware
121
- * const hw = await HardwareContext.auto();
122
- * const decoder = await Decoder.create(stream, { hardware: hw });
87
+ * @example
88
+ * ```typescript
89
+ * // Complex filter chain
123
90
  * const filter = await FilterAPI.create(
124
- * 'scale_vt=640:480,hwdownload,format=yuv420p',
125
- * decoder.getOutputStreamInfo(),
126
- * { hardware: hw }
91
+ * 'crop=640:480:0:0,rotate=PI/4',
92
+ * videoInfo
127
93
  * );
94
+ * ```
128
95
  *
129
- * // From StreamInfo (for raw data)
130
- * const filter = await FilterAPI.create('scale=640:480', {
131
- * type: 'video',
132
- * width: 1920,
133
- * height: 1080,
134
- * pixelFormat: AV_PIX_FMT_YUV420P,
135
- * timeBase: { num: 1, den: 30 }
136
- * });
96
+ * @example
97
+ * ```typescript
98
+ * // Audio filter
99
+ * const filter = await FilterAPI.create(
100
+ * 'volume=0.5,aecho=0.8:0.9:1000:0.3',
101
+ * audioInfo
102
+ * );
137
103
  * ```
104
+ *
105
+ * @see {@link process} For frame processing
106
+ * @see {@link FilterOptions} For configuration options
138
107
  */
139
108
  static async create(description, input, options = {}) {
140
109
  let config;
@@ -172,29 +141,44 @@ export class FilterAPI {
172
141
  return filter;
173
142
  }
174
143
  /**
175
- * Process a single frame through the filter.
176
- *
177
- * Sends a frame through the filter graph and returns the filtered result.
178
- * May return null if the filter needs more input frames.
144
+ * Process a frame through the filter.
179
145
  *
180
- * On first frame with hw_frames_ctx, initializes the filter graph (lazy initialization).
181
- * Subsequent frames are processed normally. FFmpeg automatically propagates
182
- * hw_frames_ctx through the filter chain.
146
+ * Applies filter operations to input frame.
147
+ * May buffer frames internally before producing output.
148
+ * For video, performs lazy initialization on first frame.
183
149
  *
184
- * @param frame - Input frame to filter
150
+ * Direct mapping to av_buffersrc_add_frame() and av_buffersink_get_frame().
185
151
  *
186
- * @returns Promise resolving to filtered frame or null if more input needed
152
+ * @param frame - Input frame to process
153
+ * @returns Filtered frame or null if buffered
187
154
  *
155
+ * @throws {Error} If filter not ready
188
156
  * @throws {FFmpegError} If processing fails
189
- * @throws {Error} If filter not initialized or hardware frame required but not provided
190
157
  *
191
158
  * @example
192
159
  * ```typescript
193
- * const outputFrame = await filter.process(inputFrame);
194
- * if (outputFrame) {
195
- * // Process the filtered frame
160
+ * const output = await filter.process(inputFrame);
161
+ * if (output) {
162
+ * console.log(`Got filtered frame: pts=${output.pts}`);
163
+ * output.free();
164
+ * }
165
+ * ```
166
+ *
167
+ * @example
168
+ * ```typescript
169
+ * // Process and drain
170
+ * const output = await filter.process(frame);
171
+ * if (output) yield output;
172
+ *
173
+ * // Drain buffered frames
174
+ * let buffered;
175
+ * while ((buffered = await filter.receive()) !== null) {
176
+ * yield buffered;
196
177
  * }
197
178
  * ```
179
+ *
180
+ * @see {@link receive} For draining buffered frames
181
+ * @see {@link frames} For stream processing
198
182
  */
199
183
  async process(frame) {
200
184
  // Lazy initialization for video filters (detect hardware from first frame)
@@ -214,7 +198,7 @@ export class FilterAPI {
214
198
  if (getRet >= 0) {
215
199
  return outputFrame;
216
200
  }
217
- else if (FFmpegError.is(getRet, AVERROR_EAGAIN)) {
201
+ else if (getRet === AVERROR_EAGAIN) {
218
202
  // Need more input
219
203
  outputFrame.free();
220
204
  return null;
@@ -226,21 +210,27 @@ export class FilterAPI {
226
210
  }
227
211
  }
228
212
  /**
229
- * Process multiple frames through the filter.
213
+ * Process multiple frames at once.
230
214
  *
231
- * Batch processing for better performance.
232
- * Returns all available output frames.
215
+ * Processes batch of frames and drains all output.
216
+ * Useful for filters that buffer multiple frames.
233
217
  *
234
218
  * @param frames - Array of input frames
219
+ * @returns Array of all output frames
235
220
  *
236
- * @returns Promise resolving to array of filtered frames
237
- *
221
+ * @throws {Error} If filter not ready
238
222
  * @throws {FFmpegError} If processing fails
239
223
  *
240
224
  * @example
241
225
  * ```typescript
242
- * const outputFrames = await filter.processMultiple(inputFrames);
226
+ * const outputs = await filter.processMultiple([frame1, frame2, frame3]);
227
+ * for (const output of outputs) {
228
+ * console.log(`Output frame: pts=${output.pts}`);
229
+ * output.free();
230
+ * }
243
231
  * ```
232
+ *
233
+ * @see {@link process} For single frame processing
244
234
  */
245
235
  async processMultiple(frames) {
246
236
  const outputFrames = [];
@@ -260,24 +250,30 @@ export class FilterAPI {
260
250
  return outputFrames;
261
251
  }
262
252
  /**
263
- * Receive a filtered frame without sending input.
253
+ * Receive buffered frame from filter.
254
+ *
255
+ * Drains frames buffered by the filter.
256
+ * Call repeatedly until null to get all buffered frames.
264
257
  *
265
- * Used to drain buffered frames from the filter.
266
- * Returns null when no more frames are available.
258
+ * Direct mapping to av_buffersink_get_frame().
267
259
  *
268
- * @returns Promise resolving to filtered frame or null
260
+ * @returns Buffered frame or null if none available
269
261
  *
270
- * @throws {FFmpegError} If receiving fails
262
+ * @throws {Error} If filter not ready
263
+ * @throws {FFmpegError} If receive fails
271
264
  *
272
265
  * @example
273
266
  * ```typescript
274
- * // Drain all buffered frames
275
- * while (true) {
276
- * const frame = await filter.receive();
277
- * if (!frame) break;
278
- * // Process frame
267
+ * // Drain buffered frames
268
+ * let frame;
269
+ * while ((frame = await filter.receive()) !== null) {
270
+ * console.log(`Buffered frame: pts=${frame.pts}`);
271
+ * frame.free();
279
272
  * }
280
273
  * ```
274
+ *
275
+ * @see {@link process} For input processing
276
+ * @see {@link flush} For end-of-stream
281
277
  */
282
278
  async receive() {
283
279
  if (!this.initialized || !this.buffersinkCtx) {
@@ -291,7 +287,7 @@ export class FilterAPI {
291
287
  }
292
288
  else {
293
289
  frame.free();
294
- if (FFmpegError.is(ret, AVERROR_EAGAIN) || FFmpegError.is(ret, AVERROR_EOF)) {
290
+ if (ret === AVERROR_EAGAIN || ret === AVERROR_EOF) {
295
291
  return null;
296
292
  }
297
293
  FFmpegError.throwIfError(ret, 'Failed to receive frame from filter');
@@ -299,53 +295,58 @@ export class FilterAPI {
299
295
  }
300
296
  }
301
297
  /**
302
- * Flush the filter by sending null frame.
298
+ * Flush filter and signal end-of-stream.
303
299
  *
304
- * Signals end of stream to the filter.
305
- * Use receive() to get any remaining frames.
300
+ * Sends null frame to flush buffered data.
301
+ * Must call receive() to get flushed frames.
306
302
  *
307
- * @returns Promise resolving when flush is complete
303
+ * Direct mapping to av_buffersrc_add_frame(NULL).
308
304
  *
305
+ * @throws {Error} If filter not ready
309
306
  * @throws {FFmpegError} If flush fails
310
307
  *
311
308
  * @example
312
309
  * ```typescript
313
310
  * await filter.flush();
314
311
  * // Get remaining frames
315
- * while (true) {
316
- * const frame = await filter.receive();
317
- * if (!frame) break;
318
- * // Process final frames
312
+ * let frame;
313
+ * while ((frame = await filter.receive()) !== null) {
314
+ * frame.free();
319
315
  * }
320
316
  * ```
317
+ *
318
+ * @see {@link flushFrames} For async iteration
319
+ * @see {@link receive} For draining frames
321
320
  */
322
321
  async flush() {
323
322
  if (!this.initialized || !this.buffersrcCtx) {
324
323
  throw new Error('Filter not initialized');
325
324
  }
326
325
  const ret = await this.buffersrcCtx.buffersrcAddFrame(null);
327
- if (ret < 0 && !FFmpegError.is(ret, AVERROR_EOF)) {
326
+ if (ret < 0 && ret !== AVERROR_EOF) {
328
327
  FFmpegError.throwIfError(ret, 'Failed to flush filter');
329
328
  }
330
329
  }
331
330
  /**
332
- * Flush filter and yield all remaining frames as a generator.
331
+ * Flush filter and yield remaining frames.
333
332
  *
334
- * More convenient than calling flush() + receive() in a loop.
335
- * Automatically sends flush signal and yields all buffered frames.
333
+ * Convenient async generator for flushing.
334
+ * Combines flush and receive operations.
336
335
  *
337
- * @returns Async generator of remaining frames
338
- *
339
- * @throws {Error} If filter is not initialized
336
+ * @yields Remaining frames from filter
337
+ * @throws {Error} If filter not ready
338
+ * @throws {FFmpegError} If flush fails
340
339
  *
341
340
  * @example
342
341
  * ```typescript
343
- * // Process all remaining frames with generator
344
342
  * for await (const frame of filter.flushFrames()) {
345
- * // Process final frame
346
- * using _ = frame; // Auto cleanup
343
+ * console.log(`Flushed frame: pts=${frame.pts}`);
344
+ * frame.free();
347
345
  * }
348
346
  * ```
347
+ *
348
+ * @see {@link flush} For manual flush
349
+ * @see {@link frames} For complete pipeline
349
350
  */
350
351
  async *flushFrames() {
351
352
  if (!this.initialized || !this.buffersrcCtx) {
@@ -360,26 +361,40 @@ export class FilterAPI {
360
361
  }
361
362
  }
362
363
  /**
363
- * Process frames as an async generator.
364
+ * Process frame stream through filter.
364
365
  *
365
- * Provides a convenient iterator interface for filtering.
366
- * Automatically handles buffering and draining.
367
- * Input frames are automatically freed after processing.
366
+ * High-level async generator for filtering frame streams.
367
+ * Automatically handles buffering and flushing.
368
+ * Frees input frames after processing.
368
369
  *
369
- * IMPORTANT: The yielded frames MUST be freed by the caller!
370
- * Input frames are automatically freed after processing.
371
- *
372
- * @param frames - Async generator of input frames (will be freed automatically)
370
+ * @param frames - Async generator of input frames
371
+ * @yields Filtered frames
372
+ * @throws {Error} If filter not ready
373
+ * @throws {FFmpegError} If processing fails
373
374
  *
374
- * @returns Async generator of filtered frames (ownership transferred to caller)
375
+ * @example
376
+ * ```typescript
377
+ * // Filter decoded frames
378
+ * for await (const frame of filter.frames(decoder.frames(packets))) {
379
+ * await encoder.encode(frame);
380
+ * frame.free();
381
+ * }
382
+ * ```
375
383
  *
376
384
  * @example
377
385
  * ```typescript
378
- * for await (const filtered of filter.frames(decoder.frames())) {
379
- * // Process filtered frame
380
- * using _ = filtered; // Auto cleanup with using statement
386
+ * // Chain filters
387
+ * const filter1 = await FilterAPI.create('scale=640:480', info);
388
+ * const filter2 = await FilterAPI.create('rotate=PI/4', info);
389
+ *
390
+ * for await (const frame of filter2.frames(filter1.frames(input))) {
391
+ * // Process filtered frames
392
+ * frame.free();
381
393
  * }
382
394
  * ```
395
+ *
396
+ * @see {@link process} For single frame processing
397
+ * @see {@link flush} For end-of-stream handling
383
398
  */
384
399
  async *frames(frames) {
385
400
  for await (const frame of frames) {
@@ -412,32 +427,30 @@ export class FilterAPI {
412
427
  }
413
428
  }
414
429
  /**
415
- * Send a command to a filter in the graph.
430
+ * Send command to filter.
431
+ *
432
+ * Sends runtime command to specific filter in graph.
433
+ * Allows dynamic parameter adjustment.
416
434
  *
417
- * Allows runtime modification of filter parameters without recreating the graph.
418
- * Not all filters support commands - check filter documentation.
435
+ * Direct mapping to avfilter_graph_send_command().
419
436
  *
420
- * @param target - Filter name or "all" to send to all filters
421
- * @param cmd - Command name (e.g., "volume", "hue", "brightness")
422
- * @param arg - Command argument value
423
- * @param flags - Optional command flags
437
+ * @param target - Target filter name
438
+ * @param cmd - Command name
439
+ * @param arg - Command argument
440
+ * @param flags - Command flags
441
+ * @returns Response string from filter
424
442
  *
425
- * @returns Command response
443
+ * @throws {Error} If filter not ready
444
+ * @throws {FFmpegError} If command fails
426
445
  *
427
446
  * @example
428
447
  * ```typescript
429
- * // Change volume dynamically
448
+ * // Change volume at runtime
430
449
  * const response = filter.sendCommand('volume', 'volume', '0.5');
431
- * if (response) {
432
- * console.log('Volume changed successfully');
433
- * }
450
+ * console.log(`Volume changed: ${response}`);
434
451
  * ```
435
452
  *
436
- * @example
437
- * ```typescript
438
- * // Enable/disable all filters at runtime
439
- * filter.sendCommand('all', 'enable', 'expr=gte(t,10)');
440
- * ```
453
+ * @see {@link queueCommand} For delayed commands
441
454
  */
442
455
  sendCommand(target, cmd, arg, flags) {
443
456
  if (!this.initialized || !this.graph) {
@@ -450,24 +463,28 @@ export class FilterAPI {
450
463
  return result.response;
451
464
  }
452
465
  /**
453
- * Queue a command to be executed at a specific time.
466
+ * Queue command for later execution.
467
+ *
468
+ * Schedules command to execute at specific timestamp.
469
+ * Useful for synchronized parameter changes.
454
470
  *
455
- * Commands are executed when processing frames with matching timestamps.
456
- * Useful for scripted filter changes synchronized with media playback.
471
+ * Direct mapping to avfilter_graph_queue_command().
457
472
  *
458
- * @param target - Filter name or "all" to send to all filters
459
- * @param cmd - Command name (e.g., "volume", "hue", "brightness")
460
- * @param arg - Command argument value
461
- * @param ts - Timestamp when command should execute (in seconds)
462
- * @param flags - Optional command flags
473
+ * @param target - Target filter name
474
+ * @param cmd - Command name
475
+ * @param arg - Command argument
476
+ * @param ts - Timestamp for execution
477
+ * @param flags - Command flags
478
+ * @throws {Error} If filter not ready
479
+ * @throws {FFmpegError} If queue fails
463
480
  *
464
481
  * @example
465
482
  * ```typescript
466
- * // Schedule volume changes at specific times
467
- * filter.queueCommand('volume', 'volume', '0.5', 5.0); // At 5 seconds
468
- * filter.queueCommand('volume', 'volume', '0.8', 10.0); // At 10 seconds
469
- * filter.queueCommand('volume', 'volume', '0.2', 15.0); // At 15 seconds
483
+ * // Queue volume change at 10 seconds
484
+ * filter.queueCommand('volume', 'volume', '0.8', 10.0);
470
485
  * ```
486
+ *
487
+ * @see {@link sendCommand} For immediate commands
471
488
  */
472
489
  queueCommand(target, cmd, arg, ts, flags) {
473
490
  if (!this.initialized || !this.graph) {
@@ -477,17 +494,19 @@ export class FilterAPI {
477
494
  FFmpegError.throwIfError(ret, 'Failed to queue filter command');
478
495
  }
479
496
  /**
480
- * Get the filter graph description.
497
+ * Get filter graph description.
481
498
  *
482
- * Returns a string representation of the filter graph in DOT format.
483
- * Useful for debugging and visualization.
499
+ * Returns human-readable graph structure.
500
+ * Useful for debugging filter chains.
501
+ *
502
+ * Direct mapping to avfilter_graph_dump().
484
503
  *
485
504
  * @returns Graph description or null if not initialized
486
505
  *
487
506
  * @example
488
507
  * ```typescript
489
508
  * const description = filter.getGraphDescription();
490
- * console.log(description);
509
+ * console.log('Filter graph:', description);
491
510
  * ```
492
511
  */
493
512
  getGraphDescription() {
@@ -497,32 +516,47 @@ export class FilterAPI {
497
516
  return this.graph.dump();
498
517
  }
499
518
  /**
500
- * Check if the filter is initialized and ready.
519
+ * Check if filter is ready for processing.
520
+ *
521
+ * @returns true if initialized and ready
501
522
  *
502
- * @returns true if the filter is ready for processing
523
+ * @example
524
+ * ```typescript
525
+ * if (filter.isReady()) {
526
+ * const output = await filter.process(frame);
527
+ * }
528
+ * ```
503
529
  */
504
530
  isReady() {
505
531
  return this.initialized && this.buffersrcCtx !== null && this.buffersinkCtx !== null;
506
532
  }
507
533
  /**
508
- * Get the media type of this filter.
534
+ * Get media type of filter.
535
+ *
536
+ * @returns AVMEDIA_TYPE_VIDEO or AVMEDIA_TYPE_AUDIO
509
537
  *
510
- * @returns The media type (video or audio)
538
+ * @example
539
+ * ```typescript
540
+ * if (filter.getMediaType() === AVMEDIA_TYPE_VIDEO) {
541
+ * console.log('Video filter');
542
+ * }
543
+ * ```
511
544
  */
512
545
  getMediaType() {
513
546
  return this.mediaType;
514
547
  }
515
548
  /**
516
- * Free all filter resources.
549
+ * Free filter resources.
517
550
  *
518
- * Releases the filter graph and all associated filters.
519
- * The filter instance cannot be used after calling this.
551
+ * Releases filter graph and contexts.
552
+ * Safe to call multiple times.
520
553
  *
521
554
  * @example
522
555
  * ```typescript
523
556
  * filter.free();
524
- * // filter is now invalid
525
557
  * ```
558
+ *
559
+ * @see {@link Symbol.dispose} For automatic cleanup
526
560
  */
527
561
  free() {
528
562
  if (this.graph) {
@@ -534,15 +568,14 @@ export class FilterAPI {
534
568
  this.initialized = false;
535
569
  }
536
570
  /**
537
- * Initialize the filter graph.
571
+ * Initialize filter graph.
538
572
  *
539
- * Sets up buffer source, buffer sink, and parses the filter description.
540
- * Configures the graph for processing.
573
+ * Creates and configures filter graph components.
574
+ * For video, may use hardware frames context from first frame.
541
575
  *
542
- * For hardware inputs: Uses hw_frames_ctx from first frame
543
- * For software inputs: Initializes without hw_frames_ctx
544
- *
545
- * @internal
576
+ * @param firstFrame - First frame for hardware detection (video only)
577
+ * @throws {Error} If initialization fails
578
+ * @throws {FFmpegError} If configuration fails
546
579
  */
547
580
  async initialize(firstFrame) {
548
581
  // Create graph
@@ -587,7 +620,9 @@ export class FilterAPI {
587
620
  /**
588
621
  * Create buffer source with hardware frames context.
589
622
  *
590
- * @internal
623
+ * @param frame - Frame with hw_frames_ctx
624
+ * @throws {Error} If creation fails
625
+ * @throws {FFmpegError} If configuration fails
591
626
  */
592
627
  createBufferSourceWithHwFrames(frame) {
593
628
  const filterName = 'buffer';
@@ -617,9 +652,9 @@ export class FilterAPI {
617
652
  FFmpegError.throwIfError(initRet, 'Failed to initialize buffer source');
618
653
  }
619
654
  /**
620
- * Create and configure the buffer source filter without hw_frames_ctx.
655
+ * Create standard buffer source.
621
656
  *
622
- * @internal
657
+ * @throws {Error} If creation fails
623
658
  */
624
659
  createBufferSource() {
625
660
  const filterName = this.config.type === 'video' ? 'buffer' : 'abuffer';
@@ -651,9 +686,9 @@ export class FilterAPI {
651
686
  }
652
687
  }
653
688
  /**
654
- * Create and configure the buffer sink filter.
689
+ * Create buffer sink.
655
690
  *
656
- * @internal
691
+ * @throws {Error} If creation fails
657
692
  */
658
693
  createBufferSink() {
659
694
  if (!this.graph) {
@@ -670,9 +705,11 @@ export class FilterAPI {
670
705
  }
671
706
  }
672
707
  /**
673
- * Parse and connect the filter description.
708
+ * Parse filter description and build graph.
674
709
  *
675
- * @internal
710
+ * @param description - Filter description string
711
+ * @throws {Error} If parsing fails
712
+ * @throws {FFmpegError} If graph construction fails
676
713
  */
677
714
  parseFilterDescription(description) {
678
715
  if (!this.graph) {
@@ -707,14 +744,11 @@ export class FilterAPI {
707
744
  outputs.free();
708
745
  }
709
746
  /**
710
- * Check if hardware context is required for the filter chain.
711
- *
712
- * Validates that hardware context is provided when needed:
713
- * - hwupload: Always requires hardware context
714
- * - Hardware filters (AVFILTER_FLAG_HWDEVICE): Recommend hardware context
715
- * - hwdownload: Warns if input is not hardware format
747
+ * Check hardware requirements for filters.
716
748
  *
717
- * @internal
749
+ * @param description - Filter description
750
+ * @param options - Filter options
751
+ * @throws {Error} If hardware requirements not met
718
752
  */
719
753
  checkHardwareRequirements(description, options) {
720
754
  if (this.config.type !== 'video') {
@@ -743,47 +777,23 @@ export class FilterAPI {
743
777
  throw new Error(`Pixel Format '${this.config.pixelFormat}' is not hardware compatible`);
744
778
  }
745
779
  }
746
- // // Check if this is hwupload - always needs hardware context
747
- // if (filterName === 'hwupload' || filterName === 'hwupload_cuda') {
748
- // if (!options.hardware) {
749
- // throw new Error(`Filter '${filterName}' requires a hardware context`);
750
- // }
751
- // } else if (filterName === 'hwdownload') {
752
- // // Check if this is hwdownload - warn if input is not hardware format
753
- // if (this.config.type === 'video' && !avIsHardwarePixelFormat(this.config.pixelFormat)) {
754
- // // prettier-ignore
755
- // console.warn(
756
- // `Warning: 'hwdownload' filter used with software input format (${this.config.pixelFormat}). ` +
757
- // 'This will likely fail at runtime. hwdownload expects hardware frames as input. ' +
758
- // 'Consider removing hwdownload from your filter chain or ensuring hardware input.',
759
- // );
760
- // }
761
- // } else if ((lowLevelFilter.flags & AVFILTER_FLAG_HWDEVICE) !== 0) {
762
- // // Check if this is a hardware filter
763
- // if (!options.hardware) {
764
- // // prettier-ignore
765
- // console.warn(
766
- // `Warning: Hardware filter '${filterName}' used without hardware context. ` +
767
- // "This may work if hw_frames_ctx is propagated from input, but it's recommended " +
768
- // 'to pass { hardware: HardwareContext } in filter options.',
769
- // );
770
- // }
771
- // }
772
780
  }
773
781
  }
774
782
  /**
775
- * Dispose of the filter.
783
+ * Dispose of filter.
776
784
  *
777
- * Implements the Disposable interface for automatic cleanup.
785
+ * Implements Disposable interface for automatic cleanup.
778
786
  * Equivalent to calling free().
779
787
  *
780
788
  * @example
781
789
  * ```typescript
782
790
  * {
783
- * using filter = await Filter.create('scale=1280:720', config);
784
- * // ... use filter
785
- * } // Automatically freed when leaving scope
791
+ * using filter = await FilterAPI.create('scale=640:480', info);
792
+ * // Use filter...
793
+ * } // Automatically freed
786
794
  * ```
795
+ *
796
+ * @see {@link free} For manual cleanup
787
797
  */
788
798
  [Symbol.dispose]() {
789
799
  this.free();