mediabunny 1.23.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.
package/README.md CHANGED
@@ -1,6 +1,3 @@
1
- > [!NOTE]
2
- > I'm on vacation until 18 October, so expect slow or no replies to issues during that time. 🏖️
3
-
4
1
  # Mediabunny - JavaScript media toolkit
5
2
 
6
3
  [![](https://img.shields.io/npm/v/mediabunny)](https://www.npmjs.com/package/mediabunny)
@@ -313,7 +313,7 @@ var Mediabunny = (() => {
313
313
  // Linear transfer characteristics
314
314
  "iec61966-2-1": 13,
315
315
  // IEC 61966-2-1
316
- "pg": 16,
316
+ "pq": 16,
317
317
  // Rec. ITU-R BT.2100-2 perceptual quantization (PQ) system
318
318
  "hlg": 18
319
319
  // Rec. ITU-R BT.2100-2 hybrid loggamma (HLG) system
@@ -23910,7 +23910,16 @@ ${cue.notes ?? ""}`;
23910
23910
  throw new TypeError("options.video.alpha, when provided, must be either 'discard' or 'keep'.");
23911
23911
  }
23912
23912
  if (videoOptions?.keyFrameInterval !== void 0 && (!Number.isFinite(videoOptions.keyFrameInterval) || videoOptions.keyFrameInterval < 0)) {
23913
- 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.");
23914
23923
  }
23915
23924
  };
23916
23925
  var validateAudioOptions = (audioOptions) => {
@@ -23937,6 +23946,15 @@ ${cue.notes ?? ""}`;
23937
23946
  if (audioOptions?.sampleRate !== void 0 && (!Number.isInteger(audioOptions.sampleRate) || audioOptions.sampleRate <= 0)) {
23938
23947
  throw new TypeError("options.audio.sampleRate, when provided, must be a positive integer.");
23939
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
+ }
23940
23958
  };
23941
23959
  var FALLBACK_NUMBER_OF_CHANNELS = 2;
23942
23960
  var FALLBACK_SAMPLE_RATE = 48e3;
@@ -24271,7 +24289,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24271
24289
  height = ceilToMultipleOfTwo(trackOptions.height);
24272
24290
  }
24273
24291
  const firstTimestamp = await track.getFirstTimestamp();
24274
- 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;
24275
24293
  let needsRerender = width !== originalWidth || height !== originalHeight || totalRotation !== 0 && !outputSupportsRotation || !!crop;
24276
24294
  const alpha = trackOptions.alpha ?? "discard";
24277
24295
  let videoCodecs = this.output.format.getSupportedVideoCodecs();
@@ -24285,9 +24303,6 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24285
24303
  const meta = { decoderConfig: decoderConfig ?? void 0 };
24286
24304
  const endPacket = Number.isFinite(this._endTimestamp) ? await sink.getPacket(this._endTimestamp, { metadataOnly: true }) ?? void 0 : void 0;
24287
24305
  for await (const packet of sink.packets(void 0, endPacket, { verifyKeyPackets: true })) {
24288
- if (this._synchronizer.shouldWait(track.id, packet.timestamp)) {
24289
- await this._synchronizer.wait(packet.timestamp);
24290
- }
24291
24306
  if (this._canceled) {
24292
24307
  return;
24293
24308
  }
@@ -24295,8 +24310,11 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24295
24310
  delete packet.sideData.alpha;
24296
24311
  delete packet.sideData.alphaByteLength;
24297
24312
  }
24313
+ this._reportProgress(track.id, packet.timestamp);
24298
24314
  await source.add(packet, meta);
24299
- 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
+ }
24300
24318
  }
24301
24319
  source.close();
24302
24320
  this._synchronizer.closeTrack(track.id);
@@ -24314,7 +24332,11 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24314
24332
  videoCodecs = videoCodecs.filter((codec) => codec === trackOptions.codec);
24315
24333
  }
24316
24334
  const bitrate = trackOptions.bitrate ?? QUALITY_HIGH;
24317
- 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
+ });
24318
24340
  if (!encodableCodec) {
24319
24341
  this.discardedTracks.push({
24320
24342
  track,
@@ -24327,8 +24349,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24327
24349
  bitrate,
24328
24350
  keyFrameInterval: trackOptions.keyFrameInterval,
24329
24351
  sizeChangeBehavior: trackOptions.fit ?? "passThrough",
24330
- alpha,
24331
- onEncodedPacket: (sample) => this._reportProgress(track.id, sample.timestamp + sample.duration)
24352
+ alpha
24332
24353
  };
24333
24354
  const source = new VideoSampleSource(encodingConfig);
24334
24355
  videoSource = source;
@@ -24384,13 +24405,10 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24384
24405
  timestamp: lastCanvasTimestamp + i / frameRate,
24385
24406
  duration: 1 / frameRate
24386
24407
  });
24387
- await source.add(sample);
24408
+ await this._registerVideoSample(track, trackOptions, source, sample);
24388
24409
  }
24389
24410
  };
24390
24411
  for await (const { canvas, timestamp, duration } of iterator) {
24391
- if (this._synchronizer.shouldWait(track.id, timestamp)) {
24392
- await this._synchronizer.wait(timestamp);
24393
- }
24394
24412
  if (this._canceled) {
24395
24413
  return;
24396
24414
  }
@@ -24413,7 +24431,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24413
24431
  timestamp: adjustedSampleTimestamp,
24414
24432
  duration: frameRate !== void 0 ? 1 / frameRate : duration
24415
24433
  });
24416
- await source.add(sample);
24434
+ await this._registerVideoSample(track, trackOptions, source, sample);
24417
24435
  if (frameRate !== void 0) {
24418
24436
  lastCanvas = canvas;
24419
24437
  lastCanvasTimestamp = adjustedSampleTimestamp;
@@ -24444,14 +24462,11 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24444
24462
  for (let i = 1; i < frameDifference; i++) {
24445
24463
  lastSample.setTimestamp(lastSampleTimestamp + i / frameRate);
24446
24464
  lastSample.setDuration(1 / frameRate);
24447
- await source.add(lastSample);
24465
+ await this._registerVideoSample(track, trackOptions, source, lastSample);
24448
24466
  }
24449
24467
  lastSample.close();
24450
24468
  };
24451
24469
  for await (const sample of sink.samples(this._startTimestamp, this._endTimestamp)) {
24452
- if (this._synchronizer.shouldWait(track.id, sample.timestamp)) {
24453
- await this._synchronizer.wait(sample.timestamp);
24454
- }
24455
24470
  if (this._canceled) {
24456
24471
  lastSample?.close();
24457
24472
  return;
@@ -24474,7 +24489,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24474
24489
  sample.setDuration(1 / frameRate);
24475
24490
  }
24476
24491
  sample.setTimestamp(adjustedSampleTimestamp);
24477
- await source.add(sample);
24492
+ await this._registerVideoSample(track, trackOptions, source, sample);
24478
24493
  if (frameRate !== void 0) {
24479
24494
  lastSample = sample;
24480
24495
  lastSampleTimestamp = adjustedSampleTimestamp;
@@ -24505,6 +24520,49 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24505
24520
  this.utilizedTracks.push(track);
24506
24521
  }
24507
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 */
24508
24566
  async _processAudioTrack(track, trackOptions) {
24509
24567
  const sourceCodec = track.codec;
24510
24568
  if (!sourceCodec) {
@@ -24522,7 +24580,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24522
24580
  let sampleRate = trackOptions.sampleRate ?? originalSampleRate;
24523
24581
  let needsResample = numberOfChannels !== originalNumberOfChannels || sampleRate !== originalSampleRate || this._startTimestamp > 0 || firstTimestamp < 0;
24524
24582
  let audioCodecs = this.output.format.getSupportedAudioCodecs();
24525
- 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) {
24526
24584
  const source = new EncodedAudioPacketSource(sourceCodec);
24527
24585
  audioSource = source;
24528
24586
  this._trackPromises.push((async () => {
@@ -24532,14 +24590,14 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24532
24590
  const meta = { decoderConfig: decoderConfig ?? void 0 };
24533
24591
  const endPacket = Number.isFinite(this._endTimestamp) ? await sink.getPacket(this._endTimestamp, { metadataOnly: true }) ?? void 0 : void 0;
24534
24592
  for await (const packet of sink.packets(void 0, endPacket)) {
24535
- if (this._synchronizer.shouldWait(track.id, packet.timestamp)) {
24536
- await this._synchronizer.wait(packet.timestamp);
24537
- }
24538
24593
  if (this._canceled) {
24539
24594
  return;
24540
24595
  }
24596
+ this._reportProgress(track.id, packet.timestamp);
24541
24597
  await source.add(packet, meta);
24542
- 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
+ }
24543
24601
  }
24544
24602
  source.close();
24545
24603
  this._synchronizer.closeTrack(track.id);
@@ -24559,11 +24617,11 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24559
24617
  }
24560
24618
  const bitrate = trackOptions.bitrate ?? QUALITY_HIGH;
24561
24619
  const encodableCodecs = await getEncodableAudioCodecs(audioCodecs, {
24562
- numberOfChannels,
24563
- sampleRate,
24620
+ numberOfChannels: trackOptions.process && trackOptions.processedNumberOfChannels ? trackOptions.processedNumberOfChannels : numberOfChannels,
24621
+ sampleRate: trackOptions.process && trackOptions.processedSampleRate ? trackOptions.processedSampleRate : sampleRate,
24564
24622
  bitrate
24565
24623
  });
24566
- 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) {
24567
24625
  const encodableCodecsWithDefaultParams = await getEncodableAudioCodecs(audioCodecs, {
24568
24626
  numberOfChannels: FALLBACK_NUMBER_OF_CHANNELS,
24569
24627
  sampleRate: FALLBACK_SAMPLE_RATE,
@@ -24587,25 +24645,28 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24587
24645
  return;
24588
24646
  }
24589
24647
  if (needsResample) {
24590
- 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
+ );
24591
24656
  } else {
24592
24657
  const source = new AudioSampleSource({
24593
24658
  codec: codecOfChoice,
24594
- bitrate,
24595
- onEncodedPacket: (packet) => this._reportProgress(track.id, packet.timestamp + packet.duration)
24659
+ bitrate
24596
24660
  });
24597
24661
  audioSource = source;
24598
24662
  this._trackPromises.push((async () => {
24599
24663
  await this._started;
24600
24664
  const sink = new AudioSampleSink(track);
24601
24665
  for await (const sample of sink.samples(void 0, this._endTimestamp)) {
24602
- if (this._synchronizer.shouldWait(track.id, sample.timestamp)) {
24603
- await this._synchronizer.wait(sample.timestamp);
24604
- }
24605
24666
  if (this._canceled) {
24606
24667
  return;
24607
24668
  }
24608
- await source.add(sample);
24669
+ await this._registerAudioSample(track, trackOptions, source, sample);
24609
24670
  sample.close();
24610
24671
  }
24611
24672
  source.close();
@@ -24623,11 +24684,47 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24623
24684
  this.utilizedTracks.push(track);
24624
24685
  }
24625
24686
  /** @internal */
24626
- _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) {
24627
24725
  const source = new AudioSampleSource({
24628
24726
  codec,
24629
- bitrate,
24630
- onEncodedPacket: (packet) => this._reportProgress(track.id, packet.timestamp + packet.duration)
24727
+ bitrate
24631
24728
  });
24632
24729
  this._trackPromises.push((async () => {
24633
24730
  await this._started;
@@ -24636,14 +24733,11 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24636
24733
  targetSampleRate,
24637
24734
  startTime: this._startTimestamp,
24638
24735
  endTime: this._endTimestamp,
24639
- onSample: (sample) => source.add(sample)
24736
+ onSample: (sample) => this._registerAudioSample(track, trackOptions, source, sample)
24640
24737
  });
24641
24738
  const sink = new AudioSampleSink(track);
24642
24739
  const iterator = sink.samples(this._startTimestamp, this._endTimestamp);
24643
24740
  for await (const sample of iterator) {
24644
- if (this._synchronizer.shouldWait(track.id, sample.timestamp)) {
24645
- await this._synchronizer.wait(sample.timestamp);
24646
- }
24647
24741
  if (this._canceled) {
24648
24742
  return;
24649
24743
  }