node-av 5.0.4 → 5.1.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 (43) hide show
  1. package/BENCHMARK.md +113 -0
  2. package/README.md +59 -1
  3. package/dist/api/bitstream-filter.d.ts +190 -74
  4. package/dist/api/bitstream-filter.js +276 -222
  5. package/dist/api/bitstream-filter.js.map +1 -1
  6. package/dist/api/decoder.d.ts +23 -29
  7. package/dist/api/decoder.js +37 -47
  8. package/dist/api/decoder.js.map +1 -1
  9. package/dist/api/encoder.d.ts +4 -4
  10. package/dist/api/encoder.js +16 -20
  11. package/dist/api/encoder.js.map +1 -1
  12. package/dist/api/filter-complex.js +7 -3
  13. package/dist/api/filter-complex.js.map +1 -1
  14. package/dist/api/filter.d.ts +4 -4
  15. package/dist/api/filter.js +18 -16
  16. package/dist/api/filter.js.map +1 -1
  17. package/dist/api/hardware.d.ts +17 -0
  18. package/dist/api/hardware.js +73 -27
  19. package/dist/api/hardware.js.map +1 -1
  20. package/dist/api/muxer.js +8 -4
  21. package/dist/api/muxer.js.map +1 -1
  22. package/dist/api/rtp-stream.js +0 -6
  23. package/dist/api/rtp-stream.js.map +1 -1
  24. package/dist/api/types.d.ts +29 -9
  25. package/dist/constants/constants.d.ts +6 -3
  26. package/dist/constants/constants.js +5 -5
  27. package/dist/constants/constants.js.map +1 -1
  28. package/dist/ffmpeg/index.js +3 -4
  29. package/dist/ffmpeg/index.js.map +1 -1
  30. package/dist/lib/binding.js +3 -4
  31. package/dist/lib/binding.js.map +1 -1
  32. package/dist/lib/codec-context.d.ts +5 -3
  33. package/dist/lib/codec-context.js +2 -0
  34. package/dist/lib/codec-context.js.map +1 -1
  35. package/dist/lib/native-types.d.ts +2 -2
  36. package/dist/utils/electron.d.ts +49 -0
  37. package/dist/utils/electron.js +63 -0
  38. package/dist/utils/electron.js.map +1 -0
  39. package/dist/utils/index.d.ts +4 -0
  40. package/dist/utils/index.js +5 -0
  41. package/dist/utils/index.js.map +1 -0
  42. package/package.json +32 -15
  43. package/binding.gyp +0 -166
package/BENCHMARK.md ADDED
@@ -0,0 +1,113 @@
1
+ # node-av Benchmark Results
2
+
3
+ > Generated: 2026-01-27T18:12:31.045Z
4
+
5
+ ## System Information
6
+
7
+ | Property | Value |
8
+ |----------|-------|
9
+ | **OS** | darwin 25.1.0 |
10
+ | **Architecture** | arm64 |
11
+ | **CPU** | Apple M3 Max |
12
+ | **CPU Cores** | 16 |
13
+ | **RAM** | 48.0 GB |
14
+ | **GPU** | Apple M3 Max |
15
+ | **Node.js** | v24.8.0 |
16
+ | **FFmpeg** | 8.0-Jellyfin |
17
+ | **node-av** | 5.0.4 |
18
+
19
+
20
+ ## Test Inputs
21
+
22
+ | File | Codec | Resolution | FPS | Duration |
23
+ |------|-------|------------|-----|----------|
24
+ | bbb-4k-av1.mp4 | av1 | 3840x2160 | 60 | 30.0s |
25
+ | bbb-4k-h264.mp4 | h264 | 3840x2160 | 60 | 30.0s |
26
+ | bbb-4k-hevc.mp4 | hevc | 3840x2160 | 60 | 30.0s |
27
+ | bbb-4k-vp9.webm | vp9 | 3840x2160 | 60 | 30.0s |
28
+
29
+
30
+ ## Transcode Speed
31
+
32
+ ### Input: bbb-4k-av1.mp4
33
+
34
+ | Test | FFmpeg CLI (FPS) | node-av (FPS) | FFmpeg CLI (Time) | node-av (Time) | Diff |
35
+ |------|------------------|---------------|-------------------|----------------|------|
36
+ | SW H.264 Transcode | 95.1 fps | 94.5 fps | 18.93s | 19.06s | -0.6% |
37
+ | SW H.265 Transcode | 38.9 fps | 40.1 fps | 46.31s | 44.93s | +3.2% |
38
+ | HW H.264 Transcode | 54.6 fps | 54.9 fps | 32.98s | 32.79s | +0.6% |
39
+ | Stream Copy (Remux) | 50307.7 fps | 29472.5 fps | 36ms | 109ms | -41.4% |
40
+
41
+ ### Input: bbb-4k-h264.mp4
42
+
43
+ | Test | FFmpeg CLI (FPS) | node-av (FPS) | FFmpeg CLI (Time) | node-av (Time) | Diff |
44
+ |------|------------------|---------------|-------------------|----------------|------|
45
+ | SW H.264 Transcode | 96.5 fps | 97.2 fps | 18.65s | 18.52s | +0.7% |
46
+ | SW H.265 Transcode | 40.0 fps | 39.9 fps | 45.00s | 45.12s | -0.2% |
47
+ | HW H.264 Transcode | 54.6 fps | 54.9 fps | 32.98s | 32.83s | +0.5% |
48
+ | Stream Copy (Remux) | 38235.1 fps | 30884.8 fps | 48ms | 104ms | -19.2% |
49
+
50
+ ### Input: bbb-4k-hevc.mp4
51
+
52
+ | Test | FFmpeg CLI (FPS) | node-av (FPS) | FFmpeg CLI (Time) | node-av (Time) | Diff |
53
+ |------|------------------|---------------|-------------------|----------------|------|
54
+ | SW H.264 Transcode | 96.3 fps | 95.9 fps | 18.70s | 18.78s | -0.4% |
55
+ | SW H.265 Transcode | 41.4 fps | 41.8 fps | 43.49s | 43.06s | +1.0% |
56
+ | HW H.264 Transcode | 54.5 fps | 54.9 fps | 33.00s | 32.82s | +0.6% |
57
+ | Stream Copy (Remux) | 55110.3 fps | 30395.4 fps | 33ms | 106ms | -44.8% |
58
+
59
+ ### Input: bbb-4k-vp9.webm
60
+
61
+ | Test | FFmpeg CLI (FPS) | node-av (FPS) | FFmpeg CLI (Time) | node-av (Time) | Diff |
62
+ |------|------------------|---------------|-------------------|----------------|------|
63
+ | SW H.264 Transcode | 96.5 fps | 96.1 fps | 18.66s | 18.75s | -0.4% |
64
+ | SW H.265 Transcode | 41.6 fps | 42.0 fps | 43.23s | 42.90s | +0.8% |
65
+ | HW H.264 Transcode | 54.6 fps | 54.8 fps | 32.98s | 32.85s | +0.4% |
66
+ | Stream Copy (Remux) | 48635.3 fps | 31617.3 fps | 37ms | 105ms | -35.0% |
67
+
68
+
69
+ ## Memory Usage
70
+
71
+ ### Input: bbb-4k-av1.mp4
72
+
73
+ | Test | FFmpeg CLI Peak | node-av Peak | Difference |
74
+ |------|----------------|--------------|------------|
75
+ | Memory: H.264 Transcode | 3.5 GB | 3.5 GB | -2.0% |
76
+ | Memory: Stream Copy | 19.9 MB | 1.1 MB | -94.6% |
77
+
78
+ ### Input: bbb-4k-h264.mp4
79
+
80
+ | Test | FFmpeg CLI Peak | node-av Peak | Difference |
81
+ |------|----------------|--------------|------------|
82
+ | Memory: H.264 Transcode | 3.7 GB | 3.4 GB | -6.0% |
83
+ | Memory: Stream Copy | 40.7 MB | 1.9 MB | -95.2% |
84
+
85
+ ### Input: bbb-4k-hevc.mp4
86
+
87
+ | Test | FFmpeg CLI Peak | node-av Peak | Difference |
88
+ |------|----------------|--------------|------------|
89
+ | Memory: H.264 Transcode | 3.8 GB | 3.5 GB | -5.4% |
90
+ | Memory: Stream Copy | 20.5 MB | 320.0 KB | -98.5% |
91
+
92
+ ### Input: bbb-4k-vp9.webm
93
+
94
+ | Test | FFmpeg CLI Peak | node-av Peak | Difference |
95
+ |------|----------------|--------------|------------|
96
+ | Memory: H.264 Transcode | 3.5 GB | 3.3 GB | -4.7% |
97
+ | Memory: Stream Copy | 30.6 MB | 378.7 KB | -98.8% |
98
+
99
+ *Note: FFmpeg CLI memory is measured via `/usr/bin/time` (macOS: `-l`, Linux: `-v`).
100
+
101
+
102
+ ## Latency
103
+
104
+ | Metric | Mean | Min | Max | StdDev |
105
+ |--------|------|-----|-----|--------|
106
+ | Demuxer Open | 466µs | 430µs | 561µs | 41µs |
107
+ | First Packet | 530µs | 491µs | 575µs | 30µs |
108
+ | First Frame | 11.7ms | 7.2ms | 15.2ms | 2.7ms |
109
+ | First Encoded Packet | 25.2ms | 23.9ms | 26.9ms | 856µs |
110
+ | Pipeline Total | 25.8ms | 22.5ms | 27.3ms | 1.4ms |
111
+
112
+ *Note: Each metric is measured independently. "First Encoded Packet" uses default encoder settings while "Pipeline Total" uses `tune=zerolatency` for low-latency output.*
113
+
package/README.md CHANGED
@@ -35,8 +35,10 @@ Native Node.js bindings for FFmpeg with full TypeScript support. Provides direct
35
35
  - [Resource Management](#resource-management)
36
36
  - [FFmpeg Binary Access](#ffmpeg-binary-access)
37
37
  - [Performance](#performance)
38
+ - [Benchmarks](#benchmarks)
38
39
  - [Sync vs Async Operations](#sync-vs-async-operations)
39
40
  - [Memory Safety Considerations](#memory-safety-considerations)
41
+ - [Electron](#electron)
40
42
  - [Examples](#examples)
41
43
  - [Prebuilt Binaries](#prebuilt-binaries)
42
44
  - [Troubleshooting](#troubleshooting)
@@ -426,6 +428,25 @@ The FFmpeg binary is automatically downloaded during installation from GitHub re
426
428
 
427
429
  NodeAV executes all media operations directly through FFmpeg's native C libraries. The Node.js bindings add minimal overhead - mostly just the JavaScript-to-C boundary crossings. During typical operations like transcoding or filtering, most processing time is spent in FFmpeg's optimized C code.
428
430
 
431
+ ### Benchmarks
432
+
433
+ Performance comparison with FFmpeg CLI (4K 60fps, 30s test files on Apple M3 Max):
434
+
435
+ | Operation | FFmpeg CLI (FPS) | node-av (FPS) | FFmpeg CLI (Time) | node-av (Time) | Diff |
436
+ |-----------|------------------|---------------|-------------------|----------------|------|
437
+ | SW H.264 Transcode | 96 fps | 96 fps | 18.7s | 18.7s | ≈0% |
438
+ | SW H.265 Transcode | 40 fps | 41 fps | 44.5s | 43.7s | **+1.5%** |
439
+ | HW H.264 Transcode | 55 fps | 55 fps | 33.0s | 32.8s | **+0.5%** |
440
+ | Stream Copy (Remux) | 48k fps | 31k fps | 38ms | 106ms | -35% |
441
+
442
+ **Memory Usage:**
443
+ | Operation | FFmpeg CLI | node-av | Difference |
444
+ |-----------|-----------|---------|------------|
445
+ | H.264 Transcode (4K) | 3.6 GB | 3.4 GB | **-5%** |
446
+ | Stream Copy | 28 MB | 1 MB | **-96%** |
447
+
448
+ 📊 **[Full benchmark results](https://github.com/seydx/node-av/tree/main/BENCHMARK.md)**
449
+
429
450
  ### Sync vs Async Operations
430
451
 
431
452
  Every async method in NodeAV has a corresponding synchronous variant with the `Sync` suffix:
@@ -440,6 +461,22 @@ The key difference: Async methods don't block the Node.js event loop, allowing o
440
461
 
441
462
  NodeAV provides direct bindings to FFmpeg's C APIs, which work with raw memory pointers. The high-level API adds safety abstractions and automatic resource management, but incorrect usage can still cause crashes. Common issues include mismatched video dimensions, incompatible pixel formats, or improper frame buffer handling. The library validates parameters where possible, but can't guarantee complete memory safety without limiting functionality. When using the low-level API, pay attention to parameter consistency, resource cleanup, and format compatibility. Following the documented patterns helps avoid memory-related issues.
442
463
 
464
+ ## Electron
465
+
466
+ NodeAV fully supports Electron applications. The prebuilt binaries are ABI-compatible with Electron, so no native rebuild is required during packaging. Both the native bindings and the bundled FFmpeg CLI binaries work seamlessly within Electron's main process.
467
+
468
+ Two complete examples are available: one using [Electron Forge](https://github.com/seydx/node-av/tree/main/examples/electron/forge) and one using [Electron Builder](https://github.com/seydx/node-av/tree/main/examples/electron/builder).
469
+
470
+ If you encounter module resolution errors like `Cannot find module 'lib/binary-stream'`, add this override to your project's `package.json`:
471
+
472
+ ```json
473
+ {
474
+ "overrides": {
475
+ "@shinyoshiaki/binary-data": "npm:@seydx/binary-data@0.6.2"
476
+ }
477
+ }
478
+ ```
479
+
443
480
  ## Examples
444
481
 
445
482
  | Example | FFmpeg | Low-Level API | High-Level API |
@@ -473,6 +510,7 @@ NodeAV provides direct bindings to FFmpeg's C APIs, which work with raw memory p
473
510
  | `api-whisper-transcribe` | | | [✓](https://github.com/seydx/node-av/tree/main/examples/api-whisper-transcribe.ts) |
474
511
  | `frame-utils` | | [✓](https://github.com/seydx/node-av/tree/main/examples/frame-utils.ts) | |
475
512
  | `avio-read-callback` | [✓](https://github.com/FFmpeg/FFmpeg/tree/master/doc/examples/avio_read_callback.c) | [✓](https://github.com/seydx/node-av/tree/main/examples/avio-read-callback.ts) | |
513
+ | `avio-async-read-callback` | | [✓](https://github.com/seydx/node-av/tree/main/examples/avio-async-read-callback.ts) | |
476
514
  | `decode-audio` | [✓](https://github.com/FFmpeg/FFmpeg/tree/master/doc/examples/decode_audio.c) | [✓](https://github.com/seydx/node-av/tree/main/examples/decode-audio.ts) | |
477
515
  | `decode-filter-audio` | [✓](https://github.com/FFmpeg/FFmpeg/tree/master/doc/examples/decode_filter_audio.c) | [✓](https://github.com/seydx/node-av/tree/main/examples/decode-filter-audio.ts) | |
478
516
  | `decode-filter-video` | [✓](https://github.com/FFmpeg/FFmpeg/tree/master/doc/examples/decode_filter_video.c) | [✓](https://github.com/seydx/node-av/tree/main/examples/decode-filter-video.ts) | |
@@ -498,7 +536,6 @@ NodeAV provides direct bindings to FFmpeg's C APIs, which work with raw memory p
498
536
  | `transcode-aac` | [✓](https://github.com/FFmpeg/FFmpeg/tree/master/doc/examples/transcode_aac.c) | [✓](https://github.com/seydx/node-av/tree/main/examples/transcode-aac.ts) | |
499
537
  | `transcode` | [✓](https://github.com/FFmpeg/FFmpeg/tree/master/doc/examples/transcode.c) | [✓](https://github.com/seydx/node-av/tree/main/examples/transcode.ts) | |
500
538
 
501
-
502
539
  ## Prebuilt Binaries
503
540
 
504
541
  Prebuilt binaries are available for multiple platforms:
@@ -562,3 +599,24 @@ For issues and questions, please use the GitHub issue tracker.
562
599
  - [FFmpeg Doxygen](https://ffmpeg.org/doxygen/trunk/)
563
600
  - [Jellyfin FFmpeg](https://github.com/seydx/jellyfin-ffmpeg)
564
601
  - [FFmpeg MSVC](https://github.com/seydx/ffmpeg-msvc-prebuilt)
602
+
603
+ ## Star History
604
+
605
+ <picture>
606
+ <source
607
+ media="(prefers-color-scheme: dark)"
608
+ srcset="
609
+ https://api.star-history.com/svg?repos=seydx/node-av&type=Date&theme=dark
610
+ "
611
+ />
612
+ <source
613
+ media="(prefers-color-scheme: light)"
614
+ srcset="
615
+ https://api.star-history.com/svg?repos=seydx/node-av&type=Date
616
+ "
617
+ />
618
+ <img
619
+ alt="Star History Chart"
620
+ src="https://api.star-history.com/svg?repos=seydx/node-av&type=Date"
621
+ />
622
+ </picture>
@@ -2,6 +2,7 @@ import { Packet } from '../lib/packet.js';
2
2
  import { Muxer } from './muxer.js';
3
3
  import { Scheduler, SchedulerControl } from './utilities/scheduler.js';
4
4
  import type { Stream } from '../lib/stream.js';
5
+ import type { BitstreamFilterOptions } from './types.js';
5
6
  /**
6
7
  * High-level bitstream filter for packet processing.
7
8
  *
@@ -74,6 +75,8 @@ export declare class BitStreamFilterAPI implements Disposable {
74
75
  *
75
76
  * @param stream - Stream to apply filter to
76
77
  *
78
+ * @param filterOptions - Optional filter-specific options
79
+ *
77
80
  * @returns Configured bitstream filter
78
81
  *
79
82
  * @throws {Error} If initialization fails
@@ -94,13 +97,24 @@ export declare class BitStreamFilterAPI implements Disposable {
94
97
  *
95
98
  * @example
96
99
  * ```typescript
97
- * // Remove metadata
98
- * const filter = BitStreamFilterAPI.create('filter_units', stream);
100
+ * // Remove AUDs from H.264 stream
101
+ * const filter = BitStreamFilterAPI.create('h264_metadata', stream, {
102
+ * options: { aud: 'remove' }
103
+ * });
104
+ * ```
105
+ *
106
+ * @example
107
+ * ```typescript
108
+ * // Set H.264 level
109
+ * const filter = BitStreamFilterAPI.create('h264_metadata', stream, {
110
+ * options: { level: 51 }
111
+ * });
99
112
  * ```
100
113
  *
101
114
  * @see {@link BitStreamFilter.getByName} For filter discovery
115
+ * @see {@link BitstreamFilterOptions} For available options
102
116
  */
103
- static create(filterName: string, stream: Stream): BitStreamFilterAPI;
117
+ static create(filterName: string, stream: Stream, filterOptions?: BitstreamFilterOptions): BitStreamFilterAPI;
104
118
  /**
105
119
  * Get filter name.
106
120
  *
@@ -147,74 +161,112 @@ export declare class BitStreamFilterAPI implements Disposable {
147
161
  */
148
162
  get isBitstreamFilterOpen(): boolean;
149
163
  /**
150
- * Filter a packet.
151
- *
152
- * Sends a packet to the filter and attempts to receive a filtered packet.
153
- * Handles internal buffering - may return null if more packets needed.
164
+ * Send a packet to the filter.
154
165
  *
155
- * **Note**: This method receives only ONE packet per call.
156
- * A single packet can produce multiple output packets (e.g., codec buffering).
157
- * To receive all packets from a packet, use {@link filterAll} or {@link packets} instead.
166
+ * Sends a packet to the filter for processing.
167
+ * Does not return filtered packets - use {@link receive} to retrieve packets.
168
+ * A single packet can produce zero, one, or multiple packets depending on filter.
158
169
  *
159
- * Direct mapping to av_bsf_send_packet() and av_bsf_receive_packet().
170
+ * **Important**: This method only SENDS the packet to the filter.
171
+ * You must call {@link receive} separately (potentially multiple times) to get filtered packets.
160
172
  *
161
- * @param packet - Packet to filter
173
+ * Direct mapping to av_bsf_send_packet().
162
174
  *
163
- * @returns Filtered packet, null if more data needed, or null if filter is closed
175
+ * @param packet - Packet to send to filter, or null to flush
164
176
  *
165
- * @throws {FFmpegError} If filtering fails
177
+ * @throws {FFmpegError} If sending fails
166
178
  *
167
179
  * @example
168
180
  * ```typescript
169
- * const outPacket = await filter.filter(inputPacket);
170
- * if (outPacket) {
171
- * console.log(`Filtered packet: pts=${outPacket.pts}`);
181
+ * // Send packet and receive filtered packets
182
+ * await filter.filter(inputPacket);
183
+ *
184
+ * // Receive all available filtered packets
185
+ * while (true) {
186
+ * const outPacket = await filter.receive();
187
+ * if (!outPacket) break;
188
+ * console.log(`Filtered packet with PTS: ${outPacket.pts}`);
172
189
  * await output.writePacket(outPacket);
173
190
  * outPacket.free();
174
191
  * }
175
192
  * ```
176
193
  *
177
- * @see {@link filterAll} For multiple packet filtering
178
- * @see {@link packets} For stream processing
194
+ * @example
195
+ * ```typescript
196
+ * for await (const packet of input.packets()) {
197
+ * // packet is null at end of stream - automatically flushes filter
198
+ * await filter.filter(packet);
199
+ *
200
+ * // Receive available filtered packets
201
+ * let outPacket;
202
+ * while ((outPacket = await filter.receive())) {
203
+ * await output.writePacket(outPacket);
204
+ * outPacket.free();
205
+ * }
206
+ * }
207
+ * ```
208
+ *
209
+ * @see {@link receive} For receiving filtered packets
210
+ * @see {@link filterAll} For combined send+receive operation
211
+ * @see {@link packets} For automatic packet iteration
179
212
  * @see {@link flush} For end-of-stream handling
180
213
  * @see {@link filterSync} For synchronous version
181
214
  */
182
- filter(packet: Packet): Promise<Packet | null>;
215
+ filter(packet: Packet | null): Promise<void>;
183
216
  /**
184
- * Filter a packet synchronously.
217
+ * Send a packet to the filter synchronously.
185
218
  * Synchronous version of filter.
186
219
  *
187
- * Sends a packet to the filter and attempts to receive a filtered packet.
188
- * Handles internal buffering - may return null if more packets needed.
220
+ * Sends a packet to the filter for processing.
221
+ * Does not return filtered packets - use {@link receiveSync} to retrieve packets.
222
+ * A single packet can produce zero, one, or multiple packets depending on filter.
189
223
  *
190
- * **Note**: This method receives only ONE packet per call.
191
- * A single packet can produce multiple output packets (e.g., codec buffering).
192
- * To receive all packets from a packet, use {@link filterAllSync} or {@link packetsSync} instead.
224
+ * **Important**: This method only SENDS the packet to the filter.
225
+ * You must call {@link receiveSync} separately (potentially multiple times) to get filtered packets.
193
226
  *
194
- * Direct mapping to av_bsf_send_packet() and av_bsf_receive_packet().
227
+ * Direct mapping to av_bsf_send_packet().
195
228
  *
196
- * @param packet - Packet to filter
229
+ * @param packet - Packet to send to filter, or null to flush
197
230
  *
198
- * @returns Filtered packet, null if more data needed, or null if filter is closed
199
- *
200
- * @throws {FFmpegError} If filtering fails
231
+ * @throws {FFmpegError} If sending fails
201
232
  *
202
233
  * @example
203
234
  * ```typescript
204
- * const outPacket = filter.filterSync(inputPacket);
205
- * if (outPacket) {
206
- * console.log(`Filtered packet: pts=${outPacket.pts}`);
235
+ * // Send packet and receive filtered packets
236
+ * filter.filterSync(inputPacket);
237
+ *
238
+ * // Receive all available filtered packets
239
+ * while (true) {
240
+ * const outPacket = filter.receiveSync();
241
+ * if (!outPacket) break;
242
+ * console.log(`Filtered packet with PTS: ${outPacket.pts}`);
207
243
  * output.writePacketSync(outPacket);
208
244
  * outPacket.free();
209
245
  * }
210
246
  * ```
211
247
  *
212
- * @see {@link filterAllSync} For multiple packet filtering
213
- * @see {@link packetsSync} For stream processing
248
+ * @example
249
+ * ```typescript
250
+ * for (const packet of packets) {
251
+ * // packet is null at end of stream - automatically flushes filter
252
+ * filter.filterSync(packet);
253
+ *
254
+ * // Receive available filtered packets
255
+ * let outPacket;
256
+ * while ((outPacket = filter.receiveSync())) {
257
+ * output.writePacketSync(outPacket);
258
+ * outPacket.free();
259
+ * }
260
+ * }
261
+ * ```
262
+ *
263
+ * @see {@link receiveSync} For receiving filtered packets
264
+ * @see {@link filterAllSync} For combined send+receive operation
265
+ * @see {@link packetsSync} For automatic packet iteration
214
266
  * @see {@link flushSync} For end-of-stream handling
215
267
  * @see {@link filter} For async version
216
268
  */
217
- filterSync(packet: Packet): Packet | null;
269
+ filterSync(packet: Packet | null): void;
218
270
  /**
219
271
  * Filter a packet to packets.
220
272
  *
@@ -224,7 +276,7 @@ export declare class BitStreamFilterAPI implements Disposable {
224
276
  *
225
277
  * Direct mapping to av_bsf_send_packet() and av_bsf_receive_packet().
226
278
  *
227
- * @param packet - Packet to filter
279
+ * @param packet - Packet to filter, or null to flush
228
280
  *
229
281
  * @returns Array of filtered packets (empty if more data needed or filter is closed)
230
282
  *
@@ -240,12 +292,22 @@ export declare class BitStreamFilterAPI implements Disposable {
240
292
  * }
241
293
  * ```
242
294
  *
295
+ * @example
296
+ * ```typescript
297
+ * // Flush remaining packets at end of stream
298
+ * const remaining = await filter.filterAll(null);
299
+ * for (const packet of remaining) {
300
+ * await output.writePacket(packet);
301
+ * packet.free();
302
+ * }
303
+ * ```
304
+ *
243
305
  * @see {@link filter} For single packet filtering
244
306
  * @see {@link packets} For stream processing
245
307
  * @see {@link flush} For end-of-stream handling
246
308
  * @see {@link filterAllSync} For synchronous version
247
309
  */
248
- filterAll(packet: Packet): Promise<Packet[]>;
310
+ filterAll(packet: Packet | null): Promise<Packet[]>;
249
311
  /**
250
312
  * Filter a packet to packets synchronously.
251
313
  * Synchronous version of filterAll.
@@ -256,7 +318,7 @@ export declare class BitStreamFilterAPI implements Disposable {
256
318
  *
257
319
  * Direct mapping to av_bsf_send_packet() and av_bsf_receive_packet().
258
320
  *
259
- * @param packet - Packet to filter
321
+ * @param packet - Packet to filter, or null to flush
260
322
  *
261
323
  * @returns Array of filtered packets (empty if more data needed or filter is closed)
262
324
  *
@@ -272,88 +334,142 @@ export declare class BitStreamFilterAPI implements Disposable {
272
334
  * }
273
335
  * ```
274
336
  *
337
+ * @example
338
+ * ```typescript
339
+ * // Flush remaining packets at end of stream
340
+ * const remaining = filter.filterAllSync(null);
341
+ * for (const packet of remaining) {
342
+ * output.writePacketSync(packet);
343
+ * packet.free();
344
+ * }
345
+ * ```
346
+ *
275
347
  * @see {@link filterSync} For single packet filtering
276
348
  * @see {@link packetsSync} For stream processing
277
349
  * @see {@link flushSync} For end-of-stream handling
278
350
  * @see {@link filterAll} For async version
279
351
  */
280
- filterAllSync(packet: Packet): Packet[];
352
+ filterAllSync(packet: Packet | null): Packet[];
281
353
  /**
282
- * Process packet stream through filter.
354
+ * Filter packet stream to filtered packet stream.
283
355
  *
284
- * High-level async generator for filtering packet streams.
285
- * Automatically handles flushing at end of stream.
286
- * Yields filtered packets ready for output.
356
+ * High-level async generator for complete filtering pipeline.
357
+ * Filter is only flushed when EOF (null) signal is explicitly received.
358
+ * Primary interface for stream-based filtering.
287
359
  *
288
- * @param packets - Async iterable of packets
360
+ * **EOF Handling:**
361
+ * - Send null to flush filter and get remaining buffered packets
362
+ * - Generator yields null after flushing when null is received
363
+ * - No automatic flushing - filter stays open until EOF or close()
289
364
  *
290
- * @yields {Packet} Filtered packets
365
+ * @param packets - Async iterable of packets, single packet, or null to flush
366
+ *
367
+ * @yields {Packet | null} Filtered packets, followed by null when explicitly flushed
291
368
  *
292
369
  * @throws {FFmpegError} If filtering fails
293
370
  *
294
371
  * @example
295
372
  * ```typescript
296
- * // Filter entire stream
373
+ * // Stream of packets with automatic EOF propagation
297
374
  * for await (const packet of filter.packets(input.packets())) {
375
+ * if (packet === null) {
376
+ * console.log('Filter flushed');
377
+ * break;
378
+ * }
298
379
  * await output.writePacket(packet);
299
- * packet.free();
380
+ * packet.free(); // Must free output packets
300
381
  * }
301
382
  * ```
302
383
  *
303
384
  * @example
304
385
  * ```typescript
305
- * // Chain with decoder
306
- * const decoder = await Decoder.create(stream);
307
- * const filter = BitStreamFilterAPI.create('h264_mp4toannexb', stream);
386
+ * // Single packet - no automatic flush
387
+ * for await (const packet of filter.packets(singlePacket)) {
388
+ * await output.writePacket(packet);
389
+ * packet.free();
390
+ * }
391
+ * // Filter remains open, buffered packets not flushed
392
+ * ```
308
393
  *
309
- * for await (const frame of decoder.frames(filter.packets(input.packets()))) {
310
- * // Process frames
311
- * frame.free();
394
+ * @example
395
+ * ```typescript
396
+ * // Explicit flush with EOF
397
+ * for await (const packet of filter.packets(null)) {
398
+ * if (packet === null) {
399
+ * console.log('All buffered packets flushed');
400
+ * break;
401
+ * }
402
+ * console.log('Buffered packet:', packet.pts);
403
+ * await output.writePacket(packet);
404
+ * packet.free();
312
405
  * }
313
406
  * ```
314
407
  *
315
- * @see {@link filterAll} For filtering single packets
316
- * @see {@link flush} For end-of-stream handling
408
+ * @see {@link filter} For single packet filtering
409
+ * @see {@link Demuxer.packets} For packet source
410
+ * @see {@link packetsSync} For sync version
317
411
  */
318
- packets(packets: AsyncIterable<Packet | null>): AsyncGenerator<Packet | null>;
412
+ packets(packets: AsyncIterable<Packet | null> | Packet | null): AsyncGenerator<Packet | null>;
319
413
  /**
320
- * Process packet stream through filter synchronously.
414
+ * Filter packet stream to filtered packet stream synchronously.
321
415
  * Synchronous version of packets.
322
416
  *
323
- * High-level sync generator for filtering packet streams.
324
- * Automatically handles flushing at end of stream.
325
- * Yields filtered packets ready for output.
417
+ * High-level sync generator for complete filtering pipeline.
418
+ * Filter is only flushed when EOF (null) signal is explicitly received.
419
+ * Primary interface for stream-based filtering.
420
+ *
421
+ * **EOF Handling:**
422
+ * - Send null to flush filter and get remaining buffered packets
423
+ * - Generator yields null after flushing when null is received
424
+ * - No automatic flushing - filter stays open until EOF or close()
326
425
  *
327
- * @param packets - Iterable of packets
426
+ * @param packets - Iterable of packets, single packet, or null to flush
328
427
  *
329
- * @yields {Packet} Filtered packets
428
+ * @yields {Packet | null} Filtered packets, followed by null when explicitly flushed
330
429
  *
331
430
  * @throws {FFmpegError} If filtering fails
332
431
  *
333
432
  * @example
334
433
  * ```typescript
335
- * // Filter entire stream
336
- * for (const packet of filter.packetsSync(packets)) {
434
+ * // Stream of packets with automatic EOF propagation
435
+ * for (const packet of filter.packetsSync(inputPackets)) {
436
+ * if (packet === null) {
437
+ * console.log('Filter flushed');
438
+ * break;
439
+ * }
337
440
  * output.writePacketSync(packet);
338
- * packet.free();
441
+ * packet.free(); // Must free output packets
339
442
  * }
340
443
  * ```
341
444
  *
342
445
  * @example
343
446
  * ```typescript
344
- * // Chain with decoder
345
- * const decoder = await Decoder.create(stream);
346
- * const filter = BitStreamFilterAPI.create('h264_mp4toannexb', stream);
447
+ * // Single packet - no automatic flush
448
+ * for (const packet of filter.packetsSync(singlePacket)) {
449
+ * output.writePacketSync(packet);
450
+ * packet.free();
451
+ * }
452
+ * // Filter remains open, buffered packets not flushed
453
+ * ```
347
454
  *
348
- * for (const frame of decoder.framesSync(filter.packetsSync(packets))) {
349
- * // Process frames
350
- * frame.free();
455
+ * @example
456
+ * ```typescript
457
+ * // Explicit flush with EOF
458
+ * for (const packet of filter.packetsSync(null)) {
459
+ * if (packet === null) {
460
+ * console.log('All buffered packets flushed');
461
+ * break;
462
+ * }
463
+ * console.log('Buffered packet:', packet.pts);
464
+ * output.writePacketSync(packet);
465
+ * packet.free();
351
466
  * }
352
467
  * ```
353
468
  *
469
+ * @see {@link filterSync} For single packet filtering
354
470
  * @see {@link packets} For async version
355
471
  */
356
- packetsSync(packets: Iterable<Packet | null>): Generator<Packet | null>;
472
+ packetsSync(packets: Iterable<Packet | null> | Packet | null): Generator<Packet | null>;
357
473
  /**
358
474
  * Flush filter and signal end-of-stream.
359
475
  *