mediabunny 1.34.5 → 1.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/bundles/mediabunny.cjs +355 -33
  2. package/dist/bundles/mediabunny.min.cjs +8 -8
  3. package/dist/bundles/mediabunny.min.mjs +8 -8
  4. package/dist/bundles/mediabunny.mjs +355 -33
  5. package/dist/mediabunny.d.ts +65 -9
  6. package/dist/modules/src/codec-data.d.ts +3 -0
  7. package/dist/modules/src/codec-data.d.ts.map +1 -1
  8. package/dist/modules/src/codec-data.js +45 -4
  9. package/dist/modules/src/conversion.d.ts.map +1 -1
  10. package/dist/modules/src/conversion.js +20 -12
  11. package/dist/modules/src/encode.d.ts +2 -0
  12. package/dist/modules/src/encode.d.ts.map +1 -1
  13. package/dist/modules/src/encode.js +2 -0
  14. package/dist/modules/src/index.d.ts +1 -1
  15. package/dist/modules/src/index.d.ts.map +1 -1
  16. package/dist/modules/src/input-track.d.ts +14 -3
  17. package/dist/modules/src/input-track.d.ts.map +1 -1
  18. package/dist/modules/src/input-track.js +17 -5
  19. package/dist/modules/src/isobmff/isobmff-boxes.d.ts +2 -0
  20. package/dist/modules/src/isobmff/isobmff-boxes.d.ts.map +1 -1
  21. package/dist/modules/src/isobmff/isobmff-boxes.js +11 -0
  22. package/dist/modules/src/isobmff/isobmff-demuxer.d.ts +2 -0
  23. package/dist/modules/src/isobmff/isobmff-demuxer.d.ts.map +1 -1
  24. package/dist/modules/src/isobmff/isobmff-demuxer.js +30 -0
  25. package/dist/modules/src/isobmff/isobmff-muxer.d.ts +2 -0
  26. package/dist/modules/src/isobmff/isobmff-muxer.d.ts.map +1 -1
  27. package/dist/modules/src/isobmff/isobmff-muxer.js +10 -1
  28. package/dist/modules/src/matroska/ebml.d.ts +3 -0
  29. package/dist/modules/src/matroska/ebml.d.ts.map +1 -1
  30. package/dist/modules/src/matroska/ebml.js +3 -0
  31. package/dist/modules/src/matroska/matroska-demuxer.d.ts +5 -0
  32. package/dist/modules/src/matroska/matroska-demuxer.d.ts.map +1 -1
  33. package/dist/modules/src/matroska/matroska-demuxer.js +50 -0
  34. package/dist/modules/src/matroska/matroska-muxer.d.ts.map +1 -1
  35. package/dist/modules/src/matroska/matroska-muxer.js +15 -1
  36. package/dist/modules/src/media-sink.d.ts +2 -1
  37. package/dist/modules/src/media-sink.d.ts.map +1 -1
  38. package/dist/modules/src/media-sink.js +2 -2
  39. package/dist/modules/src/media-source.d.ts.map +1 -1
  40. package/dist/modules/src/media-source.js +2 -0
  41. package/dist/modules/src/misc.d.ts +28 -0
  42. package/dist/modules/src/misc.d.ts.map +1 -1
  43. package/dist/modules/src/misc.js +33 -0
  44. package/dist/modules/src/mpeg-ts/mpeg-ts-demuxer.d.ts +2 -0
  45. package/dist/modules/src/mpeg-ts/mpeg-ts-demuxer.d.ts.map +1 -1
  46. package/dist/modules/src/mpeg-ts/mpeg-ts-demuxer.js +30 -0
  47. package/dist/modules/src/sample.d.ts +25 -7
  48. package/dist/modules/src/sample.d.ts.map +1 -1
  49. package/dist/modules/src/sample.js +110 -18
  50. package/dist/modules/src/tsconfig.tsbuildinfo +1 -1
  51. package/package.json +1 -1
  52. package/src/codec-data.ts +48 -4
  53. package/src/conversion.ts +23 -14
  54. package/src/encode.ts +4 -0
  55. package/src/index.ts +2 -0
  56. package/src/input-track.ts +27 -5
  57. package/src/isobmff/isobmff-boxes.ts +13 -0
  58. package/src/isobmff/isobmff-demuxer.ts +33 -0
  59. package/src/isobmff/isobmff-muxer.ts +12 -1
  60. package/src/matroska/ebml.ts +3 -0
  61. package/src/matroska/matroska-demuxer.ts +59 -0
  62. package/src/matroska/matroska-muxer.ts +23 -0
  63. package/src/media-sink.ts +4 -3
  64. package/src/media-source.ts +2 -0
  65. package/src/misc.ts +66 -0
  66. package/src/mpeg-ts/mpeg-ts-demuxer.ts +44 -0
  67. package/src/sample.ts +156 -21
@@ -761,6 +761,38 @@ var Mediabunny = (() => {
761
761
  var isNumber = (x) => {
762
762
  return typeof x === "number" && !Number.isNaN(x);
763
763
  };
764
+ var simplifyRational = (rational) => {
765
+ assert(rational.den !== 0);
766
+ let a = Math.abs(rational.num);
767
+ let b = Math.abs(rational.den);
768
+ while (b !== 0) {
769
+ const t = a % b;
770
+ a = b;
771
+ b = t;
772
+ }
773
+ const gcd = a || 1;
774
+ return {
775
+ num: rational.num / gcd,
776
+ den: rational.den / gcd
777
+ };
778
+ };
779
+ var validateRectangle = (rect, propertyPath) => {
780
+ if (typeof rect !== "object" || !rect) {
781
+ throw new TypeError(`${propertyPath} must be an object.`);
782
+ }
783
+ if (!Number.isInteger(rect.left) || rect.left < 0) {
784
+ throw new TypeError(`${propertyPath}.left must be a non-negative integer.`);
785
+ }
786
+ if (!Number.isInteger(rect.top) || rect.top < 0) {
787
+ throw new TypeError(`${propertyPath}.top must be a non-negative integer.`);
788
+ }
789
+ if (!Number.isInteger(rect.width) || rect.width < 0) {
790
+ throw new TypeError(`${propertyPath}.width must be a non-negative integer.`);
791
+ }
792
+ if (!Number.isInteger(rect.height) || rect.height < 0) {
793
+ throw new TypeError(`${propertyPath}.height must be a non-negative integer.`);
794
+ }
795
+ };
764
796
 
765
797
  // src/metadata.ts
766
798
  var RichImageData = class {
@@ -2340,6 +2372,24 @@ var Mediabunny = (() => {
2340
2372
  return null;
2341
2373
  }
2342
2374
  };
2375
+ var AVC_HEVC_ASPECT_RATIO_IDC_TABLE = {
2376
+ 1: { num: 1, den: 1 },
2377
+ 2: { num: 12, den: 11 },
2378
+ 3: { num: 10, den: 11 },
2379
+ 4: { num: 16, den: 11 },
2380
+ 5: { num: 40, den: 33 },
2381
+ 6: { num: 24, den: 11 },
2382
+ 7: { num: 20, den: 11 },
2383
+ 8: { num: 32, den: 11 },
2384
+ 9: { num: 80, den: 33 },
2385
+ 10: { num: 18, den: 11 },
2386
+ 11: { num: 15, den: 11 },
2387
+ 12: { num: 64, den: 33 },
2388
+ 13: { num: 160, den: 99 },
2389
+ 14: { num: 4, den: 3 },
2390
+ 15: { num: 3, den: 2 },
2391
+ 16: { num: 2, den: 1 }
2392
+ };
2343
2393
  var parseAvcSps = (sps) => {
2344
2394
  try {
2345
2395
  const bitstream = new Bitstream(removeEmulationPreventionBytes(sps));
@@ -2435,6 +2485,7 @@ var Mediabunny = (() => {
2435
2485
  let transferCharacteristics = 2;
2436
2486
  let matrixCoefficients = 2;
2437
2487
  let fullRangeFlag = 0;
2488
+ let pixelAspectRatio = { num: 1, den: 1 };
2438
2489
  let numReorderFrames = null;
2439
2490
  let maxDecFrameBuffering = null;
2440
2491
  const vuiParametersPresentFlag = bitstream.readBits(1);
@@ -2443,8 +2494,15 @@ var Mediabunny = (() => {
2443
2494
  if (aspectRatioInfoPresentFlag) {
2444
2495
  const aspectRatioIdc = bitstream.readBits(8);
2445
2496
  if (aspectRatioIdc === 255) {
2446
- bitstream.skipBits(16);
2447
- bitstream.skipBits(16);
2497
+ pixelAspectRatio = {
2498
+ num: bitstream.readBits(16),
2499
+ den: bitstream.readBits(16)
2500
+ };
2501
+ } else {
2502
+ const aspectRatio = AVC_HEVC_ASPECT_RATIO_IDC_TABLE[aspectRatioIdc];
2503
+ if (aspectRatio) {
2504
+ pixelAspectRatio = aspectRatio;
2505
+ }
2448
2506
  }
2449
2507
  }
2450
2508
  const overscanInfoPresentFlag = bitstream.readBits(1);
@@ -2530,6 +2588,7 @@ var Mediabunny = (() => {
2530
2588
  codedHeight,
2531
2589
  displayWidth,
2532
2590
  displayHeight,
2591
+ pixelAspectRatio,
2533
2592
  colourPrimaries,
2534
2593
  matrixCoefficients,
2535
2594
  transferCharacteristics,
@@ -2659,8 +2718,10 @@ var Mediabunny = (() => {
2659
2718
  let matrixCoefficients = 2;
2660
2719
  let fullRangeFlag = 0;
2661
2720
  let minSpatialSegmentationIdc = 0;
2721
+ let pixelAspectRatio = { num: 1, den: 1 };
2662
2722
  if (bitstream.readBits(1)) {
2663
2723
  const vui = parseHevcVui(bitstream, spsMaxSubLayersMinus1);
2724
+ pixelAspectRatio = vui.pixelAspectRatio;
2664
2725
  colourPrimaries = vui.colourPrimaries;
2665
2726
  transferCharacteristics = vui.transferCharacteristics;
2666
2727
  matrixCoefficients = vui.matrixCoefficients;
@@ -2670,6 +2731,7 @@ var Mediabunny = (() => {
2670
2731
  return {
2671
2732
  displayWidth,
2672
2733
  displayHeight,
2734
+ pixelAspectRatio,
2673
2735
  colourPrimaries,
2674
2736
  transferCharacteristics,
2675
2737
  matrixCoefficients,
@@ -2910,11 +2972,19 @@ var Mediabunny = (() => {
2910
2972
  let matrixCoefficients = 2;
2911
2973
  let fullRangeFlag = 0;
2912
2974
  let minSpatialSegmentationIdc = 0;
2975
+ let pixelAspectRatio = { num: 1, den: 1 };
2913
2976
  if (bitstream.readBits(1)) {
2914
2977
  const aspect_ratio_idc = bitstream.readBits(8);
2915
2978
  if (aspect_ratio_idc === 255) {
2916
- bitstream.readBits(16);
2917
- bitstream.readBits(16);
2979
+ pixelAspectRatio = {
2980
+ num: bitstream.readBits(16),
2981
+ den: bitstream.readBits(16)
2982
+ };
2983
+ } else {
2984
+ const aspectRatio = AVC_HEVC_ASPECT_RATIO_IDC_TABLE[aspect_ratio_idc];
2985
+ if (aspectRatio) {
2986
+ pixelAspectRatio = aspectRatio;
2987
+ }
2918
2988
  }
2919
2989
  }
2920
2990
  if (bitstream.readBits(1)) {
@@ -2963,6 +3033,7 @@ var Mediabunny = (() => {
2963
3033
  readExpGolomb(bitstream);
2964
3034
  }
2965
3035
  return {
3036
+ pixelAspectRatio,
2966
3037
  colourPrimaries,
2967
3038
  transferCharacteristics,
2968
3039
  matrixCoefficients,
@@ -4718,15 +4789,56 @@ var Mediabunny = (() => {
4718
4789
  if (init.duration !== void 0 && (!Number.isFinite(init.duration) || init.duration < 0)) {
4719
4790
  throw new TypeError("init.duration, when provided, must be a non-negative number.");
4720
4791
  }
4792
+ if (init.layout !== void 0) {
4793
+ if (!Array.isArray(init.layout)) {
4794
+ throw new TypeError("init.layout, when provided, must be an array.");
4795
+ }
4796
+ for (const plane of init.layout) {
4797
+ if (!plane || typeof plane !== "object" || Array.isArray(plane)) {
4798
+ throw new TypeError("Each entry in init.layout must be an object.");
4799
+ }
4800
+ if (!Number.isInteger(plane.offset) || plane.offset < 0) {
4801
+ throw new TypeError("plane.offset must be a non-negative integer.");
4802
+ }
4803
+ if (!Number.isInteger(plane.stride) || plane.stride < 0) {
4804
+ throw new TypeError("plane.stride must be a non-negative integer.");
4805
+ }
4806
+ }
4807
+ }
4808
+ if (init.visibleRect !== void 0) {
4809
+ validateRectangle(init.visibleRect, "init.visibleRect");
4810
+ }
4811
+ if (init.displayWidth !== void 0 && (!Number.isInteger(init.displayWidth) || init.displayWidth <= 0)) {
4812
+ throw new TypeError("init.displayWidth, when provided, must be a positive integer.");
4813
+ }
4814
+ if (init.displayHeight !== void 0 && (!Number.isInteger(init.displayHeight) || init.displayHeight <= 0)) {
4815
+ throw new TypeError("init.displayHeight, when provided, must be a positive integer.");
4816
+ }
4817
+ if (init.displayWidth !== void 0 !== (init.displayHeight !== void 0)) {
4818
+ throw new TypeError(
4819
+ "init.displayWidth and init.displayHeight must be either both provided or both omitted."
4820
+ );
4821
+ }
4721
4822
  this._data = toUint8Array(data).slice();
4722
4823
  this._layout = init.layout ?? createDefaultPlaneLayout(init.format, init.codedWidth, init.codedHeight);
4723
4824
  this.format = init.format;
4724
- this.codedWidth = init.codedWidth;
4725
- this.codedHeight = init.codedHeight;
4726
4825
  this.rotation = init.rotation ?? 0;
4727
4826
  this.timestamp = init.timestamp;
4728
4827
  this.duration = init.duration ?? 0;
4729
4828
  this.colorSpace = new VideoSampleColorSpace(init.colorSpace);
4829
+ this.visibleRect = {
4830
+ left: init.visibleRect?.left ?? 0,
4831
+ top: init.visibleRect?.top ?? 0,
4832
+ width: init.visibleRect?.width ?? init.codedWidth,
4833
+ height: init.visibleRect?.height ?? init.codedHeight
4834
+ };
4835
+ if (init.displayWidth !== void 0) {
4836
+ this.squarePixelWidth = this.rotation % 180 === 0 ? init.displayWidth : init.displayHeight;
4837
+ this.squarePixelHeight = this.rotation % 180 === 0 ? init.displayHeight : init.displayWidth;
4838
+ } else {
4839
+ this.squarePixelWidth = this.codedWidth;
4840
+ this.squarePixelHeight = this.codedHeight;
4841
+ }
4730
4842
  } else if (typeof VideoFrame !== "undefined" && data instanceof VideoFrame) {
4731
4843
  if (init?.rotation !== void 0 && ![0, 90, 180, 270].includes(init.rotation)) {
4732
4844
  throw new TypeError("init.rotation, when provided, must be 0, 90, 180, or 270.");
@@ -4737,12 +4849,21 @@ var Mediabunny = (() => {
4737
4849
  if (init?.duration !== void 0 && (!Number.isFinite(init.duration) || init.duration < 0)) {
4738
4850
  throw new TypeError("init.duration, when provided, must be a non-negative number.");
4739
4851
  }
4852
+ if (init?.visibleRect !== void 0) {
4853
+ validateRectangle(init.visibleRect, "init.visibleRect");
4854
+ }
4740
4855
  this._data = data;
4741
4856
  this._layout = null;
4742
4857
  this.format = data.format;
4743
- this.codedWidth = data.displayWidth;
4744
- this.codedHeight = data.displayHeight;
4858
+ this.visibleRect = {
4859
+ left: data.visibleRect?.x ?? 0,
4860
+ top: data.visibleRect?.y ?? 0,
4861
+ width: data.visibleRect?.width ?? data.codedWidth,
4862
+ height: data.visibleRect?.height ?? data.codedHeight
4863
+ };
4745
4864
  this.rotation = init?.rotation ?? 0;
4865
+ this.squarePixelWidth = data.displayWidth;
4866
+ this.squarePixelHeight = data.displayHeight;
4746
4867
  this.timestamp = init?.timestamp ?? data.timestamp / 1e6;
4747
4868
  this.duration = init?.duration ?? (data.duration ?? 0) / 1e6;
4748
4869
  this.colorSpace = new VideoSampleColorSpace(data.colorSpace);
@@ -4795,8 +4916,9 @@ var Mediabunny = (() => {
4795
4916
  this._data = canvas;
4796
4917
  this._layout = null;
4797
4918
  this.format = "RGBX";
4798
- this.codedWidth = width;
4799
- this.codedHeight = height;
4919
+ this.visibleRect = { left: 0, top: 0, width, height };
4920
+ this.squarePixelWidth = width;
4921
+ this.squarePixelHeight = height;
4800
4922
  this.rotation = init.rotation ?? 0;
4801
4923
  this.timestamp = init.timestamp;
4802
4924
  this.duration = init.duration ?? 0;
@@ -4809,15 +4931,27 @@ var Mediabunny = (() => {
4809
4931
  } else {
4810
4932
  throw new TypeError("Invalid data type: Must be a BufferSource or CanvasImageSource.");
4811
4933
  }
4934
+ this.pixelAspectRatio = simplifyRational({
4935
+ num: this.squarePixelWidth * this.codedHeight,
4936
+ den: this.squarePixelHeight * this.codedWidth
4937
+ });
4812
4938
  finalizationRegistry?.register(this, { type: "video", data: this._data }, this);
4813
4939
  }
4814
- /** The width of the frame in pixels after rotation. */
4940
+ /** The width of the frame in pixels. */
4941
+ get codedWidth() {
4942
+ return this.visibleRect.width;
4943
+ }
4944
+ /** The height of the frame in pixels. */
4945
+ get codedHeight() {
4946
+ return this.visibleRect.height;
4947
+ }
4948
+ /** The display width of the frame in pixels, after aspect ratio adjustment and rotation. */
4815
4949
  get displayWidth() {
4816
- return this.rotation % 180 === 0 ? this.codedWidth : this.codedHeight;
4950
+ return this.rotation % 180 === 0 ? this.squarePixelWidth : this.squarePixelHeight;
4817
4951
  }
4818
- /** The height of the frame in pixels after rotation. */
4952
+ /** The display height of the frame in pixels, after aspect ratio adjustment and rotation. */
4819
4953
  get displayHeight() {
4820
- return this.rotation % 180 === 0 ? this.codedHeight : this.codedWidth;
4954
+ return this.rotation % 180 === 0 ? this.squarePixelHeight : this.squarePixelWidth;
4821
4955
  }
4822
4956
  /** The presentation timestamp of the frame in microseconds. */
4823
4957
  get microsecondTimestamp() {
@@ -4856,7 +4990,10 @@ var Mediabunny = (() => {
4856
4990
  timestamp: this.timestamp,
4857
4991
  duration: this.duration,
4858
4992
  colorSpace: this.colorSpace,
4859
- rotation: this.rotation
4993
+ rotation: this.rotation,
4994
+ visibleRect: this.visibleRect,
4995
+ displayWidth: this.displayWidth,
4996
+ displayHeight: this.displayHeight
4860
4997
  });
4861
4998
  } else {
4862
4999
  return new _VideoSample(this._data, {
@@ -4866,7 +5003,10 @@ var Mediabunny = (() => {
4866
5003
  timestamp: this.timestamp,
4867
5004
  duration: this.duration,
4868
5005
  colorSpace: this.colorSpace,
4869
- rotation: this.rotation
5006
+ rotation: this.rotation,
5007
+ visibleRect: this.visibleRect,
5008
+ displayWidth: this.displayWidth,
5009
+ displayHeight: this.displayHeight
4870
5010
  });
4871
5011
  }
4872
5012
  }
@@ -5094,7 +5234,7 @@ var Mediabunny = (() => {
5094
5234
  const canvasWidth = context.canvas.width;
5095
5235
  const canvasHeight = context.canvas.height;
5096
5236
  const rotation = options.rotation ?? this.rotation;
5097
- const [rotatedWidth, rotatedHeight] = rotation % 180 === 0 ? [this.codedWidth, this.codedHeight] : [this.codedHeight, this.codedWidth];
5237
+ const [rotatedWidth, rotatedHeight] = rotation % 180 === 0 ? [this.squarePixelWidth, this.squarePixelHeight] : [this.squarePixelHeight, this.squarePixelWidth];
5098
5238
  if (options.crop) {
5099
5239
  clampCropRectangle(options.crop, rotatedWidth, rotatedHeight);
5100
5240
  }
@@ -5136,18 +5276,18 @@ var Mediabunny = (() => {
5136
5276
  if (rotation === 90) {
5137
5277
  [sx, sy, sWidth, sHeight] = [
5138
5278
  sy,
5139
- this.codedHeight - sx - sWidth,
5279
+ this.squarePixelHeight - sx - sWidth,
5140
5280
  sHeight,
5141
5281
  sWidth
5142
5282
  ];
5143
5283
  } else if (rotation === 180) {
5144
5284
  [sx, sy] = [
5145
- this.codedWidth - sx - sWidth,
5146
- this.codedHeight - sy - sHeight
5285
+ this.squarePixelWidth - sx - sWidth,
5286
+ this.squarePixelHeight - sy - sHeight
5147
5287
  ];
5148
5288
  } else if (rotation === 270) {
5149
5289
  [sx, sy, sWidth, sHeight] = [
5150
- this.codedWidth - sy - sHeight,
5290
+ this.squarePixelWidth - sy - sHeight,
5151
5291
  sx,
5152
5292
  sHeight,
5153
5293
  sWidth
@@ -5204,6 +5344,32 @@ var Mediabunny = (() => {
5204
5344
  var VideoSampleColorSpace = class {
5205
5345
  /** Creates a new VideoSampleColorSpace. */
5206
5346
  constructor(init) {
5347
+ if (init !== void 0) {
5348
+ if (!init || typeof init !== "object") {
5349
+ throw new TypeError("init.colorSpace, when provided, must be an object.");
5350
+ }
5351
+ const primariesValues = Object.keys(COLOR_PRIMARIES_MAP);
5352
+ if (init.primaries != null && !primariesValues.includes(init.primaries)) {
5353
+ throw new TypeError(
5354
+ `init.colorSpace.primaries, when provided, must be one of ${primariesValues.join(", ")}.`
5355
+ );
5356
+ }
5357
+ const transferValues = Object.keys(TRANSFER_CHARACTERISTICS_MAP);
5358
+ if (init.transfer != null && !transferValues.includes(init.transfer)) {
5359
+ throw new TypeError(
5360
+ `init.colorSpace.transfer, when provided, must be one of ${transferValues.join(", ")}.`
5361
+ );
5362
+ }
5363
+ const matrixValues = Object.keys(MATRIX_COEFFICIENTS_MAP);
5364
+ if (init.matrix != null && !matrixValues.includes(init.matrix)) {
5365
+ throw new TypeError(
5366
+ `init.colorSpace.matrix, when provided, must be one of ${matrixValues.join(", ")}.`
5367
+ );
5368
+ }
5369
+ if (init.fullRange != null && typeof init.fullRange !== "boolean") {
5370
+ throw new TypeError("init.colorSpace.fullRange, when provided, must be a boolean.");
5371
+ }
5372
+ }
5207
5373
  this.primaries = init?.primaries ?? null;
5208
5374
  this.transfer = init?.transfer ?? null;
5209
5375
  this.matrix = init?.matrix ?? null;
@@ -6983,7 +7149,7 @@ var Mediabunny = (() => {
6983
7149
  throw new TypeError("poolSize must be a non-negative integer.");
6984
7150
  }
6985
7151
  const rotation = options.rotation ?? videoTrack.rotation;
6986
- const [rotatedWidth, rotatedHeight] = rotation % 180 === 0 ? [videoTrack.codedWidth, videoTrack.codedHeight] : [videoTrack.codedHeight, videoTrack.codedWidth];
7152
+ const [rotatedWidth, rotatedHeight] = rotation % 180 === 0 ? [videoTrack.squarePixelWidth, videoTrack.squarePixelHeight] : [videoTrack.squarePixelHeight, videoTrack.squarePixelWidth];
6987
7153
  const crop = options.crop;
6988
7154
  if (crop) {
6989
7155
  clampCropRectangle(crop, rotatedWidth, rotatedHeight);
@@ -7600,6 +7766,10 @@ var Mediabunny = (() => {
7600
7766
  constructor(input, backing) {
7601
7767
  super(input, backing);
7602
7768
  this._backing = backing;
7769
+ this.pixelAspectRatio = simplifyRational({
7770
+ num: this._backing.getSquarePixelWidth() * this._backing.getCodedHeight(),
7771
+ den: this._backing.getSquarePixelHeight() * this._backing.getCodedWidth()
7772
+ });
7603
7773
  }
7604
7774
  get type() {
7605
7775
  return "video";
@@ -7619,15 +7789,23 @@ var Mediabunny = (() => {
7619
7789
  get rotation() {
7620
7790
  return this._backing.getRotation();
7621
7791
  }
7622
- /** The width in pixels of the track's frames after rotation. */
7792
+ /** The width of the track's frames in square pixels, adjusted for pixel aspect ratio but before rotation. */
7793
+ get squarePixelWidth() {
7794
+ return this._backing.getSquarePixelWidth();
7795
+ }
7796
+ /** The height of the track's frames in square pixels, adjusted for pixel aspect ratio but before rotation. */
7797
+ get squarePixelHeight() {
7798
+ return this._backing.getSquarePixelHeight();
7799
+ }
7800
+ /** The display width of the track's frames in pixels, after aspect ratio adjustment and rotation. */
7623
7801
  get displayWidth() {
7624
7802
  const rotation = this._backing.getRotation();
7625
- return rotation % 180 === 0 ? this._backing.getCodedWidth() : this._backing.getCodedHeight();
7803
+ return rotation % 180 === 0 ? this.squarePixelWidth : this.squarePixelHeight;
7626
7804
  }
7627
- /** The height in pixels of the track's frames after rotation. */
7805
+ /** The display height of the track's frames in pixels, after aspect ratio adjustment and rotation. */
7628
7806
  get displayHeight() {
7629
7807
  const rotation = this._backing.getRotation();
7630
- return rotation % 180 === 0 ? this._backing.getCodedHeight() : this._backing.getCodedWidth();
7808
+ return rotation % 180 === 0 ? this.squarePixelHeight : this.squarePixelWidth;
7631
7809
  }
7632
7810
  /** Returns the color space of the track's samples. */
7633
7811
  getColorSpace() {
@@ -8314,6 +8492,8 @@ var Mediabunny = (() => {
8314
8492
  type: "video",
8315
8493
  width: -1,
8316
8494
  height: -1,
8495
+ squarePixelWidth: -1,
8496
+ squarePixelHeight: -1,
8317
8497
  codec: null,
8318
8498
  codecDescription: null,
8319
8499
  colorSpace: null,
@@ -8385,6 +8565,8 @@ var Mediabunny = (() => {
8385
8565
  slice.skip(6 * 1 + 2 + 2 + 2 + 3 * 4);
8386
8566
  track.info.width = readU16Be(slice);
8387
8567
  track.info.height = readU16Be(slice);
8568
+ track.info.squarePixelWidth = track.info.width;
8569
+ track.info.squarePixelHeight = track.info.height;
8388
8570
  slice.skip(4 + 4 + 4 + 2 + 32 + 2 + 2);
8389
8571
  this.readContiguousBoxes(
8390
8572
  slice.slice(
@@ -8622,6 +8804,23 @@ var Mediabunny = (() => {
8622
8804
  }
8623
8805
  ;
8624
8806
  break;
8807
+ case "pasp":
8808
+ {
8809
+ const track = this.currentTrack;
8810
+ if (!track) {
8811
+ break;
8812
+ }
8813
+ assert(track.info?.type === "video");
8814
+ const num = readU32Be(slice);
8815
+ const den = readU32Be(slice);
8816
+ if (num > den) {
8817
+ track.info.squarePixelWidth = Math.round(track.info.width * num / den);
8818
+ } else {
8819
+ track.info.squarePixelHeight = Math.round(track.info.height * den / num);
8820
+ }
8821
+ }
8822
+ ;
8823
+ break;
8625
8824
  case "wave":
8626
8825
  {
8627
8826
  this.readContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize));
@@ -10092,6 +10291,12 @@ var Mediabunny = (() => {
10092
10291
  getCodedHeight() {
10093
10292
  return this.internalTrack.info.height;
10094
10293
  }
10294
+ getSquarePixelWidth() {
10295
+ return this.internalTrack.info.squarePixelWidth;
10296
+ }
10297
+ getSquarePixelHeight() {
10298
+ return this.internalTrack.info.squarePixelHeight;
10299
+ }
10095
10300
  getRotation() {
10096
10301
  return this.internalTrack.rotation;
10097
10302
  }
@@ -10122,6 +10327,8 @@ var Mediabunny = (() => {
10122
10327
  codec: extractVideoCodecString(this.internalTrack.info),
10123
10328
  codedWidth: this.internalTrack.info.width,
10124
10329
  codedHeight: this.internalTrack.info.height,
10330
+ displayAspectWidth: this.internalTrack.info.squarePixelWidth,
10331
+ displayAspectHeight: this.internalTrack.info.squarePixelHeight,
10125
10332
  description: this.internalTrack.info.codecDescription ?? void 0,
10126
10333
  colorSpace: this.internalTrack.info.colorSpace ?? void 0
10127
10334
  };
@@ -11390,6 +11597,21 @@ var Mediabunny = (() => {
11390
11597
  const slashIndex = this.currentTrack.codecId.indexOf("/");
11391
11598
  const codecIdWithoutSuffix = slashIndex === -1 ? this.currentTrack.codecId : this.currentTrack.codecId.slice(0, slashIndex);
11392
11599
  if (this.currentTrack.info.type === "video" && this.currentTrack.info.width !== -1 && this.currentTrack.info.height !== -1) {
11600
+ this.currentTrack.info.squarePixelWidth = this.currentTrack.info.width;
11601
+ this.currentTrack.info.squarePixelHeight = this.currentTrack.info.height;
11602
+ if (this.currentTrack.info.displayWidth !== null && this.currentTrack.info.displayHeight !== null) {
11603
+ const num = this.currentTrack.info.displayWidth * this.currentTrack.info.height;
11604
+ const den = this.currentTrack.info.displayHeight * this.currentTrack.info.width;
11605
+ if (num > den) {
11606
+ this.currentTrack.info.squarePixelWidth = Math.round(
11607
+ this.currentTrack.info.width * num / den
11608
+ );
11609
+ } else {
11610
+ this.currentTrack.info.squarePixelHeight = Math.round(
11611
+ this.currentTrack.info.height * den / num
11612
+ );
11613
+ }
11614
+ }
11393
11615
  if (this.currentTrack.codecId === CODEC_STRING_MAP.avc) {
11394
11616
  this.currentTrack.info.codec = "avc";
11395
11617
  this.currentTrack.info.codecDescription = this.currentTrack.codecPrivate;
@@ -11486,6 +11708,11 @@ var Mediabunny = (() => {
11486
11708
  type: "video",
11487
11709
  width: -1,
11488
11710
  height: -1,
11711
+ displayWidth: null,
11712
+ displayHeight: null,
11713
+ displayUnit: null,
11714
+ squarePixelWidth: -1,
11715
+ squarePixelHeight: -1,
11489
11716
  rotation: 0,
11490
11717
  codec: null,
11491
11718
  codecDescription: null,
@@ -11633,6 +11860,27 @@ var Mediabunny = (() => {
11633
11860
  }
11634
11861
  ;
11635
11862
  break;
11863
+ case 21680 /* DisplayWidth */:
11864
+ {
11865
+ if (this.currentTrack?.info?.type !== "video") break;
11866
+ this.currentTrack.info.displayWidth = readUnsignedInt(slice, size);
11867
+ }
11868
+ ;
11869
+ break;
11870
+ case 21690 /* DisplayHeight */:
11871
+ {
11872
+ if (this.currentTrack?.info?.type !== "video") break;
11873
+ this.currentTrack.info.displayHeight = readUnsignedInt(slice, size);
11874
+ }
11875
+ ;
11876
+ break;
11877
+ case 21682 /* DisplayUnit */:
11878
+ {
11879
+ if (this.currentTrack?.info?.type !== "video") break;
11880
+ this.currentTrack.info.displayUnit = readUnsignedInt(slice, size);
11881
+ }
11882
+ ;
11883
+ break;
11636
11884
  case 21440 /* AlphaMode */:
11637
11885
  {
11638
11886
  if (this.currentTrack?.info?.type !== "video") break;
@@ -12609,6 +12857,12 @@ var Mediabunny = (() => {
12609
12857
  getCodedHeight() {
12610
12858
  return this.internalTrack.info.height;
12611
12859
  }
12860
+ getSquarePixelWidth() {
12861
+ return this.internalTrack.info.squarePixelWidth;
12862
+ }
12863
+ getSquarePixelHeight() {
12864
+ return this.internalTrack.info.squarePixelHeight;
12865
+ }
12612
12866
  getRotation() {
12613
12867
  return this.internalTrack.info.rotation;
12614
12868
  }
@@ -12649,6 +12903,8 @@ var Mediabunny = (() => {
12649
12903
  }),
12650
12904
  codedWidth: this.internalTrack.info.width,
12651
12905
  codedHeight: this.internalTrack.info.height,
12906
+ displayAspectWidth: this.internalTrack.info.squarePixelWidth,
12907
+ displayAspectHeight: this.internalTrack.info.squarePixelHeight,
12652
12908
  description: this.internalTrack.info.codecDescription ?? void 0,
12653
12909
  colorSpace: this.internalTrack.info.colorSpace ?? void 0
12654
12910
  };
@@ -15330,6 +15586,8 @@ var Mediabunny = (() => {
15330
15586
  },
15331
15587
  width: -1,
15332
15588
  height: -1,
15589
+ squarePixelWidth: -1,
15590
+ squarePixelHeight: -1,
15333
15591
  reorderSize: -1
15334
15592
  };
15335
15593
  }
@@ -15421,6 +15679,17 @@ var Mediabunny = (() => {
15421
15679
  const spsInfo = parseAvcSps(spsUnit);
15422
15680
  elementaryStream.info.width = spsInfo.displayWidth;
15423
15681
  elementaryStream.info.height = spsInfo.displayHeight;
15682
+ if (spsInfo.pixelAspectRatio.num > spsInfo.pixelAspectRatio.den) {
15683
+ elementaryStream.info.squarePixelWidth = Math.round(
15684
+ elementaryStream.info.width * spsInfo.pixelAspectRatio.num / spsInfo.pixelAspectRatio.den
15685
+ );
15686
+ elementaryStream.info.squarePixelHeight = elementaryStream.info.height;
15687
+ } else {
15688
+ elementaryStream.info.squarePixelWidth = elementaryStream.info.width;
15689
+ elementaryStream.info.squarePixelHeight = Math.round(
15690
+ elementaryStream.info.height * spsInfo.pixelAspectRatio.den / spsInfo.pixelAspectRatio.num
15691
+ );
15692
+ }
15424
15693
  elementaryStream.info.colorSpace = {
15425
15694
  primaries: COLOR_PRIMARIES_MAP_INVERSE[spsInfo.colourPrimaries],
15426
15695
  transfer: TRANSFER_CHARACTERISTICS_MAP_INVERSE[spsInfo.transferCharacteristics],
@@ -15444,6 +15713,17 @@ var Mediabunny = (() => {
15444
15713
  const spsInfo = parseHevcSps(spsUnit);
15445
15714
  elementaryStream.info.width = spsInfo.displayWidth;
15446
15715
  elementaryStream.info.height = spsInfo.displayHeight;
15716
+ if (spsInfo.pixelAspectRatio.num > spsInfo.pixelAspectRatio.den) {
15717
+ elementaryStream.info.squarePixelWidth = Math.round(
15718
+ elementaryStream.info.width * spsInfo.pixelAspectRatio.num / spsInfo.pixelAspectRatio.den
15719
+ );
15720
+ elementaryStream.info.squarePixelHeight = elementaryStream.info.height;
15721
+ } else {
15722
+ elementaryStream.info.squarePixelWidth = elementaryStream.info.width;
15723
+ elementaryStream.info.squarePixelHeight = Math.round(
15724
+ elementaryStream.info.height * spsInfo.pixelAspectRatio.den / spsInfo.pixelAspectRatio.num
15725
+ );
15726
+ }
15447
15727
  elementaryStream.info.colorSpace = {
15448
15728
  primaries: COLOR_PRIMARIES_MAP_INVERSE[spsInfo.colourPrimaries],
15449
15729
  transfer: TRANSFER_CHARACTERISTICS_MAP_INVERSE[spsInfo.transferCharacteristics],
@@ -16196,6 +16476,8 @@ var Mediabunny = (() => {
16196
16476
  }),
16197
16477
  codedWidth: this.elementaryStream.info.width,
16198
16478
  codedHeight: this.elementaryStream.info.height,
16479
+ displayAspectWidth: this.elementaryStream.info.squarePixelWidth,
16480
+ displayAspectHeight: this.elementaryStream.info.squarePixelHeight,
16199
16481
  colorSpace: this.elementaryStream.info.colorSpace
16200
16482
  };
16201
16483
  }
@@ -16208,6 +16490,12 @@ var Mediabunny = (() => {
16208
16490
  getCodedHeight() {
16209
16491
  return this.elementaryStream.info.height;
16210
16492
  }
16493
+ getSquarePixelWidth() {
16494
+ return this.elementaryStream.info.squarePixelWidth;
16495
+ }
16496
+ getSquarePixelHeight() {
16497
+ return this.elementaryStream.info.squarePixelHeight;
16498
+ }
16211
16499
  getRotation() {
16212
16500
  return 0;
16213
16501
  }
@@ -20400,8 +20688,18 @@ var Mediabunny = (() => {
20400
20688
  // Pre-defined
20401
20689
  ], [
20402
20690
  VIDEO_CODEC_TO_CONFIGURATION_BOX[trackData.track.source._codec](trackData),
20691
+ pasp(trackData),
20403
20692
  colorSpaceIsComplete(trackData.info.decoderConfig.colorSpace) ? colr(trackData) : null
20404
20693
  ]);
20694
+ var pasp = (trackData) => {
20695
+ if (trackData.info.pixelAspectRatio.num === trackData.info.pixelAspectRatio.den) {
20696
+ return null;
20697
+ }
20698
+ return box("pasp", [
20699
+ u32(trackData.info.pixelAspectRatio.num),
20700
+ u32(trackData.info.pixelAspectRatio.den)
20701
+ ]);
20702
+ };
20405
20703
  var colr = (trackData) => box("colr", [
20406
20704
  ascii("nclx"),
20407
20705
  // Colour type
@@ -22110,6 +22408,12 @@ var Mediabunny = (() => {
22110
22408
  requiresAnnexBTransformation = true;
22111
22409
  }
22112
22410
  const timescale = computeRationalApproximation(1 / (track.metadata.frameRate ?? 57600), 1e6).denominator;
22411
+ const displayAspectWidth = decoderConfig.displayAspectWidth;
22412
+ const displayAspectHeight = decoderConfig.displayAspectHeight;
22413
+ const pixelAspectRatio = displayAspectWidth === void 0 || displayAspectHeight === void 0 ? { num: 1, den: 1 } : simplifyRational({
22414
+ num: displayAspectWidth * decoderConfig.codedHeight,
22415
+ den: displayAspectHeight * decoderConfig.codedWidth
22416
+ });
22113
22417
  const newTrackData = {
22114
22418
  muxer: this,
22115
22419
  track,
@@ -22117,6 +22421,7 @@ var Mediabunny = (() => {
22117
22421
  info: {
22118
22422
  width: decoderConfig.codedWidth,
22119
22423
  height: decoderConfig.codedHeight,
22424
+ pixelAspectRatio,
22120
22425
  decoderConfig,
22121
22426
  requiresAnnexBTransformation
22122
22427
  },
@@ -23062,10 +23367,15 @@ var Mediabunny = (() => {
23062
23367
  } : null
23063
23368
  ];
23064
23369
  const flippedRotation = rotation ? normalizeRotation(-rotation) : 0;
23370
+ const hasNonSquarePixelAspectRatio = !!trackData.info.aspectRatio && trackData.info.aspectRatio.num * trackData.info.height !== trackData.info.aspectRatio.den * trackData.info.width;
23065
23371
  const colorSpace = trackData.info.decoderConfig.colorSpace;
23066
23372
  const videoElement = { id: 224 /* Video */, data: [
23067
23373
  { id: 176 /* PixelWidth */, data: trackData.info.width },
23068
23374
  { id: 186 /* PixelHeight */, data: trackData.info.height },
23375
+ hasNonSquarePixelAspectRatio ? { id: 21680 /* DisplayWidth */, data: trackData.info.aspectRatio.num } : null,
23376
+ hasNonSquarePixelAspectRatio ? { id: 21690 /* DisplayHeight */, data: trackData.info.aspectRatio.den } : null,
23377
+ hasNonSquarePixelAspectRatio ? { id: 21682 /* DisplayUnit */, data: 3 } : null,
23378
+ // 3 = display aspect ratio
23069
23379
  trackData.info.alphaMode ? { id: 21440 /* AlphaMode */, data: 1 } : null,
23070
23380
  colorSpaceIsComplete(colorSpace) ? {
23071
23381
  id: 21936 /* Colour */,
@@ -23394,12 +23704,19 @@ var Mediabunny = (() => {
23394
23704
  assert(meta.decoderConfig);
23395
23705
  assert(meta.decoderConfig.codedWidth !== void 0);
23396
23706
  assert(meta.decoderConfig.codedHeight !== void 0);
23707
+ const displayAspectWidth = meta.decoderConfig.displayAspectWidth;
23708
+ const displayAspectHeight = meta.decoderConfig.displayAspectHeight;
23709
+ const aspectRatio = displayAspectWidth === void 0 || displayAspectHeight === void 0 ? null : simplifyRational({
23710
+ num: displayAspectWidth,
23711
+ den: displayAspectHeight
23712
+ });
23397
23713
  const newTrackData = {
23398
23714
  track,
23399
23715
  type: "video",
23400
23716
  info: {
23401
23717
  width: meta.decoderConfig.codedWidth,
23402
23718
  height: meta.decoderConfig.codedHeight,
23719
+ aspectRatio,
23403
23720
  decoderConfig: meta.decoderConfig,
23404
23721
  alphaMode: !!packet.sideData.alpha
23405
23722
  // The first packet determines if this track has alpha or not
@@ -25804,6 +26121,8 @@ ${cue.notes ?? ""}`;
25804
26121
  ),
25805
26122
  width: options.width,
25806
26123
  height: options.height,
26124
+ displayWidth: options.squarePixelWidth,
26125
+ displayHeight: options.squarePixelHeight,
25807
26126
  bitrate: resolvedBitrate,
25808
26127
  bitrateMode: options.bitrateMode,
25809
26128
  alpha: options.alpha ?? "discard",
@@ -26424,6 +26743,8 @@ ${cue.notes ?? ""}`;
26424
26743
  const encoderConfig = buildVideoEncoderConfig({
26425
26744
  width: videoSample.codedWidth,
26426
26745
  height: videoSample.codedHeight,
26746
+ squarePixelWidth: videoSample.squarePixelWidth,
26747
+ squarePixelHeight: videoSample.squarePixelHeight,
26427
26748
  ...this.encodingConfig,
26428
26749
  framerate: this.source._connectedTrack?.metadata.frameRate
26429
26750
  });
@@ -28528,8 +28849,9 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
28528
28849
  }
28529
28850
  let videoSource;
28530
28851
  const totalRotation = normalizeRotation(track.rotation + (trackOptions.rotate ?? 0));
28852
+ let outputTrackRotation = totalRotation;
28531
28853
  const canUseRotationMetadata = this.output.format.supportsVideoRotationMetadata && (trackOptions.allowRotationMetadata ?? true);
28532
- const [rotatedWidth, rotatedHeight] = totalRotation % 180 === 0 ? [track.codedWidth, track.codedHeight] : [track.codedHeight, track.codedWidth];
28854
+ const [rotatedWidth, rotatedHeight] = totalRotation % 180 === 0 ? [track.squarePixelWidth, track.squarePixelHeight] : [track.squarePixelHeight, track.squarePixelWidth];
28533
28855
  const crop = trackOptions.crop;
28534
28856
  if (crop) {
28535
28857
  clampCropRectangle(crop, rotatedWidth, rotatedHeight);
@@ -28550,11 +28872,10 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
28550
28872
  height = ceilToMultipleOfTwo(trackOptions.height);
28551
28873
  }
28552
28874
  const firstTimestamp = await track.getFirstTimestamp();
28553
- const needsTranscode = !!trackOptions.forceTranscode || firstTimestamp < this._startTimestamp || !!trackOptions.frameRate || trackOptions.keyFrameInterval !== void 0 || trackOptions.process !== void 0;
28554
- let needsRerender = width !== originalWidth || height !== originalHeight || totalRotation !== 0 && (!canUseRotationMetadata || trackOptions.process !== void 0) || !!crop;
28555
- const alpha = trackOptions.alpha ?? "discard";
28556
28875
  let videoCodecs = this.output.format.getSupportedVideoCodecs();
28557
- if (!needsTranscode && !trackOptions.bitrate && !needsRerender && videoCodecs.includes(sourceCodec) && (!trackOptions.codec || trackOptions.codec === sourceCodec)) {
28876
+ const needsTranscode = !!trackOptions.forceTranscode || firstTimestamp < this._startTimestamp || !!trackOptions.frameRate || trackOptions.keyFrameInterval !== void 0 || trackOptions.process !== void 0 || trackOptions.bitrate !== void 0 || !videoCodecs.includes(sourceCodec) || trackOptions.codec && trackOptions.codec !== sourceCodec || width !== originalWidth || height !== originalHeight || totalRotation !== 0 && !canUseRotationMetadata || !!crop;
28877
+ const alpha = trackOptions.alpha ?? "discard";
28878
+ if (!needsTranscode) {
28558
28879
  const source = new EncodedVideoPacketSource(sourceCodec);
28559
28880
  videoSource = source;
28560
28881
  this._trackPromises.push((async () => {
@@ -28616,6 +28937,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
28616
28937
  };
28617
28938
  const source = new VideoSampleSource(encodingConfig);
28618
28939
  videoSource = source;
28940
+ let needsRerender = width !== originalWidth || height !== originalHeight || totalRotation !== 0 && (!canUseRotationMetadata || trackOptions.process !== void 0) || !!crop || track.squarePixelWidth !== track.codedWidth || track.squarePixelHeight !== track.codedHeight;
28619
28941
  if (!needsRerender) {
28620
28942
  const tempOutput = new Output({
28621
28943
  format: new Mp4OutputFormat(),
@@ -28656,6 +28978,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
28656
28978
  });
28657
28979
  const iterator = sink.canvases(this._startTimestamp, this._endTimestamp);
28658
28980
  const frameRate = trackOptions.frameRate;
28981
+ outputTrackRotation = 0;
28659
28982
  let lastCanvas = null;
28660
28983
  let lastCanvasTimestamp = null;
28661
28984
  let lastCanvasEndTimestamp = null;
@@ -28777,8 +29100,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
28777
29100
  languageCode: isIso639Dash2LanguageCode(track.languageCode) ? track.languageCode : void 0,
28778
29101
  name: track.name ?? void 0,
28779
29102
  disposition: track.disposition,
28780
- rotation: needsRerender ? 0 : totalRotation
28781
- // Rerendering will bake the rotation into the output
29103
+ rotation: outputTrackRotation
28782
29104
  });
28783
29105
  this._addedCounts.video++;
28784
29106
  this._totalTrackCount++;