node-av 6.0.0-beta.3 → 6.0.0-beta.4

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.
@@ -1,8 +1,9 @@
1
1
  import { Packet } from '../lib/packet.js';
2
+ import { Stream } from '../lib/stream.js';
3
+ import { Encoder } from './encoder.js';
2
4
  import { Muxer } from './muxer.js';
3
5
  import { Scheduler, SchedulerControl } from './utilities/scheduler.js';
4
- import type { BsfOptionsFor } from '../constants/index.js';
5
- import type { Stream } from '../lib/stream.js';
6
+ import type { BsfName, BsfOptionsFor } from '../constants/index.js';
6
7
  /**
7
8
  * Options for bitstream filter creation.
8
9
  *
@@ -68,7 +69,8 @@ export interface BitstreamFilterOptions<N extends string = string> {
68
69
  export declare class BitStreamFilterAPI implements Disposable {
69
70
  private ctx;
70
71
  private bsf;
71
- private stream;
72
+ private source;
73
+ private initialized;
72
74
  private packet;
73
75
  private isClosed;
74
76
  private inputQueue;
@@ -82,63 +84,55 @@ export declare class BitStreamFilterAPI implements Disposable {
82
84
  *
83
85
  * @param ctx - Filter context
84
86
  *
85
- * @param stream - Associated stream
87
+ * @param source - Input source for lazy parameter resolution
86
88
  *
87
89
  * @internal
88
90
  */
89
91
  private constructor();
90
92
  /**
91
- * Create a bitstream filter for a stream.
93
+ * Create a bitstream filter.
92
94
  *
93
- * Initializes filter with stream codec parameters.
94
- * Configures time base and prepares for packet processing.
95
+ * The input source provides the codec parameters the filter is configured with.
96
+ * Pass a {@link Stream} to filter its packets directly (stream copy), or an
97
+ * {@link Encoder} to filter that encoder's output (transcode) - parameters are
98
+ * derived from the encoder once it is open. Another {@link BitStreamFilterAPI}
99
+ * may be passed to chain filters.
100
+ *
101
+ * Initialization is lazy: filter options are validated immediately, but the
102
+ * input parameters are bound and the filter is initialized on the first packet,
103
+ * so an `Encoder` source (opened lazily on its first frame) is ready in time.
95
104
  *
96
105
  * Direct mapping to av_bsf_get_by_name() and av_bsf_alloc().
97
106
  *
98
107
  * @param filterName - Name of the bitstream filter
99
108
  *
100
- * @param stream - Stream to apply filter to
109
+ * @param source - Input source: a `Stream` (copy), `Encoder` (transcode), or upstream filter (chain)
101
110
  *
102
111
  * @param filterOptions - Optional filter-specific options
103
112
  *
104
113
  * @returns Configured bitstream filter
105
114
  *
106
- * @throws {Error} If initialization fails
107
- *
108
- * @throws {FFmpegError} If allocation or initialization fails
115
+ * @throws {FFmpegError} If the filter is not found, allocation fails, or an option is invalid
109
116
  *
110
117
  * @example
111
118
  * ```typescript
112
- * // H.264 MP4 to Annex B conversion
119
+ * // Stream copy: H.264 MP4 to Annex B conversion
113
120
  * const filter = BitStreamFilterAPI.create('h264_mp4toannexb', videoStream);
114
121
  * ```
115
122
  *
116
123
  * @example
117
124
  * ```typescript
118
- * // AAC ADTS to ASC conversion
119
- * const filter = BitStreamFilterAPI.create('aac_adtstoasc', audioStream);
120
- * ```
121
- *
122
- * @example
123
- * ```typescript
124
- * // Remove AUDs from H.264 stream
125
- * const filter = BitStreamFilterAPI.create('h264_metadata', stream, {
126
- * options: { aud: 'remove' }
127
- * });
128
- * ```
129
- *
130
- * @example
131
- * ```typescript
132
- * // Set H.264 level
133
- * const filter = BitStreamFilterAPI.create('h264_metadata', stream, {
134
- * options: { level: 51 }
125
+ * // Transcode: derive parameters from the encoder's output
126
+ * const filter = BitStreamFilterAPI.create('h264_metadata', encoder, {
127
+ * options: { aud: 'remove', level: '4.1' },
135
128
  * });
129
+ * pipeline(input, decoder, encoder, filter, output);
136
130
  * ```
137
131
  *
138
132
  * @see {@link BitStreamFilter.getByName} For filter discovery
139
133
  * @see {@link BitstreamFilterOptions} For available options
140
134
  */
141
- static create<const N extends string = string>(filterName: N, stream: Stream, filterOptions?: BitstreamFilterOptions<N>): BitStreamFilterAPI;
135
+ static create<const N extends BsfName | (string & {}) = BsfName | (string & {})>(filterName: N, source: Stream | Encoder | BitStreamFilterAPI, filterOptions?: BitstreamFilterOptions<N>): BitStreamFilterAPI;
142
136
  /**
143
137
  * Get filter name.
144
138
  *
@@ -184,6 +178,38 @@ export declare class BitStreamFilterAPI implements Disposable {
184
178
  * ```
185
179
  */
186
180
  get isBitstreamFilterOpen(): boolean;
181
+ /**
182
+ * Check if the filter has been initialized.
183
+ *
184
+ * Initialization is lazy and happens on the first processed packet, after
185
+ * which {@link outputCodecParameters} reflect the filter's output.
186
+ *
187
+ * @example
188
+ * ```typescript
189
+ * if (filter.isInitialized) {
190
+ * const params = filter.outputCodecParameters;
191
+ * }
192
+ * ```
193
+ */
194
+ get isInitialized(): boolean;
195
+ /**
196
+ * Get associated stream.
197
+ *
198
+ * Returns the stream this filter was created for. Only available when the
199
+ * filter was created from a {@link Stream}; filters created from an `Encoder`
200
+ * derive their parameters from the encoder's output instead.
201
+ *
202
+ * @returns Associated stream
203
+ *
204
+ * @throws {Error} If the filter was not created from a stream
205
+ *
206
+ * @example
207
+ * ```typescript
208
+ * const stream = filter.getStream();
209
+ * console.log(`Filtering stream ${stream.index}`);
210
+ * ```
211
+ */
212
+ getStream(): Stream;
187
213
  /**
188
214
  * Send a packet to the filter.
189
215
  *
@@ -642,6 +668,43 @@ export declare class BitStreamFilterAPI implements Disposable {
642
668
  * @see {@link receive} For async version
643
669
  */
644
670
  receiveSync(): Packet | null;
671
+ /**
672
+ * Pipe output to another bitstream filter.
673
+ *
674
+ * Starts background worker for packet processing.
675
+ * Packets flow through: input → this filter → target filter.
676
+ *
677
+ * @param target - Target bitstream filter
678
+ *
679
+ * @returns Scheduler for continued chaining
680
+ *
681
+ * @example
682
+ * ```typescript
683
+ * const filter1 = BitStreamFilterAPI.create('h264_mp4toannexb', stream);
684
+ * const filter2 = BitStreamFilterAPI.create('dump_extra', stream);
685
+ * filter1.pipeTo(filter2).pipeTo(output, 0);
686
+ * ```
687
+ */
688
+ pipeTo(target: BitStreamFilterAPI): Scheduler<Packet>;
689
+ /**
690
+ * Pipe output to muxer.
691
+ *
692
+ * Terminal stage - writes filtered packets to output file.
693
+ *
694
+ * @param output - Muxer to write to
695
+ *
696
+ * @param streamIndex - Stream index in output
697
+ *
698
+ * @returns Control interface for pipeline
699
+ *
700
+ * @example
701
+ * ```typescript
702
+ * const filter = BitStreamFilterAPI.create('h264_mp4toannexb', stream);
703
+ * const control = filter.pipeTo(output, 0);
704
+ * await control.send(packet);
705
+ * ```
706
+ */
707
+ pipeTo(output: Muxer, streamIndex: number): SchedulerControl<Packet>;
645
708
  /**
646
709
  * Flush all buffered packets as async generator.
647
710
  *
@@ -691,20 +754,6 @@ export declare class BitStreamFilterAPI implements Disposable {
691
754
  * @see {@link flushPackets} For async version
692
755
  */
693
756
  flushPacketsSync(): Generator<Packet>;
694
- /**
695
- * Get associated stream.
696
- *
697
- * Returns the stream this filter was created for.
698
- *
699
- * @returns Associated stream
700
- *
701
- * @example
702
- * ```typescript
703
- * const stream = filter.getStream();
704
- * console.log(`Filtering stream ${stream.index}`);
705
- * ```
706
- */
707
- getStream(): Stream;
708
757
  /**
709
758
  * Reset filter state.
710
759
  *
@@ -753,6 +802,12 @@ export declare class BitStreamFilterAPI implements Disposable {
753
802
  * @see {@link close} For manual cleanup
754
803
  */
755
804
  [Symbol.dispose](): void;
805
+ /**
806
+ * Bind input parameters from the source and initialize the filter.
807
+ *
808
+ * @internal
809
+ */
810
+ private ensureInitialized;
756
811
  /**
757
812
  * Send packet to input queue or flush the pipeline.
758
813
  *
@@ -770,7 +825,7 @@ export declare class BitStreamFilterAPI implements Disposable {
770
825
  *
771
826
  * @internal
772
827
  */
773
- sendToQueue(packet: Packet | null): Promise<void>;
828
+ private sendToQueue;
774
829
  /**
775
830
  * Receive packet from output queue.
776
831
  *
@@ -778,48 +833,11 @@ export declare class BitStreamFilterAPI implements Disposable {
778
833
  *
779
834
  * @internal
780
835
  */
781
- receiveFromQueue(): Promise<Packet | null>;
836
+ private receiveFromQueue;
782
837
  /**
783
838
  * Worker loop for push-based processing.
784
839
  *
785
840
  * @internal
786
841
  */
787
842
  private runWorker;
788
- /**
789
- * Pipe output to another bitstream filter.
790
- *
791
- * Starts background worker for packet processing.
792
- * Packets flow through: input → this filter → target filter.
793
- *
794
- * @param target - Target bitstream filter
795
- *
796
- * @returns Scheduler for continued chaining
797
- *
798
- * @example
799
- * ```typescript
800
- * const filter1 = BitStreamFilterAPI.create('h264_mp4toannexb', stream);
801
- * const filter2 = BitStreamFilterAPI.create('dump_extra', stream);
802
- * filter1.pipeTo(filter2).pipeTo(output, 0);
803
- * ```
804
- */
805
- pipeTo(target: BitStreamFilterAPI): Scheduler<Packet>;
806
- /**
807
- * Pipe output to muxer.
808
- *
809
- * Terminal stage - writes filtered packets to output file.
810
- *
811
- * @param output - Muxer to write to
812
- *
813
- * @param streamIndex - Stream index in output
814
- *
815
- * @returns Control interface for pipeline
816
- *
817
- * @example
818
- * ```typescript
819
- * const filter = BitStreamFilterAPI.create('h264_mp4toannexb', stream);
820
- * const control = filter.pipeTo(output, 0);
821
- * await control.send(packet);
822
- * ```
823
- */
824
- pipeTo(output: Muxer, streamIndex: number): SchedulerControl<Packet>;
825
843
  }
@@ -55,7 +55,9 @@ import { BitStreamFilterContext } from '../lib/bitstream-filter-context.js';
55
55
  import { BitStreamFilter } from '../lib/bitstream-filter.js';
56
56
  import { FFmpegError } from '../lib/error.js';
57
57
  import { Packet } from '../lib/packet.js';
58
+ import { Stream } from '../lib/stream.js';
58
59
  import { PACKET_THREAD_QUEUE_SIZE } from './constants.js';
60
+ import { Encoder } from './encoder.js';
59
61
  import { Muxer } from './muxer.js';
60
62
  import { AsyncQueue } from './utilities/async-queue.js';
61
63
  import { Scheduler, SchedulerControl } from './utilities/scheduler.js';
@@ -101,7 +103,8 @@ import { Scheduler, SchedulerControl } from './utilities/scheduler.js';
101
103
  export class BitStreamFilterAPI {
102
104
  ctx;
103
105
  bsf;
104
- stream;
106
+ source;
107
+ initialized = false;
105
108
  packet;
106
109
  isClosed = false;
107
110
  // Worker pattern for push-based processing
@@ -116,73 +119,66 @@ export class BitStreamFilterAPI {
116
119
  *
117
120
  * @param ctx - Filter context
118
121
  *
119
- * @param stream - Associated stream
122
+ * @param source - Input source for lazy parameter resolution
120
123
  *
121
124
  * @internal
122
125
  */
123
- constructor(bsf, ctx, stream) {
126
+ constructor(bsf, ctx, source) {
124
127
  this.bsf = bsf;
125
128
  this.ctx = ctx;
126
- this.stream = stream;
129
+ this.source = source;
127
130
  this.packet = new Packet();
128
131
  this.packet.alloc();
129
132
  this.inputQueue = new AsyncQueue(PACKET_THREAD_QUEUE_SIZE);
130
133
  this.outputQueue = new AsyncQueue(PACKET_THREAD_QUEUE_SIZE);
131
134
  }
132
135
  /**
133
- * Create a bitstream filter for a stream.
136
+ * Create a bitstream filter.
134
137
  *
135
- * Initializes filter with stream codec parameters.
136
- * Configures time base and prepares for packet processing.
138
+ * The input source provides the codec parameters the filter is configured with.
139
+ * Pass a {@link Stream} to filter its packets directly (stream copy), or an
140
+ * {@link Encoder} to filter that encoder's output (transcode) - parameters are
141
+ * derived from the encoder once it is open. Another {@link BitStreamFilterAPI}
142
+ * may be passed to chain filters.
143
+ *
144
+ * Initialization is lazy: filter options are validated immediately, but the
145
+ * input parameters are bound and the filter is initialized on the first packet,
146
+ * so an `Encoder` source (opened lazily on its first frame) is ready in time.
137
147
  *
138
148
  * Direct mapping to av_bsf_get_by_name() and av_bsf_alloc().
139
149
  *
140
150
  * @param filterName - Name of the bitstream filter
141
151
  *
142
- * @param stream - Stream to apply filter to
152
+ * @param source - Input source: a `Stream` (copy), `Encoder` (transcode), or upstream filter (chain)
143
153
  *
144
154
  * @param filterOptions - Optional filter-specific options
145
155
  *
146
156
  * @returns Configured bitstream filter
147
157
  *
148
- * @throws {Error} If initialization fails
149
- *
150
- * @throws {FFmpegError} If allocation or initialization fails
158
+ * @throws {FFmpegError} If the filter is not found, allocation fails, or an option is invalid
151
159
  *
152
160
  * @example
153
161
  * ```typescript
154
- * // H.264 MP4 to Annex B conversion
162
+ * // Stream copy: H.264 MP4 to Annex B conversion
155
163
  * const filter = BitStreamFilterAPI.create('h264_mp4toannexb', videoStream);
156
164
  * ```
157
165
  *
158
166
  * @example
159
167
  * ```typescript
160
- * // AAC ADTS to ASC conversion
161
- * const filter = BitStreamFilterAPI.create('aac_adtstoasc', audioStream);
162
- * ```
163
- *
164
- * @example
165
- * ```typescript
166
- * // Remove AUDs from H.264 stream
167
- * const filter = BitStreamFilterAPI.create('h264_metadata', stream, {
168
- * options: { aud: 'remove' }
169
- * });
170
- * ```
171
- *
172
- * @example
173
- * ```typescript
174
- * // Set H.264 level
175
- * const filter = BitStreamFilterAPI.create('h264_metadata', stream, {
176
- * options: { level: 51 }
168
+ * // Transcode: derive parameters from the encoder's output
169
+ * const filter = BitStreamFilterAPI.create('h264_metadata', encoder, {
170
+ * options: { aud: 'remove', level: '4.1' },
177
171
  * });
172
+ * pipeline(input, decoder, encoder, filter, output);
178
173
  * ```
179
174
  *
180
175
  * @see {@link BitStreamFilter.getByName} For filter discovery
181
176
  * @see {@link BitstreamFilterOptions} For available options
182
177
  */
183
- static create(filterName, stream, filterOptions) {
184
- if (!stream) {
185
- throw new Error('Stream is required');
178
+ // eslint-disable-next-line space-before-function-paren
179
+ static create(filterName, source, filterOptions) {
180
+ if (!source) {
181
+ throw new Error('A source (Stream, Encoder, or BitStreamFilterAPI) is required');
186
182
  }
187
183
  // Find the bitstream filter
188
184
  const filter = BitStreamFilter.getByName(filterName);
@@ -194,24 +190,15 @@ export class BitStreamFilterAPI {
194
190
  const allocRet = ctx.alloc(filter);
195
191
  FFmpegError.throwIfError(allocRet, 'Failed to allocate bitstream filter context');
196
192
  try {
197
- // Copy codec parameters from stream
198
- if (!ctx.inputCodecParameters) {
199
- throw new Error('Failed to get input codec parameters from filter context');
200
- }
201
- stream.codecpar.copy(ctx.inputCodecParameters);
202
- // Set time base
203
- ctx.inputTimeBase = stream.timeBase;
204
- // Apply filter-specific options before init
193
+ // Apply (and validate) filter-specific options now; input parameters and
194
+ // init() are deferred to ensureInitialized() on the first packet.
205
195
  if (filterOptions?.options) {
206
196
  for (const [key, value] of Object.entries(filterOptions.options)) {
207
197
  const ret = ctx.setOption(key, value);
208
198
  FFmpegError.throwIfError(ret, `Failed to set bitstream filter option '${key}'`);
209
199
  }
210
200
  }
211
- // Initialize the filter
212
- const initRet = ctx.init();
213
- FFmpegError.throwIfError(initRet, 'Failed to initialize bitstream filter');
214
- const bsfApi = new BitStreamFilterAPI(filter, ctx, stream);
201
+ const bsfApi = new BitStreamFilterAPI(filter, ctx, source);
215
202
  if (filterOptions?.signal) {
216
203
  filterOptions.signal.throwIfAborted();
217
204
  bsfApi.signal = filterOptions.signal;
@@ -277,6 +264,45 @@ export class BitStreamFilterAPI {
277
264
  get isBitstreamFilterOpen() {
278
265
  return !this.isClosed;
279
266
  }
267
+ /**
268
+ * Check if the filter has been initialized.
269
+ *
270
+ * Initialization is lazy and happens on the first processed packet, after
271
+ * which {@link outputCodecParameters} reflect the filter's output.
272
+ *
273
+ * @example
274
+ * ```typescript
275
+ * if (filter.isInitialized) {
276
+ * const params = filter.outputCodecParameters;
277
+ * }
278
+ * ```
279
+ */
280
+ get isInitialized() {
281
+ return this.initialized;
282
+ }
283
+ /**
284
+ * Get associated stream.
285
+ *
286
+ * Returns the stream this filter was created for. Only available when the
287
+ * filter was created from a {@link Stream}; filters created from an `Encoder`
288
+ * derive their parameters from the encoder's output instead.
289
+ *
290
+ * @returns Associated stream
291
+ *
292
+ * @throws {Error} If the filter was not created from a stream
293
+ *
294
+ * @example
295
+ * ```typescript
296
+ * const stream = filter.getStream();
297
+ * console.log(`Filtering stream ${stream.index}`);
298
+ * ```
299
+ */
300
+ getStream() {
301
+ if (!(this.source instanceof Stream)) {
302
+ throw new Error('No associated stream: this bitstream filter derives its parameters from an upstream component');
303
+ }
304
+ return this.source;
305
+ }
280
306
  /**
281
307
  * Send a packet to the filter.
282
308
  *
@@ -334,6 +360,7 @@ export class BitStreamFilterAPI {
334
360
  if (this.isClosed) {
335
361
  return;
336
362
  }
363
+ this.ensureInitialized();
337
364
  // Send packet to filter (null signals EOF/flush)
338
365
  const sendRet = await this.ctx.sendPacket(packet);
339
366
  if (sendRet < 0 && sendRet !== AVERROR_EOF && sendRet !== AVERROR_EAGAIN) {
@@ -397,6 +424,7 @@ export class BitStreamFilterAPI {
397
424
  if (this.isClosed) {
398
425
  return;
399
426
  }
427
+ this.ensureInitialized();
400
428
  // Send packet to filter (null signals EOF/flush)
401
429
  const sendRet = this.ctx.sendPacketSync(packet);
402
430
  if (sendRet < 0 && sendRet !== AVERROR_EOF && sendRet !== AVERROR_EAGAIN) {
@@ -748,7 +776,7 @@ export class BitStreamFilterAPI {
748
776
  */
749
777
  async flush() {
750
778
  this.signal?.throwIfAborted();
751
- if (this.isClosed) {
779
+ if (this.isClosed || !this.initialized) {
752
780
  return;
753
781
  }
754
782
  // Send EOF
@@ -791,7 +819,7 @@ export class BitStreamFilterAPI {
791
819
  * @see {@link flush} For async version
792
820
  */
793
821
  flushSync() {
794
- if (this.isClosed) {
822
+ if (this.isClosed || !this.initialized) {
795
823
  return;
796
824
  }
797
825
  // Send EOF
@@ -844,7 +872,7 @@ export class BitStreamFilterAPI {
844
872
  * @see {@link receiveSync} For synchronous version
845
873
  */
846
874
  async receive() {
847
- if (this.isClosed) {
875
+ if (this.isClosed || !this.initialized) {
848
876
  return null;
849
877
  }
850
878
  // Clear previous packet data
@@ -911,7 +939,7 @@ export class BitStreamFilterAPI {
911
939
  * @see {@link receive} For async version
912
940
  */
913
941
  receiveSync() {
914
- if (this.isClosed) {
942
+ if (this.isClosed || !this.initialized) {
915
943
  return null;
916
944
  }
917
945
  // Clear previous packet data
@@ -935,6 +963,42 @@ export class BitStreamFilterAPI {
935
963
  return null;
936
964
  }
937
965
  }
966
+ pipeTo(target, streamIndex) {
967
+ if (target instanceof Muxer) {
968
+ // Start worker if not already running
969
+ this.workerPromise ??= this.runWorker();
970
+ // Start pipe task: filter.outputQueue -> output
971
+ this.pipeToPromise = (async () => {
972
+ while (true) {
973
+ const packet = await this.receiveFromQueue();
974
+ if (!packet)
975
+ break;
976
+ await target.writePacket(packet, streamIndex);
977
+ }
978
+ })();
979
+ // Return control without pipeTo (terminal stage)
980
+ return new SchedulerControl(this);
981
+ }
982
+ else {
983
+ // BitStreamFilterAPI
984
+ const t = target;
985
+ // Store reference to next component for flush propagation
986
+ this.nextComponent = t;
987
+ // Start worker if not already running
988
+ this.workerPromise ??= this.runWorker();
989
+ // Start pipe task: filter.outputQueue -> target.inputQueue (via target.send)
990
+ this.pipeToPromise = (async () => {
991
+ while (true) {
992
+ const packet = await this.receiveFromQueue();
993
+ if (!packet)
994
+ break;
995
+ await t.sendToQueue(packet);
996
+ }
997
+ })();
998
+ // Return scheduler for chaining (target is now the last component)
999
+ return new Scheduler(this, t);
1000
+ }
1001
+ }
938
1002
  /**
939
1003
  * Flush all buffered packets as async generator.
940
1004
  *
@@ -998,22 +1062,6 @@ export class BitStreamFilterAPI {
998
1062
  yield packet;
999
1063
  }
1000
1064
  }
1001
- /**
1002
- * Get associated stream.
1003
- *
1004
- * Returns the stream this filter was created for.
1005
- *
1006
- * @returns Associated stream
1007
- *
1008
- * @example
1009
- * ```typescript
1010
- * const stream = filter.getStream();
1011
- * console.log(`Filtering stream ${stream.index}`);
1012
- * ```
1013
- */
1014
- getStream() {
1015
- return this.stream;
1016
- }
1017
1065
  /**
1018
1066
  * Reset filter state.
1019
1067
  *
@@ -1079,6 +1127,50 @@ export class BitStreamFilterAPI {
1079
1127
  [Symbol.dispose]() {
1080
1128
  this.close();
1081
1129
  }
1130
+ /**
1131
+ * Bind input parameters from the source and initialize the filter.
1132
+ *
1133
+ * @internal
1134
+ */
1135
+ ensureInitialized() {
1136
+ if (this.initialized) {
1137
+ return;
1138
+ }
1139
+ const params = this.ctx.inputCodecParameters;
1140
+ if (!params) {
1141
+ throw new Error('Failed to get input codec parameters from filter context');
1142
+ }
1143
+ const source = this.source;
1144
+ if (source instanceof Stream) {
1145
+ source.codecpar.copy(params);
1146
+ this.ctx.inputTimeBase = source.timeBase;
1147
+ }
1148
+ else if (source instanceof Encoder) {
1149
+ const codecContext = source.getCodecContext();
1150
+ if (!codecContext) {
1151
+ throw new Error('Encoder is not initialized yet; cannot derive bitstream filter parameters.');
1152
+ }
1153
+ const ret = params.fromContext(codecContext);
1154
+ FFmpegError.throwIfError(ret, 'Failed to derive bitstream filter parameters from encoder');
1155
+ this.ctx.inputTimeBase = codecContext.timeBase;
1156
+ }
1157
+ else {
1158
+ // Upstream is another bitstream filter: chain from its output.
1159
+ source.ensureInitialized();
1160
+ const upstream = source.outputCodecParameters;
1161
+ if (!upstream) {
1162
+ throw new Error('Upstream bitstream filter has no output parameters.');
1163
+ }
1164
+ upstream.copy(params);
1165
+ const tb = source.outputTimeBase;
1166
+ if (tb) {
1167
+ this.ctx.inputTimeBase = tb;
1168
+ }
1169
+ }
1170
+ const initRet = this.ctx.init();
1171
+ FFmpegError.throwIfError(initRet, 'Failed to initialize bitstream filter');
1172
+ this.initialized = true;
1173
+ }
1082
1174
  /**
1083
1175
  * Send packet to input queue or flush the pipeline.
1084
1176
  *
@@ -1146,6 +1238,7 @@ export class BitStreamFilterAPI {
1146
1238
  if (this.isClosed) {
1147
1239
  break;
1148
1240
  }
1241
+ this.ensureInitialized();
1149
1242
  // Send packet to filter
1150
1243
  const sendRet = await this.ctx.sendPacket(packet);
1151
1244
  // Handle EAGAIN
@@ -1202,41 +1295,5 @@ export class BitStreamFilterAPI {
1202
1295
  this.outputQueue?.close();
1203
1296
  }
1204
1297
  }
1205
- pipeTo(target, streamIndex) {
1206
- if (target instanceof Muxer) {
1207
- // Start worker if not already running
1208
- this.workerPromise ??= this.runWorker();
1209
- // Start pipe task: filter.outputQueue -> output
1210
- this.pipeToPromise = (async () => {
1211
- while (true) {
1212
- const packet = await this.receiveFromQueue();
1213
- if (!packet)
1214
- break;
1215
- await target.writePacket(packet, streamIndex);
1216
- }
1217
- })();
1218
- // Return control without pipeTo (terminal stage)
1219
- return new SchedulerControl(this);
1220
- }
1221
- else {
1222
- // BitStreamFilterAPI
1223
- const t = target;
1224
- // Store reference to next component for flush propagation
1225
- this.nextComponent = t;
1226
- // Start worker if not already running
1227
- this.workerPromise ??= this.runWorker();
1228
- // Start pipe task: filter.outputQueue -> target.inputQueue (via target.send)
1229
- this.pipeToPromise = (async () => {
1230
- while (true) {
1231
- const packet = await this.receiveFromQueue();
1232
- if (!packet)
1233
- break;
1234
- await t.sendToQueue(packet);
1235
- }
1236
- })();
1237
- // Return scheduler for chaining (target is now the last component)
1238
- return new Scheduler(this, t);
1239
- }
1240
- }
1241
1298
  }
1242
1299
  //# sourceMappingURL=bitstream-filter.js.map