mediabunny 1.22.0 → 1.24.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.
@@ -77,6 +77,7 @@ var Mediabunny = (() => {
77
77
  EncodedVideoPacketSource: () => EncodedVideoPacketSource,
78
78
  FLAC: () => FLAC,
79
79
  FilePathSource: () => FilePathSource,
80
+ FilePathTarget: () => FilePathTarget,
80
81
  FlacInputFormat: () => FlacInputFormat,
81
82
  FlacOutputFormat: () => FlacOutputFormat,
82
83
  Input: () => Input,
@@ -312,7 +313,7 @@ var Mediabunny = (() => {
312
313
  // Linear transfer characteristics
313
314
  "iec61966-2-1": 13,
314
315
  // IEC 61966-2-1
315
- "pg": 16,
316
+ "pq": 16,
316
317
  // Rec. ITU-R BT.2100-2 perceptual quantization (PQ) system
317
318
  "hlg": 18
318
319
  // Rec. ITU-R BT.2100-2 hybrid loggamma (HLG) system
@@ -15655,6 +15656,7 @@ var Mediabunny = (() => {
15655
15656
  this.workers = [];
15656
15657
  this.cache = [];
15657
15658
  this.currentCacheSize = 0;
15659
+ this.disposed = false;
15658
15660
  }
15659
15661
  read(innerStart, innerEnd) {
15660
15662
  assert(this.fileSize !== null);
@@ -15831,6 +15833,9 @@ var Mediabunny = (() => {
15831
15833
  }
15832
15834
  /** Called by a worker when it has read some data. */
15833
15835
  supplyWorkerData(worker, bytes2) {
15836
+ if (this.disposed) {
15837
+ return;
15838
+ }
15834
15839
  const start = worker.currentPos;
15835
15840
  const end = start + bytes2.length;
15836
15841
  this.insertIntoCache({
@@ -15962,6 +15967,7 @@ var Mediabunny = (() => {
15962
15967
  }
15963
15968
  this.workers.length = 0;
15964
15969
  this.cache.length = 0;
15970
+ this.disposed = true;
15965
15971
  }
15966
15972
  };
15967
15973
 
@@ -18494,6 +18500,8 @@ var Mediabunny = (() => {
18494
18500
  };
18495
18501
 
18496
18502
  // src/target.ts
18503
+ var nodeAlias2 = __toESM(require_node(), 1);
18504
+ var node2 = typeof nodeAlias2 !== "undefined" ? nodeAlias2 : void 0;
18497
18505
  var Target = class {
18498
18506
  constructor() {
18499
18507
  /** @internal */
@@ -18542,6 +18550,44 @@ var Mediabunny = (() => {
18542
18550
  return new StreamTargetWriter(this);
18543
18551
  }
18544
18552
  };
18553
+ var FilePathTarget = class extends Target {
18554
+ /** Creates a new {@link FilePathTarget} that writes to the file at the specified file path. */
18555
+ constructor(filePath, options = {}) {
18556
+ if (typeof filePath !== "string") {
18557
+ throw new TypeError("filePath must be a string.");
18558
+ }
18559
+ if (!options || typeof options !== "object") {
18560
+ throw new TypeError("options must be an object.");
18561
+ }
18562
+ super();
18563
+ /** @internal */
18564
+ this._fileHandle = null;
18565
+ const writable = new WritableStream({
18566
+ start: async () => {
18567
+ this._fileHandle = await node2.fs.open(filePath, "w");
18568
+ },
18569
+ write: async (chunk) => {
18570
+ assert(this._fileHandle);
18571
+ await this._fileHandle.write(chunk.data, 0, chunk.data.byteLength, chunk.position);
18572
+ },
18573
+ close: async () => {
18574
+ if (this._fileHandle) {
18575
+ await this._fileHandle.close();
18576
+ this._fileHandle = null;
18577
+ }
18578
+ }
18579
+ });
18580
+ this._streamTarget = new StreamTarget(writable, {
18581
+ chunked: true,
18582
+ ...options
18583
+ });
18584
+ this._streamTarget._output = this._output;
18585
+ }
18586
+ /** @internal */
18587
+ _createWriter() {
18588
+ return this._streamTarget._createWriter();
18589
+ }
18590
+ };
18545
18591
  var NullTarget = class extends Target {
18546
18592
  /** @internal */
18547
18593
  _createWriter() {
@@ -23864,7 +23910,16 @@ ${cue.notes ?? ""}`;
23864
23910
  throw new TypeError("options.video.alpha, when provided, must be either 'discard' or 'keep'.");
23865
23911
  }
23866
23912
  if (videoOptions?.keyFrameInterval !== void 0 && (!Number.isFinite(videoOptions.keyFrameInterval) || videoOptions.keyFrameInterval < 0)) {
23867
- throw new TypeError("config.keyFrameInterval, when provided, must be a non-negative number.");
23913
+ throw new TypeError("options.video.keyFrameInterval, when provided, must be a non-negative number.");
23914
+ }
23915
+ if (videoOptions?.process !== void 0 && typeof videoOptions.process !== "function") {
23916
+ throw new TypeError("options.video.process, when provided, must be a function.");
23917
+ }
23918
+ if (videoOptions?.processedWidth !== void 0 && (!Number.isInteger(videoOptions.processedWidth) || videoOptions.processedWidth <= 0)) {
23919
+ throw new TypeError("options.video.processedWidth, when provided, must be a positive integer.");
23920
+ }
23921
+ if (videoOptions?.processedHeight !== void 0 && (!Number.isInteger(videoOptions.processedHeight) || videoOptions.processedHeight <= 0)) {
23922
+ throw new TypeError("options.video.processedHeight, when provided, must be a positive integer.");
23868
23923
  }
23869
23924
  };
23870
23925
  var validateAudioOptions = (audioOptions) => {
@@ -23891,6 +23946,15 @@ ${cue.notes ?? ""}`;
23891
23946
  if (audioOptions?.sampleRate !== void 0 && (!Number.isInteger(audioOptions.sampleRate) || audioOptions.sampleRate <= 0)) {
23892
23947
  throw new TypeError("options.audio.sampleRate, when provided, must be a positive integer.");
23893
23948
  }
23949
+ if (audioOptions?.process !== void 0 && typeof audioOptions.process !== "function") {
23950
+ throw new TypeError("options.audio.process, when provided, must be a function.");
23951
+ }
23952
+ if (audioOptions?.processedNumberOfChannels !== void 0 && (!Number.isInteger(audioOptions.processedNumberOfChannels) || audioOptions.processedNumberOfChannels <= 0)) {
23953
+ throw new TypeError("options.audio.processedNumberOfChannels, when provided, must be a positive integer.");
23954
+ }
23955
+ if (audioOptions?.processedSampleRate !== void 0 && (!Number.isInteger(audioOptions.processedSampleRate) || audioOptions.processedSampleRate <= 0)) {
23956
+ throw new TypeError("options.audio.processedSampleRate, when provided, must be a positive integer.");
23957
+ }
23894
23958
  };
23895
23959
  var FALLBACK_NUMBER_OF_CHANNELS = 2;
23896
23960
  var FALLBACK_SAMPLE_RATE = 48e3;
@@ -24225,7 +24289,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24225
24289
  height = ceilToMultipleOfTwo(trackOptions.height);
24226
24290
  }
24227
24291
  const firstTimestamp = await track.getFirstTimestamp();
24228
- const needsTranscode = !!trackOptions.forceTranscode || this._startTimestamp > 0 || firstTimestamp < 0 || !!trackOptions.frameRate || trackOptions.keyFrameInterval !== void 0;
24292
+ const needsTranscode = !!trackOptions.forceTranscode || this._startTimestamp > 0 || firstTimestamp < 0 || !!trackOptions.frameRate || trackOptions.keyFrameInterval !== void 0 || trackOptions.process !== void 0;
24229
24293
  let needsRerender = width !== originalWidth || height !== originalHeight || totalRotation !== 0 && !outputSupportsRotation || !!crop;
24230
24294
  const alpha = trackOptions.alpha ?? "discard";
24231
24295
  let videoCodecs = this.output.format.getSupportedVideoCodecs();
@@ -24239,9 +24303,6 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24239
24303
  const meta = { decoderConfig: decoderConfig ?? void 0 };
24240
24304
  const endPacket = Number.isFinite(this._endTimestamp) ? await sink.getPacket(this._endTimestamp, { metadataOnly: true }) ?? void 0 : void 0;
24241
24305
  for await (const packet of sink.packets(void 0, endPacket, { verifyKeyPackets: true })) {
24242
- if (this._synchronizer.shouldWait(track.id, packet.timestamp)) {
24243
- await this._synchronizer.wait(packet.timestamp);
24244
- }
24245
24306
  if (this._canceled) {
24246
24307
  return;
24247
24308
  }
@@ -24249,8 +24310,11 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24249
24310
  delete packet.sideData.alpha;
24250
24311
  delete packet.sideData.alphaByteLength;
24251
24312
  }
24313
+ this._reportProgress(track.id, packet.timestamp);
24252
24314
  await source.add(packet, meta);
24253
- this._reportProgress(track.id, packet.timestamp + packet.duration);
24315
+ if (this._synchronizer.shouldWait(track.id, packet.timestamp)) {
24316
+ await this._synchronizer.wait(packet.timestamp);
24317
+ }
24254
24318
  }
24255
24319
  source.close();
24256
24320
  this._synchronizer.closeTrack(track.id);
@@ -24268,7 +24332,11 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24268
24332
  videoCodecs = videoCodecs.filter((codec) => codec === trackOptions.codec);
24269
24333
  }
24270
24334
  const bitrate = trackOptions.bitrate ?? QUALITY_HIGH;
24271
- const encodableCodec = await getFirstEncodableVideoCodec(videoCodecs, { width, height, bitrate });
24335
+ const encodableCodec = await getFirstEncodableVideoCodec(videoCodecs, {
24336
+ width: trackOptions.process && trackOptions.processedWidth ? trackOptions.processedWidth : width,
24337
+ height: trackOptions.process && trackOptions.processedHeight ? trackOptions.processedHeight : height,
24338
+ bitrate
24339
+ });
24272
24340
  if (!encodableCodec) {
24273
24341
  this.discardedTracks.push({
24274
24342
  track,
@@ -24281,8 +24349,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24281
24349
  bitrate,
24282
24350
  keyFrameInterval: trackOptions.keyFrameInterval,
24283
24351
  sizeChangeBehavior: trackOptions.fit ?? "passThrough",
24284
- alpha,
24285
- onEncodedPacket: (sample) => this._reportProgress(track.id, sample.timestamp + sample.duration)
24352
+ alpha
24286
24353
  };
24287
24354
  const source = new VideoSampleSource(encodingConfig);
24288
24355
  videoSource = source;
@@ -24338,13 +24405,10 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24338
24405
  timestamp: lastCanvasTimestamp + i / frameRate,
24339
24406
  duration: 1 / frameRate
24340
24407
  });
24341
- await source.add(sample);
24408
+ await this._registerVideoSample(track, trackOptions, source, sample);
24342
24409
  }
24343
24410
  };
24344
24411
  for await (const { canvas, timestamp, duration } of iterator) {
24345
- if (this._synchronizer.shouldWait(track.id, timestamp)) {
24346
- await this._synchronizer.wait(timestamp);
24347
- }
24348
24412
  if (this._canceled) {
24349
24413
  return;
24350
24414
  }
@@ -24367,7 +24431,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24367
24431
  timestamp: adjustedSampleTimestamp,
24368
24432
  duration: frameRate !== void 0 ? 1 / frameRate : duration
24369
24433
  });
24370
- await source.add(sample);
24434
+ await this._registerVideoSample(track, trackOptions, source, sample);
24371
24435
  if (frameRate !== void 0) {
24372
24436
  lastCanvas = canvas;
24373
24437
  lastCanvasTimestamp = adjustedSampleTimestamp;
@@ -24398,14 +24462,11 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24398
24462
  for (let i = 1; i < frameDifference; i++) {
24399
24463
  lastSample.setTimestamp(lastSampleTimestamp + i / frameRate);
24400
24464
  lastSample.setDuration(1 / frameRate);
24401
- await source.add(lastSample);
24465
+ await this._registerVideoSample(track, trackOptions, source, lastSample);
24402
24466
  }
24403
24467
  lastSample.close();
24404
24468
  };
24405
24469
  for await (const sample of sink.samples(this._startTimestamp, this._endTimestamp)) {
24406
- if (this._synchronizer.shouldWait(track.id, sample.timestamp)) {
24407
- await this._synchronizer.wait(sample.timestamp);
24408
- }
24409
24470
  if (this._canceled) {
24410
24471
  lastSample?.close();
24411
24472
  return;
@@ -24428,7 +24489,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24428
24489
  sample.setDuration(1 / frameRate);
24429
24490
  }
24430
24491
  sample.setTimestamp(adjustedSampleTimestamp);
24431
- await source.add(sample);
24492
+ await this._registerVideoSample(track, trackOptions, source, sample);
24432
24493
  if (frameRate !== void 0) {
24433
24494
  lastSample = sample;
24434
24495
  lastSampleTimestamp = adjustedSampleTimestamp;
@@ -24459,6 +24520,49 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24459
24520
  this.utilizedTracks.push(track);
24460
24521
  }
24461
24522
  /** @internal */
24523
+ async _registerVideoSample(track, trackOptions, source, sample) {
24524
+ if (this._canceled) {
24525
+ return;
24526
+ }
24527
+ this._reportProgress(track.id, sample.timestamp);
24528
+ let finalSamples;
24529
+ if (!trackOptions.process) {
24530
+ finalSamples = [sample];
24531
+ } else {
24532
+ let processed = trackOptions.process(sample);
24533
+ if (processed instanceof Promise) processed = await processed;
24534
+ if (!Array.isArray(processed)) {
24535
+ processed = processed === null ? [] : [processed];
24536
+ }
24537
+ finalSamples = processed.map((x) => {
24538
+ if (x instanceof VideoSample) {
24539
+ return x;
24540
+ }
24541
+ if (typeof VideoFrame !== "undefined" && x instanceof VideoFrame) {
24542
+ return new VideoSample(x);
24543
+ }
24544
+ return new VideoSample(x, {
24545
+ timestamp: sample.timestamp,
24546
+ duration: sample.duration
24547
+ });
24548
+ });
24549
+ }
24550
+ for (const finalSample of finalSamples) {
24551
+ if (this._canceled) {
24552
+ break;
24553
+ }
24554
+ await source.add(finalSample);
24555
+ if (this._synchronizer.shouldWait(track.id, finalSample.timestamp)) {
24556
+ await this._synchronizer.wait(finalSample.timestamp);
24557
+ }
24558
+ }
24559
+ for (const finalSample of finalSamples) {
24560
+ if (finalSample !== sample) {
24561
+ finalSample.close();
24562
+ }
24563
+ }
24564
+ }
24565
+ /** @internal */
24462
24566
  async _processAudioTrack(track, trackOptions) {
24463
24567
  const sourceCodec = track.codec;
24464
24568
  if (!sourceCodec) {
@@ -24476,7 +24580,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24476
24580
  let sampleRate = trackOptions.sampleRate ?? originalSampleRate;
24477
24581
  let needsResample = numberOfChannels !== originalNumberOfChannels || sampleRate !== originalSampleRate || this._startTimestamp > 0 || firstTimestamp < 0;
24478
24582
  let audioCodecs = this.output.format.getSupportedAudioCodecs();
24479
- if (!trackOptions.forceTranscode && !trackOptions.bitrate && !needsResample && audioCodecs.includes(sourceCodec) && (!trackOptions.codec || trackOptions.codec === sourceCodec)) {
24583
+ if (!trackOptions.forceTranscode && !trackOptions.bitrate && !needsResample && audioCodecs.includes(sourceCodec) && (!trackOptions.codec || trackOptions.codec === sourceCodec) && !trackOptions.process) {
24480
24584
  const source = new EncodedAudioPacketSource(sourceCodec);
24481
24585
  audioSource = source;
24482
24586
  this._trackPromises.push((async () => {
@@ -24486,14 +24590,14 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24486
24590
  const meta = { decoderConfig: decoderConfig ?? void 0 };
24487
24591
  const endPacket = Number.isFinite(this._endTimestamp) ? await sink.getPacket(this._endTimestamp, { metadataOnly: true }) ?? void 0 : void 0;
24488
24592
  for await (const packet of sink.packets(void 0, endPacket)) {
24489
- if (this._synchronizer.shouldWait(track.id, packet.timestamp)) {
24490
- await this._synchronizer.wait(packet.timestamp);
24491
- }
24492
24593
  if (this._canceled) {
24493
24594
  return;
24494
24595
  }
24596
+ this._reportProgress(track.id, packet.timestamp);
24495
24597
  await source.add(packet, meta);
24496
- this._reportProgress(track.id, packet.timestamp + packet.duration);
24598
+ if (this._synchronizer.shouldWait(track.id, packet.timestamp)) {
24599
+ await this._synchronizer.wait(packet.timestamp);
24600
+ }
24497
24601
  }
24498
24602
  source.close();
24499
24603
  this._synchronizer.closeTrack(track.id);
@@ -24513,11 +24617,11 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24513
24617
  }
24514
24618
  const bitrate = trackOptions.bitrate ?? QUALITY_HIGH;
24515
24619
  const encodableCodecs = await getEncodableAudioCodecs(audioCodecs, {
24516
- numberOfChannels,
24517
- sampleRate,
24620
+ numberOfChannels: trackOptions.process && trackOptions.processedNumberOfChannels ? trackOptions.processedNumberOfChannels : numberOfChannels,
24621
+ sampleRate: trackOptions.process && trackOptions.processedSampleRate ? trackOptions.processedSampleRate : sampleRate,
24518
24622
  bitrate
24519
24623
  });
24520
- if (!encodableCodecs.some((codec) => NON_PCM_AUDIO_CODECS.includes(codec)) && audioCodecs.some((codec) => NON_PCM_AUDIO_CODECS.includes(codec)) && (numberOfChannels !== FALLBACK_NUMBER_OF_CHANNELS || sampleRate !== FALLBACK_SAMPLE_RATE)) {
24624
+ if (!encodableCodecs.some((codec) => NON_PCM_AUDIO_CODECS.includes(codec)) && audioCodecs.some((codec) => NON_PCM_AUDIO_CODECS.includes(codec)) && (numberOfChannels !== FALLBACK_NUMBER_OF_CHANNELS || sampleRate !== FALLBACK_SAMPLE_RATE) && !trackOptions.process) {
24521
24625
  const encodableCodecsWithDefaultParams = await getEncodableAudioCodecs(audioCodecs, {
24522
24626
  numberOfChannels: FALLBACK_NUMBER_OF_CHANNELS,
24523
24627
  sampleRate: FALLBACK_SAMPLE_RATE,
@@ -24541,25 +24645,28 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24541
24645
  return;
24542
24646
  }
24543
24647
  if (needsResample) {
24544
- audioSource = this._resampleAudio(track, codecOfChoice, numberOfChannels, sampleRate, bitrate);
24648
+ audioSource = this._resampleAudio(
24649
+ track,
24650
+ trackOptions,
24651
+ codecOfChoice,
24652
+ numberOfChannels,
24653
+ sampleRate,
24654
+ bitrate
24655
+ );
24545
24656
  } else {
24546
24657
  const source = new AudioSampleSource({
24547
24658
  codec: codecOfChoice,
24548
- bitrate,
24549
- onEncodedPacket: (packet) => this._reportProgress(track.id, packet.timestamp + packet.duration)
24659
+ bitrate
24550
24660
  });
24551
24661
  audioSource = source;
24552
24662
  this._trackPromises.push((async () => {
24553
24663
  await this._started;
24554
24664
  const sink = new AudioSampleSink(track);
24555
24665
  for await (const sample of sink.samples(void 0, this._endTimestamp)) {
24556
- if (this._synchronizer.shouldWait(track.id, sample.timestamp)) {
24557
- await this._synchronizer.wait(sample.timestamp);
24558
- }
24559
24666
  if (this._canceled) {
24560
24667
  return;
24561
24668
  }
24562
- await source.add(sample);
24669
+ await this._registerAudioSample(track, trackOptions, source, sample);
24563
24670
  sample.close();
24564
24671
  }
24565
24672
  source.close();
@@ -24577,11 +24684,47 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24577
24684
  this.utilizedTracks.push(track);
24578
24685
  }
24579
24686
  /** @internal */
24580
- _resampleAudio(track, codec, targetNumberOfChannels, targetSampleRate, bitrate) {
24687
+ async _registerAudioSample(track, trackOptions, source, sample) {
24688
+ if (this._canceled) {
24689
+ return;
24690
+ }
24691
+ this._reportProgress(track.id, sample.timestamp);
24692
+ let finalSamples;
24693
+ if (!trackOptions.process) {
24694
+ finalSamples = [sample];
24695
+ } else {
24696
+ let processed = trackOptions.process(sample);
24697
+ if (processed instanceof Promise) processed = await processed;
24698
+ if (!Array.isArray(processed)) {
24699
+ processed = processed === null ? [] : [processed];
24700
+ }
24701
+ if (!processed.every((x) => x instanceof AudioSample)) {
24702
+ throw new TypeError(
24703
+ "The audio process function must return an AudioSample, null, or an array of AudioSamples."
24704
+ );
24705
+ }
24706
+ finalSamples = processed;
24707
+ }
24708
+ for (const finalSample of finalSamples) {
24709
+ if (this._canceled) {
24710
+ break;
24711
+ }
24712
+ await source.add(finalSample);
24713
+ if (this._synchronizer.shouldWait(track.id, finalSample.timestamp)) {
24714
+ await this._synchronizer.wait(finalSample.timestamp);
24715
+ }
24716
+ }
24717
+ for (const finalSample of finalSamples) {
24718
+ if (finalSample !== sample) {
24719
+ finalSample.close();
24720
+ }
24721
+ }
24722
+ }
24723
+ /** @internal */
24724
+ _resampleAudio(track, trackOptions, codec, targetNumberOfChannels, targetSampleRate, bitrate) {
24581
24725
  const source = new AudioSampleSource({
24582
24726
  codec,
24583
- bitrate,
24584
- onEncodedPacket: (packet) => this._reportProgress(track.id, packet.timestamp + packet.duration)
24727
+ bitrate
24585
24728
  });
24586
24729
  this._trackPromises.push((async () => {
24587
24730
  await this._started;
@@ -24590,14 +24733,11 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24590
24733
  targetSampleRate,
24591
24734
  startTime: this._startTimestamp,
24592
24735
  endTime: this._endTimestamp,
24593
- onSample: (sample) => source.add(sample)
24736
+ onSample: (sample) => this._registerAudioSample(track, trackOptions, source, sample)
24594
24737
  });
24595
24738
  const sink = new AudioSampleSink(track);
24596
24739
  const iterator = sink.samples(this._startTimestamp, this._endTimestamp);
24597
24740
  for await (const sample of iterator) {
24598
- if (this._synchronizer.shouldWait(track.id, sample.timestamp)) {
24599
- await this._synchronizer.wait(sample.timestamp);
24600
- }
24601
24741
  if (this._canceled) {
24602
24742
  return;
24603
24743
  }