@remotion/media-parser 4.0.248 → 4.0.249

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 (92) hide show
  1. package/dist/boxes/avc/key.d.ts +1 -1
  2. package/dist/boxes/iso-base-media/get-children.d.ts +14 -0
  3. package/dist/boxes/iso-base-media/get-children.js +39 -0
  4. package/dist/boxes/iso-base-media/mdat/mdat.d.ts +2 -1
  5. package/dist/boxes/iso-base-media/mdat/mdat.js +10 -1
  6. package/dist/boxes/iso-base-media/moov/moov.js +2 -3
  7. package/dist/boxes/iso-base-media/parse-boxes.d.ts +15 -0
  8. package/dist/boxes/iso-base-media/parse-boxes.js +129 -0
  9. package/dist/boxes/iso-base-media/parse-mdat-partially.d.ts +12 -0
  10. package/dist/boxes/iso-base-media/parse-mdat-partially.js +33 -0
  11. package/dist/boxes/iso-base-media/process-box.d.ts +2 -21
  12. package/dist/boxes/iso-base-media/process-box.js +53 -270
  13. package/dist/boxes/iso-base-media/stsd/mebx.js +2 -3
  14. package/dist/boxes/iso-base-media/stsd/samples.js +5 -9
  15. package/dist/boxes/iso-base-media/trak/trak.js +2 -3
  16. package/dist/boxes/mp3/get-duration.d.ts +2 -0
  17. package/dist/boxes/mp3/get-duration.js +30 -0
  18. package/dist/boxes/mp3/get-frame-length.d.ts +13 -0
  19. package/dist/boxes/mp3/get-frame-length.js +33 -0
  20. package/dist/boxes/mp3/get-metadata-from-mp3.d.ts +3 -0
  21. package/dist/boxes/mp3/get-metadata-from-mp3.js +8 -0
  22. package/dist/boxes/mp3/get-tracks-from-mp3.d.ts +4 -0
  23. package/dist/boxes/mp3/get-tracks-from-mp3.js +25 -0
  24. package/dist/boxes/mp3/id3-v1.d.ts +2 -0
  25. package/dist/boxes/mp3/id3-v1.js +12 -0
  26. package/dist/boxes/mp3/id3-v2.d.ts +0 -0
  27. package/dist/boxes/mp3/id3-v2.js +1 -0
  28. package/dist/boxes/mp3/id3.d.ts +8 -0
  29. package/dist/boxes/mp3/id3.js +78 -0
  30. package/dist/boxes/mp3/parse-mp3.d.ts +8 -0
  31. package/dist/boxes/mp3/parse-mp3.js +57 -0
  32. package/dist/boxes/mp3/parse-mpeg-header.d.ts +6 -0
  33. package/dist/boxes/mp3/parse-mpeg-header.js +274 -0
  34. package/dist/boxes/mp3/samples-per-mpeg-file.d.ts +4 -0
  35. package/dist/boxes/mp3/samples-per-mpeg-file.js +26 -0
  36. package/dist/boxes/riff/continue-after-riff-result.d.ts +13 -0
  37. package/dist/boxes/riff/continue-after-riff-result.js +34 -0
  38. package/dist/boxes/riff/expect-riff-box.d.ts +3 -1
  39. package/dist/boxes/riff/expect-riff-box.js +4 -3
  40. package/dist/boxes/riff/parse-box.d.ts +1 -7
  41. package/dist/boxes/riff/parse-box.js +4 -120
  42. package/dist/boxes/riff/parse-list-box.d.ts +3 -1
  43. package/dist/boxes/riff/parse-list-box.js +4 -3
  44. package/dist/boxes/riff/parse-riff-body.d.ts +11 -0
  45. package/dist/boxes/riff/parse-riff-body.js +105 -0
  46. package/dist/boxes/riff/parse-riff-box.d.ts +3 -1
  47. package/dist/boxes/riff/parse-riff-box.js +2 -2
  48. package/dist/boxes/transport-stream/parse-transport-stream.js +30 -41
  49. package/dist/buffer-iterator.d.ts +6 -0
  50. package/dist/buffer-iterator.js +21 -0
  51. package/dist/continue-mdat-routine.d.ts +17 -0
  52. package/dist/continue-mdat-routine.js +92 -0
  53. package/dist/emit-available-info.js +38 -24
  54. package/dist/esm/index.mjs +1820 -1046
  55. package/dist/file-types/detect-file-type.js +4 -2
  56. package/dist/get-audio-codec.js +1 -1
  57. package/dist/get-container.js +5 -1
  58. package/dist/get-dimensions.d.ts +1 -1
  59. package/dist/get-dimensions.js +3 -0
  60. package/dist/get-duration.js +5 -1
  61. package/dist/get-fields-from-callbacks.js +1 -0
  62. package/dist/get-fps.js +3 -0
  63. package/dist/get-is-hdr.js +1 -1
  64. package/dist/get-keyframes.js +1 -1
  65. package/dist/get-tracks.d.ts +1 -1
  66. package/dist/get-tracks.js +10 -3
  67. package/dist/get-video-codec.js +1 -1
  68. package/dist/has-all-info.js +4 -3
  69. package/dist/index.d.ts +20 -2
  70. package/dist/may-skip-video-data/need-samples-for-fields.js +1 -0
  71. package/dist/metadata/get-metadata.d.ts +1 -0
  72. package/dist/metadata/get-metadata.js +16 -1
  73. package/dist/options.d.ts +12 -5
  74. package/dist/parse-media.js +48 -43
  75. package/dist/parse-result.d.ts +16 -1
  76. package/dist/parse-video.js +9 -10
  77. package/dist/state/can-skip-tracks.js +1 -0
  78. package/dist/state/emitted-fields.js +1 -0
  79. package/dist/state/images.d.ts +9 -0
  80. package/dist/state/images.js +14 -0
  81. package/dist/state/mp3.d.ts +11 -0
  82. package/dist/state/mp3.js +13 -0
  83. package/dist/state/parser-state.d.ts +13 -2
  84. package/dist/state/parser-state.js +8 -1
  85. package/dist/state/sample-callbacks.js +4 -1
  86. package/dist/state/slow-duration-fps.d.ts +2 -1
  87. package/dist/state/slow-duration-fps.js +52 -18
  88. package/dist/throttled-progress.d.ts +14 -0
  89. package/dist/throttled-progress.js +44 -0
  90. package/dist/version.d.ts +1 -1
  91. package/dist/version.js +1 -1
  92. package/package.json +3 -3
@@ -996,9 +996,10 @@ var isTransportStream = (data) => {
996
996
  };
997
997
  var isMp3 = (data) => {
998
998
  const mpegPattern = new Uint8Array([255, 243, 228, 100]);
999
- const id3Pattern = new Uint8Array([73, 68, 51, 3]);
999
+ const id3v3Pattern = new Uint8Array([73, 68, 51, 3]);
1000
+ const id3v2Pattern = new Uint8Array([73, 68, 51, 2]);
1000
1001
  const subarray = data.subarray(0, 4);
1001
- return matchesPattern(mpegPattern)(subarray) || matchesPattern(id3Pattern)(subarray);
1002
+ return matchesPattern(mpegPattern)(subarray) || matchesPattern(id3v3Pattern)(subarray) || matchesPattern(id3v2Pattern)(subarray);
1002
1003
  };
1003
1004
  var isGif = (data) => {
1004
1005
  const gifPattern = new Uint8Array([71, 73, 70, 56]);
@@ -1241,6 +1242,14 @@ var getArrayBufferIterator = (initialData, maxBytes) => {
1241
1242
  let view = new DataView(data.buffer);
1242
1243
  const counter = makeOffsetCounter();
1243
1244
  let discardAllowed = true;
1245
+ const startCheckpoint = () => {
1246
+ const checkpoint = counter.getOffset();
1247
+ return {
1248
+ returnToCheckpoint: () => {
1249
+ counter.decrement(counter.getOffset() - checkpoint);
1250
+ }
1251
+ };
1252
+ };
1244
1253
  const getSlice = (amount) => {
1245
1254
  const value = data.slice(counter.getDiscardedOffset(), counter.getDiscardedOffset() + amount);
1246
1255
  counter.increment(amount);
@@ -1633,6 +1642,18 @@ var getArrayBufferIterator = (initialData, maxBytes) => {
1633
1642
  }
1634
1643
  return new TextDecoder().decode(bytes).trim();
1635
1644
  },
1645
+ planBytes: (size) => {
1646
+ const currentOffset = counter.getOffset();
1647
+ return {
1648
+ discardRest: () => {
1649
+ const toDiscard = size - (counter.getOffset() - currentOffset);
1650
+ if (toDiscard < 0) {
1651
+ throw new Error("read too many bytes");
1652
+ }
1653
+ return getSlice(toDiscard);
1654
+ }
1655
+ };
1656
+ },
1636
1657
  getFloat64: () => {
1637
1658
  const val = view.getFloat64(counter.getDiscardedOffset());
1638
1659
  counter.increment(8);
@@ -1651,7 +1672,8 @@ var getArrayBufferIterator = (initialData, maxBytes) => {
1651
1672
  disallowDiscard,
1652
1673
  allowDiscard,
1653
1674
  startBox,
1654
- readExpGolomb
1675
+ readExpGolomb,
1676
+ startCheckpoint
1655
1677
  };
1656
1678
  };
1657
1679
 
@@ -1720,6 +1742,22 @@ var parseMvhd = ({
1720
1742
  };
1721
1743
  };
1722
1744
 
1745
+ // src/convert-audio-or-video-sample.ts
1746
+ var convertAudioOrVideoSampleToWebCodecsTimestamps = (sample, timescale) => {
1747
+ const { cts, dts, timestamp } = sample;
1748
+ return {
1749
+ cts: cts * 1e6 / timescale,
1750
+ dts: dts * 1e6 / timescale,
1751
+ timestamp: timestamp * 1e6 / timescale,
1752
+ duration: sample.duration === undefined ? undefined : sample.duration * 1e6 / timescale,
1753
+ data: sample.data,
1754
+ trackId: sample.trackId,
1755
+ type: sample.type,
1756
+ offset: sample.offset,
1757
+ timescale: 1e6
1758
+ };
1759
+ };
1760
+
1723
1761
  // src/boxes/iso-base-media/traversal.ts
1724
1762
  var getMoovBox = (segments) => {
1725
1763
  const moovBox = segments.find((s) => s.type === "moov-box");
@@ -2010,6 +2048,9 @@ var getFps = (segments) => {
2010
2048
  if (segments.type === "transport-stream") {
2011
2049
  return null;
2012
2050
  }
2051
+ if (segments.type === "mp3") {
2052
+ return null;
2053
+ }
2013
2054
  throw new Error("Cannot get fps, not implemented");
2014
2055
  };
2015
2056
  var hasFpsSuitedForSlowFps = (boxes) => {
@@ -2046,7 +2087,7 @@ var getAudioCodec = (boxes, parserState) => {
2046
2087
  return null;
2047
2088
  };
2048
2089
  var hasAudioCodec = (boxes, state) => {
2049
- return hasTracks(boxes, state);
2090
+ return getHasTracks(boxes, state);
2050
2091
  };
2051
2092
  var getCodecSpecificatorFromEsdsBox = ({
2052
2093
  child
@@ -2429,7 +2470,7 @@ var getVideoCodec = (boxes, state) => {
2429
2470
  return track.videoTracks[0]?.codecWithoutConfig ?? null;
2430
2471
  };
2431
2472
  var hasVideoCodec = (boxes, state) => {
2432
- return hasTracks(boxes, state);
2473
+ return getHasTracks(boxes, state);
2433
2474
  };
2434
2475
  var getVideoPrivateData = (trakBox) => {
2435
2476
  const videoSample = getStsdVideoConfig(trakBox);
@@ -2652,6 +2693,19 @@ var makeBaseMediaTrack = (trakBox) => {
2652
2693
  return track;
2653
2694
  };
2654
2695
 
2696
+ // src/boxes/mp3/get-tracks-from-mp3.ts
2697
+ var getTracksFromMp3 = (parserState) => {
2698
+ const tracks2 = parserState.callbacks.tracks.getTracks();
2699
+ if (tracks2.length === 0) {
2700
+ throw new Error("No tracks found");
2701
+ }
2702
+ return {
2703
+ audioTracks: tracks2.filter((t) => t.type === "audio"),
2704
+ otherTracks: [],
2705
+ videoTracks: []
2706
+ };
2707
+ };
2708
+
2655
2709
  // src/boxes/avc/codec-string.ts
2656
2710
  var getCodecStringFromSpsAndPps = (sps) => {
2657
2711
  return `avc1.${sps.spsData.profile.toString(16).padStart(2, "0")}${sps.spsData.compatibility.toString(16).padStart(2, "0")}${sps.spsData.level.toString(16).padStart(2, "0")}`;
@@ -3492,7 +3546,7 @@ var isoBaseMediaHasTracks = (structure) => {
3492
3546
  const tracks2 = getTraks(moovBox);
3493
3547
  return tracks2.length === numberOfTracks;
3494
3548
  };
3495
- var hasTracks = (structure, state) => {
3549
+ var getHasTracks = (structure, state) => {
3496
3550
  if (structure.type === "matroska") {
3497
3551
  const mainSegment = getMainSegment(structure.boxes);
3498
3552
  if (!mainSegment) {
@@ -3509,6 +3563,9 @@ var hasTracks = (structure, state) => {
3509
3563
  if (structure.type === "transport-stream") {
3510
3564
  return hasAllTracksFromTransportStream(structure, state);
3511
3565
  }
3566
+ if (structure.type === "mp3") {
3567
+ return state.callbacks.tracks.getTracks().length > 0;
3568
+ }
3512
3569
  throw new Error("Unknown container " + structure);
3513
3570
  };
3514
3571
  var getTracksFromMa = (segments, state) => {
@@ -3580,56 +3637,10 @@ var getTracks = (segments, state) => {
3580
3637
  if (segments.type === "transport-stream") {
3581
3638
  return getTracksFromTransportStream(segments, state);
3582
3639
  }
3583
- throw new Error(`Unknown container${segments}`);
3584
- };
3585
-
3586
- // src/get-container.ts
3587
- var getContainer = (segments) => {
3588
- if (segments.type === "iso-base-media") {
3589
- return "mp4";
3590
- }
3591
- if (segments.type === "matroska") {
3592
- return "webm";
3593
- }
3594
- if (segments.type === "transport-stream") {
3595
- return "transport-stream";
3596
- }
3597
- if (segments.type === "riff") {
3598
- if (isRiffAvi2(segments)) {
3599
- return "avi";
3600
- }
3601
- }
3602
- throw new Error("Unknown container");
3603
- };
3604
- var hasContainer = (boxes) => {
3605
- try {
3606
- return getContainer(boxes) !== null;
3607
- } catch {
3608
- return false;
3609
- }
3610
- };
3611
-
3612
- // src/get-dimensions.ts
3613
- var getDimensions = (boxes, state) => {
3614
- const { videoTracks } = getTracks(boxes, state);
3615
- if (!videoTracks.length) {
3616
- throw new Error("Expected video track");
3617
- }
3618
- const firstVideoTrack = videoTracks[0];
3619
- return {
3620
- width: firstVideoTrack.width,
3621
- height: firstVideoTrack.height,
3622
- rotation: firstVideoTrack.rotation,
3623
- unrotatedHeight: firstVideoTrack.displayAspectHeight,
3624
- unrotatedWidth: firstVideoTrack.displayAspectWidth
3625
- };
3626
- };
3627
- var hasDimensions = (boxes, state) => {
3628
- try {
3629
- return getDimensions(boxes, state) !== null;
3630
- } catch {
3631
- return false;
3640
+ if (segments.type === "mp3") {
3641
+ return getTracksFromMp3(state);
3632
3642
  }
3643
+ throw new Error(`Unknown container${segments}`);
3633
3644
  };
3634
3645
 
3635
3646
  // src/get-sample-positions.ts
@@ -3842,153 +3853,476 @@ var getSamplePositionsFromTrack = (trakBox, moofBox) => {
3842
3853
  return samplePositions;
3843
3854
  };
3844
3855
 
3845
- // src/get-duration.ts
3846
- var getDurationFromMatroska = (segments) => {
3847
- const mainSegment = segments.find((s) => s.type === "Segment");
3848
- if (!mainSegment || mainSegment.type !== "Segment") {
3849
- return null;
3850
- }
3851
- const { value: children } = mainSegment;
3852
- if (!children) {
3853
- return null;
3854
- }
3855
- const infoSegment = children.find((s) => s.type === "Info");
3856
- const relevantBoxes = [
3857
- ...mainSegment.value,
3858
- ...infoSegment && infoSegment.type === "Info" ? infoSegment.value : []
3859
- ];
3860
- const timestampScale2 = relevantBoxes.find((s) => s.type === "TimestampScale");
3861
- if (!timestampScale2 || timestampScale2.type !== "TimestampScale") {
3862
- return null;
3863
- }
3864
- const duration2 = relevantBoxes.find((s) => s.type === "Duration");
3865
- if (!duration2 || duration2.type !== "Duration") {
3866
- return null;
3867
- }
3868
- return duration2.value.value / timestampScale2.value.value * 1000;
3869
- };
3870
- var getDurationFromIsoBaseMedia = (structure, parserState) => {
3871
- const moovBox = getMoovBox(structure.boxes);
3872
- if (!moovBox) {
3873
- return null;
3874
- }
3875
- const moofBox = getMoofBox(structure.boxes);
3876
- const mvhdBox = getMvhdBox(moovBox);
3877
- if (!mvhdBox) {
3878
- return null;
3879
- }
3880
- if (mvhdBox.type !== "mvhd-box") {
3881
- throw new Error("Expected mvhd-box");
3882
- }
3883
- if (mvhdBox.durationInSeconds > 0) {
3884
- return mvhdBox.durationInSeconds;
3856
+ // src/boxes/iso-base-media/mdat/mdat.ts
3857
+ var parseMdat = async ({
3858
+ data,
3859
+ size,
3860
+ fileOffset,
3861
+ existingBoxes,
3862
+ state,
3863
+ signal,
3864
+ maySkipSampleProcessing
3865
+ }) => {
3866
+ const alreadyHas = getHasTracks({
3867
+ type: "iso-base-media",
3868
+ boxes: existingBoxes
3869
+ }, state);
3870
+ if (!alreadyHas) {
3871
+ if (maySkipSampleProcessing) {
3872
+ data.discard(size - (data.counter.getOffset() - fileOffset));
3873
+ return Promise.resolve({
3874
+ type: "mdat-box",
3875
+ boxSize: size,
3876
+ status: "samples-skipped",
3877
+ fileOffset
3878
+ });
3879
+ }
3880
+ data.discard(size - (data.counter.getOffset() - fileOffset));
3881
+ data.disallowDiscard();
3882
+ return Promise.resolve({
3883
+ type: "mdat-box",
3884
+ boxSize: size,
3885
+ status: "samples-buffered",
3886
+ fileOffset
3887
+ });
3885
3888
  }
3886
- const tracks2 = getTracks(structure, parserState);
3889
+ const tracks2 = getTracks({ type: "iso-base-media", boxes: existingBoxes }, state);
3887
3890
  const allTracks = [
3888
3891
  ...tracks2.videoTracks,
3889
3892
  ...tracks2.audioTracks,
3890
3893
  ...tracks2.otherTracks
3891
3894
  ];
3892
- const allSamples = allTracks.map((t) => {
3893
- const { timescale: ts } = t;
3894
- const samplePositions = getSamplePositionsFromTrack(t.trakBox, moofBox);
3895
- const highest = samplePositions?.map((sp) => (sp.cts + sp.duration) / ts).reduce((a, b) => Math.max(a, b), 0);
3896
- return highest ?? 0;
3897
- });
3898
- const highestTimestamp = Math.max(...allSamples);
3899
- return highestTimestamp;
3900
- };
3901
- var getDurationFromAvi = (structure) => {
3902
- const strl = getStrlBoxes(structure);
3903
- const lengths = [];
3904
- for (const s of strl) {
3905
- const strh = getStrhBox(s.children);
3906
- if (!strh) {
3907
- throw new Error("No strh box");
3895
+ const flatSamples = allTracks.map((track) => {
3896
+ const samplePositions = getSamplePositionsFromTrack(track.trakBox, getMoofBox(existingBoxes));
3897
+ if (!samplePositions) {
3898
+ throw new Error("No sample positions");
3908
3899
  }
3909
- const samplesPerSecond = strh.rate / strh.scale;
3910
- const streamLength = strh.length / samplesPerSecond;
3911
- lengths.push(streamLength);
3912
- }
3913
- return Math.max(...lengths);
3914
- };
3915
- var getDuration = (structure, parserState) => {
3916
- if (structure.type === "matroska") {
3917
- return getDurationFromMatroska(structure.boxes);
3918
- }
3919
- if (structure.type === "iso-base-media") {
3920
- return getDurationFromIsoBaseMedia(structure, parserState);
3921
- }
3922
- if (structure.type === "riff") {
3923
- return getDurationFromAvi(structure);
3924
- }
3925
- if (structure.type === "transport-stream") {
3926
- return null;
3927
- }
3928
- throw new Error("Has no duration " + structure);
3929
- };
3930
- var hasDuration = (structure, parserState) => {
3931
- return hasTracks(structure, parserState);
3932
- };
3933
- var hasSlowDuration = (structure, parserState) => {
3934
- try {
3935
- return getDuration(structure, parserState) !== null;
3936
- } catch {
3937
- return false;
3938
- }
3939
- };
3940
-
3941
- // src/get-is-hdr.ts
3942
- var isVideoTrackHdr = (track) => {
3943
- return track.color.matrixCoefficients === "bt2020" && track.color.transferCharacteristics === "arib-std-b67" && track.color.primaries === "bt2020";
3944
- };
3945
- var getIsHdr = (boxes, state) => {
3946
- const { videoTracks } = getTracks(boxes, state);
3947
- return videoTracks.some((track) => isVideoTrackHdr(track));
3948
- };
3949
- var hasHdr = (boxes, state) => {
3950
- return hasTracks(boxes, state);
3951
- };
3952
-
3953
- // src/boxes/iso-base-media/get-keyframes.ts
3954
- var getKeyframesFromIsoBaseMedia = (structure) => {
3955
- const { videoTracks } = getTracksFromIsoBaseMedia(structure.boxes);
3956
- const moofBox = getMoofBox(structure.boxes);
3957
- const allSamples = videoTracks.map((t) => {
3958
- const { timescale: ts } = t;
3959
- const samplePositions = getSamplePositionsFromTrack(t.trakBox, moofBox);
3960
- const keyframes = samplePositions.filter((k) => {
3961
- return k.isKeyframe;
3962
- }).map((k) => {
3900
+ return samplePositions.map((samplePosition) => {
3963
3901
  return {
3964
- trackId: t.trackId,
3965
- presentationTimeInSeconds: k.cts / ts,
3966
- decodingTimeInSeconds: k.dts / ts,
3967
- positionInBytes: k.offset,
3968
- sizeInBytes: k.size
3902
+ track: { ...track },
3903
+ samplePosition
3969
3904
  };
3970
3905
  });
3971
- return keyframes;
3972
- });
3973
- return allSamples.flat();
3974
- };
3975
-
3976
- // src/get-keyframes.ts
3977
- var getKeyframes = (structure) => {
3978
- if (structure.type === "iso-base-media") {
3979
- return getKeyframesFromIsoBaseMedia(structure);
3980
- }
3981
- return null;
3982
- };
3983
- var hasKeyframes = (structure, parserState) => {
3984
- if (structure.type === "iso-base-media") {
3985
- return hasTracks(structure, parserState);
3986
- }
3987
- return true;
3988
- };
3989
-
3990
- // src/may-skip-video-data/need-samples-for-fields.ts
3991
- var needsSamples = {
3906
+ }).flat(1);
3907
+ while (true) {
3908
+ if (signal && signal.aborted) {
3909
+ break;
3910
+ }
3911
+ const samplesWithIndex = flatSamples.find((sample) => {
3912
+ return sample.samplePosition.offset === data.counter.getOffset();
3913
+ });
3914
+ if (!samplesWithIndex) {
3915
+ const nextSample_ = flatSamples.filter((s) => s.samplePosition.offset > data.counter.getOffset()).sort((a, b) => a.samplePosition.offset - b.samplePosition.offset)[0];
3916
+ if (nextSample_) {
3917
+ data.discard(nextSample_.samplePosition.offset - data.counter.getOffset());
3918
+ continue;
3919
+ } else {
3920
+ const bytesRemaining = size + fileOffset - data.counter.getOffset();
3921
+ data.discard(bytesRemaining);
3922
+ break;
3923
+ }
3924
+ }
3925
+ if (data.bytesRemaining() < samplesWithIndex.samplePosition.size) {
3926
+ break;
3927
+ }
3928
+ const bytes = data.getSlice(samplesWithIndex.samplePosition.size);
3929
+ const { cts, dts, duration: duration2, isKeyframe, offset } = samplesWithIndex.samplePosition;
3930
+ if (samplesWithIndex.track.type === "audio") {
3931
+ await state.callbacks.onAudioSample(samplesWithIndex.track.trackId, convertAudioOrVideoSampleToWebCodecsTimestamps({
3932
+ data: bytes,
3933
+ timestamp: cts,
3934
+ duration: duration2,
3935
+ cts,
3936
+ dts,
3937
+ trackId: samplesWithIndex.track.trackId,
3938
+ type: isKeyframe ? "key" : "delta",
3939
+ offset,
3940
+ timescale: samplesWithIndex.track.timescale
3941
+ }, samplesWithIndex.track.timescale));
3942
+ }
3943
+ if (samplesWithIndex.track.type === "video") {
3944
+ const nalUnitType = bytes[4] & 31;
3945
+ let isRecoveryPoint = false;
3946
+ if (nalUnitType === 6) {
3947
+ const seiType = bytes[5];
3948
+ isRecoveryPoint = seiType === 6;
3949
+ }
3950
+ await state.callbacks.onVideoSample(samplesWithIndex.track.trackId, convertAudioOrVideoSampleToWebCodecsTimestamps({
3951
+ data: bytes,
3952
+ timestamp: cts,
3953
+ duration: duration2,
3954
+ cts,
3955
+ dts,
3956
+ trackId: samplesWithIndex.track.trackId,
3957
+ type: isKeyframe && !isRecoveryPoint ? "key" : "delta",
3958
+ offset,
3959
+ timescale: samplesWithIndex.track.timescale
3960
+ }, samplesWithIndex.track.timescale));
3961
+ }
3962
+ const remaining = size - (data.counter.getOffset() - fileOffset);
3963
+ data.removeBytesRead();
3964
+ if (remaining === 0) {
3965
+ break;
3966
+ }
3967
+ }
3968
+ const expectedOffsetNow = size + fileOffset;
3969
+ const actualOffsetNow = data.counter.getOffset();
3970
+ if (expectedOffsetNow !== actualOffsetNow) {
3971
+ return Promise.resolve({
3972
+ type: "partial-mdat-box",
3973
+ boxSize: size,
3974
+ fileOffset
3975
+ });
3976
+ }
3977
+ return Promise.resolve({
3978
+ type: "mdat-box",
3979
+ boxSize: size,
3980
+ status: "samples-processed",
3981
+ fileOffset
3982
+ });
3983
+ };
3984
+
3985
+ // src/boxes/iso-base-media/parse-mdat-partially.ts
3986
+ var parseMdatPartially = async ({
3987
+ iterator,
3988
+ boxSize,
3989
+ fileOffset,
3990
+ parsedBoxes,
3991
+ state,
3992
+ signal
3993
+ }) => {
3994
+ const box = await parseMdat({
3995
+ data: iterator,
3996
+ size: boxSize,
3997
+ fileOffset,
3998
+ existingBoxes: parsedBoxes,
3999
+ state,
4000
+ signal,
4001
+ maySkipSampleProcessing: state.supportsContentRange
4002
+ });
4003
+ if (box.type === "partial-mdat-box") {
4004
+ return box;
4005
+ }
4006
+ if ((box.status === "samples-processed" || box.status === "samples-buffered") && box.fileOffset + boxSize === iterator.counter.getOffset()) {
4007
+ return {
4008
+ type: "complete",
4009
+ box,
4010
+ size: boxSize,
4011
+ skipTo: null
4012
+ };
4013
+ }
4014
+ return {
4015
+ type: "partial-mdat-box",
4016
+ boxSize,
4017
+ fileOffset
4018
+ };
4019
+ };
4020
+
4021
+ // src/get-container.ts
4022
+ var getContainer = (segments) => {
4023
+ if (segments.type === "iso-base-media") {
4024
+ return "mp4";
4025
+ }
4026
+ if (segments.type === "matroska") {
4027
+ return "webm";
4028
+ }
4029
+ if (segments.type === "transport-stream") {
4030
+ return "transport-stream";
4031
+ }
4032
+ if (segments.type === "mp3") {
4033
+ return "mp3";
4034
+ }
4035
+ if (segments.type === "riff") {
4036
+ if (isRiffAvi2(segments)) {
4037
+ return "avi";
4038
+ }
4039
+ throw new Error("Unknown RIFF container " + segments.type);
4040
+ }
4041
+ throw new Error("Unknown container " + segments);
4042
+ };
4043
+ var hasContainer = (boxes) => {
4044
+ try {
4045
+ return getContainer(boxes) !== null;
4046
+ } catch {
4047
+ return false;
4048
+ }
4049
+ };
4050
+
4051
+ // src/get-dimensions.ts
4052
+ var getDimensions = (boxes, state) => {
4053
+ const { videoTracks } = getTracks(boxes, state);
4054
+ if (!videoTracks.length) {
4055
+ if (boxes.type === "mp3") {
4056
+ return null;
4057
+ }
4058
+ throw new Error("Expected video track");
4059
+ }
4060
+ const firstVideoTrack = videoTracks[0];
4061
+ return {
4062
+ width: firstVideoTrack.width,
4063
+ height: firstVideoTrack.height,
4064
+ rotation: firstVideoTrack.rotation,
4065
+ unrotatedHeight: firstVideoTrack.displayAspectHeight,
4066
+ unrotatedWidth: firstVideoTrack.displayAspectWidth
4067
+ };
4068
+ };
4069
+ var hasDimensions = (boxes, state) => {
4070
+ try {
4071
+ return getDimensions(boxes, state) !== null;
4072
+ } catch {
4073
+ return false;
4074
+ }
4075
+ };
4076
+
4077
+ // src/boxes/mp3/get-frame-length.ts
4078
+ var getUnroundedMpegFrameLength = ({
4079
+ samplesPerFrame,
4080
+ bitrateKbit,
4081
+ samplingFrequency: samplingFrequency2,
4082
+ padding,
4083
+ layer
4084
+ }) => {
4085
+ if (layer === 1) {
4086
+ throw new Error("MPEG Layer I is not supported");
4087
+ }
4088
+ return samplesPerFrame / 8 * bitrateKbit / samplingFrequency2 * 1000 + (padding ? layer === 1 ? 4 : 1 : 0);
4089
+ };
4090
+ var getAverageMpegFrameLength = ({
4091
+ samplesPerFrame,
4092
+ bitrateKbit,
4093
+ samplingFrequency: samplingFrequency2,
4094
+ layer
4095
+ }) => {
4096
+ const withoutPadding = getUnroundedMpegFrameLength({
4097
+ bitrateKbit,
4098
+ layer,
4099
+ padding: false,
4100
+ samplesPerFrame,
4101
+ samplingFrequency: samplingFrequency2
4102
+ });
4103
+ const rounded = Math.floor(withoutPadding);
4104
+ const rest = withoutPadding % 1;
4105
+ return rest * (rounded + 1) + (1 - rest) * rounded;
4106
+ };
4107
+ var getMpegFrameLength = ({
4108
+ samplesPerFrame,
4109
+ bitrateKbit,
4110
+ samplingFrequency: samplingFrequency2,
4111
+ padding,
4112
+ layer
4113
+ }) => {
4114
+ return Math.floor(getUnroundedMpegFrameLength({
4115
+ bitrateKbit,
4116
+ layer,
4117
+ padding,
4118
+ samplesPerFrame,
4119
+ samplingFrequency: samplingFrequency2
4120
+ }));
4121
+ };
4122
+
4123
+ // src/boxes/mp3/samples-per-mpeg-file.ts
4124
+ var getSamplesPerMpegFrame = ({
4125
+ mpegVersion,
4126
+ layer
4127
+ }) => {
4128
+ if (mpegVersion === 1) {
4129
+ if (layer === 1) {
4130
+ return 384;
4131
+ }
4132
+ if (layer === 2 || layer === 3) {
4133
+ return 1152;
4134
+ }
4135
+ }
4136
+ if (mpegVersion === 2) {
4137
+ if (layer === 1) {
4138
+ return 384;
4139
+ }
4140
+ if (layer === 2) {
4141
+ return 1152;
4142
+ }
4143
+ if (layer === 3) {
4144
+ return 576;
4145
+ }
4146
+ }
4147
+ throw new Error("Invalid MPEG layer");
4148
+ };
4149
+
4150
+ // src/boxes/mp3/get-duration.ts
4151
+ var getDurationFromMp3 = (state) => {
4152
+ if (state.contentLength === null) {
4153
+ return null;
4154
+ }
4155
+ const mp3Info = state.mp3Info.getMp3Info();
4156
+ if (!mp3Info) {
4157
+ throw new Error("No mp3 info");
4158
+ }
4159
+ const samplesPerFrame = getSamplesPerMpegFrame({
4160
+ layer: mp3Info.layer,
4161
+ mpegVersion: mp3Info.mpegVersion
4162
+ });
4163
+ const frameLengthInBytes = getMpegFrameLength({
4164
+ bitrateKbit: mp3Info.bitrateKbit,
4165
+ padding: false,
4166
+ samplesPerFrame,
4167
+ samplingFrequency: mp3Info.sampleRate,
4168
+ layer: mp3Info.layer
4169
+ });
4170
+ const frames = Math.floor((state.contentLength - mp3Info.startOfMpegStream) / frameLengthInBytes);
4171
+ const samples = frames * samplesPerFrame;
4172
+ const durationInSeconds = samples / mp3Info.sampleRate;
4173
+ return durationInSeconds;
4174
+ };
4175
+
4176
+ // src/get-duration.ts
4177
+ var getDurationFromMatroska = (segments) => {
4178
+ const mainSegment = segments.find((s) => s.type === "Segment");
4179
+ if (!mainSegment || mainSegment.type !== "Segment") {
4180
+ return null;
4181
+ }
4182
+ const { value: children } = mainSegment;
4183
+ if (!children) {
4184
+ return null;
4185
+ }
4186
+ const infoSegment = children.find((s) => s.type === "Info");
4187
+ const relevantBoxes = [
4188
+ ...mainSegment.value,
4189
+ ...infoSegment && infoSegment.type === "Info" ? infoSegment.value : []
4190
+ ];
4191
+ const timestampScale2 = relevantBoxes.find((s) => s.type === "TimestampScale");
4192
+ if (!timestampScale2 || timestampScale2.type !== "TimestampScale") {
4193
+ return null;
4194
+ }
4195
+ const duration2 = relevantBoxes.find((s) => s.type === "Duration");
4196
+ if (!duration2 || duration2.type !== "Duration") {
4197
+ return null;
4198
+ }
4199
+ return duration2.value.value / timestampScale2.value.value * 1000;
4200
+ };
4201
+ var getDurationFromIsoBaseMedia = (structure, parserState) => {
4202
+ const moovBox = getMoovBox(structure.boxes);
4203
+ if (!moovBox) {
4204
+ return null;
4205
+ }
4206
+ const moofBox = getMoofBox(structure.boxes);
4207
+ const mvhdBox = getMvhdBox(moovBox);
4208
+ if (!mvhdBox) {
4209
+ return null;
4210
+ }
4211
+ if (mvhdBox.type !== "mvhd-box") {
4212
+ throw new Error("Expected mvhd-box");
4213
+ }
4214
+ if (mvhdBox.durationInSeconds > 0) {
4215
+ return mvhdBox.durationInSeconds;
4216
+ }
4217
+ const tracks2 = getTracks(structure, parserState);
4218
+ const allTracks = [
4219
+ ...tracks2.videoTracks,
4220
+ ...tracks2.audioTracks,
4221
+ ...tracks2.otherTracks
4222
+ ];
4223
+ const allSamples = allTracks.map((t) => {
4224
+ const { timescale: ts } = t;
4225
+ const samplePositions = getSamplePositionsFromTrack(t.trakBox, moofBox);
4226
+ const highest = samplePositions?.map((sp) => (sp.cts + sp.duration) / ts).reduce((a, b) => Math.max(a, b), 0);
4227
+ return highest ?? 0;
4228
+ });
4229
+ const highestTimestamp = Math.max(...allSamples);
4230
+ return highestTimestamp;
4231
+ };
4232
+ var getDurationFromAvi = (structure) => {
4233
+ const strl = getStrlBoxes(structure);
4234
+ const lengths = [];
4235
+ for (const s of strl) {
4236
+ const strh = getStrhBox(s.children);
4237
+ if (!strh) {
4238
+ throw new Error("No strh box");
4239
+ }
4240
+ const samplesPerSecond = strh.rate / strh.scale;
4241
+ const streamLength = strh.length / samplesPerSecond;
4242
+ lengths.push(streamLength);
4243
+ }
4244
+ return Math.max(...lengths);
4245
+ };
4246
+ var getDuration = (structure, parserState) => {
4247
+ if (structure.type === "matroska") {
4248
+ return getDurationFromMatroska(structure.boxes);
4249
+ }
4250
+ if (structure.type === "iso-base-media") {
4251
+ return getDurationFromIsoBaseMedia(structure, parserState);
4252
+ }
4253
+ if (structure.type === "riff") {
4254
+ return getDurationFromAvi(structure);
4255
+ }
4256
+ if (structure.type === "transport-stream") {
4257
+ return null;
4258
+ }
4259
+ if (structure.type === "mp3") {
4260
+ return getDurationFromMp3(parserState);
4261
+ }
4262
+ throw new Error("Has no duration " + structure);
4263
+ };
4264
+ var hasDuration = (structure, parserState) => {
4265
+ return getHasTracks(structure, parserState);
4266
+ };
4267
+ var hasSlowDuration = (structure, parserState) => {
4268
+ try {
4269
+ return getDuration(structure, parserState) !== null;
4270
+ } catch {
4271
+ return false;
4272
+ }
4273
+ };
4274
+
4275
+ // src/get-is-hdr.ts
4276
+ var isVideoTrackHdr = (track) => {
4277
+ return track.color.matrixCoefficients === "bt2020" && track.color.transferCharacteristics === "arib-std-b67" && track.color.primaries === "bt2020";
4278
+ };
4279
+ var getIsHdr = (boxes, state) => {
4280
+ const { videoTracks } = getTracks(boxes, state);
4281
+ return videoTracks.some((track) => isVideoTrackHdr(track));
4282
+ };
4283
+ var hasHdr = (boxes, state) => {
4284
+ return getHasTracks(boxes, state);
4285
+ };
4286
+
4287
+ // src/boxes/iso-base-media/get-keyframes.ts
4288
+ var getKeyframesFromIsoBaseMedia = (structure) => {
4289
+ const { videoTracks } = getTracksFromIsoBaseMedia(structure.boxes);
4290
+ const moofBox = getMoofBox(structure.boxes);
4291
+ const allSamples = videoTracks.map((t) => {
4292
+ const { timescale: ts } = t;
4293
+ const samplePositions = getSamplePositionsFromTrack(t.trakBox, moofBox);
4294
+ const keyframes = samplePositions.filter((k) => {
4295
+ return k.isKeyframe;
4296
+ }).map((k) => {
4297
+ return {
4298
+ trackId: t.trackId,
4299
+ presentationTimeInSeconds: k.cts / ts,
4300
+ decodingTimeInSeconds: k.dts / ts,
4301
+ positionInBytes: k.offset,
4302
+ sizeInBytes: k.size
4303
+ };
4304
+ });
4305
+ return keyframes;
4306
+ });
4307
+ return allSamples.flat();
4308
+ };
4309
+
4310
+ // src/get-keyframes.ts
4311
+ var getKeyframes = (structure) => {
4312
+ if (structure.type === "iso-base-media") {
4313
+ return getKeyframesFromIsoBaseMedia(structure);
4314
+ }
4315
+ return null;
4316
+ };
4317
+ var hasKeyframes = (structure, parserState) => {
4318
+ if (structure.type === "iso-base-media") {
4319
+ return getHasTracks(structure, parserState);
4320
+ }
4321
+ return true;
4322
+ };
4323
+
4324
+ // src/may-skip-video-data/need-samples-for-fields.ts
4325
+ var needsSamples = {
3992
4326
  slowDurationInSeconds: true,
3993
4327
  slowFps: true,
3994
4328
  slowKeyframes: true,
@@ -4010,7 +4344,8 @@ var needsSamples = {
4010
4344
  metadata: false,
4011
4345
  location: false,
4012
4346
  mimeType: false,
4013
- keyframes: false
4347
+ keyframes: false,
4348
+ images: false
4014
4349
  };
4015
4350
  var needsToIterateOverSamples = ({
4016
4351
  fields,
@@ -4029,6 +4364,216 @@ var maySkipVideoData = ({ state }) => {
4029
4364
  });
4030
4365
  };
4031
4366
 
4367
+ // src/boxes/mp3/get-metadata-from-mp3.ts
4368
+ var getMetadataFromMp3 = (mp3Structure) => {
4369
+ const findHeader = mp3Structure.boxes.find((b) => b.type === "id3-header");
4370
+ return findHeader ? findHeader.metatags : null;
4371
+ };
4372
+
4373
+ // src/metadata/metadata-from-iso.ts
4374
+ var mapToKey = (index) => {
4375
+ if (index === 2839630420) {
4376
+ return "artist";
4377
+ }
4378
+ if (index === 2841734242) {
4379
+ return "album";
4380
+ }
4381
+ if (index === 2841865588) {
4382
+ return "comment";
4383
+ }
4384
+ if (index === 2841928057) {
4385
+ return "releaseDate";
4386
+ }
4387
+ if (index === 2842125678) {
4388
+ return "genre";
4389
+ }
4390
+ if (index === 2842583405) {
4391
+ return "title";
4392
+ }
4393
+ if (index === 2842980207) {
4394
+ return "encoder";
4395
+ }
4396
+ if (index === 2843177588) {
4397
+ return "writer";
4398
+ }
4399
+ if (index === 2841866361) {
4400
+ return "copyright";
4401
+ }
4402
+ if (index === 2841930098) {
4403
+ return "director";
4404
+ }
4405
+ if (index === 2842718820) {
4406
+ return "producer";
4407
+ }
4408
+ if (index === 2841929075) {
4409
+ return "description";
4410
+ }
4411
+ return null;
4412
+ };
4413
+ var parseIlstBoxWithoutKeys = (ilstBox) => {
4414
+ return ilstBox.entries.map((entry) => {
4415
+ const key = mapToKey(entry.index);
4416
+ if (!key) {
4417
+ return null;
4418
+ }
4419
+ if (entry.value.type === "unknown") {
4420
+ return null;
4421
+ }
4422
+ return {
4423
+ trackId: null,
4424
+ key,
4425
+ value: entry.value.value
4426
+ };
4427
+ }).filter(truthy);
4428
+ };
4429
+ var parseIsoMetaBox = (meta, trackId) => {
4430
+ const ilstBox = meta.children.find((b) => b.type === "ilst-box");
4431
+ const keysBox = meta.children.find((b) => b.type === "keys-box");
4432
+ if (!ilstBox || !keysBox) {
4433
+ if (ilstBox) {
4434
+ return parseIlstBoxWithoutKeys(ilstBox);
4435
+ }
4436
+ return [];
4437
+ }
4438
+ const entries = [];
4439
+ for (let i = 0;i < ilstBox.entries.length; i++) {
4440
+ const ilstEntry = ilstBox.entries[i];
4441
+ const keysEntry = keysBox.entries[i];
4442
+ if (ilstEntry.value.type !== "unknown") {
4443
+ const value = typeof ilstEntry.value.value === "string" && ilstEntry.value.value.endsWith("\x00") ? ilstEntry.value.value.slice(0, -1) : ilstEntry.value.value;
4444
+ entries.push({
4445
+ key: keysEntry.value,
4446
+ value,
4447
+ trackId
4448
+ });
4449
+ }
4450
+ }
4451
+ return entries;
4452
+ };
4453
+ var getMetadataFromIsoBase = (isoBase) => {
4454
+ const moov = getMoovBox(isoBase.boxes);
4455
+ if (!moov) {
4456
+ return [];
4457
+ }
4458
+ const traks = getTraks(moov);
4459
+ const meta = moov.children.find((b) => b.type === "regular-box" && b.boxType === "meta");
4460
+ const udta = moov.children.find((b) => b.type === "regular-box" && b.boxType === "udta");
4461
+ const metaInUdta = udta?.children.find((b) => {
4462
+ return b.type === "regular-box" && b.boxType === "meta";
4463
+ });
4464
+ const metaInTracks = traks.map((t) => {
4465
+ const metaBox = t.children.find((child) => child.type === "regular-box" && child.boxType === "meta");
4466
+ if (metaBox) {
4467
+ const tkhd = getTkhdBox(t);
4468
+ if (!tkhd) {
4469
+ throw new Error("No tkhd box found");
4470
+ }
4471
+ return parseIsoMetaBox(metaBox, tkhd.trackId);
4472
+ }
4473
+ return null;
4474
+ }).filter(truthy);
4475
+ return [
4476
+ ...meta ? parseIsoMetaBox(meta, null) : [],
4477
+ ...metaInUdta ? parseIsoMetaBox(metaInUdta, null) : [],
4478
+ ...metaInTracks.flat(1)
4479
+ ];
4480
+ };
4481
+
4482
+ // src/metadata/metadata-from-matroska.ts
4483
+ var removeEndZeroes = (value) => {
4484
+ return value.endsWith("\x00") ? removeEndZeroes(value.slice(0, -1)) : value;
4485
+ };
4486
+ var parseSimpleTagIntoEbml = (children, trackId) => {
4487
+ const tagName = children.find((c) => c.type === "TagName");
4488
+ const tagString = children.find((c) => c.type === "TagString");
4489
+ if (!tagName || !tagString) {
4490
+ return null;
4491
+ }
4492
+ return {
4493
+ trackId,
4494
+ key: tagName.value.toLowerCase(),
4495
+ value: removeEndZeroes(tagString.value)
4496
+ };
4497
+ };
4498
+ var getMetadataFromMatroska = (structure) => {
4499
+ const entries = [];
4500
+ for (const segment of structure.boxes) {
4501
+ if (segment.type !== "Segment") {
4502
+ continue;
4503
+ }
4504
+ const tags2 = segment.value.filter((s) => s.type === "Tags");
4505
+ for (const tag of tags2) {
4506
+ for (const child of tag.value) {
4507
+ if (child.type !== "Tag") {
4508
+ continue;
4509
+ }
4510
+ let trackId = null;
4511
+ const target = child.value.find((c) => c.type === "Targets");
4512
+ if (target) {
4513
+ const tagTrackId = target.value.find((c) => c.type === "TagTrackUID")?.value;
4514
+ if (tagTrackId) {
4515
+ trackId = getTrackWithUid(segment, tagTrackId);
4516
+ }
4517
+ }
4518
+ const simpleTags = child.value.filter((s) => s.type === "SimpleTag");
4519
+ for (const simpleTag of simpleTags) {
4520
+ const parsed = parseSimpleTagIntoEbml(simpleTag.value, trackId);
4521
+ if (parsed) {
4522
+ entries.push(parsed);
4523
+ }
4524
+ }
4525
+ }
4526
+ }
4527
+ }
4528
+ return entries;
4529
+ };
4530
+
4531
+ // src/metadata/metadata-from-riff.ts
4532
+ var getMetadataFromRiff = (structure) => {
4533
+ const boxes = structure.boxes.find((b) => b.type === "list-box" && b.listType === "INFO");
4534
+ if (!boxes) {
4535
+ return [];
4536
+ }
4537
+ const { children } = boxes;
4538
+ return children.map((child) => {
4539
+ if (child.type !== "isft-box") {
4540
+ return null;
4541
+ }
4542
+ return {
4543
+ trackId: null,
4544
+ key: "encoder",
4545
+ value: child.software
4546
+ };
4547
+ }).filter(truthy);
4548
+ };
4549
+
4550
+ // src/metadata/get-metadata.ts
4551
+ var getMetadata = (structure) => {
4552
+ if (structure.type === "matroska") {
4553
+ return getMetadataFromMatroska(structure);
4554
+ }
4555
+ if (structure.type === "riff") {
4556
+ return getMetadataFromRiff(structure);
4557
+ }
4558
+ if (structure.type === "transport-stream") {
4559
+ return [];
4560
+ }
4561
+ if (structure.type === "mp3") {
4562
+ const tags2 = getMetadataFromMp3(structure);
4563
+ if (tags2 === null) {
4564
+ throw new Error("Failed to get metadata from mp3");
4565
+ }
4566
+ return tags2;
4567
+ }
4568
+ return getMetadataFromIsoBase(structure);
4569
+ };
4570
+ var hasMetadata = (structure) => {
4571
+ if (structure.type === "mp3") {
4572
+ return getMetadataFromMp3(structure) !== null;
4573
+ }
4574
+ return false;
4575
+ };
4576
+
4032
4577
  // src/has-all-info.ts
4033
4578
  var getAvailableInfo = ({
4034
4579
  fieldsToFetch,
@@ -4066,7 +4611,7 @@ var getAvailableInfo = ({
4066
4611
  return Boolean(structure && hasAudioCodec(structure, state));
4067
4612
  }
4068
4613
  if (key === "tracks") {
4069
- return Boolean(structure && hasTracks(structure, state));
4614
+ return Boolean(structure && getHasTracks(structure, state));
4070
4615
  }
4071
4616
  if (key === "keyframes") {
4072
4617
  return Boolean(structure && hasKeyframes(structure, state));
@@ -4086,33 +4631,122 @@ var getAvailableInfo = ({
4086
4631
  if (key === "container") {
4087
4632
  return Boolean(structure && hasContainer(structure));
4088
4633
  }
4089
- if (key === "metadata" || key === "location") {
4090
- return false;
4634
+ if (key === "metadata" || key === "location" || key === "images") {
4635
+ return Boolean(structure && hasMetadata(structure));
4091
4636
  }
4092
4637
  if (key === "slowKeyframes") {
4093
4638
  return false;
4094
4639
  }
4095
- if (key === "slowNumberOfFrames") {
4096
- return false;
4640
+ if (key === "slowNumberOfFrames") {
4641
+ return false;
4642
+ }
4643
+ throw new Error(`Unknown key: ${key}`);
4644
+ });
4645
+ const entries = [];
4646
+ let i = 0;
4647
+ for (const [key] of keys) {
4648
+ entries.push([key, infos[i++]]);
4649
+ }
4650
+ return Object.fromEntries(entries);
4651
+ };
4652
+ var hasAllInfo = ({
4653
+ fields,
4654
+ state
4655
+ }) => {
4656
+ const availableInfo = getAvailableInfo({
4657
+ fieldsToFetch: fields ?? {},
4658
+ state
4659
+ });
4660
+ return Object.values(availableInfo).every(Boolean) && (maySkipVideoData({ state }) || state.callbacks.canSkipTracksState.canSkipTracks());
4661
+ };
4662
+
4663
+ // src/continue-mdat-routine.ts
4664
+ var continueMdatRoutine = async ({
4665
+ iterator,
4666
+ maxBytes,
4667
+ allowIncompleteBoxes,
4668
+ initialBoxes,
4669
+ state,
4670
+ continueMdat,
4671
+ signal,
4672
+ logLevel,
4673
+ fields
4674
+ }) => {
4675
+ const initialOffset = iterator.counter.getOffset();
4676
+ while (iterator.bytesRemaining() > 0 && iterator.counter.getOffset() - initialOffset < maxBytes) {
4677
+ const result = await parseMdatPartially({
4678
+ iterator,
4679
+ boxSize: continueMdat.boxSize,
4680
+ fileOffset: continueMdat.fileOffset,
4681
+ parsedBoxes: initialBoxes,
4682
+ state,
4683
+ signal
4684
+ });
4685
+ if (result.type === "incomplete") {
4686
+ throw new Error("Incomplete boxes are not allowed in this routine");
4687
+ }
4688
+ if (result.type === "partial-mdat-box") {
4689
+ return {
4690
+ status: "incomplete",
4691
+ continueParsing: () => {
4692
+ return Promise.resolve(continueMdatRoutine({
4693
+ iterator,
4694
+ maxBytes,
4695
+ allowIncompleteBoxes,
4696
+ initialBoxes,
4697
+ state,
4698
+ continueMdat: result,
4699
+ signal,
4700
+ logLevel,
4701
+ fields
4702
+ }));
4703
+ },
4704
+ skipTo: null
4705
+ };
4706
+ }
4707
+ const alreadyHasMdat = state.structure.getStructureOrNull()?.boxes.find((b) => b.type === "mdat-box");
4708
+ if (result.box.type === "mdat-box" && alreadyHasMdat) {
4709
+ initialBoxes = initialBoxes.filter((b) => b.type !== "mdat-box");
4710
+ initialBoxes.push(result.box);
4711
+ iterator.allowDiscard();
4712
+ break;
4713
+ } else {
4714
+ initialBoxes.push(result.box);
4715
+ if (hasAllInfo({ fields, state })) {
4716
+ return {
4717
+ status: "done"
4718
+ };
4719
+ }
4097
4720
  }
4098
- throw new Error(`Unknown key: ${key}`);
4099
- });
4100
- const entries = [];
4101
- let i = 0;
4102
- for (const [key] of keys) {
4103
- entries.push([key, infos[i++]]);
4721
+ iterator.removeBytesRead();
4104
4722
  }
4105
- return Object.fromEntries(entries);
4106
- };
4107
- var hasAllInfo = ({
4108
- fields,
4109
- state
4110
- }) => {
4111
- const availableInfo = getAvailableInfo({
4112
- fieldsToFetch: fields ?? {},
4113
- state
4114
- });
4115
- return Object.values(availableInfo).every(Boolean) && (maySkipVideoData({ state }) || state.callbacks.canSkipTracksState.canSkipTracks());
4723
+ const mdatState = getMdatBox(initialBoxes);
4724
+ const skipped = mdatState?.status === "samples-skipped" && !maySkipVideoData({ state }) && state.supportsContentRange;
4725
+ const buffered = mdatState?.status === "samples-buffered" && !maySkipVideoData({ state });
4726
+ if (skipped || buffered) {
4727
+ return {
4728
+ status: "incomplete",
4729
+ continueParsing: () => {
4730
+ if (buffered) {
4731
+ iterator.skipTo(mdatState.fileOffset, false);
4732
+ }
4733
+ return parseIsoBaseMediaBoxes({
4734
+ iterator,
4735
+ maxBytes,
4736
+ allowIncompleteBoxes: false,
4737
+ initialBoxes,
4738
+ state,
4739
+ signal,
4740
+ logLevel,
4741
+ fields
4742
+ });
4743
+ },
4744
+ skipTo: skipped ? mdatState.fileOffset : null
4745
+ };
4746
+ }
4747
+ return {
4748
+ status: "done"
4749
+ };
4116
4750
  };
4117
4751
 
4118
4752
  // src/register-track.ts
@@ -4225,199 +4859,98 @@ var processDescriptor = ({
4225
4859
  upStream,
4226
4860
  avgBitrate,
4227
4861
  maxBitrate,
4228
- decoderSpecificConfigs
4229
- }
4230
- };
4231
- }
4232
- if (tag === 6) {
4233
- const size = iterator.getPaddedFourByteNumber();
4234
- iterator.discard(size);
4235
- return {
4236
- descriptor: {
4237
- type: "sl-config-descriptor"
4238
- }
4239
- };
4240
- }
4241
- return {
4242
- descriptor: null
4243
- };
4244
- };
4245
- var parseDescriptors = (iterator, maxBytes) => {
4246
- const descriptors = [];
4247
- const initialOffset = iterator.counter.getOffset();
4248
- while (iterator.bytesRemaining() > 0 && iterator.counter.getOffset() - initialOffset < maxBytes) {
4249
- const { descriptor } = processDescriptor({
4250
- iterator
4251
- });
4252
- if (descriptor) {
4253
- descriptors.push(descriptor);
4254
- } else {
4255
- break;
4256
- }
4257
- }
4258
- return descriptors;
4259
- };
4260
-
4261
- // src/boxes/iso-base-media/esds/esds.ts
4262
- var parseEsds = ({
4263
- data,
4264
- size,
4265
- fileOffset
4266
- }) => {
4267
- const version = data.getUint8();
4268
- data.discard(3);
4269
- const tag = data.getUint8();
4270
- const sizeOfInstance = data.getPaddedFourByteNumber();
4271
- const esId = data.getUint16();
4272
- data.discard(1);
4273
- const remaining = size - (data.counter.getOffset() - fileOffset);
4274
- const descriptors = parseDescriptors(data, remaining);
4275
- const remainingNow = size - (data.counter.getOffset() - fileOffset);
4276
- data.discard(remainingNow);
4277
- return {
4278
- type: "esds-box",
4279
- version,
4280
- tag,
4281
- sizeOfInstance,
4282
- esId,
4283
- descriptors
4284
- };
4285
- };
4286
-
4287
- // src/convert-audio-or-video-sample.ts
4288
- var convertAudioOrVideoSampleToWebCodecsTimestamps = (sample, timescale) => {
4289
- const { cts, dts, timestamp } = sample;
4290
- return {
4291
- cts: cts * 1e6 / timescale,
4292
- dts: dts * 1e6 / timescale,
4293
- timestamp: timestamp * 1e6 / timescale,
4294
- duration: sample.duration === undefined ? undefined : sample.duration * 1e6 / timescale,
4295
- data: sample.data,
4296
- trackId: sample.trackId,
4297
- type: sample.type,
4298
- offset: sample.offset,
4299
- timescale: 1e6
4300
- };
4301
- };
4302
-
4303
- // src/boxes/iso-base-media/mdat/mdat.ts
4304
- var parseMdat = async ({
4305
- data,
4306
- size,
4307
- fileOffset,
4308
- existingBoxes,
4309
- state,
4310
- signal,
4311
- maySkipSampleProcessing
4312
- }) => {
4313
- const alreadyHas = hasTracks({
4314
- type: "iso-base-media",
4315
- boxes: existingBoxes
4316
- }, state);
4317
- if (!alreadyHas) {
4318
- if (maySkipSampleProcessing) {
4319
- data.discard(size - (data.counter.getOffset() - fileOffset));
4320
- return Promise.resolve({
4321
- type: "mdat-box",
4322
- boxSize: size,
4323
- status: "samples-skipped",
4324
- fileOffset
4325
- });
4326
- }
4327
- data.discard(size - (data.counter.getOffset() - fileOffset));
4328
- data.disallowDiscard();
4329
- return Promise.resolve({
4330
- type: "mdat-box",
4331
- boxSize: size,
4332
- status: "samples-buffered",
4333
- fileOffset
4334
- });
4335
- }
4336
- const tracks2 = getTracks({ type: "iso-base-media", boxes: existingBoxes }, state);
4337
- const allTracks = [
4338
- ...tracks2.videoTracks,
4339
- ...tracks2.audioTracks,
4340
- ...tracks2.otherTracks
4341
- ];
4342
- const flatSamples = allTracks.map((track) => {
4343
- const samplePositions = getSamplePositionsFromTrack(track.trakBox, getMoofBox(existingBoxes));
4344
- if (!samplePositions) {
4345
- throw new Error("No sample positions");
4346
- }
4347
- return samplePositions.map((samplePosition) => {
4348
- return {
4349
- track: { ...track },
4350
- samplePosition
4351
- };
4352
- });
4353
- }).flat(1);
4354
- while (true) {
4355
- if (signal && signal.aborted) {
4356
- break;
4357
- }
4358
- const samplesWithIndex = flatSamples.find((sample) => {
4359
- return sample.samplePosition.offset === data.counter.getOffset();
4360
- });
4361
- if (!samplesWithIndex) {
4362
- const nextSample_ = flatSamples.filter((s) => s.samplePosition.offset > data.counter.getOffset()).sort((a, b) => a.samplePosition.offset - b.samplePosition.offset)[0];
4363
- if (nextSample_) {
4364
- data.discard(nextSample_.samplePosition.offset - data.counter.getOffset());
4365
- continue;
4366
- } else {
4367
- const bytesRemaining = size + fileOffset - data.counter.getOffset();
4368
- data.discard(bytesRemaining);
4369
- break;
4370
- }
4371
- }
4372
- if (data.bytesRemaining() < samplesWithIndex.samplePosition.size) {
4373
- break;
4374
- }
4375
- const bytes = data.getSlice(samplesWithIndex.samplePosition.size);
4376
- const { cts, dts, duration: duration2, isKeyframe, offset } = samplesWithIndex.samplePosition;
4377
- if (samplesWithIndex.track.type === "audio") {
4378
- await state.callbacks.onAudioSample(samplesWithIndex.track.trackId, convertAudioOrVideoSampleToWebCodecsTimestamps({
4379
- data: bytes,
4380
- timestamp: cts,
4381
- duration: duration2,
4382
- cts,
4383
- dts,
4384
- trackId: samplesWithIndex.track.trackId,
4385
- type: isKeyframe ? "key" : "delta",
4386
- offset,
4387
- timescale: samplesWithIndex.track.timescale
4388
- }, samplesWithIndex.track.timescale));
4389
- }
4390
- if (samplesWithIndex.track.type === "video") {
4391
- const nalUnitType = bytes[4] & 31;
4392
- let isRecoveryPoint = false;
4393
- if (nalUnitType === 6) {
4394
- const seiType = bytes[5];
4395
- isRecoveryPoint = seiType === 6;
4396
- }
4397
- await state.callbacks.onVideoSample(samplesWithIndex.track.trackId, convertAudioOrVideoSampleToWebCodecsTimestamps({
4398
- data: bytes,
4399
- timestamp: cts,
4400
- duration: duration2,
4401
- cts,
4402
- dts,
4403
- trackId: samplesWithIndex.track.trackId,
4404
- type: isKeyframe && !isRecoveryPoint ? "key" : "delta",
4405
- offset,
4406
- timescale: samplesWithIndex.track.timescale
4407
- }, samplesWithIndex.track.timescale));
4408
- }
4409
- const remaining = size - (data.counter.getOffset() - fileOffset);
4410
- data.removeBytesRead();
4411
- if (remaining === 0) {
4862
+ decoderSpecificConfigs
4863
+ }
4864
+ };
4865
+ }
4866
+ if (tag === 6) {
4867
+ const size = iterator.getPaddedFourByteNumber();
4868
+ iterator.discard(size);
4869
+ return {
4870
+ descriptor: {
4871
+ type: "sl-config-descriptor"
4872
+ }
4873
+ };
4874
+ }
4875
+ return {
4876
+ descriptor: null
4877
+ };
4878
+ };
4879
+ var parseDescriptors = (iterator, maxBytes) => {
4880
+ const descriptors = [];
4881
+ const initialOffset = iterator.counter.getOffset();
4882
+ while (iterator.bytesRemaining() > 0 && iterator.counter.getOffset() - initialOffset < maxBytes) {
4883
+ const { descriptor } = processDescriptor({
4884
+ iterator
4885
+ });
4886
+ if (descriptor) {
4887
+ descriptors.push(descriptor);
4888
+ } else {
4412
4889
  break;
4413
4890
  }
4414
4891
  }
4415
- return Promise.resolve({
4416
- type: "mdat-box",
4417
- boxSize: size,
4418
- status: "samples-processed",
4419
- fileOffset
4420
- });
4892
+ return descriptors;
4893
+ };
4894
+
4895
+ // src/boxes/iso-base-media/esds/esds.ts
4896
+ var parseEsds = ({
4897
+ data,
4898
+ size,
4899
+ fileOffset
4900
+ }) => {
4901
+ const version = data.getUint8();
4902
+ data.discard(3);
4903
+ const tag = data.getUint8();
4904
+ const sizeOfInstance = data.getPaddedFourByteNumber();
4905
+ const esId = data.getUint16();
4906
+ data.discard(1);
4907
+ const remaining = size - (data.counter.getOffset() - fileOffset);
4908
+ const descriptors = parseDescriptors(data, remaining);
4909
+ const remainingNow = size - (data.counter.getOffset() - fileOffset);
4910
+ data.discard(remainingNow);
4911
+ return {
4912
+ type: "esds-box",
4913
+ version,
4914
+ tag,
4915
+ sizeOfInstance,
4916
+ esId,
4917
+ descriptors
4918
+ };
4919
+ };
4920
+
4921
+ // src/boxes/iso-base-media/get-children.ts
4922
+ var getChildren = async ({
4923
+ boxType,
4924
+ iterator,
4925
+ bytesRemainingInBox,
4926
+ state,
4927
+ signal,
4928
+ logLevel,
4929
+ fields
4930
+ }) => {
4931
+ const parseChildren = boxType === "mdia" || boxType === "minf" || boxType === "stbl" || boxType === "udta" || boxType === "moof" || boxType === "dims" || boxType === "meta" || boxType === "wave" || boxType === "traf" || boxType === "stsb";
4932
+ if (parseChildren) {
4933
+ const boxes = [];
4934
+ const parsed = await parseIsoBaseMediaBoxes({
4935
+ iterator,
4936
+ maxBytes: bytesRemainingInBox,
4937
+ allowIncompleteBoxes: false,
4938
+ initialBoxes: boxes,
4939
+ state,
4940
+ signal,
4941
+ logLevel,
4942
+ fields
4943
+ });
4944
+ if (parsed.status === "incomplete") {
4945
+ throw new Error("Incomplete boxes are not allowed");
4946
+ }
4947
+ return boxes;
4948
+ }
4949
+ if (bytesRemainingInBox < 0) {
4950
+ throw new Error("Box size is too big " + JSON.stringify({ boxType }));
4951
+ }
4952
+ iterator.discard(bytesRemainingInBox);
4953
+ return [];
4421
4954
  };
4422
4955
 
4423
4956
  // src/boxes/iso-base-media/mdhd.ts
@@ -4580,7 +5113,6 @@ var parseMoov = async ({
4580
5113
  allowIncompleteBoxes: false,
4581
5114
  initialBoxes: boxes,
4582
5115
  state,
4583
- continueMdat: false,
4584
5116
  signal,
4585
5117
  logLevel,
4586
5118
  fields
@@ -4882,7 +5414,6 @@ var parseMebx = async ({
4882
5414
  allowIncompleteBoxes: false,
4883
5415
  initialBoxes: boxes,
4884
5416
  state,
4885
- continueMdat: false,
4886
5417
  signal,
4887
5418
  logLevel: "info",
4888
5419
  fields
@@ -5291,7 +5822,6 @@ var parseTrak = async ({
5291
5822
  allowIncompleteBoxes: false,
5292
5823
  initialBoxes,
5293
5824
  state: options,
5294
- continueMdat: false,
5295
5825
  signal,
5296
5826
  logLevel,
5297
5827
  fields
@@ -5350,71 +5880,6 @@ var parseTrun = ({
5350
5880
  };
5351
5881
 
5352
5882
  // src/boxes/iso-base-media/process-box.ts
5353
- var getChildren = async ({
5354
- boxType,
5355
- iterator,
5356
- bytesRemainingInBox,
5357
- state,
5358
- signal,
5359
- logLevel,
5360
- fields
5361
- }) => {
5362
- const parseChildren = boxType === "mdia" || boxType === "minf" || boxType === "stbl" || boxType === "udta" || boxType === "moof" || boxType === "dims" || boxType === "meta" || boxType === "wave" || boxType === "traf" || boxType === "stsb";
5363
- if (parseChildren) {
5364
- const boxes = [];
5365
- const parsed = await parseIsoBaseMediaBoxes({
5366
- iterator,
5367
- maxBytes: bytesRemainingInBox,
5368
- allowIncompleteBoxes: false,
5369
- initialBoxes: boxes,
5370
- state,
5371
- continueMdat: false,
5372
- signal,
5373
- logLevel,
5374
- fields
5375
- });
5376
- if (parsed.status === "incomplete") {
5377
- throw new Error("Incomplete boxes are not allowed");
5378
- }
5379
- return boxes;
5380
- }
5381
- if (bytesRemainingInBox < 0) {
5382
- throw new Error("Box size is too big " + JSON.stringify({ boxType }));
5383
- }
5384
- iterator.discard(bytesRemainingInBox);
5385
- return [];
5386
- };
5387
- var parseMdatPartially = async ({
5388
- iterator,
5389
- boxSize,
5390
- fileOffset,
5391
- parsedBoxes,
5392
- state,
5393
- signal
5394
- }) => {
5395
- const box = await parseMdat({
5396
- data: iterator,
5397
- size: boxSize,
5398
- fileOffset,
5399
- existingBoxes: parsedBoxes,
5400
- state,
5401
- signal,
5402
- maySkipSampleProcessing: state.supportsContentRange
5403
- });
5404
- if ((box.status === "samples-processed" || box.status === "samples-buffered") && box.fileOffset + boxSize === iterator.counter.getOffset()) {
5405
- return {
5406
- type: "complete",
5407
- box,
5408
- size: boxSize,
5409
- skipTo: null
5410
- };
5411
- }
5412
- return {
5413
- type: "partial-mdat-box",
5414
- boxSize,
5415
- fileOffset
5416
- };
5417
- };
5418
5883
  var processBox = async ({
5419
5884
  iterator,
5420
5885
  allowIncompleteBoxes,
@@ -5427,15 +5892,6 @@ var processBox = async ({
5427
5892
  const fileOffset = iterator.counter.getOffset();
5428
5893
  const bytesRemaining = iterator.bytesRemaining();
5429
5894
  const boxSizeRaw = iterator.getFourByteNumber();
5430
- if (boxSizeRaw === 1 && iterator.bytesRemaining() < 12 || iterator.bytesRemaining() < 4) {
5431
- iterator.counter.decrement(iterator.counter.getOffset() - fileOffset);
5432
- if (allowIncompleteBoxes) {
5433
- return {
5434
- type: "incomplete"
5435
- };
5436
- }
5437
- throw new Error(`Expected box size of ${bytesRemaining}, got ${boxSizeRaw}. Incomplete boxes are not allowed.`);
5438
- }
5439
5895
  if (boxSizeRaw === 0) {
5440
5896
  return {
5441
5897
  type: "complete",
@@ -5447,45 +5903,54 @@ var processBox = async ({
5447
5903
  skipTo: null
5448
5904
  };
5449
5905
  }
5906
+ if (boxSizeRaw === 1 && iterator.bytesRemaining() < 12 || iterator.bytesRemaining() < 4) {
5907
+ iterator.counter.decrement(iterator.counter.getOffset() - fileOffset);
5908
+ if (!allowIncompleteBoxes) {
5909
+ throw new Error(`Expected box size of ${bytesRemaining}, got ${boxSizeRaw}. Incomplete boxes are not allowed.`);
5910
+ }
5911
+ return {
5912
+ type: "incomplete"
5913
+ };
5914
+ }
5450
5915
  const boxType = iterator.getByteString(4, false);
5451
5916
  const boxSize = boxSizeRaw === 1 ? iterator.getEightByteNumber() : boxSizeRaw;
5452
5917
  if (bytesRemaining < boxSize) {
5453
- if (boxType === "mdat") {
5454
- const shouldSkip = maySkipVideoData({ state }) || !hasTracks({ type: "iso-base-media", boxes: parsedBoxes }, state) && state.supportsContentRange;
5455
- if (shouldSkip) {
5456
- const skipTo = fileOffset + boxSize;
5457
- const bytesToSkip = skipTo - iterator.counter.getOffset();
5458
- if (bytesToSkip > 1e6) {
5459
- return {
5460
- type: "complete",
5461
- box: {
5462
- type: "mdat-box",
5463
- boxSize,
5464
- fileOffset,
5465
- status: "samples-skipped"
5466
- },
5467
- size: boxSize,
5468
- skipTo: fileOffset + boxSize
5469
- };
5470
- }
5471
- } else {
5472
- return parseMdatPartially({
5473
- iterator,
5474
- boxSize,
5475
- fileOffset,
5476
- parsedBoxes,
5477
- state,
5478
- signal
5479
- });
5918
+ if (boxType !== "mdat") {
5919
+ iterator.counter.decrement(iterator.counter.getOffset() - fileOffset);
5920
+ if (!allowIncompleteBoxes) {
5921
+ throw new Error(`Expected box size of ${bytesRemaining}, got ${boxSize}. Incomplete boxes are not allowed.`);
5480
5922
  }
5481
- }
5482
- iterator.counter.decrement(iterator.counter.getOffset() - fileOffset);
5483
- if (allowIncompleteBoxes) {
5484
5923
  return {
5485
5924
  type: "incomplete"
5486
5925
  };
5487
5926
  }
5488
- throw new Error(`Expected box size of ${bytesRemaining}, got ${boxSize}. Incomplete boxes are not allowed.`);
5927
+ const shouldSkip = maySkipVideoData({ state }) || !getHasTracks({ type: "iso-base-media", boxes: parsedBoxes }, state) && state.supportsContentRange;
5928
+ if (shouldSkip) {
5929
+ const skipTo = fileOffset + boxSize;
5930
+ const bytesToSkip = skipTo - iterator.counter.getOffset();
5931
+ if (bytesToSkip > 1e6) {
5932
+ return {
5933
+ type: "complete",
5934
+ box: {
5935
+ type: "mdat-box",
5936
+ boxSize,
5937
+ fileOffset,
5938
+ status: "samples-skipped"
5939
+ },
5940
+ size: boxSize,
5941
+ skipTo: fileOffset + boxSize
5942
+ };
5943
+ }
5944
+ } else {
5945
+ return parseMdatPartially({
5946
+ iterator,
5947
+ boxSize,
5948
+ fileOffset,
5949
+ parsedBoxes,
5950
+ state,
5951
+ signal
5952
+ });
5953
+ }
5489
5954
  }
5490
5955
  if (boxType === "ftyp") {
5491
5956
  const box = parseFtyp({ iterator, size: boxSize, offset: fileOffset });
@@ -5828,8 +6293,8 @@ var processBox = async ({
5828
6293
  signal,
5829
6294
  maySkipSampleProcessing: state.supportsContentRange
5830
6295
  });
5831
- if (box === null) {
5832
- throw new Error("Unexpected null");
6296
+ if (box.type === "partial-mdat-box") {
6297
+ return box;
5833
6298
  }
5834
6299
  return {
5835
6300
  type: "complete",
@@ -5861,28 +6326,34 @@ var processBox = async ({
5861
6326
  skipTo: null
5862
6327
  };
5863
6328
  };
6329
+
6330
+ // src/boxes/iso-base-media/parse-boxes.ts
5864
6331
  var parseIsoBaseMediaBoxes = async ({
5865
6332
  iterator,
5866
6333
  maxBytes,
5867
6334
  allowIncompleteBoxes,
5868
6335
  initialBoxes,
5869
6336
  state,
5870
- continueMdat,
5871
6337
  signal,
5872
6338
  logLevel,
5873
6339
  fields
5874
6340
  }) => {
5875
6341
  const initialOffset = iterator.counter.getOffset();
5876
- const alreadyHasMdat = state.structure.getStructureOrNull()?.boxes.find((b) => b.type === "mdat-box");
5877
- while (iterator.bytesRemaining() > 0 && iterator.counter.getOffset() - initialOffset < maxBytes) {
5878
- const result = continueMdat ? await parseMdatPartially({
6342
+ const continueParsing = () => {
6343
+ return parseIsoBaseMediaBoxes({
5879
6344
  iterator,
5880
- boxSize: continueMdat.boxSize,
5881
- fileOffset: continueMdat.fileOffset,
5882
- parsedBoxes: initialBoxes,
6345
+ maxBytes,
6346
+ allowIncompleteBoxes,
6347
+ initialBoxes,
5883
6348
  state,
5884
- signal
5885
- }) : await processBox({
6349
+ signal,
6350
+ logLevel,
6351
+ fields
6352
+ });
6353
+ };
6354
+ const alreadyHasMdat = state.structure.getStructureOrNull()?.boxes.find((b) => b.type === "mdat-box");
6355
+ while (iterator.bytesRemaining() > 0 && iterator.counter.getOffset() - initialOffset < maxBytes) {
6356
+ const result = await processBox({
5886
6357
  iterator,
5887
6358
  allowIncompleteBoxes,
5888
6359
  parsedBoxes: initialBoxes,
@@ -5897,19 +6368,7 @@ var parseIsoBaseMediaBoxes = async ({
5897
6368
  }
5898
6369
  return {
5899
6370
  status: "incomplete",
5900
- continueParsing: () => {
5901
- return parseIsoBaseMediaBoxes({
5902
- iterator,
5903
- maxBytes,
5904
- allowIncompleteBoxes,
5905
- initialBoxes,
5906
- state,
5907
- continueMdat: false,
5908
- signal,
5909
- logLevel,
5910
- fields
5911
- });
5912
- },
6371
+ continueParsing,
5913
6372
  skipTo: null
5914
6373
  };
5915
6374
  }
@@ -5917,7 +6376,7 @@ var parseIsoBaseMediaBoxes = async ({
5917
6376
  return {
5918
6377
  status: "incomplete",
5919
6378
  continueParsing: () => {
5920
- return Promise.resolve(parseIsoBaseMediaBoxes({
6379
+ return Promise.resolve(continueMdatRoutine({
5921
6380
  iterator,
5922
6381
  maxBytes,
5923
6382
  allowIncompleteBoxes,
@@ -5951,38 +6410,14 @@ var parseIsoBaseMediaBoxes = async ({
5951
6410
  }
5952
6411
  return {
5953
6412
  status: "incomplete",
5954
- continueParsing: () => {
5955
- return parseIsoBaseMediaBoxes({
5956
- iterator,
5957
- maxBytes,
5958
- allowIncompleteBoxes,
5959
- initialBoxes,
5960
- state,
5961
- continueMdat: false,
5962
- signal,
5963
- logLevel,
5964
- fields
5965
- });
5966
- },
6413
+ continueParsing,
5967
6414
  skipTo: result.skipTo
5968
6415
  };
5969
6416
  }
5970
6417
  if (iterator.bytesRemaining() < 0) {
5971
6418
  return {
5972
6419
  status: "incomplete",
5973
- continueParsing: () => {
5974
- return parseIsoBaseMediaBoxes({
5975
- iterator,
5976
- maxBytes,
5977
- allowIncompleteBoxes,
5978
- initialBoxes,
5979
- state,
5980
- continueMdat: false,
5981
- signal,
5982
- logLevel,
5983
- fields
5984
- });
5985
- },
6420
+ continueParsing,
5986
6421
  skipTo: null
5987
6422
  };
5988
6423
  }
@@ -6004,7 +6439,6 @@ var parseIsoBaseMediaBoxes = async ({
6004
6439
  allowIncompleteBoxes: false,
6005
6440
  initialBoxes,
6006
6441
  state,
6007
- continueMdat: false,
6008
6442
  signal,
6009
6443
  logLevel,
6010
6444
  fields
@@ -6126,7 +6560,6 @@ var processSample = async ({
6126
6560
  maxBytes: bytesRemainingInBox,
6127
6561
  initialBoxes,
6128
6562
  state: options,
6129
- continueMdat: false,
6130
6563
  signal,
6131
6564
  logLevel,
6132
6565
  fields
@@ -6175,7 +6608,6 @@ var processSample = async ({
6175
6608
  maxBytes: bytesRemainingInBox,
6176
6609
  initialBoxes,
6177
6610
  state: options,
6178
- continueMdat: false,
6179
6611
  signal,
6180
6612
  logLevel,
6181
6613
  fields
@@ -6227,7 +6659,6 @@ var processSample = async ({
6227
6659
  maxBytes: bytesRemainingInBox,
6228
6660
  initialBoxes: [],
6229
6661
  state: options,
6230
- continueMdat: false,
6231
6662
  signal,
6232
6663
  logLevel,
6233
6664
  fields
@@ -6284,7 +6715,6 @@ var processSample = async ({
6284
6715
  maxBytes: bytesRemainingInBox,
6285
6716
  initialBoxes,
6286
6717
  state: options,
6287
- continueMdat: false,
6288
6718
  signal,
6289
6719
  logLevel,
6290
6720
  fields
@@ -6670,11 +7100,24 @@ var emittedState = () => {
6670
7100
  slowFps: false,
6671
7101
  slowKeyframes: false,
6672
7102
  slowNumberOfFrames: false,
6673
- keyframes: false
7103
+ keyframes: false,
7104
+ images: false
6674
7105
  };
6675
7106
  return emittedFields;
6676
7107
  };
6677
7108
 
7109
+ // src/state/images.ts
7110
+ var imagesState = () => {
7111
+ const images = [];
7112
+ const addImage = (image) => {
7113
+ images.push(image);
7114
+ };
7115
+ return {
7116
+ images,
7117
+ addImage
7118
+ };
7119
+ };
7120
+
6678
7121
  // src/state/keyframes.ts
6679
7122
  var keyframesState = () => {
6680
7123
  const keyframes = [];
@@ -6688,6 +7131,17 @@ var keyframesState = () => {
6688
7131
  };
6689
7132
  };
6690
7133
 
7134
+ // src/state/mp3.ts
7135
+ var makeMp3State = () => {
7136
+ let mp3Info = null;
7137
+ return {
7138
+ getMp3Info: () => mp3Info,
7139
+ setMp3Info: (info) => {
7140
+ mp3Info = info;
7141
+ }
7142
+ };
7143
+ };
7144
+
6691
7145
  // src/state/riff.ts
6692
7146
  var riffSpecificState = () => {
6693
7147
  let avcProfile = null;
@@ -6741,7 +7195,8 @@ var needsTracksField = {
6741
7195
  mimeType: false,
6742
7196
  slowKeyframes: true,
6743
7197
  slowNumberOfFrames: true,
6744
- keyframes: true
7198
+ keyframes: true,
7199
+ images: true
6745
7200
  };
6746
7201
  var makeCanSkipTracksState = ({
6747
7202
  hasAudioTrackHandlers,
@@ -6831,6 +7286,9 @@ var sampleCallback = ({
6831
7286
  await callback(audioSample);
6832
7287
  }
6833
7288
  }
7289
+ if (needsToIterateOverSamples({ emittedFields, fields })) {
7290
+ slowDurationAndFpsState.addAudioSample(audioSample);
7291
+ }
6834
7292
  },
6835
7293
  getSamplesForTrack: (trackId) => {
6836
7294
  return samplesForTrack[trackId] ?? 0;
@@ -6862,7 +7320,7 @@ var sampleCallback = ({
6862
7320
  sizeInBytes: videoSample.data.length
6863
7321
  });
6864
7322
  }
6865
- slowDurationAndFpsState.addSample(videoSample);
7323
+ slowDurationAndFpsState.addVideoSample(videoSample);
6866
7324
  }
6867
7325
  },
6868
7326
  canSkipTracksState,
@@ -6885,33 +7343,64 @@ var sampleCallback = ({
6885
7343
 
6886
7344
  // src/state/slow-duration-fps.ts
6887
7345
  var slowDurationAndFpsState = () => {
6888
- let smallestSample;
6889
- let largestSample;
6890
- let samples = 0;
7346
+ let smallestVideoSample;
7347
+ let largestVideoSample;
7348
+ let smallestAudioSample;
7349
+ let largestAudioSample;
7350
+ let videoSamples = 0;
7351
+ let audioSamples = 0;
7352
+ const getSlowVideoDurationInSeconds = () => {
7353
+ let videoDuration = null;
7354
+ if (smallestVideoSample !== undefined && largestVideoSample !== undefined) {
7355
+ const startingTimestampDifference = largestVideoSample - smallestVideoSample;
7356
+ const timeBetweenSamples = startingTimestampDifference / (videoSamples - 1);
7357
+ videoDuration = timeBetweenSamples * videoSamples;
7358
+ }
7359
+ return videoDuration;
7360
+ };
6891
7361
  const getSlowDurationInSeconds = () => {
6892
- if (smallestSample !== undefined && largestSample !== undefined) {
6893
- const startingTimestampDifference = largestSample - smallestSample;
6894
- const timeBetweenSamples = startingTimestampDifference / (samples - 1);
6895
- return timeBetweenSamples * samples;
7362
+ const videoDuration = getSlowVideoDurationInSeconds();
7363
+ let audioDuration = null;
7364
+ if (smallestAudioSample !== undefined && largestAudioSample !== undefined) {
7365
+ const startingTimestampDifferenceAudio = largestAudioSample - smallestAudioSample;
7366
+ const timeBetweenSamplesAudio = startingTimestampDifferenceAudio / (audioSamples - 1);
7367
+ audioDuration = timeBetweenSamplesAudio * audioSamples;
6896
7368
  }
6897
- throw new Error("No samples");
7369
+ if (videoDuration === null && audioDuration === null) {
7370
+ throw new Error("No samples");
7371
+ }
7372
+ return Math.max(videoDuration ?? 0, audioDuration ?? 0);
6898
7373
  };
6899
7374
  return {
6900
- addSample: (videoSample) => {
6901
- samples++;
7375
+ addVideoSample: (videoSample) => {
7376
+ videoSamples++;
6902
7377
  const presentationTimeInSeconds = videoSample.cts / videoSample.timescale;
6903
- if (largestSample === undefined || presentationTimeInSeconds > largestSample) {
6904
- largestSample = presentationTimeInSeconds;
7378
+ if (largestVideoSample === undefined || presentationTimeInSeconds > largestVideoSample) {
7379
+ largestVideoSample = presentationTimeInSeconds;
7380
+ }
7381
+ if (smallestVideoSample === undefined || presentationTimeInSeconds < smallestVideoSample) {
7382
+ smallestVideoSample = presentationTimeInSeconds;
6905
7383
  }
6906
- if (smallestSample === undefined || presentationTimeInSeconds < smallestSample) {
6907
- smallestSample = presentationTimeInSeconds;
7384
+ },
7385
+ addAudioSample: (audioSample) => {
7386
+ audioSamples++;
7387
+ const presentationTimeInSeconds = audioSample.cts / audioSample.timescale;
7388
+ if (largestAudioSample === undefined || presentationTimeInSeconds > largestAudioSample) {
7389
+ largestAudioSample = presentationTimeInSeconds;
7390
+ }
7391
+ if (smallestAudioSample === undefined || presentationTimeInSeconds < smallestAudioSample) {
7392
+ smallestAudioSample = presentationTimeInSeconds;
6908
7393
  }
6909
7394
  },
6910
7395
  getSlowDurationInSeconds,
6911
7396
  getFps: () => {
6912
- return samples / getSlowDurationInSeconds();
7397
+ const videoDuration = getSlowVideoDurationInSeconds() ?? 0;
7398
+ if (videoDuration === 0) {
7399
+ return 0;
7400
+ }
7401
+ return videoSamples / videoDuration;
6913
7402
  },
6914
- getSlowNumberOfFrames: () => samples
7403
+ getSlowNumberOfFrames: () => videoSamples
6915
7404
  };
6916
7405
  };
6917
7406
 
@@ -7002,7 +7491,8 @@ var makeParserState = ({
7002
7491
  fields,
7003
7492
  onAudioTrack,
7004
7493
  onVideoTrack,
7005
- supportsContentRange
7494
+ supportsContentRange,
7495
+ contentLength
7006
7496
  }) => {
7007
7497
  let skippedBytes = 0;
7008
7498
  const increaseSkippedBytes = (bytes) => {
@@ -7012,6 +7502,8 @@ var makeParserState = ({
7012
7502
  const keyframes = keyframesState();
7013
7503
  const emittedFields = emittedState();
7014
7504
  const slowDurationAndFps = slowDurationAndFpsState();
7505
+ const mp3Info = makeMp3State();
7506
+ const images = imagesState();
7015
7507
  return {
7016
7508
  riff: riffSpecificState(),
7017
7509
  callbacks: sampleCallback({
@@ -7037,7 +7529,10 @@ var makeParserState = ({
7037
7529
  webm: webmState(),
7038
7530
  emittedFields,
7039
7531
  fields,
7040
- slowDurationAndFps
7532
+ slowDurationAndFps,
7533
+ mp3Info,
7534
+ contentLength,
7535
+ images
7041
7536
  };
7042
7537
  };
7043
7538
 
@@ -7395,299 +7890,108 @@ class IsAGifError extends Error {
7395
7890
  fileName
7396
7891
  }) {
7397
7892
  super(message);
7398
- this.fileName = "IsAGifError";
7399
- this.mimeType = mimeType;
7400
- this.sizeInBytes = sizeInBytes;
7401
- this.fileName = fileName;
7402
- if (Error.captureStackTrace) {
7403
- Error.captureStackTrace(this, IsAGifError);
7404
- }
7405
- }
7406
- }
7407
-
7408
- class IsAnImageError extends Error {
7409
- imageType;
7410
- dimensions;
7411
- mimeType;
7412
- sizeInBytes;
7413
- fileName;
7414
- constructor({
7415
- dimensions,
7416
- imageType,
7417
- message,
7418
- mimeType,
7419
- sizeInBytes,
7420
- fileName
7421
- }) {
7422
- super(message);
7423
- this.name = "IsAnImageError";
7424
- this.imageType = imageType;
7425
- this.dimensions = dimensions;
7426
- this.mimeType = mimeType;
7427
- this.sizeInBytes = sizeInBytes;
7428
- this.fileName = fileName;
7429
- if (Error.captureStackTrace) {
7430
- Error.captureStackTrace(this, IsAnImageError);
7431
- }
7432
- }
7433
- }
7434
-
7435
- class IsAPdfError extends Error {
7436
- mimeType;
7437
- sizeInBytes;
7438
- fileName;
7439
- constructor({
7440
- message,
7441
- mimeType,
7442
- sizeInBytes,
7443
- fileName
7444
- }) {
7445
- super(message);
7446
- this.name = "IsAPdfError";
7447
- this.mimeType = mimeType;
7448
- this.sizeInBytes = sizeInBytes;
7449
- this.fileName = fileName;
7450
- if (Error.captureStackTrace) {
7451
- Error.captureStackTrace(this, IsAPdfError);
7452
- }
7453
- }
7454
- }
7455
-
7456
- class IsAnUnsupportedFileTypeError extends Error {
7457
- mimeType;
7458
- sizeInBytes;
7459
- fileName;
7460
- constructor({
7461
- message,
7462
- mimeType,
7463
- sizeInBytes,
7464
- fileName
7465
- }) {
7466
- super(message);
7467
- this.name = "IsAnUnsupportedFileTypeError";
7468
- this.mimeType = mimeType;
7469
- this.sizeInBytes = sizeInBytes;
7470
- this.fileName = fileName;
7471
- if (Error.captureStackTrace) {
7472
- Error.captureStackTrace(this, IsAnUnsupportedFileTypeError);
7473
- }
7474
- }
7475
- }
7476
-
7477
- class IsAnUnsupportedAudioTypeError extends Error {
7478
- mimeType;
7479
- sizeInBytes;
7480
- fileName;
7481
- audioType;
7482
- constructor({
7483
- message,
7484
- mimeType,
7485
- sizeInBytes,
7486
- fileName,
7487
- audioType
7488
- }) {
7489
- super(message);
7490
- this.name = "IsAnUnsupportedAudioTypeError";
7491
- this.mimeType = mimeType;
7492
- this.sizeInBytes = sizeInBytes;
7493
- this.fileName = fileName;
7494
- this.audioType = audioType;
7495
- if (Error.captureStackTrace) {
7496
- Error.captureStackTrace(this, IsAnUnsupportedAudioTypeError);
7497
- }
7498
- }
7499
- }
7500
- // src/metadata/metadata-from-iso.ts
7501
- var mapToKey = (index) => {
7502
- if (index === 2839630420) {
7503
- return "artist";
7504
- }
7505
- if (index === 2841734242) {
7506
- return "album";
7507
- }
7508
- if (index === 2841865588) {
7509
- return "comment";
7510
- }
7511
- if (index === 2841928057) {
7512
- return "releaseDate";
7513
- }
7514
- if (index === 2842125678) {
7515
- return "genre";
7516
- }
7517
- if (index === 2842583405) {
7518
- return "title";
7519
- }
7520
- if (index === 2842980207) {
7521
- return "encoder";
7522
- }
7523
- if (index === 2843177588) {
7524
- return "writer";
7525
- }
7526
- if (index === 2841866361) {
7527
- return "copyright";
7528
- }
7529
- if (index === 2841930098) {
7530
- return "director";
7531
- }
7532
- if (index === 2842718820) {
7533
- return "producer";
7534
- }
7535
- if (index === 2841929075) {
7536
- return "description";
7537
- }
7538
- return null;
7539
- };
7540
- var parseIlstBoxWithoutKeys = (ilstBox) => {
7541
- return ilstBox.entries.map((entry) => {
7542
- const key = mapToKey(entry.index);
7543
- if (!key) {
7544
- return null;
7545
- }
7546
- if (entry.value.type === "unknown") {
7547
- return null;
7548
- }
7549
- return {
7550
- trackId: null,
7551
- key,
7552
- value: entry.value.value
7553
- };
7554
- }).filter(truthy);
7555
- };
7556
- var parseIsoMetaBox = (meta, trackId) => {
7557
- const ilstBox = meta.children.find((b) => b.type === "ilst-box");
7558
- const keysBox = meta.children.find((b) => b.type === "keys-box");
7559
- if (!ilstBox || !keysBox) {
7560
- if (ilstBox) {
7561
- return parseIlstBoxWithoutKeys(ilstBox);
7562
- }
7563
- return [];
7564
- }
7565
- const entries = [];
7566
- for (let i = 0;i < ilstBox.entries.length; i++) {
7567
- const ilstEntry = ilstBox.entries[i];
7568
- const keysEntry = keysBox.entries[i];
7569
- if (ilstEntry.value.type !== "unknown") {
7570
- const value = typeof ilstEntry.value.value === "string" && ilstEntry.value.value.endsWith("\x00") ? ilstEntry.value.value.slice(0, -1) : ilstEntry.value.value;
7571
- entries.push({
7572
- key: keysEntry.value,
7573
- value,
7574
- trackId
7575
- });
7576
- }
7577
- }
7578
- return entries;
7579
- };
7580
- var getMetadataFromIsoBase = (isoBase) => {
7581
- const moov = getMoovBox(isoBase.boxes);
7582
- if (!moov) {
7583
- return [];
7584
- }
7585
- const traks = getTraks(moov);
7586
- const meta = moov.children.find((b) => b.type === "regular-box" && b.boxType === "meta");
7587
- const udta = moov.children.find((b) => b.type === "regular-box" && b.boxType === "udta");
7588
- const metaInUdta = udta?.children.find((b) => {
7589
- return b.type === "regular-box" && b.boxType === "meta";
7590
- });
7591
- const metaInTracks = traks.map((t) => {
7592
- const metaBox = t.children.find((child) => child.type === "regular-box" && child.boxType === "meta");
7593
- if (metaBox) {
7594
- const tkhd = getTkhdBox(t);
7595
- if (!tkhd) {
7596
- throw new Error("No tkhd box found");
7597
- }
7598
- return parseIsoMetaBox(metaBox, tkhd.trackId);
7599
- }
7600
- return null;
7601
- }).filter(truthy);
7602
- return [
7603
- ...meta ? parseIsoMetaBox(meta, null) : [],
7604
- ...metaInUdta ? parseIsoMetaBox(metaInUdta, null) : [],
7605
- ...metaInTracks.flat(1)
7606
- ];
7607
- };
7608
-
7609
- // src/metadata/metadata-from-matroska.ts
7610
- var removeEndZeroes = (value) => {
7611
- return value.endsWith("\x00") ? removeEndZeroes(value.slice(0, -1)) : value;
7612
- };
7613
- var parseSimpleTagIntoEbml = (children, trackId) => {
7614
- const tagName = children.find((c) => c.type === "TagName");
7615
- const tagString = children.find((c) => c.type === "TagString");
7616
- if (!tagName || !tagString) {
7617
- return null;
7618
- }
7619
- return {
7620
- trackId,
7621
- key: tagName.value.toLowerCase(),
7622
- value: removeEndZeroes(tagString.value)
7623
- };
7624
- };
7625
- var getMetadataFromMatroska = (structure) => {
7626
- const entries = [];
7627
- for (const segment of structure.boxes) {
7628
- if (segment.type !== "Segment") {
7629
- continue;
7630
- }
7631
- const tags2 = segment.value.filter((s) => s.type === "Tags");
7632
- for (const tag of tags2) {
7633
- for (const child of tag.value) {
7634
- if (child.type !== "Tag") {
7635
- continue;
7636
- }
7637
- let trackId = null;
7638
- const target = child.value.find((c) => c.type === "Targets");
7639
- if (target) {
7640
- const tagTrackId = target.value.find((c) => c.type === "TagTrackUID")?.value;
7641
- if (tagTrackId) {
7642
- trackId = getTrackWithUid(segment, tagTrackId);
7643
- }
7644
- }
7645
- const simpleTags = child.value.filter((s) => s.type === "SimpleTag");
7646
- for (const simpleTag of simpleTags) {
7647
- const parsed = parseSimpleTagIntoEbml(simpleTag.value, trackId);
7648
- if (parsed) {
7649
- entries.push(parsed);
7650
- }
7651
- }
7652
- }
7893
+ this.fileName = "IsAGifError";
7894
+ this.mimeType = mimeType;
7895
+ this.sizeInBytes = sizeInBytes;
7896
+ this.fileName = fileName;
7897
+ if (Error.captureStackTrace) {
7898
+ Error.captureStackTrace(this, IsAGifError);
7653
7899
  }
7654
7900
  }
7655
- return entries;
7656
- };
7901
+ }
7657
7902
 
7658
- // src/metadata/metadata-from-riff.ts
7659
- var getMetadataFromRiff = (structure) => {
7660
- const boxes = structure.boxes.find((b) => b.type === "list-box" && b.listType === "INFO");
7661
- if (!boxes) {
7662
- return [];
7663
- }
7664
- const { children } = boxes;
7665
- return children.map((child) => {
7666
- if (child.type !== "isft-box") {
7667
- return null;
7903
+ class IsAnImageError extends Error {
7904
+ imageType;
7905
+ dimensions;
7906
+ mimeType;
7907
+ sizeInBytes;
7908
+ fileName;
7909
+ constructor({
7910
+ dimensions,
7911
+ imageType,
7912
+ message,
7913
+ mimeType,
7914
+ sizeInBytes,
7915
+ fileName
7916
+ }) {
7917
+ super(message);
7918
+ this.name = "IsAnImageError";
7919
+ this.imageType = imageType;
7920
+ this.dimensions = dimensions;
7921
+ this.mimeType = mimeType;
7922
+ this.sizeInBytes = sizeInBytes;
7923
+ this.fileName = fileName;
7924
+ if (Error.captureStackTrace) {
7925
+ Error.captureStackTrace(this, IsAnImageError);
7668
7926
  }
7669
- return {
7670
- trackId: null,
7671
- key: "encoder",
7672
- value: child.software
7673
- };
7674
- }).filter(truthy);
7675
- };
7676
-
7677
- // src/metadata/get-metadata.ts
7678
- var getMetadata = (structure) => {
7679
- if (structure.type === "matroska") {
7680
- return getMetadataFromMatroska(structure);
7681
7927
  }
7682
- if (structure.type === "riff") {
7683
- return getMetadataFromRiff(structure);
7928
+ }
7929
+
7930
+ class IsAPdfError extends Error {
7931
+ mimeType;
7932
+ sizeInBytes;
7933
+ fileName;
7934
+ constructor({
7935
+ message,
7936
+ mimeType,
7937
+ sizeInBytes,
7938
+ fileName
7939
+ }) {
7940
+ super(message);
7941
+ this.name = "IsAPdfError";
7942
+ this.mimeType = mimeType;
7943
+ this.sizeInBytes = sizeInBytes;
7944
+ this.fileName = fileName;
7945
+ if (Error.captureStackTrace) {
7946
+ Error.captureStackTrace(this, IsAPdfError);
7947
+ }
7684
7948
  }
7685
- if (structure.type === "transport-stream") {
7686
- return [];
7949
+ }
7950
+
7951
+ class IsAnUnsupportedFileTypeError extends Error {
7952
+ mimeType;
7953
+ sizeInBytes;
7954
+ fileName;
7955
+ constructor({
7956
+ message,
7957
+ mimeType,
7958
+ sizeInBytes,
7959
+ fileName
7960
+ }) {
7961
+ super(message);
7962
+ this.name = "IsAnUnsupportedFileTypeError";
7963
+ this.mimeType = mimeType;
7964
+ this.sizeInBytes = sizeInBytes;
7965
+ this.fileName = fileName;
7966
+ if (Error.captureStackTrace) {
7967
+ Error.captureStackTrace(this, IsAnUnsupportedFileTypeError);
7968
+ }
7687
7969
  }
7688
- return getMetadataFromIsoBase(structure);
7689
- };
7970
+ }
7690
7971
 
7972
+ class IsAnUnsupportedAudioTypeError extends Error {
7973
+ mimeType;
7974
+ sizeInBytes;
7975
+ fileName;
7976
+ audioType;
7977
+ constructor({
7978
+ message,
7979
+ mimeType,
7980
+ sizeInBytes,
7981
+ fileName,
7982
+ audioType
7983
+ }) {
7984
+ super(message);
7985
+ this.name = "IsAnUnsupportedAudioTypeError";
7986
+ this.mimeType = mimeType;
7987
+ this.sizeInBytes = sizeInBytes;
7988
+ this.fileName = fileName;
7989
+ this.audioType = audioType;
7990
+ if (Error.captureStackTrace) {
7991
+ Error.captureStackTrace(this, IsAnUnsupportedAudioTypeError);
7992
+ }
7993
+ }
7994
+ }
7691
7995
  // src/get-location.ts
7692
7996
  function parseLocation(locationString) {
7693
7997
  const locationPattern = /^([+-]\d{2}\.?\d{0,10})([+-]\d{3}\.?\d{0,10})([+-]\d+(\.\d+)?)?\/$/;
@@ -7808,7 +8112,7 @@ var emitAvailableInfo = ({
7808
8112
  if (key === "dimensions") {
7809
8113
  if (hasInfo.dimensions && !emittedFields.dimensions && parseResult && segments) {
7810
8114
  const dimensionsQueried = getDimensions(segments, state);
7811
- const dimensions = {
8115
+ const dimensions = dimensionsQueried === null ? null : {
7812
8116
  height: dimensionsQueried.height,
7813
8117
  width: dimensionsQueried.width
7814
8118
  };
@@ -7823,7 +8127,7 @@ var emitAvailableInfo = ({
7823
8127
  if (key === "unrotatedDimensions") {
7824
8128
  if (hasInfo.unrotatedDimensions && !emittedFields.unrotatedDimensions && parseResult && segments) {
7825
8129
  const dimensionsQueried = getDimensions(segments, state);
7826
- const unrotatedDimensions = {
8130
+ const unrotatedDimensions = dimensionsQueried === null ? null : {
7827
8131
  height: dimensionsQueried.unrotatedHeight,
7828
8132
  width: dimensionsQueried.unrotatedWidth
7829
8133
  };
@@ -7838,7 +8142,7 @@ var emitAvailableInfo = ({
7838
8142
  if (key === "rotation") {
7839
8143
  if (hasInfo.rotation && !emittedFields.rotation && parseResult && segments) {
7840
8144
  const dimensionsQueried = getDimensions(segments, state);
7841
- const { rotation } = dimensionsQueried;
8145
+ const rotation = dimensionsQueried?.rotation ?? 0;
7842
8146
  callbacks.onRotation?.(rotation);
7843
8147
  if (fieldsInReturnValue.rotation) {
7844
8148
  returnValue.rotation = rotation;
@@ -7994,41 +8298,489 @@ var emitAvailableInfo = ({
7994
8298
  }
7995
8299
  continue;
7996
8300
  }
8301
+ if (key === "images") {
8302
+ if (!emittedFields.images && hasInfo.images && parseResult) {
8303
+ callbacks.onImages?.(state.images.images);
8304
+ if (fieldsInReturnValue.images) {
8305
+ returnValue.images = state.images.images;
8306
+ }
8307
+ emittedFields.images = true;
8308
+ }
8309
+ continue;
8310
+ }
7997
8311
  throw new Error(`Unhandled key: ${key}`);
7998
8312
  }
7999
8313
  };
8000
8314
 
8001
- // src/get-fields-from-callbacks.ts
8002
- var getFieldsFromCallback = ({
8003
- fields,
8004
- callbacks
8315
+ // src/get-fields-from-callbacks.ts
8316
+ var getFieldsFromCallback = ({
8317
+ fields,
8318
+ callbacks
8319
+ }) => {
8320
+ const newFields = {
8321
+ audioCodec: Boolean(callbacks.onAudioCodec),
8322
+ container: Boolean(callbacks.onContainer),
8323
+ dimensions: Boolean(callbacks.onDimensions),
8324
+ durationInSeconds: Boolean(callbacks.onDurationInSeconds),
8325
+ fps: Boolean(callbacks.onFps),
8326
+ internalStats: Boolean(callbacks.onInternalStats),
8327
+ isHdr: Boolean(callbacks.onIsHdr),
8328
+ location: Boolean(callbacks.onLocation),
8329
+ metadata: Boolean(callbacks.onMetadata),
8330
+ mimeType: Boolean(callbacks.onMimeType),
8331
+ name: Boolean(callbacks.onName),
8332
+ rotation: Boolean(callbacks.onRotation),
8333
+ size: Boolean(callbacks.onSize),
8334
+ structure: Boolean(callbacks.onStructure),
8335
+ tracks: Boolean(callbacks.onTracks),
8336
+ unrotatedDimensions: Boolean(callbacks.onUnrotatedDimensions),
8337
+ videoCodec: Boolean(callbacks.onVideoCodec),
8338
+ slowKeyframes: Boolean(callbacks.onSlowKeyframes),
8339
+ slowDurationInSeconds: Boolean(callbacks.onSlowDurationInSeconds),
8340
+ slowFps: Boolean(callbacks.onSlowFps),
8341
+ slowNumberOfFrames: Boolean(callbacks.onSlowNumberOfFrames),
8342
+ keyframes: Boolean(callbacks.onKeyframes),
8343
+ images: Boolean(callbacks.onImages),
8344
+ ...fields
8345
+ };
8346
+ return newFields;
8347
+ };
8348
+
8349
+ // src/boxes/mp3/id3.ts
8350
+ function combine28Bits(a, b, c, d) {
8351
+ const val1 = a & 127;
8352
+ const val2 = b & 127;
8353
+ const val3 = c & 127;
8354
+ const val4 = d & 127;
8355
+ return val1 << 21 | val2 << 14 | val3 << 7 | val4;
8356
+ }
8357
+ var parseId3 = ({
8358
+ iterator,
8359
+ structure,
8360
+ state
8361
+ }) => {
8362
+ if (iterator.bytesRemaining() < 9) {
8363
+ return;
8364
+ }
8365
+ const { returnToCheckpoint } = iterator.startCheckpoint();
8366
+ iterator.discard(3);
8367
+ const versionMajor = iterator.getUint8();
8368
+ const versionMinor = iterator.getUint8();
8369
+ const flags = iterator.getUint8();
8370
+ const sizeArr = iterator.getSlice(4);
8371
+ const size = combine28Bits(sizeArr[0], sizeArr[1], sizeArr[2], sizeArr[3]);
8372
+ if (iterator.bytesRemaining() < size) {
8373
+ returnToCheckpoint();
8374
+ return;
8375
+ }
8376
+ const entries = [];
8377
+ const initial = iterator.counter.getOffset();
8378
+ while (iterator.counter.getOffset() < size + initial) {
8379
+ const name = versionMajor === 3 ? iterator.getByteString(4, true) : iterator.getByteString(3, true);
8380
+ if (name === "") {
8381
+ iterator.discard(size + initial - iterator.counter.getOffset());
8382
+ break;
8383
+ }
8384
+ const s = versionMajor === 3 ? iterator.getUint32() : iterator.getUint24();
8385
+ if (versionMajor === 3) {
8386
+ iterator.getUint16();
8387
+ }
8388
+ let subtract = 0;
8389
+ if (!name.startsWith("W")) {
8390
+ iterator.getUint8();
8391
+ subtract += 1;
8392
+ }
8393
+ if (name === "APIC") {
8394
+ const { discardRest } = iterator.planBytes(s - subtract);
8395
+ const mimeType = iterator.readUntilNullTerminator();
8396
+ iterator.getUint16();
8397
+ const description = iterator.readUntilNullTerminator();
8398
+ iterator.discard(1);
8399
+ const data = discardRest();
8400
+ state.images.addImage({
8401
+ data,
8402
+ description,
8403
+ mimeType
8404
+ });
8405
+ } else {
8406
+ const information = iterator.getByteString(s - subtract, true);
8407
+ entries.push({
8408
+ key: name,
8409
+ value: information,
8410
+ trackId: null
8411
+ });
8412
+ }
8413
+ }
8414
+ structure.boxes.push({
8415
+ type: "id3-header",
8416
+ flags,
8417
+ size,
8418
+ versionMajor,
8419
+ versionMinor,
8420
+ metatags: entries
8421
+ });
8422
+ };
8423
+
8424
+ // src/boxes/mp3/id3-v1.ts
8425
+ var parseID3V1 = (iterator) => {
8426
+ if (iterator.bytesRemaining() < 128) {
8427
+ return;
8428
+ }
8429
+ iterator.discard(128);
8430
+ };
8431
+
8432
+ // src/boxes/mp3/parse-mpeg-header.ts
8433
+ function getSamplingFrequency({
8434
+ bits,
8435
+ mpegVersion
8436
+ }) {
8437
+ const samplingTable = {
8438
+ 0: { MPEG1: 44100, MPEG2: 22050 },
8439
+ 1: { MPEG1: 48000, MPEG2: 24000 },
8440
+ 2: { MPEG1: 32000, MPEG2: 16000 },
8441
+ 3: { MPEG1: "reserved", MPEG2: "reserved" }
8442
+ };
8443
+ const key = `MPEG${mpegVersion}`;
8444
+ const value = samplingTable[bits][key];
8445
+ if (value === "reserved") {
8446
+ throw new Error("Reserved sampling frequency");
8447
+ }
8448
+ if (!value) {
8449
+ throw new Error("Invalid sampling frequency for MPEG version: " + JSON.stringify({ bits, version: mpegVersion }));
8450
+ }
8451
+ return value;
8452
+ }
8453
+ function getBitrateKB({
8454
+ bits,
8455
+ mpegVersion,
8456
+ level
8457
+ }) {
8458
+ const bitrateTable = {
8459
+ 0: {
8460
+ "V1,L1": "free",
8461
+ "V1,L2": "free",
8462
+ "V1,L3": "free",
8463
+ "V2,L1": "free",
8464
+ "V2,L2&L3": "free"
8465
+ },
8466
+ 1: { "V1,L1": 32, "V1,L2": 32, "V1,L3": 32, "V2,L1": 32, "V2,L2&L3": 8 },
8467
+ 2: {
8468
+ "V1,L1": 64,
8469
+ "V1,L2": 48,
8470
+ "V1,L3": 40,
8471
+ "V2,L1": 48,
8472
+ "V2,L2&L3": 16
8473
+ },
8474
+ 3: {
8475
+ "V1,L1": 96,
8476
+ "V1,L2": 56,
8477
+ "V1,L3": 48,
8478
+ "V2,L1": 56,
8479
+ "V2,L2&L3": 24
8480
+ },
8481
+ 4: {
8482
+ "V1,L1": 128,
8483
+ "V1,L2": 64,
8484
+ "V1,L3": 56,
8485
+ "V2,L1": 64,
8486
+ "V2,L2&L3": 32
8487
+ },
8488
+ 5: {
8489
+ "V1,L1": 160,
8490
+ "V1,L2": 80,
8491
+ "V1,L3": 64,
8492
+ "V2,L1": 80,
8493
+ "V2,L2&L3": 40
8494
+ },
8495
+ 6: {
8496
+ "V1,L1": 192,
8497
+ "V1,L2": 96,
8498
+ "V1,L3": 80,
8499
+ "V2,L1": 96,
8500
+ "V2,L2&L3": 48
8501
+ },
8502
+ 7: {
8503
+ "V1,L1": 224,
8504
+ "V1,L2": 112,
8505
+ "V1,L3": 96,
8506
+ "V2,L1": 112,
8507
+ "V2,L2&L3": 56
8508
+ },
8509
+ 8: {
8510
+ "V1,L1": 256,
8511
+ "V1,L2": 128,
8512
+ "V1,L3": 112,
8513
+ "V2,L1": 128,
8514
+ "V2,L2&L3": 64
8515
+ },
8516
+ 9: {
8517
+ "V1,L1": 288,
8518
+ "V1,L2": 160,
8519
+ "V1,L3": 128,
8520
+ "V2,L1": 144,
8521
+ "V2,L2&L3": 80
8522
+ },
8523
+ 10: {
8524
+ "V1,L1": 320,
8525
+ "V1,L2": 192,
8526
+ "V1,L3": 160,
8527
+ "V2,L1": 160,
8528
+ "V2,L2&L3": 96
8529
+ },
8530
+ 11: {
8531
+ "V1,L1": 352,
8532
+ "V1,L2": 224,
8533
+ "V1,L3": 192,
8534
+ "V2,L1": 176,
8535
+ "V2,L2&L3": 112
8536
+ },
8537
+ 12: {
8538
+ "V1,L1": 384,
8539
+ "V1,L2": 256,
8540
+ "V1,L3": 224,
8541
+ "V2,L1": 192,
8542
+ "V2,L2&L3": 128
8543
+ },
8544
+ 13: {
8545
+ "V1,L1": 416,
8546
+ "V1,L2": 320,
8547
+ "V1,L3": 256,
8548
+ "V2,L1": 224,
8549
+ "V2,L2&L3": 144
8550
+ },
8551
+ 14: {
8552
+ "V1,L1": 448,
8553
+ "V1,L2": 384,
8554
+ "V1,L3": 320,
8555
+ "V2,L1": 256,
8556
+ "V2,L2&L3": 160
8557
+ },
8558
+ 15: {
8559
+ "V1,L1": "bad",
8560
+ "V1,L2": "bad",
8561
+ "V1,L3": "bad",
8562
+ "V2,L1": "bad",
8563
+ "V2,L2&L3": "bad"
8564
+ }
8565
+ };
8566
+ let key;
8567
+ if (mpegVersion === 2 && (level === 2 || level === 3)) {
8568
+ key = "V2,L2&L3";
8569
+ } else {
8570
+ key = `V${mpegVersion},L${level}`;
8571
+ }
8572
+ return bitrateTable[bits][key];
8573
+ }
8574
+ var parseMpegHeader = async ({
8575
+ iterator,
8576
+ state
8577
+ }) => {
8578
+ const initialOffset = iterator.counter.getOffset();
8579
+ if (iterator.bytesRemaining() < 32) {
8580
+ return;
8581
+ }
8582
+ iterator.startReadingBits();
8583
+ for (let i = 0;i < 11; i++) {
8584
+ const expectToBe1 = iterator.getBits(1);
8585
+ if (expectToBe1 !== 1) {
8586
+ throw new Error("Expected 1");
8587
+ }
8588
+ }
8589
+ const audioVersionId = iterator.getBits(2);
8590
+ if (audioVersionId !== 3 && audioVersionId !== 2) {
8591
+ throw new Error("Expected MPEG Version 1 or 2");
8592
+ }
8593
+ const mpegVersion = audioVersionId === 3 ? 1 : 2;
8594
+ const layerBits = iterator.getBits(2);
8595
+ if (layerBits === 0) {
8596
+ throw new Error("Expected Layer I, II or III");
8597
+ }
8598
+ const layer = layerBits === 3 ? 1 : layerBits === 2 ? 2 : 3;
8599
+ const protectionBit = iterator.getBits(1);
8600
+ if (protectionBit !== 1) {
8601
+ throw new Error("Does not support CRC yet");
8602
+ }
8603
+ const bitrateIndex = iterator.getBits(4);
8604
+ const bitrateKbit = getBitrateKB({
8605
+ bits: bitrateIndex,
8606
+ mpegVersion,
8607
+ level: audioVersionId
8608
+ });
8609
+ if (bitrateKbit === "bad") {
8610
+ throw new Error("Invalid bitrate");
8611
+ }
8612
+ if (bitrateKbit === "free") {
8613
+ throw new Error("Free bitrate not supported");
8614
+ }
8615
+ const samplingFrequencyIndex = iterator.getBits(2);
8616
+ const sampleRate = getSamplingFrequency({
8617
+ bits: samplingFrequencyIndex,
8618
+ mpegVersion
8619
+ });
8620
+ const padding = Boolean(iterator.getBits(1));
8621
+ iterator.getBits(1);
8622
+ const channelMode = iterator.getBits(2);
8623
+ iterator.getBits(2);
8624
+ iterator.getBits(1);
8625
+ iterator.getBits(1);
8626
+ iterator.getBits(2);
8627
+ const numberOfChannels = channelMode === 3 ? 1 : 2;
8628
+ const samplesPerFrame = getSamplesPerMpegFrame({ mpegVersion, layer });
8629
+ const frameLength = getMpegFrameLength({
8630
+ bitrateKbit,
8631
+ padding,
8632
+ samplesPerFrame,
8633
+ samplingFrequency: sampleRate,
8634
+ layer
8635
+ });
8636
+ iterator.stopReadingBits();
8637
+ const offsetNow = iterator.counter.getOffset();
8638
+ iterator.counter.decrement(offsetNow - initialOffset);
8639
+ const data = iterator.getSlice(frameLength);
8640
+ if (state.callbacks.tracks.getTracks().length === 0) {
8641
+ state.mp3Info.setMp3Info({
8642
+ layer,
8643
+ mpegVersion,
8644
+ sampleRate,
8645
+ bitrateKbit,
8646
+ startOfMpegStream: initialOffset
8647
+ });
8648
+ await registerTrack({
8649
+ container: "mp3",
8650
+ state,
8651
+ track: {
8652
+ type: "audio",
8653
+ codec: "mp3",
8654
+ codecPrivate: null,
8655
+ codecWithoutConfig: "mp3",
8656
+ description: undefined,
8657
+ numberOfChannels,
8658
+ sampleRate,
8659
+ timescale: 1e6,
8660
+ trackId: 0,
8661
+ trakBox: null
8662
+ }
8663
+ });
8664
+ state.callbacks.tracks.setIsDone();
8665
+ }
8666
+ const mp3Info = state.mp3Info.getMp3Info();
8667
+ if (!mp3Info) {
8668
+ throw new Error("No MP3 info by now");
8669
+ }
8670
+ const avgLength = getAverageMpegFrameLength({
8671
+ bitrateKbit,
8672
+ layer,
8673
+ samplesPerFrame,
8674
+ samplingFrequency: sampleRate
8675
+ });
8676
+ const nthFrame = Math.round((initialOffset - mp3Info.startOfMpegStream) / avgLength);
8677
+ const durationInSeconds = samplesPerFrame / sampleRate;
8678
+ const timeInSeconds = nthFrame * samplesPerFrame / sampleRate;
8679
+ const timestamp = Math.round(timeInSeconds * 1e6);
8680
+ const duration2 = Math.round(durationInSeconds * 1e6);
8681
+ await state.callbacks.onAudioSample(0, {
8682
+ data,
8683
+ cts: timestamp,
8684
+ dts: timestamp,
8685
+ duration: duration2,
8686
+ offset: initialOffset,
8687
+ timescale: 1e6,
8688
+ timestamp,
8689
+ trackId: 0,
8690
+ type: "key"
8691
+ });
8692
+ };
8693
+
8694
+ // src/boxes/mp3/parse-mp3.ts
8695
+ var parseMp3 = async ({
8696
+ iterator,
8697
+ structure,
8698
+ state
8699
+ }) => {
8700
+ const continueParsing = () => {
8701
+ return parseMp3({ iterator, structure, state });
8702
+ };
8703
+ if (iterator.bytesRemaining() === 0) {
8704
+ return Promise.resolve({
8705
+ status: "done"
8706
+ });
8707
+ }
8708
+ if (iterator.bytesRemaining() < 3) {
8709
+ return {
8710
+ status: "incomplete",
8711
+ skipTo: null,
8712
+ continueParsing
8713
+ };
8714
+ }
8715
+ const { returnToCheckpoint } = iterator.startCheckpoint();
8716
+ const bytes = iterator.getSlice(3);
8717
+ returnToCheckpoint();
8718
+ if (bytes[0] === 84 && bytes[1] === 65 && bytes[2] === 71) {
8719
+ parseID3V1(iterator);
8720
+ return {
8721
+ status: "incomplete",
8722
+ continueParsing,
8723
+ skipTo: null
8724
+ };
8725
+ }
8726
+ if (bytes[0] === 73 && bytes[1] === 68 && bytes[2] === 51) {
8727
+ parseId3({ iterator, structure, state });
8728
+ return {
8729
+ status: "incomplete",
8730
+ continueParsing,
8731
+ skipTo: null
8732
+ };
8733
+ }
8734
+ if (bytes[0] === 255) {
8735
+ await parseMpegHeader({
8736
+ iterator,
8737
+ state
8738
+ });
8739
+ return {
8740
+ status: "incomplete",
8741
+ continueParsing,
8742
+ skipTo: null
8743
+ };
8744
+ }
8745
+ throw new Error("Unknown MP3 header ");
8746
+ };
8747
+
8748
+ // src/boxes/riff/continue-after-riff-result.ts
8749
+ var continueAfterRiffBoxResult = ({
8750
+ result,
8751
+ structure,
8752
+ iterator,
8753
+ maxOffset,
8754
+ state: options,
8755
+ fields
8005
8756
  }) => {
8006
- const newFields = {
8007
- audioCodec: Boolean(callbacks.onAudioCodec),
8008
- container: Boolean(callbacks.onContainer),
8009
- dimensions: Boolean(callbacks.onDimensions),
8010
- durationInSeconds: Boolean(callbacks.onDurationInSeconds),
8011
- fps: Boolean(callbacks.onFps),
8012
- internalStats: Boolean(callbacks.onInternalStats),
8013
- isHdr: Boolean(callbacks.onIsHdr),
8014
- location: Boolean(callbacks.onLocation),
8015
- metadata: Boolean(callbacks.onMetadata),
8016
- mimeType: Boolean(callbacks.onMimeType),
8017
- name: Boolean(callbacks.onName),
8018
- rotation: Boolean(callbacks.onRotation),
8019
- size: Boolean(callbacks.onSize),
8020
- structure: Boolean(callbacks.onStructure),
8021
- tracks: Boolean(callbacks.onTracks),
8022
- unrotatedDimensions: Boolean(callbacks.onUnrotatedDimensions),
8023
- videoCodec: Boolean(callbacks.onVideoCodec),
8024
- slowKeyframes: Boolean(callbacks.onSlowKeyframes),
8025
- slowDurationInSeconds: Boolean(callbacks.onSlowDurationInSeconds),
8026
- slowFps: Boolean(callbacks.onSlowFps),
8027
- slowNumberOfFrames: Boolean(callbacks.onSlowNumberOfFrames),
8028
- keyframes: Boolean(callbacks.onKeyframes),
8029
- ...fields
8030
- };
8031
- return newFields;
8757
+ if (result.type === "incomplete") {
8758
+ return Promise.resolve({
8759
+ status: "incomplete",
8760
+ async continueParsing() {
8761
+ return Promise.resolve(continueAfterRiffBoxResult({
8762
+ result: await result.continueParsing(),
8763
+ structure,
8764
+ iterator,
8765
+ maxOffset,
8766
+ state: options,
8767
+ fields
8768
+ }));
8769
+ },
8770
+ segments: structure,
8771
+ skipTo: null
8772
+ });
8773
+ }
8774
+ if (result.type === "complete" && result.box) {
8775
+ structure.boxes.push(result.box);
8776
+ }
8777
+ return parseRiffBody({
8778
+ iterator,
8779
+ maxOffset,
8780
+ state: options,
8781
+ structure,
8782
+ fields
8783
+ });
8032
8784
  };
8033
8785
 
8034
8786
  // src/boxes/riff/is-movi.ts
@@ -8508,7 +9260,8 @@ var parseIsft = ({
8508
9260
  var parseListBox = async ({
8509
9261
  iterator,
8510
9262
  size,
8511
- state
9263
+ state,
9264
+ fields
8512
9265
  }) => {
8513
9266
  const counter = iterator.counter.getOffset();
8514
9267
  const listType = iterator.getByteString(4, false);
@@ -8523,7 +9276,8 @@ var parseListBox = async ({
8523
9276
  structure,
8524
9277
  iterator,
8525
9278
  maxOffset: counter + size,
8526
- state
9279
+ state,
9280
+ fields
8527
9281
  });
8528
9282
  if (result.status === "incomplete") {
8529
9283
  throw new Error(`Should only parse complete boxes (${listType})`);
@@ -8663,13 +9417,14 @@ var parseRiffBox = ({
8663
9417
  size,
8664
9418
  id,
8665
9419
  boxes,
8666
- state
9420
+ state,
9421
+ fields
8667
9422
  }) => {
8668
9423
  if (id === "fmt") {
8669
9424
  return Promise.resolve(parseFmtBox({ iterator, boxes, size }));
8670
9425
  }
8671
9426
  if (id === "LIST") {
8672
- return parseListBox({ iterator, size, state });
9427
+ return parseListBox({ iterator, size, state, fields });
8673
9428
  }
8674
9429
  if (id === "ISFT") {
8675
9430
  return Promise.resolve(parseIsft({ iterator, size }));
@@ -8696,13 +9451,14 @@ var parseRiffBox = ({
8696
9451
  var expectRiffBox = async ({
8697
9452
  iterator,
8698
9453
  state,
8699
- structure
9454
+ structure,
9455
+ fields
8700
9456
  }) => {
8701
9457
  if (iterator.bytesRemaining() < 16) {
8702
9458
  return {
8703
9459
  type: "incomplete",
8704
9460
  continueParsing() {
8705
- return expectRiffBox({ structure, iterator, state });
9461
+ return expectRiffBox({ structure, iterator, state, fields });
8706
9462
  }
8707
9463
  };
8708
9464
  }
@@ -8722,7 +9478,7 @@ var expectRiffBox = async ({
8722
9478
  return {
8723
9479
  type: "incomplete",
8724
9480
  continueParsing: () => {
8725
- return expectRiffBox({ structure, iterator, state });
9481
+ return expectRiffBox({ structure, iterator, state, fields });
8726
9482
  }
8727
9483
  };
8728
9484
  }
@@ -8733,52 +9489,27 @@ var expectRiffBox = async ({
8733
9489
  iterator,
8734
9490
  size: ckSize,
8735
9491
  boxes: structure.boxes,
8736
- state
9492
+ state,
9493
+ fields
8737
9494
  }),
8738
9495
  skipTo: null
8739
9496
  };
8740
9497
  };
8741
9498
 
8742
- // src/boxes/riff/parse-box.ts
8743
- var continueAfterRiffBoxResult = ({
8744
- result,
8745
- structure,
8746
- iterator,
8747
- maxOffset,
8748
- state: options
8749
- }) => {
8750
- if (result.type === "incomplete") {
8751
- return Promise.resolve({
8752
- status: "incomplete",
8753
- async continueParsing() {
8754
- return Promise.resolve(continueAfterRiffBoxResult({
8755
- result: await result.continueParsing(),
8756
- structure,
8757
- iterator,
8758
- maxOffset,
8759
- state: options
8760
- }));
8761
- },
8762
- segments: structure,
8763
- skipTo: null
8764
- });
8765
- }
8766
- if (result.type === "complete" && result.box) {
8767
- structure.boxes.push(result.box);
8768
- }
8769
- return parseRiffBody({ iterator, maxOffset, state: options, structure });
8770
- };
9499
+ // src/boxes/riff/parse-riff-body.ts
8771
9500
  var parseRiffBody = async ({
8772
9501
  iterator,
8773
9502
  structure,
8774
9503
  maxOffset,
8775
- state
9504
+ state,
9505
+ fields
8776
9506
  }) => {
8777
- while (iterator.bytesRemaining() > 0 && iterator.counter.getOffset() < maxOffset) {
9507
+ while (iterator.bytesRemaining() > 0 && iterator.counter.getOffset() < maxOffset && !hasAllInfo({ fields, state })) {
8778
9508
  const result = await expectRiffBox({
8779
9509
  iterator,
8780
9510
  state,
8781
- structure
9511
+ structure,
9512
+ fields
8782
9513
  });
8783
9514
  if (result.type === "complete" && result.skipTo !== null) {
8784
9515
  return {
@@ -8790,7 +9521,8 @@ var parseRiffBody = async ({
8790
9521
  maxOffset,
8791
9522
  state,
8792
9523
  result,
8793
- structure
9524
+ structure,
9525
+ fields
8794
9526
  }));
8795
9527
  }
8796
9528
  };
@@ -8804,7 +9536,8 @@ var parseRiffBody = async ({
8804
9536
  maxOffset,
8805
9537
  state,
8806
9538
  result: await result.continueParsing(),
8807
- structure
9539
+ structure,
9540
+ fields
8808
9541
  }));
8809
9542
  },
8810
9543
  skipTo: null
@@ -8859,6 +9592,8 @@ var parseRiffBody = async ({
8859
9592
  status: "done"
8860
9593
  };
8861
9594
  };
9595
+
9596
+ // src/boxes/riff/parse-box.ts
8862
9597
  var parseRiff = ({
8863
9598
  iterator,
8864
9599
  state,
@@ -8888,7 +9623,8 @@ var parseRiff = ({
8888
9623
  iterator,
8889
9624
  maxOffset: Infinity,
8890
9625
  state,
8891
- structure
9626
+ structure,
9627
+ fields
8892
9628
  });
8893
9629
  };
8894
9630
 
@@ -9605,57 +10341,46 @@ var parseTransportStream = async ({
9605
10341
  structure
9606
10342
  });
9607
10343
  return Promise.resolve({
9608
- status: "done",
9609
- segments: structure
10344
+ status: "done"
9610
10345
  });
9611
10346
  }
9612
- while (true) {
9613
- if (hasAllInfo({
9614
- fields,
9615
- state
9616
- })) {
9617
- break;
9618
- }
9619
- if (iterator.bytesRemaining() < 188) {
9620
- return Promise.resolve({
9621
- status: "incomplete",
9622
- segments: structure,
9623
- skipTo: null,
9624
- continueParsing: () => {
9625
- return parseTransportStream({
9626
- iterator,
9627
- state,
9628
- streamBuffers,
9629
- fields,
9630
- nextPesHeaderStore
9631
- });
9632
- }
9633
- });
9634
- }
9635
- const packet = await parsePacket({
10347
+ const continueParsing = () => {
10348
+ return parseTransportStream({
9636
10349
  iterator,
9637
- structure,
10350
+ state,
9638
10351
  streamBuffers,
9639
- parserState: state,
10352
+ fields,
9640
10353
  nextPesHeaderStore
9641
10354
  });
9642
- if (packet) {
9643
- structure.boxes.push(packet);
9644
- break;
9645
- }
10355
+ };
10356
+ if (hasAllInfo({
10357
+ fields,
10358
+ state
10359
+ })) {
10360
+ return Promise.resolve({
10361
+ status: "done"
10362
+ });
10363
+ }
10364
+ if (iterator.bytesRemaining() < 188) {
10365
+ return Promise.resolve({
10366
+ status: "incomplete",
10367
+ continueParsing,
10368
+ skipTo: null
10369
+ });
10370
+ }
10371
+ const packet = await parsePacket({
10372
+ iterator,
10373
+ structure,
10374
+ streamBuffers,
10375
+ parserState: state,
10376
+ nextPesHeaderStore
10377
+ });
10378
+ if (packet) {
10379
+ structure.boxes.push(packet);
9646
10380
  }
9647
10381
  return Promise.resolve({
9648
- segments: structure,
9649
10382
  status: "incomplete",
9650
- continueParsing() {
9651
- return parseTransportStream({
9652
- iterator,
9653
- state,
9654
- streamBuffers,
9655
- fields,
9656
- nextPesHeaderStore
9657
- });
9658
- },
10383
+ continueParsing,
9659
10384
  skipTo: null
9660
10385
  });
9661
10386
  };
@@ -9733,7 +10458,6 @@ var parseVideo = ({
9733
10458
  allowIncompleteBoxes: true,
9734
10459
  initialBoxes,
9735
10460
  state,
9736
- continueMdat: false,
9737
10461
  signal,
9738
10462
  logLevel,
9739
10463
  fields
@@ -9762,13 +10486,12 @@ var parseVideo = ({
9762
10486
  });
9763
10487
  }
9764
10488
  if (fileType.type === "mp3") {
9765
- return Promise.reject(new IsAnUnsupportedAudioTypeError({
9766
- message: "MP3 files are not yet supported",
9767
- mimeType,
9768
- sizeInBytes: contentLength,
9769
- fileName: name,
9770
- audioType: "mp3"
9771
- }));
10489
+ const structure = {
10490
+ boxes: [],
10491
+ type: "mp3"
10492
+ };
10493
+ state.structure.setStructure(structure);
10494
+ return parseMp3({ iterator, structure, state });
9772
10495
  }
9773
10496
  if (fileType.type === "wav") {
9774
10497
  return Promise.reject(new IsAnUnsupportedAudioTypeError({
@@ -9825,6 +10548,53 @@ var parseVideo = ({
9825
10548
  return Promise.reject(new Error("Unknown video format " + fileType));
9826
10549
  };
9827
10550
 
10551
+ // src/throttled-progress.ts
10552
+ var throttledStateUpdate = ({
10553
+ updateFn,
10554
+ everyMilliseconds,
10555
+ signal
10556
+ }) => {
10557
+ let currentState = {
10558
+ bytes: 0,
10559
+ percentage: null,
10560
+ totalBytes: null
10561
+ };
10562
+ if (!updateFn) {
10563
+ return {
10564
+ get: () => currentState,
10565
+ update: null,
10566
+ stopAndGetLastProgress: () => {
10567
+ }
10568
+ };
10569
+ }
10570
+ let lastUpdated = null;
10571
+ const callUpdateIfChanged = () => {
10572
+ if (currentState === lastUpdated) {
10573
+ return;
10574
+ }
10575
+ updateFn(currentState);
10576
+ lastUpdated = currentState;
10577
+ };
10578
+ const interval = setInterval(() => {
10579
+ callUpdateIfChanged();
10580
+ }, everyMilliseconds);
10581
+ const onAbort = () => {
10582
+ clearInterval(interval);
10583
+ };
10584
+ signal?.addEventListener("abort", onAbort, { once: true });
10585
+ return {
10586
+ get: () => currentState,
10587
+ update: (fn) => {
10588
+ currentState = fn(currentState);
10589
+ },
10590
+ stopAndGetLastProgress: () => {
10591
+ clearInterval(interval);
10592
+ signal?.removeEventListener("abort", onAbort);
10593
+ return currentState;
10594
+ }
10595
+ };
10596
+ };
10597
+
9828
10598
  // src/parse-media.ts
9829
10599
  var parseMedia = async function({
9830
10600
  src,
@@ -9834,10 +10604,10 @@ var parseMedia = async function({
9834
10604
  onVideoTrack,
9835
10605
  signal,
9836
10606
  logLevel = "info",
9837
- onParseProgress,
10607
+ onParseProgress: onParseProgressDoNotCallDirectly,
10608
+ progressIntervalInMs,
9838
10609
  ...more
9839
10610
  }) {
9840
- let iterator = null;
9841
10611
  let parseResult = null;
9842
10612
  const fieldsInReturnValue = _fieldsInReturnValue ?? {};
9843
10613
  const fields = getFieldsFromCallback({
@@ -9851,6 +10621,7 @@ var parseMedia = async function({
9851
10621
  contentType,
9852
10622
  supportsContentRange: readerSupportsContentRange
9853
10623
  } = await readerInterface.read(src, null, signal);
10624
+ const iterator = getArrayBufferIterator(new Uint8Array([]), contentLength ?? 1e9);
9854
10625
  const supportsContentRange = readerSupportsContentRange && !(typeof process !== "undefined" && typeof process.env !== "undefined" && process.env.DISABLE_CONTENT_RANGE === "true");
9855
10626
  const state = makeParserState({
9856
10627
  hasAudioTrackHandlers: Boolean(onAudioTrack),
@@ -9860,11 +10631,18 @@ var parseMedia = async function({
9860
10631
  fields,
9861
10632
  onAudioTrack: onAudioTrack ?? null,
9862
10633
  onVideoTrack: onVideoTrack ?? null,
9863
- supportsContentRange
10634
+ supportsContentRange,
10635
+ contentLength
9864
10636
  });
9865
10637
  let currentReader = reader;
9866
10638
  const returnValue = {};
9867
10639
  const moreFields = more;
10640
+ const throttledState = throttledStateUpdate({
10641
+ updateFn: onParseProgressDoNotCallDirectly ?? null,
10642
+ everyMilliseconds: progressIntervalInMs ?? 100,
10643
+ signal,
10644
+ totalBytes: contentLength
10645
+ });
9868
10646
  const triggerInfoEmit = () => {
9869
10647
  const availableInfo = getAvailableInfo({
9870
10648
  fieldsToFetch: fields,
@@ -9882,38 +10660,44 @@ var parseMedia = async function({
9882
10660
  mimeType: contentType
9883
10661
  });
9884
10662
  };
9885
- triggerInfoEmit();
9886
- while (parseResult === null || parseResult.status === "incomplete") {
9887
- while (true) {
9888
- if (signal?.aborted) {
9889
- throw new Error("Aborted");
10663
+ const checkIfDone = () => {
10664
+ if (hasAllInfo({
10665
+ fields,
10666
+ state
10667
+ })) {
10668
+ Log.verbose(logLevel, "Got all info, skipping to the end.");
10669
+ if (contentLength !== null) {
10670
+ state.increaseSkippedBytes(contentLength - iterator.counter.getOffset());
9890
10671
  }
10672
+ return true;
10673
+ }
10674
+ return false;
10675
+ };
10676
+ triggerInfoEmit();
10677
+ let didProgress = false;
10678
+ while (!checkIfDone() && (parseResult === null || parseResult.status === "incomplete")) {
10679
+ if (signal?.aborted) {
10680
+ throw new Error("Aborted");
10681
+ }
10682
+ const offsetBefore = iterator.counter.getOffset();
10683
+ const fetchMoreData = async () => {
9891
10684
  const result = await currentReader.reader.read();
9892
- if (iterator) {
9893
- if (!result.done) {
9894
- iterator.addData(result.value);
9895
- }
9896
- } else {
9897
- if (result.done) {
9898
- throw new Error("Unexpectedly reached EOF");
9899
- }
9900
- iterator = getArrayBufferIterator(result.value, contentLength ?? 1e9);
9901
- }
9902
- if (iterator.bytesRemaining() >= 0) {
9903
- break;
9904
- }
9905
- if (result.done) {
9906
- break;
10685
+ if (result.value) {
10686
+ iterator.addData(result.value);
9907
10687
  }
10688
+ };
10689
+ while (iterator.bytesRemaining() < 0) {
10690
+ await fetchMoreData();
9908
10691
  }
9909
- if (!iterator) {
9910
- throw new Error("Unexpected null");
10692
+ const hasBigBuffer = iterator.bytesRemaining() > 1e5;
10693
+ if (!didProgress || !hasBigBuffer) {
10694
+ await fetchMoreData();
9911
10695
  }
9912
- await onParseProgress?.({
10696
+ throttledState.update?.(() => ({
9913
10697
  bytes: iterator.counter.getOffset(),
9914
10698
  percentage: contentLength ? iterator.counter.getOffset() / contentLength : null,
9915
10699
  totalBytes: contentLength
9916
- });
10700
+ }));
9917
10701
  triggerInfoEmit();
9918
10702
  if (parseResult && parseResult.status === "incomplete") {
9919
10703
  Log.trace(logLevel, "Continuing parsing of file, currently at position", iterator.counter.getOffset());
@@ -9933,16 +10717,6 @@ var parseMedia = async function({
9933
10717
  if (parseResult.status === "incomplete" && parseResult.skipTo !== null) {
9934
10718
  state.increaseSkippedBytes(parseResult.skipTo - iterator.counter.getOffset());
9935
10719
  }
9936
- if (hasAllInfo({
9937
- fields,
9938
- state
9939
- })) {
9940
- Log.verbose(logLevel, "Got all info, skipping to the end.");
9941
- if (contentLength !== null) {
9942
- state.increaseSkippedBytes(contentLength - iterator.counter.getOffset());
9943
- }
9944
- break;
9945
- }
9946
10720
  if (parseResult.status === "incomplete" && parseResult.skipTo !== null) {
9947
10721
  if (!supportsContentRange) {
9948
10722
  throw new Error("Content-Range header is not supported by the reader, but was asked to seek");
@@ -9957,16 +10731,16 @@ var parseMedia = async function({
9957
10731
  currentReader = newReader;
9958
10732
  iterator.skipTo(parseResult.skipTo, true);
9959
10733
  }
10734
+ didProgress = iterator.counter.getOffset() > offsetBefore;
9960
10735
  }
9961
10736
  Log.verbose(logLevel, "Finished parsing file");
9962
- const hasInfo = Object.keys(fields).reduce((acc, key) => {
9963
- if (fields?.[key]) {
9964
- acc[key] = true;
9965
- }
9966
- return acc;
9967
- }, {});
9968
10737
  emitAvailableInfo({
9969
- hasInfo,
10738
+ hasInfo: Object.keys(fields).reduce((acc, key) => {
10739
+ if (fields?.[key]) {
10740
+ acc[key] = true;
10741
+ }
10742
+ return acc;
10743
+ }, {}),
9970
10744
  callbacks: moreFields,
9971
10745
  fieldsInReturnValue,
9972
10746
  parseResult,
@@ -9982,7 +10756,7 @@ var parseMedia = async function({
9982
10756
  return returnValue;
9983
10757
  };
9984
10758
  // src/version.ts
9985
- var VERSION = "4.0.248";
10759
+ var VERSION = "4.0.249";
9986
10760
 
9987
10761
  // src/index.ts
9988
10762
  var MediaParserInternals = {