mediabunny 1.53.1 → 1.55.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 (69) hide show
  1. package/dist/bundles/mediabunny.cjs +916 -46
  2. package/dist/bundles/mediabunny.min.cjs +14 -14
  3. package/dist/bundles/mediabunny.min.mjs +14 -14
  4. package/dist/bundles/mediabunny.mjs +916 -46
  5. package/dist/bundles/mediabunny.node.cjs +916 -46
  6. package/dist/mediabunny.d.ts +77 -2
  7. package/dist/modules/shared/dts-misc.d.ts +17 -0
  8. package/dist/modules/shared/dts-misc.d.ts.map +1 -0
  9. package/dist/modules/shared/dts-misc.js +26 -0
  10. package/dist/modules/src/codec-data.d.ts +98 -1
  11. package/dist/modules/src/codec-data.d.ts.map +1 -1
  12. package/dist/modules/src/codec-data.js +346 -1
  13. package/dist/modules/src/codec.d.ts +6 -3
  14. package/dist/modules/src/codec.d.ts.map +1 -1
  15. package/dist/modules/src/codec.js +25 -2
  16. package/dist/modules/src/encode.d.ts.map +1 -1
  17. package/dist/modules/src/encode.js +3 -2
  18. package/dist/modules/src/index.d.ts +1 -1
  19. package/dist/modules/src/index.d.ts.map +1 -1
  20. package/dist/modules/src/input-track.d.ts +80 -0
  21. package/dist/modules/src/input-track.d.ts.map +1 -1
  22. package/dist/modules/src/input-track.js +300 -0
  23. package/dist/modules/src/isobmff/isobmff-boxes.d.ts.map +1 -1
  24. package/dist/modules/src/isobmff/isobmff-boxes.js +16 -4
  25. package/dist/modules/src/isobmff/isobmff-demuxer.d.ts +2 -1
  26. package/dist/modules/src/isobmff/isobmff-demuxer.d.ts.map +1 -1
  27. package/dist/modules/src/isobmff/isobmff-demuxer.js +44 -9
  28. package/dist/modules/src/isobmff/isobmff-muxer.d.ts.map +1 -1
  29. package/dist/modules/src/isobmff/isobmff-muxer.js +5 -2
  30. package/dist/modules/src/matroska/ebml.d.ts.map +1 -1
  31. package/dist/modules/src/matroska/ebml.js +1 -0
  32. package/dist/modules/src/matroska/matroska-demuxer.d.ts +2 -1
  33. package/dist/modules/src/matroska/matroska-demuxer.d.ts.map +1 -1
  34. package/dist/modules/src/matroska/matroska-demuxer.js +30 -12
  35. package/dist/modules/src/matroska/matroska-muxer.d.ts.map +1 -1
  36. package/dist/modules/src/matroska/matroska-muxer.js +10 -1
  37. package/dist/modules/src/media-sink.d.ts.map +1 -1
  38. package/dist/modules/src/media-sink.js +9 -3
  39. package/dist/modules/src/misc.d.ts +1 -0
  40. package/dist/modules/src/misc.d.ts.map +1 -1
  41. package/dist/modules/src/misc.js +8 -0
  42. package/dist/modules/src/mpeg-ts/mpeg-ts-demuxer.d.ts +2 -1
  43. package/dist/modules/src/mpeg-ts/mpeg-ts-demuxer.d.ts.map +1 -1
  44. package/dist/modules/src/mpeg-ts/mpeg-ts-demuxer.js +149 -11
  45. package/dist/modules/src/mpeg-ts/mpeg-ts-misc.d.ts +5 -0
  46. package/dist/modules/src/mpeg-ts/mpeg-ts-misc.d.ts.map +1 -1
  47. package/dist/modules/src/mpeg-ts/mpeg-ts-muxer.d.ts.map +1 -1
  48. package/dist/modules/src/mpeg-ts/mpeg-ts-muxer.js +9 -2
  49. package/dist/modules/src/output-format.js +1 -1
  50. package/dist/modules/src/tsconfig.tsbuildinfo +1 -1
  51. package/dist/modules/src/wave/wave-demuxer.d.ts +1 -1
  52. package/package.json +2 -2
  53. package/src/codec-data.ts +494 -1
  54. package/src/codec.ts +27 -2
  55. package/src/encode.ts +3 -2
  56. package/src/index.ts +2 -0
  57. package/src/input-track.ts +479 -0
  58. package/src/isobmff/isobmff-boxes.ts +35 -4
  59. package/src/isobmff/isobmff-demuxer.ts +49 -7
  60. package/src/isobmff/isobmff-muxer.ts +6 -2
  61. package/src/matroska/ebml.ts +1 -0
  62. package/src/matroska/matroska-demuxer.ts +32 -11
  63. package/src/matroska/matroska-muxer.ts +10 -1
  64. package/src/media-sink.ts +11 -3
  65. package/src/misc.ts +11 -0
  66. package/src/mpeg-ts/mpeg-ts-demuxer.ts +186 -11
  67. package/src/mpeg-ts/mpeg-ts-misc.ts +5 -0
  68. package/src/mpeg-ts/mpeg-ts-muxer.ts +7 -2
  69. package/src/output-format.ts +1 -1
@@ -590,6 +590,14 @@ var Mediabunny = (() => {
590
590
  }
591
591
  return ret;
592
592
  };
593
+ var popcount = (value) => {
594
+ let count = 0;
595
+ while (value !== 0) {
596
+ value &= value - 1;
597
+ count++;
598
+ }
599
+ return count;
600
+ };
593
601
  var ISO_639_2_REGEX = /^[a-z]{3}$/;
594
602
  var isIso639Dash2LanguageCode = (x) => {
595
603
  return ISO_639_2_REGEX.test(x);
@@ -1528,7 +1536,8 @@ var Mediabunny = (() => {
1528
1536
  "vorbis",
1529
1537
  "flac",
1530
1538
  "ac3",
1531
- "eac3"
1539
+ "eac3",
1540
+ "dts"
1532
1541
  ];
1533
1542
  var AUDIO_CODECS = [
1534
1543
  ...NON_PCM_AUDIO_CODECS,
@@ -1717,6 +1726,16 @@ var Mediabunny = (() => {
1717
1726
  "apco"
1718
1727
  // ProRes 422 Proxy
1719
1728
  ];
1729
+ var DTS_FOURCCS = [
1730
+ "dtsc",
1731
+ // DTS core
1732
+ "dtsh",
1733
+ // DTS-HD, core plus extension substreams
1734
+ "dtsl",
1735
+ // DTS-HD Lossless, no core
1736
+ "dtse"
1737
+ // DTS Express
1738
+ ];
1720
1739
  var PRORES_PROFILE_TARGET_BITRATES = [
1721
1740
  { fourCc: "apco", bitrate: 45e6, alpha: false },
1722
1741
  // 422 Proxy
@@ -1992,13 +2011,15 @@ var Mediabunny = (() => {
1992
2011
  return "ac-3";
1993
2012
  } else if (codec === "eac3") {
1994
2013
  return "ec-3";
2014
+ } else if (codec === "dts") {
2015
+ return "dtsc";
1995
2016
  } else if (PCM_AUDIO_CODECS.includes(codec)) {
1996
2017
  return codec;
1997
2018
  }
1998
2019
  throw new TypeError(`Unhandled codec '${codec}'.`);
1999
2020
  };
2000
2021
  var extractAudioCodecString = (trackInfo) => {
2001
- const { codec, codecDescription, aacCodecInfo } = trackInfo;
2022
+ const { codec, codecDescription, aacCodecInfo, dtsFormat } = trackInfo;
2002
2023
  if (codec === "aac") {
2003
2024
  if (!aacCodecInfo) {
2004
2025
  throw new TypeError("AAC codec info must be provided.");
@@ -2027,6 +2048,8 @@ var Mediabunny = (() => {
2027
2048
  return "ac-3";
2028
2049
  } else if (codec === "eac3") {
2029
2050
  return "ec-3";
2051
+ } else if (codec === "dts") {
2052
+ return dtsFormat ?? "dtsc";
2030
2053
  } else if (codec && PCM_AUDIO_CODECS.includes(codec)) {
2031
2054
  return codec;
2032
2055
  }
@@ -2114,6 +2137,8 @@ var Mediabunny = (() => {
2114
2137
  return "ac3";
2115
2138
  } else if (codecString === "ec-3" || codecString === "eac3") {
2116
2139
  return "eac3";
2140
+ } else if (DTS_FOURCCS.includes(codecString)) {
2141
+ return "dts";
2117
2142
  } else if (codecString === "ulaw") {
2118
2143
  return "ulaw";
2119
2144
  } else if (codecString === "alaw") {
@@ -2301,7 +2326,8 @@ var Mediabunny = (() => {
2301
2326
  "alaw",
2302
2327
  "pcm",
2303
2328
  "ac-3",
2304
- "ec-3"
2329
+ "ec-3",
2330
+ "dts"
2305
2331
  ];
2306
2332
  var validateAudioChunkMetadata = (metadata, trackCodec) => {
2307
2333
  if (!metadata) {
@@ -2390,6 +2416,12 @@ var Mediabunny = (() => {
2390
2416
  if (metadata.decoderConfig.codec !== "ec-3") {
2391
2417
  throw new TypeError('Audio chunk metadata decoder configuration codec string for EC-3 must be "ec-3".');
2392
2418
  }
2419
+ } else if (metadata.decoderConfig.codec.startsWith("dts")) {
2420
+ if (!DTS_FOURCCS.includes(metadata.decoderConfig.codec)) {
2421
+ throw new TypeError(
2422
+ `Audio chunk metadata decoder configuration codec string for DTS must be one of the following four-character codes: ${DTS_FOURCCS.join(", ")}.`
2423
+ );
2424
+ }
2393
2425
  } else if (metadata.decoderConfig.codec.startsWith("pcm") || metadata.decoderConfig.codec.startsWith("ulaw") || metadata.decoderConfig.codec.startsWith("alaw")) {
2394
2426
  if (!PCM_AUDIO_CODECS.includes(metadata.decoderConfig.codec)) {
2395
2427
  throw new TypeError(
@@ -5059,6 +5091,364 @@ var Mediabunny = (() => {
5059
5091
  }
5060
5092
  return channels;
5061
5093
  };
5094
+ var DTS_EXSS_SYNC_WORD = 1683496997;
5095
+ var DTS_CORE_FRAME_HEADER_SIZE = 18;
5096
+ var DTS_EXSS_HEADER_PREFIX_SIZE = 10;
5097
+ var DTS_EXSS_MAX_HEADER_SIZE = 4096;
5098
+ var DTS_PCM_BLOCK_SAMPLES = 32;
5099
+ var DTS_SPECIFIC_BOX_SIZE = 20;
5100
+ var DTS_SUBBAND_SAMPLES = 8;
5101
+ var DTS_CORE_SAMPLE_RATES = [
5102
+ 0,
5103
+ 8e3,
5104
+ 16e3,
5105
+ 32e3,
5106
+ 0,
5107
+ 0,
5108
+ 11025,
5109
+ 22050,
5110
+ 44100,
5111
+ 0,
5112
+ 0,
5113
+ 12e3,
5114
+ 24e3,
5115
+ 48e3,
5116
+ 96e3,
5117
+ 192e3
5118
+ ];
5119
+ var DTS_CORE_BIT_RATES = [
5120
+ 32e3,
5121
+ 56e3,
5122
+ 64e3,
5123
+ 96e3,
5124
+ 112e3,
5125
+ 128e3,
5126
+ 192e3,
5127
+ 224e3,
5128
+ 256e3,
5129
+ 32e4,
5130
+ 384e3,
5131
+ 448e3,
5132
+ 512e3,
5133
+ 576e3,
5134
+ 64e4,
5135
+ 768e3,
5136
+ 96e4,
5137
+ 1024e3,
5138
+ 1152e3,
5139
+ 128e4,
5140
+ 1344e3,
5141
+ 1408e3,
5142
+ 1411200,
5143
+ 1472e3,
5144
+ 1536e3,
5145
+ 192e4,
5146
+ 2048e3,
5147
+ 3072e3,
5148
+ 384e4,
5149
+ 0,
5150
+ 0,
5151
+ 0
5152
+ ];
5153
+ var DTS_PCM_RESOLUTIONS = [16, 16, 20, 20, 0, 24, 24, 0];
5154
+ var DTS_AMODE_CHANNEL_COUNTS = [1, 2, 2, 2, 2, 3, 3, 4, 4, 5, 6, 6, 6, 7, 8, 8];
5155
+ var DTS_AMODE_CHANNEL_LAYOUTS = [
5156
+ 1,
5157
+ 2,
5158
+ 2,
5159
+ 2,
5160
+ 2,
5161
+ 3,
5162
+ 18,
5163
+ 19,
5164
+ 6,
5165
+ 7,
5166
+ 518,
5167
+ 323,
5168
+ 83,
5169
+ 519,
5170
+ 582,
5171
+ 535
5172
+ ];
5173
+ var DTS_CHANNEL_LAYOUT_LFE1 = 8;
5174
+ var DTS_CHANNEL_LAYOUT_PAIR_MASK = 44646;
5175
+ var DTS_EXSS_REF_CLOCKS = [32e3, 44100, 48e3, 0];
5176
+ var DTS_EXSS_SAMPLE_RATES = [
5177
+ 8e3,
5178
+ 16e3,
5179
+ 32e3,
5180
+ 64e3,
5181
+ 128e3,
5182
+ 22050,
5183
+ 44100,
5184
+ 88200,
5185
+ 176400,
5186
+ 352800,
5187
+ 12e3,
5188
+ 24e3,
5189
+ 48e3,
5190
+ 96e3,
5191
+ 192e3,
5192
+ 384e3
5193
+ ];
5194
+ var DTS_SPECIFIC_BOX_FRAME_DURATIONS = [512, 1024, 2048, 4096];
5195
+ var parseDtsFrame = (data) => {
5196
+ const core = parseDtsCoreFrameHeader(data);
5197
+ const view2 = toDataView(data);
5198
+ let offset = core ? Math.ceil(core.frameSize / 4) * 4 : 0;
5199
+ let firstExss = null;
5200
+ while (offset + 4 <= data.length && view2.getUint32(offset) === DTS_EXSS_SYNC_WORD) {
5201
+ const exss = parseDtsExssHeader(data.subarray(offset));
5202
+ if (!exss) {
5203
+ break;
5204
+ }
5205
+ firstExss ??= exss;
5206
+ offset += exss.frameSize;
5207
+ }
5208
+ if (core) {
5209
+ return {
5210
+ frameSize: firstExss ? offset : core.frameSize,
5211
+ sampleRate: core.sampleRate,
5212
+ numberOfChannels: core.numberOfChannels,
5213
+ sampleCount: core.sampleCount,
5214
+ channelLayout: core.channelLayout,
5215
+ pcmResolution: core.pcmResolution,
5216
+ bitRate: core.bitRate,
5217
+ core,
5218
+ hasExtensions: firstExss !== null
5219
+ };
5220
+ }
5221
+ if (!firstExss?.asset) {
5222
+ return null;
5223
+ }
5224
+ const { asset } = firstExss;
5225
+ return {
5226
+ frameSize: offset,
5227
+ sampleRate: asset.sampleRate,
5228
+ numberOfChannels: asset.numberOfChannels,
5229
+ sampleCount: asset.sampleCount,
5230
+ channelLayout: asset.channelLayout,
5231
+ pcmResolution: asset.pcmResolution,
5232
+ bitRate: 0,
5233
+ core: null,
5234
+ hasExtensions: true
5235
+ };
5236
+ };
5237
+ var extractDtsFourCcFromPacket = (data) => {
5238
+ const frameInfo = parseDtsFrame(data);
5239
+ if (!frameInfo?.core) {
5240
+ return null;
5241
+ }
5242
+ return frameInfo.hasExtensions ? "dtsh" : "dtsc";
5243
+ };
5244
+ var parseDtsCoreFrameHeader = (data) => {
5245
+ if (data.length < DTS_CORE_FRAME_HEADER_SIZE) {
5246
+ return null;
5247
+ }
5248
+ if (data[0] !== 127 || data[1] !== 254 || data[2] !== 128 || data[3] !== 1) {
5249
+ return null;
5250
+ }
5251
+ const bitstream = new Bitstream(data);
5252
+ bitstream.skipBits(32);
5253
+ bitstream.skipBits(1);
5254
+ if (bitstream.readBits(5) !== DTS_PCM_BLOCK_SAMPLES - 1) {
5255
+ return null;
5256
+ }
5257
+ const cpf = bitstream.readBits(1);
5258
+ const npcmblocks = bitstream.readBits(7) + 1;
5259
+ if (npcmblocks % DTS_SUBBAND_SAMPLES !== 0) {
5260
+ return null;
5261
+ }
5262
+ const frameSize = bitstream.readBits(14) + 1;
5263
+ if (frameSize < 96) {
5264
+ return null;
5265
+ }
5266
+ const amode = bitstream.readBits(6);
5267
+ if (amode >= DTS_AMODE_CHANNEL_COUNTS.length) {
5268
+ return null;
5269
+ }
5270
+ const sampleRate = DTS_CORE_SAMPLE_RATES[bitstream.readBits(4)];
5271
+ if (sampleRate === 0) {
5272
+ return null;
5273
+ }
5274
+ const bitRate = DTS_CORE_BIT_RATES[bitstream.readBits(5)];
5275
+ if (bitstream.readBits(1) !== 0) {
5276
+ return null;
5277
+ }
5278
+ bitstream.skipBits(1 + 1 + 1 + 1);
5279
+ bitstream.skipBits(3 + 1 + 1);
5280
+ const lff = bitstream.readBits(2);
5281
+ if (lff === 3) {
5282
+ return null;
5283
+ }
5284
+ bitstream.skipBits(1);
5285
+ if (cpf) {
5286
+ bitstream.skipBits(16);
5287
+ }
5288
+ bitstream.skipBits(1 + 4 + 2);
5289
+ const pcmResolution = DTS_PCM_RESOLUTIONS[bitstream.readBits(3)];
5290
+ if (pcmResolution === 0) {
5291
+ return null;
5292
+ }
5293
+ const lfePresent = lff !== 0;
5294
+ return {
5295
+ frameSize,
5296
+ sampleRate,
5297
+ numberOfChannels: DTS_AMODE_CHANNEL_COUNTS[amode] + (lfePresent ? 1 : 0),
5298
+ sampleCount: npcmblocks * DTS_PCM_BLOCK_SAMPLES,
5299
+ channelLayout: DTS_AMODE_CHANNEL_LAYOUTS[amode] | (lfePresent ? DTS_CHANNEL_LAYOUT_LFE1 : 0),
5300
+ amode,
5301
+ lfePresent,
5302
+ bitRate,
5303
+ pcmResolution
5304
+ };
5305
+ };
5306
+ var parseDtsExssHeader = (data) => {
5307
+ if (data.length < DTS_EXSS_HEADER_PREFIX_SIZE) {
5308
+ return null;
5309
+ }
5310
+ if (data[0] !== 100 || data[1] !== 88 || data[2] !== 32 || data[3] !== 37) {
5311
+ return null;
5312
+ }
5313
+ const bitstream = new Bitstream(data);
5314
+ bitstream.skipBits(32);
5315
+ bitstream.skipBits(8);
5316
+ const extSsIndex = bitstream.readBits(2);
5317
+ const wideHeader = bitstream.readBits(1);
5318
+ const headerSizeBits = 8 + 4 * wideHeader;
5319
+ const frameSizeBits = 16 + 4 * wideHeader;
5320
+ bitstream.skipBits(headerSizeBits);
5321
+ const frameSize = bitstream.readBits(frameSizeBits) + 1;
5322
+ const incomplete = { frameSize, asset: null };
5323
+ if (!bitstream.readBits(1)) {
5324
+ return incomplete;
5325
+ }
5326
+ const refClock = DTS_EXSS_REF_CLOCKS[bitstream.readBits(2)];
5327
+ const frameDurationCycles = 512 * (bitstream.readBits(3) + 1);
5328
+ if (bitstream.readBits(1)) {
5329
+ bitstream.skipBits(32 + 4);
5330
+ }
5331
+ const numAudioPresentations = bitstream.readBits(3) + 1;
5332
+ const numAssets = bitstream.readBits(3) + 1;
5333
+ const activeExssMasks = [];
5334
+ for (let i = 0; i < numAudioPresentations; i++) {
5335
+ activeExssMasks.push(bitstream.readBits(extSsIndex + 1));
5336
+ }
5337
+ for (const mask of activeExssMasks) {
5338
+ bitstream.skipBits(8 * popcount(mask));
5339
+ }
5340
+ if (bitstream.readBits(1)) {
5341
+ bitstream.skipBits(2);
5342
+ const spkrMaskBits = bitstream.readBits(2) + 1 << 2;
5343
+ const numMixOutConfigs = bitstream.readBits(2) + 1;
5344
+ bitstream.skipBits(numMixOutConfigs * spkrMaskBits);
5345
+ }
5346
+ for (let i = 0; i < numAssets; i++) {
5347
+ bitstream.skipBits(frameSizeBits);
5348
+ }
5349
+ bitstream.skipBits(9);
5350
+ bitstream.skipBits(3);
5351
+ if (bitstream.readBits(1)) {
5352
+ bitstream.skipBits(4);
5353
+ }
5354
+ if (bitstream.readBits(1)) {
5355
+ bitstream.skipBits(24);
5356
+ }
5357
+ if (bitstream.readBits(1)) {
5358
+ bitstream.skipBits(8 * (bitstream.readBits(10) + 1));
5359
+ }
5360
+ const pcmResolution = bitstream.readBits(5) + 1;
5361
+ const sampleRate = DTS_EXSS_SAMPLE_RATES[bitstream.readBits(4)];
5362
+ const numberOfChannels = bitstream.readBits(8) + 1;
5363
+ let channelLayout = 0;
5364
+ if (bitstream.readBits(1)) {
5365
+ if (numberOfChannels > 2) {
5366
+ bitstream.skipBits(1);
5367
+ }
5368
+ if (numberOfChannels > 6) {
5369
+ bitstream.skipBits(1);
5370
+ }
5371
+ if (bitstream.readBits(1)) {
5372
+ const spkrMaskBits = bitstream.readBits(2) + 1 << 2;
5373
+ channelLayout = bitstream.readBits(spkrMaskBits);
5374
+ }
5375
+ }
5376
+ if (refClock === 0 || bitstream.getBitsLeft() < 0) {
5377
+ return incomplete;
5378
+ }
5379
+ return {
5380
+ frameSize,
5381
+ asset: {
5382
+ sampleRate,
5383
+ numberOfChannels,
5384
+ sampleCount: Math.round(frameDurationCycles * sampleRate / refClock),
5385
+ channelLayout,
5386
+ pcmResolution
5387
+ }
5388
+ };
5389
+ };
5390
+ var parseDtsSpecificBox = (data) => {
5391
+ if (data.length < DTS_SPECIFIC_BOX_SIZE) {
5392
+ return null;
5393
+ }
5394
+ const view2 = toDataView(data);
5395
+ const sampleRate = view2.getUint32(0);
5396
+ if (sampleRate === 0) {
5397
+ return null;
5398
+ }
5399
+ const bitstream = new Bitstream(data);
5400
+ bitstream.seekToByte(13);
5401
+ const frameDuration = bitstream.readBits(2);
5402
+ bitstream.skipBits(5);
5403
+ const coreLfePresent = bitstream.readBits(1);
5404
+ const coreLayout = bitstream.readBits(6);
5405
+ bitstream.skipBits(14);
5406
+ bitstream.skipBits(1);
5407
+ bitstream.skipBits(3);
5408
+ const channelLayout = bitstream.readBits(16);
5409
+ let numberOfChannels = null;
5410
+ if (channelLayout !== 0) {
5411
+ numberOfChannels = getDtsChannelCount(channelLayout);
5412
+ } else if (coreLayout < DTS_AMODE_CHANNEL_COUNTS.length) {
5413
+ numberOfChannels = DTS_AMODE_CHANNEL_COUNTS[coreLayout] + coreLfePresent;
5414
+ }
5415
+ return {
5416
+ sampleRate,
5417
+ maxBitrate: view2.getUint32(4),
5418
+ avgBitrate: view2.getUint32(8),
5419
+ pcmSampleDepth: data[12],
5420
+ sampleCount: DTS_SPECIFIC_BOX_FRAME_DURATIONS[frameDuration],
5421
+ channelLayout,
5422
+ numberOfChannels
5423
+ };
5424
+ };
5425
+ var buildDtsSpecificBox = (frameInfo) => {
5426
+ const bytes2 = new Uint8Array(DTS_SPECIFIC_BOX_SIZE);
5427
+ const view2 = toDataView(bytes2);
5428
+ view2.setUint32(0, frameInfo.sampleRate);
5429
+ view2.setUint32(4, frameInfo.bitRate);
5430
+ view2.setUint32(8, frameInfo.bitRate);
5431
+ bytes2[12] = frameInfo.pcmResolution;
5432
+ const streamConstruction = frameInfo.core && !frameInfo.hasExtensions ? 1 : 0;
5433
+ const bitstream = new Bitstream(bytes2);
5434
+ bitstream.seekToByte(13);
5435
+ bitstream.writeBits(2, Math.max(DTS_SPECIFIC_BOX_FRAME_DURATIONS.indexOf(frameInfo.sampleCount), 0));
5436
+ bitstream.writeBits(5, streamConstruction);
5437
+ bitstream.writeBits(1, frameInfo.core?.lfePresent ? 1 : 0);
5438
+ bitstream.writeBits(6, frameInfo.core?.amode ?? 0);
5439
+ bitstream.writeBits(14, frameInfo.core ? frameInfo.core.frameSize - 1 : 0);
5440
+ bitstream.writeBits(1, 0);
5441
+ bitstream.writeBits(3, 0);
5442
+ bitstream.writeBits(16, frameInfo.channelLayout);
5443
+ bitstream.writeBits(1, 0);
5444
+ bitstream.writeBits(1, 0);
5445
+ bitstream.writeBits(1, 0);
5446
+ bitstream.writeBits(5, 0);
5447
+ return bytes2;
5448
+ };
5449
+ var getDtsChannelCount = (channelLayout) => {
5450
+ return popcount(channelLayout) + popcount(channelLayout & DTS_CHANNEL_LAYOUT_PAIR_MASK);
5451
+ };
5062
5452
 
5063
5453
  // src/demuxer.ts
5064
5454
  var Demuxer = class {
@@ -6122,6 +6512,7 @@ var Mediabunny = (() => {
6122
6512
  codec: null,
6123
6513
  codecDescription: null,
6124
6514
  aacCodecInfo: null,
6515
+ dtsFormat: null,
6125
6516
  pcmLittleEndian: false,
6126
6517
  pcmSampleSize: null
6127
6518
  };
@@ -6244,6 +6635,9 @@ var Mediabunny = (() => {
6244
6635
  track.info.codec = "ac3";
6245
6636
  } else if (codecName === "ec-3") {
6246
6637
  track.info.codec = "eac3";
6638
+ } else if (DTS_FOURCCS.includes(codecName)) {
6639
+ track.info.codec = "dts";
6640
+ track.info.dtsFormat = codecName;
6247
6641
  } else if (codecName === "twos") {
6248
6642
  if (sampleSize === 8) {
6249
6643
  track.info.codec = "pcm-s8";
@@ -6603,6 +6997,8 @@ var Mediabunny = (() => {
6603
6997
  track.info.codec = "mp3";
6604
6998
  } else if (objectTypeIndication === 221) {
6605
6999
  track.info.codec = "vorbis";
7000
+ } else if (objectTypeIndication === 169) {
7001
+ track.info.codec = "dts";
6606
7002
  } else {
6607
7003
  Logging._warn(
6608
7004
  `Unsupported audio codec (objectTypeIndication ${objectTypeIndication}) - discarding track.`
@@ -6769,6 +7165,26 @@ var Mediabunny = (() => {
6769
7165
  }
6770
7166
  ;
6771
7167
  break;
7168
+ case "ddts":
7169
+ {
7170
+ const track = this.currentTrack;
7171
+ if (!track) {
7172
+ break;
7173
+ }
7174
+ assert(track.info?.type === "audio");
7175
+ const bytes2 = readBytes(slice, Math.min(boxInfo.contentSize, DTS_SPECIFIC_BOX_SIZE));
7176
+ const config = parseDtsSpecificBox(bytes2);
7177
+ if (!config) {
7178
+ Logging._warn("Invalid ddts box contents, ignoring.");
7179
+ break;
7180
+ }
7181
+ track.info.sampleRate = config.sampleRate;
7182
+ if (config.numberOfChannels !== null) {
7183
+ track.info.numberOfChannels = config.numberOfChannels;
7184
+ }
7185
+ }
7186
+ ;
7187
+ break;
6772
7188
  case "stts":
6773
7189
  {
6774
7190
  const track = this.currentTrack;
@@ -8184,7 +8600,7 @@ var Mediabunny = (() => {
8184
8600
  var IsobmffAudioTrackBacking = class extends IsobmffTrackBacking {
8185
8601
  constructor(internalTrack) {
8186
8602
  super(internalTrack);
8187
- this.decoderConfig = null;
8603
+ this.decoderConfigPromise = null;
8188
8604
  this.internalTrack = internalTrack;
8189
8605
  }
8190
8606
  getType() {
@@ -8203,12 +8619,18 @@ var Mediabunny = (() => {
8203
8619
  if (!this.internalTrack.info.codec) {
8204
8620
  return null;
8205
8621
  }
8206
- return this.decoderConfig ??= {
8207
- codec: extractAudioCodecString(this.internalTrack.info),
8208
- numberOfChannels: this.internalTrack.info.numberOfChannels,
8209
- sampleRate: this.internalTrack.info.sampleRate,
8210
- description: this.internalTrack.info.codecDescription ?? void 0
8211
- };
8622
+ return this.decoderConfigPromise ??= (async () => {
8623
+ if (this.internalTrack.info.codec === "dts" && !this.internalTrack.info.dtsFormat) {
8624
+ const firstPacket = await this.getFirstPacket({});
8625
+ this.internalTrack.info.dtsFormat = firstPacket && extractDtsFourCcFromPacket(firstPacket.data);
8626
+ }
8627
+ return {
8628
+ codec: extractAudioCodecString(this.internalTrack.info),
8629
+ numberOfChannels: this.internalTrack.info.numberOfChannels,
8630
+ sampleRate: this.internalTrack.info.sampleRate,
8631
+ description: this.internalTrack.info.codecDescription ?? void 0
8632
+ };
8633
+ })();
8212
8634
  }
8213
8635
  };
8214
8636
  var getSampleIndexForTimestamp = (sampleTable, timescaleUnits) => {
@@ -9055,6 +9477,7 @@ var Mediabunny = (() => {
9055
9477
  "flac": "A_FLAC",
9056
9478
  "ac3": "A_AC3",
9057
9479
  "eac3": "A_EAC3",
9480
+ "dts": "A_DTS",
9058
9481
  "pcm-u8": "A_PCM/INT/LIT",
9059
9482
  "pcm-s16": "A_PCM/INT/LIT",
9060
9483
  "pcm-s16be": "A_PCM/INT/BIG",
@@ -9749,6 +10172,13 @@ var Mediabunny = (() => {
9749
10172
  } else if (codecIdWithoutSuffix === CODEC_STRING_MAP.eac3) {
9750
10173
  this.currentTrack.info.codec = "eac3";
9751
10174
  this.currentTrack.info.codecDescription = this.currentTrack.codecPrivate;
10175
+ } else if (codecIdWithoutSuffix === CODEC_STRING_MAP.dts) {
10176
+ this.currentTrack.info.codec = "dts";
10177
+ if (this.currentTrack.codecId === "A_DTS/EXPRESS") {
10178
+ this.currentTrack.info.dtsFormat = "dtse";
10179
+ } else if (this.currentTrack.codecId === "A_DTS/LOSSLESS") {
10180
+ this.currentTrack.info.dtsFormat = "dtsl";
10181
+ }
9752
10182
  } else if (this.currentTrack.codecId === "A_PCM/INT/LIT") {
9753
10183
  if (this.currentTrack.info.bitDepth === 8) {
9754
10184
  this.currentTrack.info.codec = "pcm-u8";
@@ -9823,7 +10253,8 @@ var Mediabunny = (() => {
9823
10253
  bitDepth: -1,
9824
10254
  codec: null,
9825
10255
  codecDescription: null,
9826
- aacCodecInfo: null
10256
+ aacCodecInfo: null,
10257
+ dtsFormat: null
9827
10258
  };
9828
10259
  }
9829
10260
  }
@@ -11056,7 +11487,7 @@ var Mediabunny = (() => {
11056
11487
  var MatroskaAudioTrackBacking = class extends MatroskaTrackBacking {
11057
11488
  constructor(internalTrack) {
11058
11489
  super(internalTrack);
11059
- this.decoderConfig = null;
11490
+ this.decoderConfigPromise = null;
11060
11491
  this.internalTrack = internalTrack;
11061
11492
  }
11062
11493
  getType() {
@@ -11075,16 +11506,23 @@ var Mediabunny = (() => {
11075
11506
  if (!this.internalTrack.info.codec) {
11076
11507
  return null;
11077
11508
  }
11078
- return this.decoderConfig ??= {
11079
- codec: extractAudioCodecString({
11080
- codec: this.internalTrack.info.codec,
11081
- codecDescription: this.internalTrack.info.codecDescription,
11082
- aacCodecInfo: this.internalTrack.info.aacCodecInfo
11083
- }),
11084
- numberOfChannels: this.internalTrack.info.numberOfChannels,
11085
- sampleRate: this.internalTrack.info.sampleRate,
11086
- description: this.internalTrack.info.codecDescription ?? void 0
11087
- };
11509
+ return this.decoderConfigPromise ??= (async () => {
11510
+ if (this.internalTrack.info.codec === "dts" && !this.internalTrack.info.dtsFormat) {
11511
+ const firstPacket = await this.getFirstPacket({});
11512
+ this.internalTrack.info.dtsFormat = firstPacket && extractDtsFourCcFromPacket(firstPacket.data);
11513
+ }
11514
+ return {
11515
+ codec: extractAudioCodecString({
11516
+ codec: this.internalTrack.info.codec,
11517
+ codecDescription: this.internalTrack.info.codecDescription,
11518
+ aacCodecInfo: this.internalTrack.info.aacCodecInfo,
11519
+ dtsFormat: this.internalTrack.info.dtsFormat
11520
+ }),
11521
+ numberOfChannels: this.internalTrack.info.numberOfChannels,
11522
+ sampleRate: this.internalTrack.info.sampleRate,
11523
+ description: this.internalTrack.info.codecDescription ?? void 0
11524
+ };
11525
+ })();
11088
11526
  }
11089
11527
  };
11090
11528
 
@@ -13737,6 +14175,15 @@ var Mediabunny = (() => {
13737
14175
 
13738
14176
  // src/mpeg-ts/mpeg-ts-demuxer.ts
13739
14177
  var MISSING_PTS_ERROR_MESSAGE = "PES packet is missing PTS where it was expected. PES packets without PTS are not currently supported. If you think this file should be supported, please report it.";
14178
+ var REGISTRATION_DESCRIPTOR_TAG = 5;
14179
+ var HDMV_FORMAT_IDENTIFIER = 1212435798;
14180
+ var HDPR_FORMAT_IDENTIFIER = 1212436562;
14181
+ var DTS_FORMAT_IDENTIFIER_PREFIX = 1146376960;
14182
+ var BLU_RAY_DTS_STREAM_TYPES = /* @__PURE__ */ new Set([
14183
+ 133 /* BLU_RAY_DTS_HD */,
14184
+ 134 /* BLU_RAY_DTS_HD_MASTER */,
14185
+ 162 /* BLU_RAY_DTS_EXPRESS_SECONDARY */
14186
+ ]);
13740
14187
  var ignoredStreamTypes = /* @__PURE__ */ new Set();
13741
14188
  var MpegTsDemuxer = class extends Demuxer {
13742
14189
  constructor(input) {
@@ -13845,7 +14292,19 @@ var Mediabunny = (() => {
13845
14292
  const pcrPid = bitstream.readBits(13);
13846
14293
  bitstream.skipBits(6);
13847
14294
  const programInfoLength = bitstream.readBits(10);
13848
- bitstream.skipBits(8 * programInfoLength);
14295
+ const programInfoEndPos = bitstream.pos + 8 * programInfoLength;
14296
+ let isBluRayProgram = false;
14297
+ while (bitstream.pos < programInfoEndPos) {
14298
+ const descriptorTag = bitstream.readBits(8);
14299
+ const descriptorLength = bitstream.readBits(8);
14300
+ const descriptorEndPos = bitstream.pos + 8 * descriptorLength;
14301
+ if (descriptorTag === REGISTRATION_DESCRIPTOR_TAG && descriptorLength >= 4) {
14302
+ const formatIdentifier = bitstream.readBits(32);
14303
+ isBluRayProgram ||= formatIdentifier === HDMV_FORMAT_IDENTIFIER || formatIdentifier === HDPR_FORMAT_IDENTIFIER;
14304
+ }
14305
+ bitstream.pos = descriptorEndPos;
14306
+ }
14307
+ bitstream.pos = programInfoEndPos;
13849
14308
  while (8 * (sectionLength + BYTES_BEFORE_SECTION_LENGTH) - bitstream.pos > BITS_IN_CRC_32) {
13850
14309
  const streamType = bitstream.readBits(8);
13851
14310
  bitstream.skipBits(3);
@@ -13855,18 +14314,26 @@ var Mediabunny = (() => {
13855
14314
  const esInfoEndPos = bitstream.pos + 8 * esInfoLength;
13856
14315
  let hasAc3Descriptor = false;
13857
14316
  let hasEac3Descriptor = false;
14317
+ let hasDtsDescriptor = false;
13858
14318
  while (bitstream.pos < esInfoEndPos) {
13859
14319
  const descriptorTag = bitstream.readBits(8);
13860
14320
  const descriptorLength = bitstream.readBits(8);
14321
+ const descriptorEndPos = bitstream.pos + 8 * descriptorLength;
13861
14322
  if (descriptorTag === 106) {
13862
14323
  hasAc3Descriptor = true;
13863
14324
  } else if (descriptorTag === 122 || descriptorTag === 204) {
13864
14325
  hasEac3Descriptor = true;
14326
+ } else if (descriptorTag === 123) {
14327
+ hasDtsDescriptor = true;
14328
+ } else if (descriptorTag === REGISTRATION_DESCRIPTOR_TAG && descriptorLength >= 4) {
14329
+ const formatIdentifier = bitstream.readBits(32);
14330
+ hasDtsDescriptor ||= (formatIdentifier & 4294967040) === DTS_FORMAT_IDENTIFIER_PREFIX;
13865
14331
  }
13866
- bitstream.skipBits(8 * descriptorLength);
14332
+ bitstream.pos = descriptorEndPos;
13867
14333
  }
13868
14334
  let info = null;
13869
- switch (streamType) {
14335
+ const effectiveStreamType = isBluRayProgram && BLU_RAY_DTS_STREAM_TYPES.has(streamType) ? 130 /* DTS */ : streamType;
14336
+ switch (effectiveStreamType) {
13870
14337
  case 27 /* AVC */:
13871
14338
  case 36 /* HEVC */:
13872
14339
  {
@@ -13897,24 +14364,27 @@ var Mediabunny = (() => {
13897
14364
  case 15 /* AAC */:
13898
14365
  case 129 /* AC3_SYSTEM_A */:
13899
14366
  case 135 /* EAC3_SYSTEM_A */:
14367
+ case 130 /* DTS */:
14368
+ case 138 /* DTS_ATSC */:
13900
14369
  {
13901
14370
  let codec;
13902
- if (streamType === 3 /* MP3_MPEG1 */ || streamType === 4 /* MP3_MPEG2 */) {
14371
+ if (effectiveStreamType === 3 /* MP3_MPEG1 */ || effectiveStreamType === 4 /* MP3_MPEG2 */) {
13903
14372
  codec = "mp3";
13904
- } else if (streamType === 15 /* AAC */) {
14373
+ } else if (effectiveStreamType === 15 /* AAC */) {
13905
14374
  codec = "aac";
13906
- } else if (streamType === 129 /* AC3_SYSTEM_A */) {
14375
+ } else if (effectiveStreamType === 129 /* AC3_SYSTEM_A */) {
13907
14376
  codec = "ac3";
13908
- } else if (streamType === 135 /* EAC3_SYSTEM_A */) {
14377
+ } else if (effectiveStreamType === 135 /* EAC3_SYSTEM_A */) {
13909
14378
  codec = "eac3";
13910
14379
  } else {
13911
- throw new Error("Unreachable.");
14380
+ codec = "dts";
13912
14381
  }
13913
14382
  info = {
13914
14383
  type: "audio",
13915
14384
  codec,
13916
14385
  decoderConfig: null,
13917
14386
  aacCodecInfo: null,
14387
+ dtsFormat: null,
13918
14388
  numberOfChannels: -1,
13919
14389
  sampleRate: -1
13920
14390
  };
@@ -13929,6 +14399,7 @@ var Mediabunny = (() => {
13929
14399
  codec: "eac3",
13930
14400
  decoderConfig: null,
13931
14401
  aacCodecInfo: null,
14402
+ dtsFormat: null,
13932
14403
  numberOfChannels: -1,
13933
14404
  sampleRate: -1
13934
14405
  };
@@ -13938,6 +14409,17 @@ var Mediabunny = (() => {
13938
14409
  codec: "ac3",
13939
14410
  decoderConfig: null,
13940
14411
  aacCodecInfo: null,
14412
+ dtsFormat: null,
14413
+ numberOfChannels: -1,
14414
+ sampleRate: -1
14415
+ };
14416
+ } else if (hasDtsDescriptor) {
14417
+ info = {
14418
+ type: "audio",
14419
+ codec: "dts",
14420
+ decoderConfig: null,
14421
+ aacCodecInfo: null,
14422
+ dtsFormat: null,
13941
14423
  numberOfChannels: -1,
13942
14424
  sampleRate: -1
13943
14425
  };
@@ -14157,6 +14639,18 @@ var Mediabunny = (() => {
14157
14639
  }
14158
14640
  elementaryStream.info.numberOfChannels = getEac3ChannelCount(frameInfo);
14159
14641
  elementaryStream.info.sampleRate = sampleRate;
14642
+ } else if (elementaryStream.info.codec === "dts") {
14643
+ const frameInfo = parseDtsFrame(context.suppliedPacket.data);
14644
+ if (!frameInfo) {
14645
+ throw new Error(
14646
+ "Invalid DTS audio stream; could not read frame header from first packet."
14647
+ );
14648
+ }
14649
+ elementaryStream.info.numberOfChannels = frameInfo.numberOfChannels;
14650
+ elementaryStream.info.sampleRate = frameInfo.sampleRate;
14651
+ if (frameInfo.core) {
14652
+ elementaryStream.info.dtsFormat = frameInfo.hasExtensions ? "dtsh" : "dtsc";
14653
+ }
14160
14654
  } else {
14161
14655
  throw new Error("Unhandled.");
14162
14656
  }
@@ -14164,7 +14658,8 @@ var Mediabunny = (() => {
14164
14658
  codec: extractAudioCodecString({
14165
14659
  codec: elementaryStream.info.codec,
14166
14660
  codecDescription: null,
14167
- aacCodecInfo: elementaryStream.info.aacCodecInfo
14661
+ aacCodecInfo: elementaryStream.info.aacCodecInfo,
14662
+ dtsFormat: elementaryStream.info.dtsFormat
14168
14663
  }),
14169
14664
  numberOfChannels: elementaryStream.info.numberOfChannels,
14170
14665
  sampleRate: elementaryStream.info.sampleRate
@@ -15312,6 +15807,63 @@ var Mediabunny = (() => {
15312
15807
  samplesPerFrame * TIMESCALE / elementaryStream.info.sampleRate
15313
15808
  );
15314
15809
  return this.supplyPacket(remaining2, duration);
15810
+ } else if (codec === "dts") {
15811
+ if (byte !== 127 && byte !== 100) {
15812
+ continue;
15813
+ }
15814
+ this.skip(-1);
15815
+ const possibleSyncPos = this.currentPos;
15816
+ let remaining2 = this.ensureBuffered(DTS_CORE_FRAME_HEADER_SIZE);
15817
+ if (remaining2 instanceof Promise) remaining2 = await remaining2;
15818
+ if (remaining2 < DTS_CORE_FRAME_HEADER_SIZE) {
15819
+ return;
15820
+ }
15821
+ const headerBytes = this.readBytes(DTS_CORE_FRAME_HEADER_SIZE);
15822
+ const core = parseDtsCoreFrameHeader(headerBytes);
15823
+ let leadingExss = core ? null : parseDtsExssHeader(headerBytes);
15824
+ if (!core && !leadingExss) {
15825
+ this.seekTo(possibleSyncPos + 1);
15826
+ continue;
15827
+ }
15828
+ if (leadingExss && !leadingExss.asset) {
15829
+ this.seekTo(possibleSyncPos);
15830
+ const headerBound = Math.min(leadingExss.frameSize, DTS_EXSS_MAX_HEADER_SIZE);
15831
+ let remaining3 = this.ensureBuffered(headerBound);
15832
+ if (remaining3 instanceof Promise) remaining3 = await remaining3;
15833
+ leadingExss = parseDtsExssHeader(this.readBytes(remaining3)) ?? leadingExss;
15834
+ }
15835
+ let frameSize = core ? core.frameSize : leadingExss.frameSize;
15836
+ if (core) {
15837
+ let nextSubstreamPos = Math.ceil(core.frameSize / 4) * 4;
15838
+ while (true) {
15839
+ this.seekTo(possibleSyncPos);
15840
+ const neededBytes = nextSubstreamPos + DTS_EXSS_HEADER_PREFIX_SIZE;
15841
+ let remaining3 = this.ensureBuffered(neededBytes);
15842
+ if (remaining3 instanceof Promise) remaining3 = await remaining3;
15843
+ if (remaining3 < neededBytes) {
15844
+ break;
15845
+ }
15846
+ this.seekTo(possibleSyncPos + nextSubstreamPos);
15847
+ const exss = parseDtsExssHeader(this.readBytes(DTS_EXSS_HEADER_PREFIX_SIZE));
15848
+ if (!exss) {
15849
+ break;
15850
+ }
15851
+ nextSubstreamPos += exss.frameSize;
15852
+ frameSize = nextSubstreamPos;
15853
+ }
15854
+ }
15855
+ const sampleCount = core?.sampleCount ?? leadingExss.asset?.sampleCount;
15856
+ if (sampleCount === void 0) {
15857
+ this.seekTo(possibleSyncPos + 1);
15858
+ continue;
15859
+ }
15860
+ this.seekTo(possibleSyncPos);
15861
+ remaining2 = this.ensureBuffered(frameSize);
15862
+ if (remaining2 instanceof Promise) remaining2 = await remaining2;
15863
+ const duration = Math.round(
15864
+ sampleCount * TIMESCALE / elementaryStream.info.sampleRate
15865
+ );
15866
+ return this.supplyPacket(remaining2, duration);
15315
15867
  } else {
15316
15868
  throw new Error("Unhandled.");
15317
15869
  }
@@ -21938,8 +22490,10 @@ var Mediabunny = (() => {
21938
22490
  // 64kbps base for Vorbis
21939
22491
  ac3: 384e3,
21940
22492
  // 384kbps base for AC-3
21941
- eac3: 192e3
22493
+ eac3: 192e3,
21942
22494
  // 192kbps base for E-AC-3
22495
+ dts: 768e3
22496
+ // 768kbps base for DTS
21943
22497
  };
21944
22498
  const baseBitrate = baseRates[codec];
21945
22499
  if (!baseBitrate) {
@@ -22072,7 +22626,7 @@ var Mediabunny = (() => {
22072
22626
  throw new TypeError("bitrate must be a positive integer or a quality.");
22073
22627
  }
22074
22628
  validateVideoEncodingAdditionalOptions(codec, restOptions);
22075
- const resolvedQuality = resolveQuality(quality, bitrate) ?? new Quality({ bitrate: 1e6 });
22629
+ const resolvedQuality = resolveQuality(quality, bitrate) ?? new Quality("medium");
22076
22630
  let candidates;
22077
22631
  try {
22078
22632
  candidates = buildVideoEncoderConfigs({
@@ -22181,7 +22735,7 @@ var Mediabunny = (() => {
22181
22735
  throw new TypeError("bitrate must be a positive integer.");
22182
22736
  }
22183
22737
  validateAudioEncodingAdditionalOptions(codec, restOptions);
22184
- const resolvedQuality = resolveQuality(quality, bitrate) ?? new Quality({ bitrate: 128e3 });
22738
+ const resolvedQuality = resolveQuality(quality, bitrate) ?? new Quality("medium");
22185
22739
  const encoderConfig = buildAudioEncoderConfig({
22186
22740
  codec,
22187
22741
  numberOfChannels,
@@ -23312,8 +23866,12 @@ var Mediabunny = (() => {
23312
23866
  void this.customDecoderCallSerializer.call(() => this.customDecoder.close());
23313
23867
  } else {
23314
23868
  assert(this.decoder);
23315
- this.decoder.close();
23316
- this.alphaDecoder?.close();
23869
+ if (this.decoder.state !== "closed") {
23870
+ this.decoder.close();
23871
+ }
23872
+ if (this.alphaDecoder && this.alphaDecoder.state !== "closed") {
23873
+ this.alphaDecoder.close();
23874
+ }
23317
23875
  this.colorQueue.forEach((x) => x.close());
23318
23876
  this.colorQueue.length = 0;
23319
23877
  this.alphaQueue.forEach((x) => x?.close());
@@ -23906,7 +24464,9 @@ var Mediabunny = (() => {
23906
24464
  void this.customDecoderCallSerializer.call(() => this.customDecoder.close());
23907
24465
  } else {
23908
24466
  assert(this.decoder);
23909
- this.decoder.close();
24467
+ if (this.decoder.state !== "closed") {
24468
+ this.decoder.close();
24469
+ }
23910
24470
  }
23911
24471
  }
23912
24472
  };
@@ -24778,6 +25338,100 @@ var Mediabunny = (() => {
24778
25338
  assert(decoderConfig);
24779
25339
  return determineVideoPacketType(codec, decoderConfig, packet.data);
24780
25340
  }
25341
+ /**
25342
+ * Computes frame rate metrics for this video track, i.e. estimates the video's frame rate. Frame rate is never
25343
+ * determined from file metadata (which is unreliable) but is always deduced directly from the actual frame
25344
+ * timestamps.
25345
+ */
25346
+ async computeFrameRateMetrics(options = {}) {
25347
+ if (!options || typeof options !== "object") {
25348
+ throw new TypeError("options must be an object.");
25349
+ }
25350
+ if (options.targetPacketCount !== void 0 && (!Number.isFinite(options.targetPacketCount) || options.targetPacketCount < 0)) {
25351
+ throw new TypeError("options.targetPacketCount must be a non-negative number.");
25352
+ }
25353
+ const timeResolution = await this.getTimeResolution();
25354
+ const targetPacketCount = options.targetPacketCount ?? 256;
25355
+ const sink = new EncodedPacketSink(this);
25356
+ const timestamps = [];
25357
+ let maxTimestamp = -Infinity;
25358
+ let probedPacketCount = 0;
25359
+ for await (const packet of sink.packets(void 0, void 0, { metadataOnly: true })) {
25360
+ if (timestamps.length >= targetPacketCount && packet.timestamp >= maxTimestamp) {
25361
+ break;
25362
+ }
25363
+ timestamps.push(packet.timestamp);
25364
+ maxTimestamp = Math.max(maxTimestamp, packet.timestamp);
25365
+ probedPacketCount++;
25366
+ }
25367
+ const ticks = new Float64Array(timestamps.length);
25368
+ for (let i = 0; i < timestamps.length; i++) {
25369
+ ticks[i] = Math.round(timestamps[i] * timeResolution);
25370
+ }
25371
+ ticks.sort();
25372
+ let n = 1;
25373
+ for (let i = 1; i < ticks.length; i++) {
25374
+ if (ticks[i] !== ticks[n - 1]) {
25375
+ ticks[n++] = ticks[i];
25376
+ }
25377
+ }
25378
+ if (n < 2) {
25379
+ return {
25380
+ underlyingFrameRate: null,
25381
+ bestGuessFrameRate: timeResolution,
25382
+ minFrameRate: timeResolution,
25383
+ maxFrameRate: timeResolution,
25384
+ averageFrameRate: timeResolution,
25385
+ medianFrameRate: timeResolution,
25386
+ frameRateIsConstant: true,
25387
+ probedPacketCount
25388
+ };
25389
+ }
25390
+ const activeTicks = ticks.subarray(0, n);
25391
+ const underlyingFrameRate = findUnderlyingFrameRate(activeTicks, timeResolution);
25392
+ const unitRate = underlyingFrameRate ?? timeResolution;
25393
+ const ticksPerFrame = underlyingFrameRate !== null ? timeResolution / underlyingFrameRate : null;
25394
+ const histogram = /* @__PURE__ */ new Map();
25395
+ let minDifference = Infinity;
25396
+ let maxDifference = -Infinity;
25397
+ let totalDifference = 0;
25398
+ for (let i = 1; i < n; i++) {
25399
+ const tickDifference = activeTicks[i] - activeTicks[i - 1];
25400
+ const difference = ticksPerFrame !== null ? Math.max(1, Math.round(tickDifference / ticksPerFrame)) : tickDifference;
25401
+ histogram.set(difference, (histogram.get(difference) ?? 0) + 1);
25402
+ minDifference = Math.min(minDifference, difference);
25403
+ maxDifference = Math.max(maxDifference, difference);
25404
+ totalDifference += difference;
25405
+ }
25406
+ const differenceCount = n - 1;
25407
+ const sortedDifferences = [...histogram.keys()].sort((a, b) => a - b);
25408
+ const middleA = differenceCount - 1 >> 1;
25409
+ const middleB = differenceCount >> 1;
25410
+ let medianDifferenceA = 0;
25411
+ let medianDifferenceB = 0;
25412
+ let cumulativeCount = 0;
25413
+ for (const difference of sortedDifferences) {
25414
+ cumulativeCount += histogram.get(difference);
25415
+ if (medianDifferenceA === 0 && cumulativeCount > middleA) {
25416
+ medianDifferenceA = difference;
25417
+ }
25418
+ if (cumulativeCount > middleB) {
25419
+ medianDifferenceB = difference;
25420
+ break;
25421
+ }
25422
+ }
25423
+ const medianFrameRate = (unitRate / medianDifferenceA + unitRate / medianDifferenceB) / 2;
25424
+ return {
25425
+ underlyingFrameRate,
25426
+ bestGuessFrameRate: underlyingFrameRate !== null ? underlyingFrameRate : getBestGuessFrameRate(medianFrameRate),
25427
+ minFrameRate: unitRate / maxDifference,
25428
+ maxFrameRate: unitRate / minDifference,
25429
+ averageFrameRate: unitRate * differenceCount / totalDifference,
25430
+ medianFrameRate,
25431
+ frameRateIsConstant: underlyingFrameRate !== null && minDifference === 1 && maxDifference === 1,
25432
+ probedPacketCount
25433
+ };
25434
+ }
24781
25435
  };
24782
25436
  var InputAudioTrack = class extends InputTrack {
24783
25437
  /** @internal */
@@ -24995,6 +25649,183 @@ var Mediabunny = (() => {
24995
25649
  return 0;
24996
25650
  }).map((x) => x.track);
24997
25651
  };
25652
+ var findUnderlyingFrameRate = (ticks, resolution) => {
25653
+ const MAX_DENOMINATOR = 1e6;
25654
+ const MIN_INLIER_RATIO = 0.98;
25655
+ const DELTA_TOLERANCE = 1 + 1e-9;
25656
+ const MAX_EFFECTIVE_FRAME_SPAN = 1e3;
25657
+ const KNOWN_FRAME_RATES = [
25658
+ 12,
25659
+ 15,
25660
+ 20,
25661
+ 24e3 / 1001,
25662
+ 24,
25663
+ 25,
25664
+ 3e4 / 1001,
25665
+ 30,
25666
+ 48,
25667
+ 50,
25668
+ 6e4 / 1001,
25669
+ 60,
25670
+ 100,
25671
+ 12e4 / 1001,
25672
+ 120,
25673
+ 144,
25674
+ 240
25675
+ ];
25676
+ if (ticks.length < 2) {
25677
+ return null;
25678
+ }
25679
+ const gaps = new Float64Array(ticks.length - 1);
25680
+ for (let i = 1; i < ticks.length; i++) {
25681
+ const gap = ticks[i] - ticks[i - 1];
25682
+ if (!(gap > 0)) {
25683
+ return null;
25684
+ }
25685
+ gaps[i - 1] = gap;
25686
+ }
25687
+ const sortedGaps = gaps.slice();
25688
+ sortedGaps.sort();
25689
+ let period = sortedGaps[Math.floor(sortedGaps.length * 0.05)];
25690
+ for (let iteration = 0; iteration < 6; iteration++) {
25691
+ let totalTicks2 = 0;
25692
+ let totalFrames2 = 0;
25693
+ for (const gap of gaps) {
25694
+ const multiple = Math.max(1, Math.round(gap / period));
25695
+ if (Math.abs(gap - multiple * period) >= DELTA_TOLERANCE) {
25696
+ continue;
25697
+ }
25698
+ totalTicks2 += gap;
25699
+ totalFrames2 += multiple;
25700
+ }
25701
+ if (totalFrames2 === 0) {
25702
+ return null;
25703
+ }
25704
+ const refinedPeriod = totalTicks2 / totalFrames2;
25705
+ if (Math.abs(refinedPeriod - period) <= 1e-12 * Math.max(1, period)) {
25706
+ period = refinedPeriod;
25707
+ break;
25708
+ }
25709
+ period = refinedPeriod;
25710
+ }
25711
+ let inlierCount = 0;
25712
+ let totalTicks = 0;
25713
+ let totalFrames = 0;
25714
+ for (const gap of gaps) {
25715
+ const multiple = Math.max(1, Math.round(gap / period));
25716
+ if (Math.abs(gap - multiple * period) >= DELTA_TOLERANCE) {
25717
+ continue;
25718
+ }
25719
+ inlierCount++;
25720
+ totalTicks += gap;
25721
+ totalFrames += multiple;
25722
+ }
25723
+ if (inlierCount / gaps.length < MIN_INLIER_RATIO) {
25724
+ return null;
25725
+ }
25726
+ period = totalTicks / totalFrames;
25727
+ const uncertainty = 1 / Math.min(
25728
+ totalFrames,
25729
+ MAX_EFFECTIVE_FRAME_SPAN
25730
+ );
25731
+ const periodLo = Math.max(Number.EPSILON, period - uncertainty);
25732
+ const periodHi = period + uncertainty;
25733
+ const fpsLo = resolution / periodHi;
25734
+ const fpsHi = resolution / periodLo;
25735
+ const fittedFps = resolution / period;
25736
+ let fps = null;
25737
+ let bestKnownError = Infinity;
25738
+ for (const candidate of KNOWN_FRAME_RATES) {
25739
+ if (candidate < fpsLo || candidate > fpsHi) {
25740
+ continue;
25741
+ }
25742
+ const error = Math.abs(candidate / fittedFps - 1);
25743
+ if (error < bestKnownError) {
25744
+ fps = candidate;
25745
+ bestKnownError = error;
25746
+ }
25747
+ }
25748
+ if (fps === null) {
25749
+ const periodFraction = simplestFractionBetween(
25750
+ periodLo,
25751
+ periodHi,
25752
+ MAX_DENOMINATOR
25753
+ );
25754
+ const fpsFraction = simplestFractionBetween(
25755
+ fpsLo,
25756
+ fpsHi,
25757
+ MAX_DENOMINATOR
25758
+ );
25759
+ if (fpsFraction && (!periodFraction || fpsFraction.den < periodFraction.den || fpsFraction.den === periodFraction.den && fpsFraction.num <= periodFraction.num)) {
25760
+ fps = fpsFraction.num / fpsFraction.den;
25761
+ } else if (periodFraction) {
25762
+ fps = resolution * periodFraction.den / periodFraction.num;
25763
+ } else {
25764
+ return null;
25765
+ }
25766
+ }
25767
+ const finalPeriod = resolution / fps;
25768
+ let finalInlierCount = 0;
25769
+ for (const gap of gaps) {
25770
+ const multiple = Math.max(1, Math.round(gap / finalPeriod));
25771
+ if (Math.abs(gap - multiple * finalPeriod) < DELTA_TOLERANCE) {
25772
+ finalInlierCount++;
25773
+ }
25774
+ }
25775
+ if (finalInlierCount / gaps.length < MIN_INLIER_RATIO) {
25776
+ return null;
25777
+ }
25778
+ return fps;
25779
+ };
25780
+ var simplestFractionBetween = (lo, hi, maxDenominator) => {
25781
+ for (let den = 1; den <= maxDenominator; den++) {
25782
+ const num = Math.floor(lo * den) + 1;
25783
+ if (num / den < hi) {
25784
+ return simplifyRational({ num, den });
25785
+ }
25786
+ }
25787
+ return null;
25788
+ };
25789
+ var getBestGuessFrameRate = (frameRate) => {
25790
+ const SPECIAL_FRAME_RATES = [
25791
+ 24 / 1.001,
25792
+ 30 / 1.001,
25793
+ 60 / 1.001,
25794
+ 120 / 1.001
25795
+ ];
25796
+ const COMMON_FRAME_RATES = [
25797
+ 12,
25798
+ 15,
25799
+ 20,
25800
+ 24,
25801
+ 25,
25802
+ 30,
25803
+ 48,
25804
+ 50,
25805
+ 60,
25806
+ 100,
25807
+ 120,
25808
+ 144,
25809
+ 240
25810
+ ];
25811
+ const SPECIAL_TOLERANCE = 5e-4;
25812
+ const COMMON_TOLERANCE = 0.025;
25813
+ for (const candidate of SPECIAL_FRAME_RATES) {
25814
+ if (Math.abs(candidate / frameRate - 1) <= SPECIAL_TOLERANCE) {
25815
+ return candidate;
25816
+ }
25817
+ }
25818
+ let best = frameRate;
25819
+ let bestError = Infinity;
25820
+ for (const candidate of COMMON_FRAME_RATES) {
25821
+ const error = Math.abs(candidate / frameRate - 1);
25822
+ if (error <= COMMON_TOLERANCE && error < bestError) {
25823
+ best = candidate;
25824
+ bestError = error;
25825
+ }
25826
+ }
25827
+ return best;
25828
+ };
24998
25829
 
24999
25830
  // src/input.ts
25000
25831
  polyfillSymbolDispose();
@@ -27597,7 +28428,11 @@ var Mediabunny = (() => {
27597
28428
  trackData
27598
28429
  );
27599
28430
  } else if (trackData.type === "audio") {
27600
- const boxName = audioCodecToBoxName(trackData.track.source._codec, trackData.muxer.isQuickTime);
28431
+ const boxName = audioCodecToBoxName(
28432
+ trackData.track.source._codec,
28433
+ trackData.info.decoderConfig.codec,
28434
+ trackData.muxer.isQuickTime
28435
+ );
27601
28436
  assert(boxName);
27602
28437
  sampleDescription = soundSampleDescription(
27603
28438
  boxName,
@@ -27887,7 +28722,11 @@ var Mediabunny = (() => {
27887
28722
  };
27888
28723
  var frma = (trackData) => {
27889
28724
  return box("frma", [
27890
- ascii(audioCodecToBoxName(trackData.track.source._codec, trackData.muxer.isQuickTime))
28725
+ ascii(audioCodecToBoxName(
28726
+ trackData.track.source._codec,
28727
+ trackData.info.decoderConfig.codec,
28728
+ trackData.muxer.isQuickTime
28729
+ ))
27891
28730
  ]);
27892
28731
  };
27893
28732
  var enda = (trackData) => {
@@ -28008,6 +28847,16 @@ var Mediabunny = (() => {
28008
28847
  }
28009
28848
  return box("dec3", [...bytes2]);
28010
28849
  };
28850
+ var ddts = (trackData) => {
28851
+ assert(trackData.info.primingPacket);
28852
+ const frameInfo = parseDtsFrame(trackData.info.primingPacket.data);
28853
+ if (!frameInfo) {
28854
+ throw new Error(
28855
+ "Couldn't extract DTS frame info from the audio packet. Ensure the packets contain valid DTS frames as specified in ETSI TS 102 114."
28856
+ );
28857
+ }
28858
+ return box("ddts", [...buildDtsSpecificBox(frameInfo)]);
28859
+ };
28011
28860
  var subtitleSampleDescription = (compressionType, trackData) => box(compressionType, [
28012
28861
  Array(6).fill(0),
28013
28862
  // Reserved
@@ -28659,7 +29508,7 @@ var Mediabunny = (() => {
28659
29508
  av1: av1C,
28660
29509
  prores: null
28661
29510
  };
28662
- var audioCodecToBoxName = (codec, isQuickTime) => {
29511
+ var audioCodecToBoxName = (codec, fullCodecString, isQuickTime) => {
28663
29512
  switch (codec) {
28664
29513
  case "aac":
28665
29514
  return "mp4a";
@@ -28683,6 +29532,8 @@ var Mediabunny = (() => {
28683
29532
  return "ac-3";
28684
29533
  case "eac3":
28685
29534
  return "ec-3";
29535
+ case "dts":
29536
+ return fullCodecString;
28686
29537
  }
28687
29538
  if (isQuickTime) {
28688
29539
  switch (codec) {
@@ -28748,6 +29599,8 @@ var Mediabunny = (() => {
28748
29599
  return dac3;
28749
29600
  case "eac3":
28750
29601
  return dec3;
29602
+ case "dts":
29603
+ return ddts;
28751
29604
  }
28752
29605
  if (isQuickTime) {
28753
29606
  switch (codec) {
@@ -29699,10 +30552,13 @@ var Mediabunny = (() => {
29699
30552
  });
29700
30553
  requiresAdtsStripping = true;
29701
30554
  }
29702
- if (track.source._codec === "ac3" || track.source._codec === "eac3") {
29703
- if (!packet) {
30555
+ if (!packet) {
30556
+ if (track.source._codec === "ac3" || track.source._codec === "eac3") {
29704
30557
  throw new Error("AC-3/E-AC-3 require a priming packet.");
29705
30558
  }
30559
+ if (track.source._codec === "dts") {
30560
+ throw new Error("DTS requires a priming packet.");
30561
+ }
29706
30562
  }
29707
30563
  const newTrackData = {
29708
30564
  muxer: this,
@@ -30679,8 +31535,15 @@ var Mediabunny = (() => {
30679
31535
  const tracksElement = { id: 374648427 /* Tracks */, data: [] };
30680
31536
  this.tracksElement = tracksElement;
30681
31537
  for (const trackData of this.trackDatas) {
30682
- const codecId = CODEC_STRING_MAP[trackData.track.source._codec];
31538
+ let codecId = CODEC_STRING_MAP[trackData.track.source._codec];
30683
31539
  assert(codecId);
31540
+ if (trackData.type === "audio" && trackData.track.source._codec === "dts") {
31541
+ if (trackData.info.decoderConfig.codec === "dtse") {
31542
+ codecId = "A_DTS/EXPRESS";
31543
+ } else if (trackData.info.decoderConfig.codec === "dtsl") {
31544
+ codecId = "A_DTS/LOSSLESS";
31545
+ }
31546
+ }
30684
31547
  let seekPreRollNs = 0;
30685
31548
  if (trackData.type === "audio" && trackData.track.source._codec === "opus") {
30686
31549
  seekPreRollNs = 1e6 * 80;
@@ -32186,7 +33049,7 @@ ${cue.notes ?? ""}`;
32186
33049
  validateAudioChunkMetadata(meta, track.source._codec);
32187
33050
  assert(meta?.decoderConfig);
32188
33051
  const codec = track.source._codec;
32189
- assert(codec === "aac" || codec === "mp3" || codec === "ac3" || codec === "eac3");
33052
+ assert(codec === "aac" || codec === "mp3" || codec === "ac3" || codec === "eac3" || codec === "dts");
32190
33053
  let streamType;
32191
33054
  let streamId;
32192
33055
  switch (codec) {
@@ -32218,6 +33081,13 @@ ${cue.notes ?? ""}`;
32218
33081
  }
32219
33082
  ;
32220
33083
  break;
33084
+ case "dts":
33085
+ {
33086
+ streamType = 130 /* DTS */;
33087
+ streamId = 189;
33088
+ }
33089
+ ;
33090
+ break;
32221
33091
  }
32222
33092
  const pid = FIRST_TRACK_PID + this.trackDatas.length;
32223
33093
  const newTrackData = {
@@ -32376,7 +33246,7 @@ ${cue.notes ?? ""}`;
32376
33246
  }
32377
33247
  prepareAudioPacket(trackData, packet, meta) {
32378
33248
  const codec = trackData.track.source._codec;
32379
- if (codec === "mp3" || codec === "ac3" || codec === "eac3") {
33249
+ if (codec === "mp3" || codec === "ac3" || codec === "eac3" || codec === "dts") {
32380
33250
  return packet.data;
32381
33251
  }
32382
33252
  if (trackData.inputIsAdts === null) {
@@ -37023,7 +37893,7 @@ ${cue.notes ?? ""}`;
37023
37893
  getSupportedCodecs() {
37024
37894
  return [
37025
37895
  ...VIDEO_CODECS.filter((codec) => ["avc", "hevc"].includes(codec)),
37026
- ...AUDIO_CODECS.filter((codec) => ["aac", "mp3", "ac3", "eac3"].includes(codec))
37896
+ ...AUDIO_CODECS.filter((codec) => ["aac", "mp3", "ac3", "eac3", "dts"].includes(codec))
37027
37897
  ];
37028
37898
  }
37029
37899
  get supportsVideoRotationMetadata() {