node-av 4.0.0 → 5.0.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 (104) hide show
  1. package/README.md +23 -0
  2. package/binding.gyp +19 -11
  3. package/dist/api/bitstream-filter.d.ts +13 -12
  4. package/dist/api/bitstream-filter.js +33 -29
  5. package/dist/api/bitstream-filter.js.map +1 -1
  6. package/dist/api/decoder.d.ts +211 -96
  7. package/dist/api/decoder.js +396 -375
  8. package/dist/api/decoder.js.map +1 -1
  9. package/dist/api/demuxer.d.ts +10 -10
  10. package/dist/api/demuxer.js +7 -10
  11. package/dist/api/demuxer.js.map +1 -1
  12. package/dist/api/encoder.d.ts +155 -122
  13. package/dist/api/encoder.js +368 -541
  14. package/dist/api/encoder.js.map +1 -1
  15. package/dist/api/filter-complex.d.ts +769 -0
  16. package/dist/api/filter-complex.js +1596 -0
  17. package/dist/api/filter-complex.js.map +1 -0
  18. package/dist/api/filter-presets.d.ts +68 -0
  19. package/dist/api/filter-presets.js +96 -0
  20. package/dist/api/filter-presets.js.map +1 -1
  21. package/dist/api/filter.d.ts +183 -113
  22. package/dist/api/filter.js +347 -365
  23. package/dist/api/filter.js.map +1 -1
  24. package/dist/api/fmp4-stream.d.ts +2 -2
  25. package/dist/api/fmp4-stream.js.map +1 -1
  26. package/dist/api/index.d.ts +2 -0
  27. package/dist/api/index.js +3 -0
  28. package/dist/api/index.js.map +1 -1
  29. package/dist/api/io-stream.d.ts +3 -3
  30. package/dist/api/io-stream.js.map +1 -1
  31. package/dist/api/muxer.d.ts +10 -10
  32. package/dist/api/muxer.js +6 -6
  33. package/dist/api/muxer.js.map +1 -1
  34. package/dist/api/pipeline.d.ts +2 -2
  35. package/dist/api/pipeline.js +22 -22
  36. package/dist/api/pipeline.js.map +1 -1
  37. package/dist/api/rtp-stream.d.ts +2 -2
  38. package/dist/api/rtp-stream.js.map +1 -1
  39. package/dist/api/types.d.ts +63 -7
  40. package/dist/api/utilities/audio-sample.d.ts +10 -0
  41. package/dist/api/utilities/audio-sample.js +10 -0
  42. package/dist/api/utilities/audio-sample.js.map +1 -1
  43. package/dist/api/utilities/channel-layout.d.ts +1 -0
  44. package/dist/api/utilities/channel-layout.js +1 -0
  45. package/dist/api/utilities/channel-layout.js.map +1 -1
  46. package/dist/api/utilities/image.d.ts +38 -0
  47. package/dist/api/utilities/image.js +38 -0
  48. package/dist/api/utilities/image.js.map +1 -1
  49. package/dist/api/utilities/index.d.ts +1 -0
  50. package/dist/api/utilities/index.js +2 -0
  51. package/dist/api/utilities/index.js.map +1 -1
  52. package/dist/api/utilities/media-type.d.ts +1 -0
  53. package/dist/api/utilities/media-type.js +1 -0
  54. package/dist/api/utilities/media-type.js.map +1 -1
  55. package/dist/api/utilities/pixel-format.d.ts +3 -0
  56. package/dist/api/utilities/pixel-format.js +3 -0
  57. package/dist/api/utilities/pixel-format.js.map +1 -1
  58. package/dist/api/utilities/sample-format.d.ts +5 -0
  59. package/dist/api/utilities/sample-format.js +5 -0
  60. package/dist/api/utilities/sample-format.js.map +1 -1
  61. package/dist/api/utilities/scheduler.d.ts +21 -52
  62. package/dist/api/utilities/scheduler.js +20 -58
  63. package/dist/api/utilities/scheduler.js.map +1 -1
  64. package/dist/api/utilities/streaming.d.ts +32 -1
  65. package/dist/api/utilities/streaming.js +32 -1
  66. package/dist/api/utilities/streaming.js.map +1 -1
  67. package/dist/api/utilities/timestamp.d.ts +14 -0
  68. package/dist/api/utilities/timestamp.js +14 -0
  69. package/dist/api/utilities/timestamp.js.map +1 -1
  70. package/dist/api/utilities/whisper-model.d.ts +310 -0
  71. package/dist/api/utilities/whisper-model.js +528 -0
  72. package/dist/api/utilities/whisper-model.js.map +1 -0
  73. package/dist/api/whisper.d.ts +324 -0
  74. package/dist/api/whisper.js +362 -0
  75. package/dist/api/whisper.js.map +1 -0
  76. package/dist/constants/constants.d.ts +3 -1
  77. package/dist/constants/constants.js +1 -0
  78. package/dist/constants/constants.js.map +1 -1
  79. package/dist/ffmpeg/index.d.ts +3 -3
  80. package/dist/ffmpeg/index.js +3 -3
  81. package/dist/ffmpeg/utils.d.ts +27 -0
  82. package/dist/ffmpeg/utils.js +28 -16
  83. package/dist/ffmpeg/utils.js.map +1 -1
  84. package/dist/lib/binding.d.ts +4 -4
  85. package/dist/lib/binding.js.map +1 -1
  86. package/dist/lib/codec-parameters.d.ts +47 -1
  87. package/dist/lib/codec-parameters.js +55 -0
  88. package/dist/lib/codec-parameters.js.map +1 -1
  89. package/dist/lib/fifo.d.ts +416 -0
  90. package/dist/lib/fifo.js +453 -0
  91. package/dist/lib/fifo.js.map +1 -0
  92. package/dist/lib/frame.d.ts +96 -1
  93. package/dist/lib/frame.js +139 -1
  94. package/dist/lib/frame.js.map +1 -1
  95. package/dist/lib/index.d.ts +1 -0
  96. package/dist/lib/index.js +2 -0
  97. package/dist/lib/index.js.map +1 -1
  98. package/dist/lib/native-types.d.ts +29 -2
  99. package/dist/lib/rational.d.ts +18 -0
  100. package/dist/lib/rational.js +19 -0
  101. package/dist/lib/rational.js.map +1 -1
  102. package/dist/lib/types.d.ts +23 -1
  103. package/install/check.js +2 -2
  104. package/package.json +30 -20
@@ -51,7 +51,7 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
51
51
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
52
  });
53
53
  /* eslint-disable @stylistic/indent-binary-ops */
54
- import { AV_BUFFERSRC_FLAG_PUSH, AVERROR_EAGAIN, AVERROR_EOF, AVFILTER_FLAG_HWDEVICE } from '../constants/constants.js';
54
+ import { AV_BUFFERSRC_FLAG_PUSH, AVERROR_EAGAIN, AVERROR_EOF, AVFILTER_FLAG_HWDEVICE, EOF } from '../constants/constants.js';
55
55
  import { FFmpegError } from '../lib/error.js';
56
56
  import { FilterGraph } from '../lib/filter-graph.js';
57
57
  import { FilterInOut } from '../lib/filter-inout.js';
@@ -138,11 +138,6 @@ export class FilterAPI {
138
138
  /**
139
139
  * Create a filter with specified description and configuration.
140
140
  *
141
- * Creates and allocates filter graph immediately.
142
- * Filter configuration is completed on first frame with frame properties.
143
- * TimeBase is automatically calculated from first frame based on CFR option.
144
- * Hardware frames context is automatically detected from input frames.
145
- *
146
141
  * Direct mapping to avfilter_graph_parse_ptr() and avfilter_graph_config().
147
142
  *
148
143
  * @param description - Filter graph description
@@ -252,7 +247,6 @@ export class FilterAPI {
252
247
  * Output frame rate from filter graph.
253
248
  *
254
249
  * Returns the frame rate determined by the filter graph output.
255
- * Matches FFmpeg CLI's av_buffersink_get_frame_rate() behavior.
256
250
  * Returns null if filter is not initialized or frame rate is not set.
257
251
  *
258
252
  * Direct mapping to av_buffersink_get_frame_rate().
@@ -518,32 +512,33 @@ export class FilterAPI {
518
512
  return !this.isClosed && this.initialized ? this.graph.dump() : null;
519
513
  }
520
514
  /**
521
- * Process a frame through the filter.
515
+ * Send a frame to the filter.
522
516
  *
523
- * Applies filter operations to input frame.
517
+ * Sends a frame to the filter for processing.
518
+ * Does not return filtered frames - use {@link receive} to retrieve frames.
524
519
  * On first frame, automatically builds filter graph with frame properties.
525
- * May buffer frames internally before producing output.
526
- * Hardware frames context is automatically detected from frame.
527
- * Returns null if filter is closed and frame is null.
520
+ * A single input frame can produce zero, one, or multiple output frames.
528
521
  *
529
- * **Note**: This method receives only ONE frame per call.
530
- * A single input frame can produce multiple output frames (e.g., fps filter, deinterlace).
531
- * To receive all frames from an input, use {@link processAll} or {@link frames} instead.
522
+ * **Important**: This method only SENDS the frame to the filter.
523
+ * You must call {@link receive} separately (potentially multiple times) to get filtered frames.
532
524
  *
533
- * Direct mapping to av_buffersrc_add_frame() and av_buffersink_get_frame().
525
+ * Direct mapping to av_buffersrc_add_frame().
534
526
  *
535
- * @param frame - Input frame to process (or null to flush)
536
- *
537
- * @returns Filtered frame or null if buffered
527
+ * @param frame - Input frame to send to filter
538
528
  *
539
529
  * @throws {Error} If filter could not be initialized
540
530
  *
541
- * @throws {FFmpegError} If processing fails
531
+ * @throws {FFmpegError} If sending frame fails
542
532
  *
543
533
  * @example
544
534
  * ```typescript
545
- * const output = await filter.process(inputFrame);
546
- * if (output) {
535
+ * // Send frame and receive filtered frames
536
+ * await filter.process(inputFrame);
537
+ *
538
+ * // Receive all available filtered frames
539
+ * while (true) {
540
+ * const output = await filter.receive();
541
+ * if (!output) break;
547
542
  * console.log(`Got filtered frame: pts=${output.pts}`);
548
543
  * output.free();
549
544
  * }
@@ -551,53 +546,51 @@ export class FilterAPI {
551
546
  *
552
547
  * @example
553
548
  * ```typescript
554
- * // Process frame - may buffer internally
555
- * const output = await filter.process(frame);
556
- * if (output) {
557
- * // Got output immediately
558
- * yield output;
549
+ * for await (const frame of decoder.frames(input.packets())) {
550
+ * // Send frame
551
+ * await filter.process(frame);
552
+ *
553
+ * // Receive available filtered frames
554
+ * let output;
555
+ * while ((output = await filter.receive())) {
556
+ * await encoder.encode(output);
557
+ * output.free();
558
+ * }
559
+ * frame.free();
559
560
  * }
560
- * // For buffered frames, use the frames() async generator
561
561
  * ```
562
562
  *
563
- * @see {@link processAll} For processing multiple output frames
563
+ * @see {@link receive} For receiving filtered frames
564
+ * @see {@link processAll} For combined send+receive operation
564
565
  * @see {@link frames} For processing frame streams
565
566
  * @see {@link flush} For end-of-stream handling
566
567
  * @see {@link processSync} For synchronous version
567
568
  */
568
569
  async process(frame) {
569
570
  if (this.isClosed) {
570
- return null;
571
+ return;
571
572
  }
572
573
  // Open filter if not already done
573
- if (!this.initialized) {
574
- if (!frame) {
575
- return null;
576
- }
577
- this.initializePromise ??= this.initialize(frame);
578
- }
574
+ this.initializePromise ??= this.initialize(frame);
579
575
  await this.initializePromise;
580
- if (!this.initialized) {
581
- return null;
582
- }
583
576
  if (!this.buffersrcCtx || !this.buffersinkCtx) {
584
577
  throw new Error('Could not initialize filter contexts');
585
578
  }
586
579
  // Check for frame property changes (FFmpeg: dropOnChange/allowReinit logic)
587
- if (frame && !this.checkFramePropertiesChanged(frame)) {
580
+ if (!this.checkFramePropertiesChanged(frame)) {
588
581
  // Frame dropped due to property change
589
- return null;
582
+ return;
590
583
  }
591
584
  // If reinitialized, reinitialize now
592
- if (!this.initialized && frame) {
585
+ if (!this.initialized) {
593
586
  this.initializePromise = this.initialize(frame);
594
587
  await this.initializePromise;
595
- if (!this.buffersrcCtx || !this.buffersinkCtx) {
596
- throw new Error('Could not reinitialize filter contexts');
597
- }
588
+ }
589
+ if (!this.buffersrcCtx || !this.buffersinkCtx) {
590
+ throw new Error('Could not reinitialize filter contexts');
598
591
  }
599
592
  // Rescale timestamps to filter's timeBase
600
- if (frame && this.calculatedTimeBase) {
593
+ if (this.calculatedTimeBase) {
601
594
  const originalTimeBase = frame.timeBase;
602
595
  frame.pts = avRescaleQ(frame.pts, originalTimeBase, this.calculatedTimeBase);
603
596
  frame.duration = avRescaleQ(frame.duration, originalTimeBase, this.calculatedTimeBase);
@@ -606,89 +599,71 @@ export class FilterAPI {
606
599
  // Send frame to filter with PUSH flag for immediate processing
607
600
  const addRet = await this.buffersrcCtx.buffersrcAddFrame(frame, AV_BUFFERSRC_FLAG_PUSH);
608
601
  FFmpegError.throwIfError(addRet, 'Failed to add frame to filter');
609
- // Try to get filtered frame using receive()
610
- return await this.receive();
611
602
  }
612
603
  /**
613
- * Process a frame through the filter synchronously.
604
+ * Send a frame to the filter synchronously.
614
605
  * Synchronous version of process.
615
606
  *
616
- * Applies filter operations to input frame.
607
+ * Sends a frame to the filter for processing.
608
+ * Does not return filtered frames - use {@link receiveSync} to retrieve frames.
617
609
  * On first frame, automatically builds filter graph with frame properties.
618
- * May buffer frames internally before producing output.
619
- * Hardware frames context is automatically detected from frame.
620
- * Returns null if filter is closed and frame is null.
610
+ * A single input frame can produce zero, one, or multiple output frames.
621
611
  *
622
- * **Note**: This method receives only ONE frame per call.
623
- * A single input frame can produce multiple output frames (e.g., fps filter, deinterlace).
624
- * To receive all frames from an input, use {@link processAllSync} or {@link framesSync} instead.
612
+ * **Important**: This method only SENDS the frame to the filter.
613
+ * You must call {@link receiveSync} separately (potentially multiple times) to get filtered frames.
625
614
  *
626
- * Direct mapping to av_buffersrc_add_frame() and av_buffersink_get_frame().
627
- *
628
- * @param frame - Input frame to process (or null to flush)
615
+ * Direct mapping to av_buffersrc_add_frame().
629
616
  *
630
- * @returns Filtered frame or null if buffered
617
+ * @param frame - Input frame to send to filter
631
618
  *
632
619
  * @throws {Error} If filter could not be initialized
633
620
  *
634
- * @throws {FFmpegError} If processing fails
621
+ * @throws {FFmpegError} If sending frame fails
635
622
  *
636
623
  * @example
637
624
  * ```typescript
638
- * const output = filter.processSync(inputFrame);
639
- * if (output) {
625
+ * // Send frame and receive filtered frames
626
+ * filter.processSync(inputFrame);
627
+ *
628
+ * // Receive all available filtered frames
629
+ * let output;
630
+ * while ((output = filter.receiveSync())) {
640
631
  * console.log(`Got filtered frame: pts=${output.pts}`);
641
632
  * output.free();
642
633
  * }
643
634
  * ```
644
635
  *
645
- * @example
646
- * ```typescript
647
- * // Process frame - may buffer internally
648
- * const output = filter.processSync(frame);
649
- * if (output) {
650
- * // Got output immediately
651
- * yield output;
652
- * }
653
- * // For buffered frames, use the framesSync() generator
654
- * ```
655
- *
656
- * @see {@link processAllSync} For processing multiple output frames
636
+ * @see {@link receiveSync} For receiving filtered frames
637
+ * @see {@link processAllSync} For combined send+receive operation
657
638
  * @see {@link framesSync} For processing frame streams
658
639
  * @see {@link flushSync} For end-of-stream handling
659
640
  * @see {@link process} For async version
660
641
  */
661
642
  processSync(frame) {
662
643
  if (this.isClosed) {
663
- return null;
644
+ return;
664
645
  }
665
646
  // Open filter if not already done
666
647
  if (!this.initialized) {
667
- if (!frame) {
668
- return null;
669
- }
670
648
  this.initializeSync(frame);
671
649
  }
672
- if (!this.initialized) {
673
- return null;
674
- }
675
650
  if (!this.buffersrcCtx || !this.buffersinkCtx) {
676
651
  throw new Error('Could not initialize filter contexts');
677
652
  }
678
653
  // Check for frame property changes (FFmpeg: dropOnChange/allowReinit logic)
679
- if (frame && !this.checkFramePropertiesChanged(frame)) {
654
+ if (!this.checkFramePropertiesChanged(frame)) {
680
655
  // Frame dropped due to property change
681
- return null;
656
+ return;
682
657
  }
683
658
  // If reinitialized, reinitialize now
684
- if (!this.initialized && frame) {
659
+ if (!this.initialized) {
685
660
  this.initializeSync(frame);
686
- if (!this.buffersrcCtx || !this.buffersinkCtx) {
687
- throw new Error('Could not reinitialize filter contexts');
688
- }
661
+ }
662
+ if (!this.buffersrcCtx || !this.buffersinkCtx) {
663
+ throw new Error('Could not reinitialize filter contexts');
689
664
  }
690
665
  // Rescale timestamps to filter's timeBase
691
- if (frame && this.calculatedTimeBase) {
666
+ if (this.calculatedTimeBase) {
692
667
  const originalTimeBase = frame.timeBase;
693
668
  frame.pts = avRescaleQ(frame.pts, originalTimeBase, this.calculatedTimeBase);
694
669
  frame.duration = avRescaleQ(frame.duration, originalTimeBase, this.calculatedTimeBase);
@@ -697,8 +672,6 @@ export class FilterAPI {
697
672
  // Send frame to filter with PUSH flag for immediate processing
698
673
  const addRet = this.buffersrcCtx.buffersrcAddFrameSync(frame, AV_BUFFERSRC_FLAG_PUSH);
699
674
  FFmpegError.throwIfError(addRet, 'Failed to add frame to filter');
700
- // Try to get filtered frame using receiveSync()
701
- return this.receiveSync();
702
675
  }
703
676
  /**
704
677
  * Process a frame through the filter.
@@ -711,7 +684,7 @@ export class FilterAPI {
711
684
  *
712
685
  * Direct mapping to av_buffersrc_add_frame() and av_buffersink_get_frame().
713
686
  *
714
- * @param frame - Input frame to process (or null to flush)
687
+ * @param frame - Input frame to process
715
688
  *
716
689
  * @returns Array of filtered frames (empty if buffered or filter closed)
717
690
  *
@@ -743,40 +716,19 @@ export class FilterAPI {
743
716
  * @see {@link processAllSync} For synchronous version
744
717
  */
745
718
  async processAll(frame) {
746
- if (this.isClosed) {
747
- return [];
748
- }
749
- // Open filter if not already done
750
- if (!this.initialized) {
751
- if (!frame) {
752
- return [];
753
- }
754
- this.initializePromise ??= this.initialize(frame);
755
- }
756
- await this.initializePromise;
757
- if (!this.initialized) {
758
- return [];
719
+ if (frame) {
720
+ await this.process(frame);
759
721
  }
760
- if (!this.buffersrcCtx || !this.buffersinkCtx) {
761
- throw new Error('Could not initialize filter contexts');
762
- }
763
- // Rescale timestamps to filter's timeBase
764
- if (frame && this.calculatedTimeBase) {
765
- const originalTimeBase = frame.timeBase;
766
- frame.pts = avRescaleQ(frame.pts, originalTimeBase, this.calculatedTimeBase);
767
- frame.duration = avRescaleQ(frame.duration, originalTimeBase, this.calculatedTimeBase);
768
- frame.timeBase = new Rational(this.calculatedTimeBase.num, this.calculatedTimeBase.den);
722
+ else {
723
+ await this.flush();
769
724
  }
770
- // Send frame to filter with PUSH flag for immediate processing
771
- const addRet = await this.buffersrcCtx.buffersrcAddFrame(frame, AV_BUFFERSRC_FLAG_PUSH);
772
- FFmpegError.throwIfError(addRet, 'Failed to add frame to filter');
773
- // Receive all available frames using receive()
725
+ // Receive all available frames
774
726
  const frames = [];
775
727
  while (true) {
776
728
  const outputFrame = await this.receive();
777
729
  if (!outputFrame)
778
- break;
779
- frames.push(outputFrame);
730
+ break; // Stop on EAGAIN or EOF
731
+ frames.push(outputFrame); // Only push actual frames
780
732
  }
781
733
  return frames;
782
734
  }
@@ -792,7 +744,7 @@ export class FilterAPI {
792
744
  *
793
745
  * Direct mapping to av_buffersrc_add_frame() and av_buffersink_get_frame().
794
746
  *
795
- * @param frame - Input frame to process (or null to flush)
747
+ * @param frame - Input frame to process
796
748
  *
797
749
  * @returns Array of filtered frames (empty if buffered or filter closed)
798
750
  *
@@ -824,36 +776,19 @@ export class FilterAPI {
824
776
  * @see {@link process} For async version
825
777
  */
826
778
  processAllSync(frame) {
827
- if (this.isClosed) {
828
- return [];
779
+ if (frame) {
780
+ this.processSync(frame);
829
781
  }
830
- // Open filter if not already done
831
- if (!this.initialized) {
832
- if (!frame) {
833
- return [];
834
- }
835
- this.initializeSync(frame);
836
- }
837
- if (!this.buffersrcCtx || !this.buffersinkCtx) {
838
- throw new Error('Could not initialize filter contexts');
839
- }
840
- // Rescale timestamps to filter's timeBase
841
- if (frame && this.calculatedTimeBase) {
842
- const originalTimeBase = frame.timeBase;
843
- frame.pts = avRescaleQ(frame.pts, originalTimeBase, this.calculatedTimeBase);
844
- frame.duration = avRescaleQ(frame.duration, originalTimeBase, this.calculatedTimeBase);
845
- frame.timeBase = new Rational(this.calculatedTimeBase.num, this.calculatedTimeBase.den);
782
+ else {
783
+ this.flushSync();
846
784
  }
847
- // Send frame to filter with PUSH flag for immediate processing
848
- const addRet = this.buffersrcCtx.buffersrcAddFrameSync(frame, AV_BUFFERSRC_FLAG_PUSH);
849
- FFmpegError.throwIfError(addRet, 'Failed to add frame to filter');
850
- // Receive all available frames using receiveSync()
785
+ // Receive all available frames
851
786
  const frames = [];
852
787
  while (true) {
853
788
  const outputFrame = this.receiveSync();
854
789
  if (!outputFrame)
855
- break;
856
- frames.push(outputFrame);
790
+ break; // Stop on EAGAIN or EOF
791
+ frames.push(outputFrame); // Only push actual frames
857
792
  }
858
793
  return frames;
859
794
  }
@@ -861,12 +796,17 @@ export class FilterAPI {
861
796
  * Process frame stream through filter.
862
797
  *
863
798
  * High-level async generator for filtering frame streams.
864
- * Automatically handles buffering and flushing.
865
- * Frees input frames after processing.
799
+ * Filter is only flushed when EOF (null) signal is explicitly received.
800
+ * Primary interface for stream-based filtering.
801
+ *
802
+ * **EOF Handling:**
803
+ * - Send null to flush filter and get remaining buffered frames
804
+ * - Generator yields null after flushing when null is received
805
+ * - No automatic flushing - filter stays open until EOF or close()
866
806
  *
867
- * @param frames - Async generator of input frames
807
+ * @param frames - Async iterable of frames, single frame, or null to flush
868
808
  *
869
- * @yields {Frame} Filtered frames
809
+ * @yields {Frame | null} Filtered frames, followed by null when explicitly flushed
870
810
  *
871
811
  * @throws {Error} If filter not ready
872
812
  *
@@ -874,8 +814,12 @@ export class FilterAPI {
874
814
  *
875
815
  * @example
876
816
  * ```typescript
877
- * // Filter decoded frames
817
+ * // Stream of frames with automatic EOF propagation
878
818
  * for await (const frame of filter.frames(decoder.frames(packets))) {
819
+ * if (frame === null) {
820
+ * console.log('Filter flushed');
821
+ * break;
822
+ * }
879
823
  * await encoder.encode(frame);
880
824
  * frame.free();
881
825
  * }
@@ -883,16 +827,23 @@ export class FilterAPI {
883
827
  *
884
828
  * @example
885
829
  * ```typescript
886
- * // Chain filters
887
- * const filter1 = FilterAPI.create('scale=640:480', {
888
- * timeBase: video.timeBase
889
- * });
890
- * const filter2 = FilterAPI.create('rotate=PI/4', {
891
- * timeBase: video.timeBase
892
- * });
830
+ * // Single frame - no automatic flush
831
+ * for await (const frame of filter.frames(singleFrame)) {
832
+ * await encoder.encode(frame);
833
+ * frame.free();
834
+ * }
835
+ * // Filter remains open, buffered frames not flushed
836
+ * ```
893
837
  *
894
- * for await (const frame of filter2.frames(filter1.frames(input))) {
895
- * // Process filtered frames
838
+ * @example
839
+ * ```typescript
840
+ * // Explicit flush with EOF
841
+ * for await (const frame of filter.frames(null)) {
842
+ * if (frame === null) {
843
+ * console.log('All buffered frames flushed');
844
+ * break;
845
+ * }
846
+ * console.log('Buffered frame:', frame.pts);
896
847
  * frame.free();
897
848
  * }
898
849
  * ```
@@ -902,55 +853,39 @@ export class FilterAPI {
902
853
  * @see {@link framesSync} For sync version
903
854
  */
904
855
  async *frames(frames) {
856
+ const self = this;
857
+ const processFrame = async function* (frame) {
858
+ await self.process(frame);
859
+ while (true) {
860
+ const filtered = await self.receive();
861
+ if (!filtered)
862
+ break;
863
+ yield filtered;
864
+ }
865
+ }.bind(this);
866
+ const finalize = async function* () {
867
+ for await (const remaining of self.flushFrames()) {
868
+ yield remaining;
869
+ }
870
+ yield null;
871
+ }.bind(this);
872
+ if (frames === null) {
873
+ yield* finalize();
874
+ return;
875
+ }
876
+ if (frames instanceof Frame) {
877
+ yield* processFrame(frames);
878
+ return;
879
+ }
905
880
  for await (const frame_1 of frames) {
906
881
  const env_1 = { stack: [], error: void 0, hasError: false };
907
882
  try {
908
883
  const frame = __addDisposableResource(env_1, frame_1, false);
909
- // Handle EOF signal
910
884
  if (frame === null) {
911
- // Flush filter
912
- await this.flush();
913
- while (true) {
914
- const remaining = await this.receive();
915
- if (!remaining)
916
- break;
917
- yield remaining;
918
- }
919
- // Signal EOF and stop processing
920
- yield null;
885
+ yield* finalize();
921
886
  return;
922
887
  }
923
- if (this.isClosed) {
924
- break;
925
- }
926
- // Open filter if not already done
927
- if (!this.initialized) {
928
- this.initializePromise ??= this.initialize(frame);
929
- }
930
- await this.initializePromise;
931
- if (!this.initialized) {
932
- continue;
933
- }
934
- if (!this.buffersrcCtx || !this.buffersinkCtx) {
935
- throw new Error('Could not initialize filter contexts');
936
- }
937
- // Rescale timestamps to filter's timeBase
938
- if (this.calculatedTimeBase) {
939
- const originalTimeBase = frame.timeBase;
940
- frame.pts = avRescaleQ(frame.pts, originalTimeBase, this.calculatedTimeBase);
941
- frame.duration = avRescaleQ(frame.duration, originalTimeBase, this.calculatedTimeBase);
942
- frame.timeBase = new Rational(this.calculatedTimeBase.num, this.calculatedTimeBase.den);
943
- }
944
- // Send frame to filter
945
- const addRet = await this.buffersrcCtx.buffersrcAddFrame(frame, AV_BUFFERSRC_FLAG_PUSH);
946
- FFmpegError.throwIfError(addRet, 'Failed to add frame to filter');
947
- // Receive all available frames
948
- while (true) {
949
- const buffered = await this.receive();
950
- if (!buffered)
951
- break;
952
- yield buffered;
953
- }
888
+ yield* processFrame(frame);
954
889
  }
955
890
  catch (e_1) {
956
891
  env_1.error = e_1;
@@ -960,28 +895,23 @@ export class FilterAPI {
960
895
  __disposeResources(env_1);
961
896
  }
962
897
  }
963
- // Flush and get remaining frames (fallback if no null was sent)
964
- await this.flush();
965
- while (true) {
966
- const remaining = await this.receive();
967
- if (!remaining)
968
- break;
969
- yield remaining;
970
- }
971
- // Signal EOF
972
- yield null;
973
898
  }
974
899
  /**
975
900
  * Process frame stream through filter synchronously.
976
901
  * Synchronous version of frames.
977
902
  *
978
903
  * High-level sync generator for filtering frame streams.
979
- * Automatically handles buffering and flushing.
980
- * Frees input frames after processing.
904
+ * Filter is only flushed when EOF (null) signal is explicitly received.
905
+ * Primary interface for stream-based filtering.
906
+ *
907
+ * **EOF Handling:**
908
+ * - Send null to flush filter and get remaining buffered frames
909
+ * - Generator yields null after flushing when null is received
910
+ * - No automatic flushing - filter stays open until EOF or close()
981
911
  *
982
- * @param frames - Generator of input frames
912
+ * @param frames - Iterable of frames, single frame, or null to flush
983
913
  *
984
- * @yields {Frame} Filtered frames
914
+ * @yields {Frame | null} Filtered frames, followed by null when explicitly flushed
985
915
  *
986
916
  * @throws {Error} If filter not ready
987
917
  *
@@ -989,8 +919,12 @@ export class FilterAPI {
989
919
  *
990
920
  * @example
991
921
  * ```typescript
992
- * // Filter decoded frames
922
+ * // Stream of frames with automatic EOF propagation
993
923
  * for (const frame of filter.framesSync(decoder.framesSync(packets))) {
924
+ * if (frame === null) {
925
+ * console.log('Filter flushed');
926
+ * break;
927
+ * }
994
928
  * encoder.encodeSync(frame);
995
929
  * frame.free();
996
930
  * }
@@ -998,16 +932,23 @@ export class FilterAPI {
998
932
  *
999
933
  * @example
1000
934
  * ```typescript
1001
- * // Chain filters
1002
- * const filter1 = FilterAPI.create('scale=640:480', {
1003
- * timeBase: video.timeBase
1004
- * });
1005
- * const filter2 = FilterAPI.create('rotate=PI/4', {
1006
- * timeBase: video.timeBase
1007
- * });
935
+ * // Single frame - no automatic flush
936
+ * for (const frame of filter.framesSync(singleFrame)) {
937
+ * encoder.encodeSync(frame);
938
+ * frame.free();
939
+ * }
940
+ * // Filter remains open, buffered frames not flushed
941
+ * ```
1008
942
  *
1009
- * for (const frame of filter2.framesSync(filter1.framesSync(input))) {
1010
- * // Process filtered frames
943
+ * @example
944
+ * ```typescript
945
+ * // Explicit flush with EOF
946
+ * for (const frame of filter.framesSync(null)) {
947
+ * if (frame === null) {
948
+ * console.log('All buffered frames flushed');
949
+ * break;
950
+ * }
951
+ * console.log('Buffered frame:', frame.pts);
1011
952
  * frame.free();
1012
953
  * }
1013
954
  * ```
@@ -1017,54 +958,47 @@ export class FilterAPI {
1017
958
  * @see {@link frames} For async version
1018
959
  */
1019
960
  *framesSync(frames) {
961
+ const self = this;
962
+ // Helper: Process frame and yield all available filtered frames (filters out EAGAIN nulls)
963
+ const processFrame = function* (frame) {
964
+ self.processSync(frame);
965
+ // Receive ALL filtered frames (filter out null/EAGAIN)
966
+ while (true) {
967
+ const filtered = self.receiveSync();
968
+ if (!filtered)
969
+ break; // EAGAIN or EOF - no more frames available
970
+ yield filtered; // Only yield actual frames, not null
971
+ }
972
+ }.bind(this);
973
+ // Helper: Flush filter and signal EOF
974
+ const finalize = function* () {
975
+ for (const remaining of self.flushFramesSync()) {
976
+ yield remaining; // Only yield actual frames
977
+ }
978
+ yield null; // Signal end-of-stream
979
+ }.bind(this);
980
+ // Case 1: EOF input -> flush only
981
+ if (frames === null) {
982
+ yield* finalize();
983
+ return;
984
+ }
985
+ // Case 2: Single frame
986
+ if (frames instanceof Frame) {
987
+ yield* processFrame(frames);
988
+ // No automatic flush - only flush on explicit EOF
989
+ return;
990
+ }
991
+ // Case 3: Iterable of frames
1020
992
  for (const frame_2 of frames) {
1021
993
  const env_2 = { stack: [], error: void 0, hasError: false };
1022
994
  try {
1023
995
  const frame = __addDisposableResource(env_2, frame_2, false);
1024
- // Handle EOF signal
996
+ // Check for EOF signal from upstream
1025
997
  if (frame === null) {
1026
- // Flush filter
1027
- this.flushSync();
1028
- while (true) {
1029
- const remaining = this.receiveSync();
1030
- if (!remaining)
1031
- break;
1032
- yield remaining;
1033
- }
1034
- // Signal EOF and stop processing
1035
- yield null;
998
+ yield* finalize();
1036
999
  return;
1037
1000
  }
1038
- if (this.isClosed) {
1039
- break;
1040
- }
1041
- // Open filter if not already done
1042
- if (!this.initialized) {
1043
- this.initializeSync(frame);
1044
- }
1045
- if (!this.initialized) {
1046
- continue;
1047
- }
1048
- if (!this.buffersrcCtx || !this.buffersinkCtx) {
1049
- throw new Error('Could not initialize filter contexts');
1050
- }
1051
- // Rescale timestamps to filter's timeBase
1052
- if (this.calculatedTimeBase) {
1053
- const originalTimeBase = frame.timeBase;
1054
- frame.pts = avRescaleQ(frame.pts, originalTimeBase, this.calculatedTimeBase);
1055
- frame.duration = avRescaleQ(frame.duration, originalTimeBase, this.calculatedTimeBase);
1056
- frame.timeBase = new Rational(this.calculatedTimeBase.num, this.calculatedTimeBase.den);
1057
- }
1058
- // Send frame to filter
1059
- const addRet = this.buffersrcCtx.buffersrcAddFrameSync(frame, AV_BUFFERSRC_FLAG_PUSH);
1060
- FFmpegError.throwIfError(addRet, 'Failed to add frame to filter');
1061
- // Receive all available frames
1062
- while (true) {
1063
- const buffered = this.receiveSync();
1064
- if (!buffered)
1065
- break;
1066
- yield buffered;
1067
- }
1001
+ yield* processFrame(frame);
1068
1002
  }
1069
1003
  catch (e_2) {
1070
1004
  env_2.error = e_2;
@@ -1074,16 +1008,7 @@ export class FilterAPI {
1074
1008
  __disposeResources(env_2);
1075
1009
  }
1076
1010
  }
1077
- // Flush and get remaining frames (fallback if no null was sent)
1078
- this.flushSync();
1079
- while (true) {
1080
- const remaining = this.receiveSync();
1081
- if (!remaining)
1082
- break;
1083
- yield remaining;
1084
- }
1085
- // Signal EOF
1086
- yield null;
1011
+ // No automatic flush - only flush on explicit EOF
1087
1012
  }
1088
1013
  /**
1089
1014
  * Flush filter and signal end-of-stream.
@@ -1182,10 +1107,12 @@ export class FilterAPI {
1182
1107
  async *flushFrames() {
1183
1108
  // Send flush signal
1184
1109
  await this.flush();
1185
- // Yield all remaining frames
1186
- let frame;
1187
- while ((frame = await this.receive()) !== null) {
1188
- yield frame;
1110
+ // Yield all remaining frames (filter out null/EAGAIN and EOF)
1111
+ while (true) {
1112
+ const frame = await this.receive();
1113
+ if (!frame)
1114
+ break; // Stop on EAGAIN or EOF
1115
+ yield frame; // Only yield actual frames
1189
1116
  }
1190
1117
  }
1191
1118
  /**
@@ -1215,40 +1142,67 @@ export class FilterAPI {
1215
1142
  *flushFramesSync() {
1216
1143
  // Send flush signal
1217
1144
  this.flushSync();
1218
- // Yield all remaining frames
1219
- let frame;
1220
- while ((frame = this.receiveSync()) !== null) {
1221
- yield frame;
1145
+ // Yield all remaining frames (filter out null/EAGAIN and EOF)
1146
+ while (true) {
1147
+ const frame = this.receiveSync();
1148
+ if (!frame)
1149
+ break; // Stop on EAGAIN or EOF
1150
+ yield frame; // Only yield actual frames
1222
1151
  }
1223
1152
  }
1224
1153
  /**
1225
1154
  * Receive buffered frame from filter.
1226
1155
  *
1227
1156
  * Drains frames buffered by the filter.
1228
- * Call repeatedly until null to get all buffered frames.
1229
- * Returns null if filter is closed, not initialized, or no frames available.
1157
+ * Call repeatedly until null or EOF to get all buffered frames.
1158
+ * Implements FFmpeg's send/receive pattern.
1159
+ *
1160
+ * **Return Values:**
1161
+ * - `Frame` - Successfully received frame (AVERROR >= 0)
1162
+ * - `null` - Need more input data (AVERROR_EAGAIN), or filter not initialized
1163
+ * - `undefined` - End of stream reached (AVERROR_EOF), or filter is closed
1230
1164
  *
1231
1165
  * Direct mapping to av_buffersink_get_frame().
1232
1166
  *
1233
- * @returns Buffered frame or null if none available
1167
+ * @returns Buffered frame, null if need more data, or undefined if stream ended
1234
1168
  *
1235
1169
  * @throws {FFmpegError} If receiving fails
1236
1170
  *
1237
1171
  * @example
1238
1172
  * ```typescript
1239
- * let frame;
1240
- * while ((frame = await filter.receive()) !== null) {
1173
+ * // Process all buffered frames
1174
+ * while (true) {
1175
+ * const frame = await filter.receive();
1176
+ * if (!frame) break; // Stop on EAGAIN or EOF
1241
1177
  * console.log(`Received frame: pts=${frame.pts}`);
1242
1178
  * frame.free();
1243
1179
  * }
1244
1180
  * ```
1245
1181
  *
1182
+ * @example
1183
+ * ```typescript
1184
+ * // Handle each return value explicitly
1185
+ * const frame = await filter.receive();
1186
+ * if (frame === EOF) {
1187
+ * console.log('Filter stream ended');
1188
+ * } else if (frame === null) {
1189
+ * console.log('Need more input data');
1190
+ * } else {
1191
+ * console.log(`Got frame: pts=${frame.pts}`);
1192
+ * frame.free();
1193
+ * }
1194
+ * ```
1195
+ *
1246
1196
  * @see {@link process} For frame processing
1247
1197
  * @see {@link flush} For flushing filter
1248
1198
  * @see {@link receiveSync} For synchronous version
1199
+ * @see {@link EOF} For end-of-stream signal
1249
1200
  */
1250
1201
  async receive() {
1251
- if (this.isClosed || !this.initialized || !this.buffersinkCtx) {
1202
+ if (this.isClosed) {
1203
+ return EOF;
1204
+ }
1205
+ if (!this.initialized || !this.buffersinkCtx) {
1252
1206
  return null;
1253
1207
  }
1254
1208
  // Reuse frame - but alloc() instead of unref() for buffersink
@@ -1261,10 +1215,15 @@ export class FilterAPI {
1261
1215
  // Clone for user (keeps internal frame for reuse)
1262
1216
  return this.frame.clone();
1263
1217
  }
1218
+ else if (ret === AVERROR_EAGAIN) {
1219
+ // Need more data
1220
+ return null;
1221
+ }
1222
+ else if (ret === AVERROR_EOF) {
1223
+ // End of stream
1224
+ return EOF;
1225
+ }
1264
1226
  else {
1265
- if (ret === AVERROR_EAGAIN || ret === AVERROR_EOF) {
1266
- return null;
1267
- }
1268
1227
  FFmpegError.throwIfError(ret, 'Failed to receive frame from filter');
1269
1228
  return null;
1270
1229
  }
@@ -1274,30 +1233,55 @@ export class FilterAPI {
1274
1233
  * Synchronous version of receive.
1275
1234
  *
1276
1235
  * Drains frames buffered by the filter.
1277
- * Call repeatedly until null to get all buffered frames.
1278
- * Returns null if filter is closed, not initialized, or no frames available.
1236
+ * Call repeatedly until null or EOF to get all buffered frames.
1237
+ * Implements FFmpeg's send/receive pattern.
1238
+ *
1239
+ * **Return Values:**
1240
+ * - `Frame` - Successfully received frame (AVERROR >= 0)
1241
+ * - `null` - Need more input data (AVERROR_EAGAIN), or filter not initialized
1242
+ * - `undefined` - End of stream reached (AVERROR_EOF), or filter is closed
1279
1243
  *
1280
1244
  * Direct mapping to av_buffersink_get_frame().
1281
1245
  *
1282
- * @returns Buffered frame or null if none available
1246
+ * @returns Buffered frame, null if need more data, or undefined if stream ended
1283
1247
  *
1284
1248
  * @throws {FFmpegError} If receiving fails
1285
1249
  *
1286
1250
  * @example
1287
1251
  * ```typescript
1288
- * let frame;
1289
- * while ((frame = filter.receiveSync()) !== null) {
1252
+ * // Process all buffered frames
1253
+ * while (true) {
1254
+ * const frame = filter.receiveSync();
1255
+ * if (!frame) break; // Stop on EAGAIN or EOF
1290
1256
  * console.log(`Received frame: pts=${frame.pts}`);
1291
1257
  * frame.free();
1292
1258
  * }
1293
1259
  * ```
1294
1260
  *
1261
+ * @example
1262
+ * ```typescript
1263
+ * // Handle each return value explicitly
1264
+ * const frame = filter.receiveSync();
1265
+ * if (frame === EOF) {
1266
+ * console.log('Filter stream ended');
1267
+ * } else if (frame === null) {
1268
+ * console.log('Need more input data');
1269
+ * } else {
1270
+ * console.log(`Got frame: pts=${frame.pts}`);
1271
+ * frame.free();
1272
+ * }
1273
+ * ```
1274
+ *
1295
1275
  * @see {@link processSync} For frame processing
1296
1276
  * @see {@link flushSync} For flushing filter
1297
1277
  * @see {@link receive} For async version
1278
+ * @see {@link EOF} For end-of-stream signal
1298
1279
  */
1299
1280
  receiveSync() {
1300
- if (this.isClosed || !this.initialized || !this.buffersinkCtx) {
1281
+ if (this.isClosed) {
1282
+ return EOF;
1283
+ }
1284
+ if (!this.initialized || !this.buffersinkCtx) {
1301
1285
  return null;
1302
1286
  }
1303
1287
  // Reuse frame - but alloc() instead of unref() for buffersink
@@ -1310,10 +1294,13 @@ export class FilterAPI {
1310
1294
  // Clone for user (keeps internal frame for reuse)
1311
1295
  return this.frame.clone();
1312
1296
  }
1297
+ else if (ret === AVERROR_EAGAIN) {
1298
+ return null; // Need more data
1299
+ }
1300
+ else if (ret === AVERROR_EOF) {
1301
+ return EOF; // End of stream
1302
+ }
1313
1303
  else {
1314
- if (ret === AVERROR_EAGAIN || ret === AVERROR_EOF) {
1315
- return null;
1316
- }
1317
1304
  FFmpegError.throwIfError(ret, 'Failed to receive frame from filter');
1318
1305
  return null;
1319
1306
  }
@@ -1463,33 +1450,13 @@ export class FilterAPI {
1463
1450
  const frame = __addDisposableResource(env_3, await this.inputQueue.receive(), false);
1464
1451
  if (!frame)
1465
1452
  break;
1466
- // Open filter if not already done
1467
- if (!this.initialized) {
1468
- this.initializePromise ??= this.initialize(frame);
1469
- }
1470
- await this.initializePromise;
1471
- if (!this.initialized) {
1472
- continue;
1473
- }
1474
- if (!this.buffersrcCtx || !this.buffersinkCtx) {
1475
- throw new Error('Could not initialize filter contexts');
1476
- }
1477
- // Rescale timestamps to filter's timeBase
1478
- if (this.calculatedTimeBase) {
1479
- const originalTimeBase = frame.timeBase;
1480
- frame.pts = avRescaleQ(frame.pts, originalTimeBase, this.calculatedTimeBase);
1481
- frame.duration = avRescaleQ(frame.duration, originalTimeBase, this.calculatedTimeBase);
1482
- frame.timeBase = new Rational(this.calculatedTimeBase.num, this.calculatedTimeBase.den);
1483
- }
1484
- // Send frame to filter
1485
- const addRet = await this.buffersrcCtx.buffersrcAddFrame(frame, AV_BUFFERSRC_FLAG_PUSH);
1486
- FFmpegError.throwIfError(addRet, 'Failed to add frame to filter');
1453
+ await this.process(frame);
1487
1454
  // Receive all available frames
1488
1455
  while (!this.outputQueue.isClosed) {
1489
1456
  const buffered = await this.receive();
1490
1457
  if (!buffered)
1491
- break;
1492
- await this.outputQueue.send(buffered);
1458
+ break; // Stop on EAGAIN or EOF
1459
+ await this.outputQueue.send(buffered); // Only send actual frames
1493
1460
  }
1494
1461
  }
1495
1462
  catch (e_3) {
@@ -1505,8 +1472,8 @@ export class FilterAPI {
1505
1472
  while (!this.outputQueue.isClosed) {
1506
1473
  const frame = await this.receive();
1507
1474
  if (!frame)
1508
- break;
1509
- await this.outputQueue.send(frame);
1475
+ break; // Stop on EAGAIN or EOF
1476
+ await this.outputQueue.send(frame); // Only send actual frames
1510
1477
  }
1511
1478
  }
1512
1479
  catch {
@@ -1518,14 +1485,54 @@ export class FilterAPI {
1518
1485
  }
1519
1486
  }
1520
1487
  /**
1521
- * Send frame to input queue.
1488
+ * Send frame to input queue or flush the pipeline.
1489
+ *
1490
+ * When frame is provided, queues it for filtering.
1491
+ * When null is provided, triggers flush sequence:
1492
+ * - Closes input queue
1493
+ * - Waits for worker completion
1494
+ * - Flushes filter and sends remaining frames to output queue
1495
+ * - Closes output queue
1496
+ * - Waits for pipeTo task completion
1497
+ * - Propagates flush to next component (if any)
1498
+ *
1499
+ * Used by scheduler system for pipeline control.
1522
1500
  *
1523
- * @param frame - Frame to send
1501
+ * @param frame - Frame to send, or null to flush
1524
1502
  *
1525
1503
  * @internal
1526
1504
  */
1527
1505
  async sendToQueue(frame) {
1528
- await this.inputQueue.send(frame);
1506
+ if (frame) {
1507
+ await this.inputQueue.send(frame);
1508
+ }
1509
+ else {
1510
+ // Close input queue to signal end of stream to worker
1511
+ this.inputQueue.close();
1512
+ // Wait for worker to finish processing all frames (if exists)
1513
+ if (this.workerPromise) {
1514
+ await this.workerPromise;
1515
+ }
1516
+ // Flush filter at end (like FFmpeg does)
1517
+ await this.flush();
1518
+ // Send all flushed frames to output queue
1519
+ while (true) {
1520
+ const frame = await this.receive();
1521
+ if (!frame)
1522
+ break; // Stop on EAGAIN or EOF
1523
+ await this.outputQueue.send(frame); // Only send actual frames
1524
+ }
1525
+ // Close output queue to signal end of stream to pipeTo() task
1526
+ this.outputQueue.close();
1527
+ // Wait for pipeTo() task to finish processing all frames (if exists)
1528
+ if (this.pipeToPromise) {
1529
+ await this.pipeToPromise;
1530
+ }
1531
+ // Then propagate flush to next component
1532
+ if (this.nextComponent) {
1533
+ await this.nextComponent.sendToQueue(null);
1534
+ }
1535
+ }
1529
1536
  }
1530
1537
  /**
1531
1538
  * Receive frame from output queue.
@@ -1537,40 +1544,6 @@ export class FilterAPI {
1537
1544
  async receiveFrame() {
1538
1545
  return await this.outputQueue.receive();
1539
1546
  }
1540
- /**
1541
- * Flush the entire filter pipeline.
1542
- *
1543
- * Propagates flush through worker, output queue, and next component.
1544
- *
1545
- * @internal
1546
- */
1547
- async flushPipeline() {
1548
- // Close input queue to signal end of stream to worker
1549
- this.inputQueue.close();
1550
- // Wait for worker to finish processing all frames (if exists)
1551
- if (this.workerPromise) {
1552
- await this.workerPromise;
1553
- }
1554
- // Flush filter at end (like FFmpeg does)
1555
- await this.flush();
1556
- // Send all flushed frames to output queue
1557
- while (true) {
1558
- const frame = await this.receive();
1559
- if (!frame)
1560
- break;
1561
- await this.outputQueue.send(frame);
1562
- }
1563
- // Close output queue to signal end of stream to pipeTo() task
1564
- this.outputQueue.close();
1565
- // Wait for pipeTo() task to finish processing all frames (if exists)
1566
- if (this.pipeToPromise) {
1567
- await this.pipeToPromise;
1568
- }
1569
- // Then propagate flush to next component
1570
- if (this.nextComponent) {
1571
- await this.nextComponent.flushPipeline();
1572
- }
1573
- }
1574
1547
  /**
1575
1548
  * Initialize filter graph from first frame.
1576
1549
  *
@@ -1698,10 +1671,19 @@ export class FilterAPI {
1698
1671
  `Format: ${this.lastFrameProps.format}->${frame.format}, ` +
1699
1672
  `Size: ${this.lastFrameProps.width}x${this.lastFrameProps.height}->${frame.width}x${frame.height}`);
1700
1673
  }
1701
- // Reinit is allowed - reinitialize filtergraph
1702
1674
  // Close current graph and reinitialize
1703
1675
  this.graph.free();
1676
+ // Create new graph
1704
1677
  this.graph = new FilterGraph();
1678
+ this.graph.alloc();
1679
+ // Configure threading
1680
+ if (this.options.threads !== undefined) {
1681
+ this.graph.nbThreads = this.options.threads;
1682
+ }
1683
+ // Configure scaler options
1684
+ if (this.options.scaleSwsOpts) {
1685
+ this.graph.scaleSwsOpts = this.options.scaleSwsOpts;
1686
+ }
1705
1687
  this.buffersrcCtx = null;
1706
1688
  this.buffersinkCtx = null;
1707
1689
  this.initialized = false;