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.
- package/README.md +23 -0
- package/binding.gyp +19 -11
- package/dist/api/bitstream-filter.d.ts +13 -12
- package/dist/api/bitstream-filter.js +33 -29
- package/dist/api/bitstream-filter.js.map +1 -1
- package/dist/api/decoder.d.ts +211 -96
- package/dist/api/decoder.js +396 -375
- package/dist/api/decoder.js.map +1 -1
- package/dist/api/demuxer.d.ts +10 -10
- package/dist/api/demuxer.js +7 -10
- package/dist/api/demuxer.js.map +1 -1
- package/dist/api/encoder.d.ts +155 -122
- package/dist/api/encoder.js +368 -541
- package/dist/api/encoder.js.map +1 -1
- package/dist/api/filter-complex.d.ts +769 -0
- package/dist/api/filter-complex.js +1596 -0
- package/dist/api/filter-complex.js.map +1 -0
- package/dist/api/filter-presets.d.ts +68 -0
- package/dist/api/filter-presets.js +96 -0
- package/dist/api/filter-presets.js.map +1 -1
- package/dist/api/filter.d.ts +183 -113
- package/dist/api/filter.js +347 -365
- package/dist/api/filter.js.map +1 -1
- package/dist/api/fmp4-stream.d.ts +2 -2
- package/dist/api/fmp4-stream.js.map +1 -1
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.js +3 -0
- package/dist/api/index.js.map +1 -1
- package/dist/api/io-stream.d.ts +3 -3
- package/dist/api/io-stream.js.map +1 -1
- package/dist/api/muxer.d.ts +10 -10
- package/dist/api/muxer.js +6 -6
- package/dist/api/muxer.js.map +1 -1
- package/dist/api/pipeline.d.ts +2 -2
- package/dist/api/pipeline.js +22 -22
- package/dist/api/pipeline.js.map +1 -1
- package/dist/api/rtp-stream.d.ts +2 -2
- package/dist/api/rtp-stream.js.map +1 -1
- package/dist/api/types.d.ts +63 -7
- package/dist/api/utilities/audio-sample.d.ts +10 -0
- package/dist/api/utilities/audio-sample.js +10 -0
- package/dist/api/utilities/audio-sample.js.map +1 -1
- package/dist/api/utilities/channel-layout.d.ts +1 -0
- package/dist/api/utilities/channel-layout.js +1 -0
- package/dist/api/utilities/channel-layout.js.map +1 -1
- package/dist/api/utilities/image.d.ts +38 -0
- package/dist/api/utilities/image.js +38 -0
- package/dist/api/utilities/image.js.map +1 -1
- package/dist/api/utilities/index.d.ts +1 -0
- package/dist/api/utilities/index.js +2 -0
- package/dist/api/utilities/index.js.map +1 -1
- package/dist/api/utilities/media-type.d.ts +1 -0
- package/dist/api/utilities/media-type.js +1 -0
- package/dist/api/utilities/media-type.js.map +1 -1
- package/dist/api/utilities/pixel-format.d.ts +3 -0
- package/dist/api/utilities/pixel-format.js +3 -0
- package/dist/api/utilities/pixel-format.js.map +1 -1
- package/dist/api/utilities/sample-format.d.ts +5 -0
- package/dist/api/utilities/sample-format.js +5 -0
- package/dist/api/utilities/sample-format.js.map +1 -1
- package/dist/api/utilities/scheduler.d.ts +21 -52
- package/dist/api/utilities/scheduler.js +20 -58
- package/dist/api/utilities/scheduler.js.map +1 -1
- package/dist/api/utilities/streaming.d.ts +32 -1
- package/dist/api/utilities/streaming.js +32 -1
- package/dist/api/utilities/streaming.js.map +1 -1
- package/dist/api/utilities/timestamp.d.ts +14 -0
- package/dist/api/utilities/timestamp.js +14 -0
- package/dist/api/utilities/timestamp.js.map +1 -1
- package/dist/api/utilities/whisper-model.d.ts +310 -0
- package/dist/api/utilities/whisper-model.js +528 -0
- package/dist/api/utilities/whisper-model.js.map +1 -0
- package/dist/api/whisper.d.ts +324 -0
- package/dist/api/whisper.js +362 -0
- package/dist/api/whisper.js.map +1 -0
- package/dist/constants/constants.d.ts +3 -1
- package/dist/constants/constants.js +1 -0
- package/dist/constants/constants.js.map +1 -1
- package/dist/ffmpeg/index.d.ts +3 -3
- package/dist/ffmpeg/index.js +3 -3
- package/dist/ffmpeg/utils.d.ts +27 -0
- package/dist/ffmpeg/utils.js +28 -16
- package/dist/ffmpeg/utils.js.map +1 -1
- package/dist/lib/binding.d.ts +4 -4
- package/dist/lib/binding.js.map +1 -1
- package/dist/lib/codec-parameters.d.ts +47 -1
- package/dist/lib/codec-parameters.js +55 -0
- package/dist/lib/codec-parameters.js.map +1 -1
- package/dist/lib/fifo.d.ts +416 -0
- package/dist/lib/fifo.js +453 -0
- package/dist/lib/fifo.js.map +1 -0
- package/dist/lib/frame.d.ts +96 -1
- package/dist/lib/frame.js +139 -1
- package/dist/lib/frame.js.map +1 -1
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.js +2 -0
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/native-types.d.ts +29 -2
- package/dist/lib/rational.d.ts +18 -0
- package/dist/lib/rational.js +19 -0
- package/dist/lib/rational.js.map +1 -1
- package/dist/lib/types.d.ts +23 -1
- package/install/check.js +2 -2
- package/package.json +30 -20
package/dist/api/filter.js
CHANGED
|
@@ -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
|
-
*
|
|
515
|
+
* Send a frame to the filter.
|
|
522
516
|
*
|
|
523
|
-
*
|
|
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
|
-
*
|
|
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
|
-
* **
|
|
530
|
-
*
|
|
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()
|
|
525
|
+
* Direct mapping to av_buffersrc_add_frame().
|
|
534
526
|
*
|
|
535
|
-
* @param frame - Input frame to
|
|
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
|
|
531
|
+
* @throws {FFmpegError} If sending frame fails
|
|
542
532
|
*
|
|
543
533
|
* @example
|
|
544
534
|
* ```typescript
|
|
545
|
-
*
|
|
546
|
-
*
|
|
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
|
-
*
|
|
555
|
-
*
|
|
556
|
-
*
|
|
557
|
-
*
|
|
558
|
-
*
|
|
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
|
|
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
|
|
571
|
+
return;
|
|
571
572
|
}
|
|
572
573
|
// Open filter if not already done
|
|
573
|
-
|
|
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 (
|
|
580
|
+
if (!this.checkFramePropertiesChanged(frame)) {
|
|
588
581
|
// Frame dropped due to property change
|
|
589
|
-
return
|
|
582
|
+
return;
|
|
590
583
|
}
|
|
591
584
|
// If reinitialized, reinitialize now
|
|
592
|
-
if (!this.initialized
|
|
585
|
+
if (!this.initialized) {
|
|
593
586
|
this.initializePromise = this.initialize(frame);
|
|
594
587
|
await this.initializePromise;
|
|
595
|
-
|
|
596
|
-
|
|
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 (
|
|
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
|
-
*
|
|
604
|
+
* Send a frame to the filter synchronously.
|
|
614
605
|
* Synchronous version of process.
|
|
615
606
|
*
|
|
616
|
-
*
|
|
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
|
-
*
|
|
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
|
-
* **
|
|
623
|
-
*
|
|
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()
|
|
627
|
-
*
|
|
628
|
-
* @param frame - Input frame to process (or null to flush)
|
|
615
|
+
* Direct mapping to av_buffersrc_add_frame().
|
|
629
616
|
*
|
|
630
|
-
* @
|
|
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
|
|
621
|
+
* @throws {FFmpegError} If sending frame fails
|
|
635
622
|
*
|
|
636
623
|
* @example
|
|
637
624
|
* ```typescript
|
|
638
|
-
*
|
|
639
|
-
*
|
|
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
|
-
* @
|
|
646
|
-
*
|
|
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
|
|
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 (
|
|
654
|
+
if (!this.checkFramePropertiesChanged(frame)) {
|
|
680
655
|
// Frame dropped due to property change
|
|
681
|
-
return
|
|
656
|
+
return;
|
|
682
657
|
}
|
|
683
658
|
// If reinitialized, reinitialize now
|
|
684
|
-
if (!this.initialized
|
|
659
|
+
if (!this.initialized) {
|
|
685
660
|
this.initializeSync(frame);
|
|
686
|
-
|
|
687
|
-
|
|
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 (
|
|
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
|
|
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 (
|
|
747
|
-
|
|
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
|
-
|
|
761
|
-
|
|
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
|
-
//
|
|
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
|
|
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 (
|
|
828
|
-
|
|
779
|
+
if (frame) {
|
|
780
|
+
this.processSync(frame);
|
|
829
781
|
}
|
|
830
|
-
|
|
831
|
-
|
|
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
|
-
//
|
|
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
|
-
*
|
|
865
|
-
*
|
|
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
|
|
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
|
-
* //
|
|
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
|
-
* //
|
|
887
|
-
* const
|
|
888
|
-
*
|
|
889
|
-
*
|
|
890
|
-
*
|
|
891
|
-
*
|
|
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
|
-
*
|
|
895
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
980
|
-
*
|
|
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 -
|
|
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
|
-
* //
|
|
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
|
-
* //
|
|
1002
|
-
* const
|
|
1003
|
-
*
|
|
1004
|
-
*
|
|
1005
|
-
*
|
|
1006
|
-
*
|
|
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
|
-
*
|
|
1010
|
-
*
|
|
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
|
-
//
|
|
996
|
+
// Check for EOF signal from upstream
|
|
1025
997
|
if (frame === null) {
|
|
1026
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
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
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
1240
|
-
* while (
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
1289
|
-
* while (
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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;
|