@remotion/media-parser 4.0.201 → 4.0.204

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 (144) hide show
  1. package/dist/av1-codec-string.d.ts +0 -5
  2. package/dist/av1-codec-string.js +1 -18
  3. package/dist/boxes/iso-base-media/ftype.d.ts +9 -0
  4. package/dist/boxes/iso-base-media/ftype.js +31 -0
  5. package/dist/boxes/iso-base-media/get-sample-positions-from-track.d.ts +4 -0
  6. package/dist/boxes/iso-base-media/get-sample-positions-from-track.js +48 -0
  7. package/dist/boxes/iso-base-media/mvhd.js +2 -2
  8. package/dist/boxes/iso-base-media/stsd/keys.js +1 -1
  9. package/dist/boxes/iso-base-media/tfdt.d.ts +12 -0
  10. package/dist/boxes/iso-base-media/tfdt.js +20 -0
  11. package/dist/boxes/iso-base-media/tfhd.d.ts +16 -0
  12. package/dist/boxes/iso-base-media/tfhd.js +41 -0
  13. package/dist/boxes/iso-base-media/trun.d.ts +21 -0
  14. package/dist/boxes/iso-base-media/trun.js +44 -0
  15. package/dist/boxes/webm/av1-codec-private.js +1 -1
  16. package/dist/boxes/webm/bitstream/av1.js +1 -10
  17. package/dist/boxes/webm/description.d.ts +2 -2
  18. package/dist/boxes/webm/description.js +2 -2
  19. package/dist/boxes/webm/ebml.d.ts +2 -2
  20. package/dist/boxes/webm/ebml.js +23 -1
  21. package/dist/boxes/webm/get-ready-tracks.d.ts +1 -1
  22. package/dist/boxes/webm/get-ready-tracks.js +3 -3
  23. package/dist/boxes/webm/get-sample-from-block.d.ts +17 -0
  24. package/dist/boxes/webm/get-sample-from-block.js +78 -0
  25. package/dist/boxes/webm/get-track.d.ts +2 -2
  26. package/dist/boxes/webm/get-track.js +26 -25
  27. package/dist/boxes/webm/make-header.d.ts +3 -8
  28. package/dist/boxes/webm/make-header.js +43 -20
  29. package/dist/boxes/webm/parse-ebml.d.ts +9 -4
  30. package/dist/boxes/webm/parse-ebml.js +122 -13
  31. package/dist/boxes/webm/segments/all-segments.d.ts +421 -107
  32. package/dist/boxes/webm/segments/all-segments.js +260 -33
  33. package/dist/boxes/webm/segments/track-entry.d.ts +3 -191
  34. package/dist/boxes/webm/segments/track-entry.js +2 -456
  35. package/dist/boxes/webm/segments.d.ts +3 -16
  36. package/dist/boxes/webm/segments.js +12 -196
  37. package/dist/boxes/webm/tracks.d.ts +8 -0
  38. package/dist/boxes/webm/tracks.js +21 -0
  39. package/dist/boxes/webm/traversal.d.ts +5 -6
  40. package/dist/boxes/webm/traversal.js +6 -6
  41. package/dist/buffer-iterator.d.ts +1 -1
  42. package/dist/buffer-iterator.js +3 -3
  43. package/dist/from-web.js +6 -15
  44. package/dist/get-audio-codec.d.ts +1 -1
  45. package/dist/get-audio-codec.js +13 -13
  46. package/dist/get-duration.js +12 -14
  47. package/dist/get-tracks.js +2 -2
  48. package/dist/get-video-codec.js +13 -13
  49. package/dist/get-video-metadata.d.ts +2 -0
  50. package/dist/get-video-metadata.js +44 -0
  51. package/dist/parse-media.js +4 -1
  52. package/dist/parser-context.d.ts +1 -0
  53. package/dist/parser-state.js +3 -2
  54. package/dist/read-and-increment-offset.d.ts +28 -0
  55. package/dist/read-and-increment-offset.js +177 -0
  56. package/dist/samples-from-moof.d.ts +6 -0
  57. package/dist/samples-from-moof.js +74 -0
  58. package/dist/traversal.d.ts +19 -17
  59. package/dist/traversal.js +38 -39
  60. package/dist/understand-vorbis.d.ts +1 -0
  61. package/dist/understand-vorbis.js +12 -0
  62. package/input.webm +0 -0
  63. package/package.json +2 -2
  64. package/src/boxes/iso-base-media/get-sample-positions-from-track.ts +69 -0
  65. package/src/boxes/iso-base-media/make-track.ts +4 -45
  66. package/src/boxes/iso-base-media/mdat/mdat.ts +33 -24
  67. package/src/boxes/iso-base-media/mdhd.ts +10 -7
  68. package/src/boxes/iso-base-media/mvhd.ts +17 -16
  69. package/src/boxes/iso-base-media/process-box.ts +42 -0
  70. package/src/boxes/iso-base-media/stsd/keys.ts +1 -1
  71. package/src/boxes/iso-base-media/tfdt.ts +37 -0
  72. package/src/boxes/iso-base-media/tfhd.ts +66 -0
  73. package/src/boxes/iso-base-media/tkhd.ts +11 -13
  74. package/src/boxes/iso-base-media/trun.ts +74 -0
  75. package/src/boxes/webm/av1-codec-private.ts +1 -1
  76. package/src/boxes/webm/description.ts +7 -4
  77. package/src/boxes/webm/ebml.ts +24 -4
  78. package/src/boxes/webm/get-ready-tracks.ts +4 -4
  79. package/src/boxes/webm/get-sample-from-block.ts +125 -0
  80. package/src/boxes/webm/get-track.ts +40 -33
  81. package/src/boxes/webm/make-header.ts +58 -51
  82. package/src/boxes/webm/parse-ebml.ts +170 -16
  83. package/src/boxes/webm/segments/all-segments.ts +379 -62
  84. package/src/boxes/webm/segments/track-entry.ts +3 -846
  85. package/src/boxes/webm/segments.ts +18 -410
  86. package/src/boxes/webm/traversal.ts +17 -17
  87. package/src/buffer-iterator.ts +8 -6
  88. package/src/get-audio-codec.ts +14 -16
  89. package/src/get-duration.ts +55 -21
  90. package/src/get-tracks.ts +6 -6
  91. package/src/get-video-codec.ts +13 -15
  92. package/src/has-all-info.ts +1 -1
  93. package/src/parse-media.ts +7 -2
  94. package/src/parse-result.ts +7 -1
  95. package/src/parser-context.ts +1 -0
  96. package/src/parser-state.ts +2 -2
  97. package/src/samples-from-moof.ts +101 -0
  98. package/src/test/create-matroska.test.ts +237 -23
  99. package/src/test/matroska.test.ts +283 -348
  100. package/src/test/mvhd.test.ts +1 -1
  101. package/src/test/parse-esds.test.ts +2 -2
  102. package/src/test/parse-stco.test.ts +2 -2
  103. package/src/test/parse-stsc.test.ts +2 -2
  104. package/src/test/parse-stsz.test.ts +2 -2
  105. package/src/test/parse-stts.test.ts +1 -1
  106. package/src/test/samples-from-moof.test.ts +2496 -0
  107. package/src/test/stream-local.test.ts +28 -30
  108. package/src/test/stream-samples.test.ts +153 -231
  109. package/src/test/stsd.test.ts +4 -2
  110. package/src/test/tkhd.test.ts +1 -1
  111. package/src/traversal.ts +118 -86
  112. package/tsconfig.tsbuildinfo +1 -1
  113. package/dist/bitstream/av1.d.ts +0 -2
  114. package/dist/bitstream/av1.js +0 -12
  115. package/dist/boxes/iso-base-media/avcc-hvcc.d.ts +0 -20
  116. package/dist/boxes/iso-base-media/avcc-hvcc.js +0 -73
  117. package/dist/boxes/iso-base-media/avcc.d.ts +0 -18
  118. package/dist/boxes/iso-base-media/avcc.js +0 -27
  119. package/dist/boxes/iso-base-media/esds-descriptors.d.ts +0 -21
  120. package/dist/boxes/iso-base-media/esds-descriptors.js +0 -62
  121. package/dist/boxes/iso-base-media/esds.d.ts +0 -15
  122. package/dist/boxes/iso-base-media/esds.js +0 -27
  123. package/dist/from-input-type-file.d.ts +0 -2
  124. package/dist/from-input-type-file.js +0 -37
  125. package/dist/get-codec.d.ts +0 -4
  126. package/dist/get-codec.js +0 -22
  127. package/dist/web-file.d.ts +0 -2
  128. package/dist/web-file.js +0 -37
  129. package/src/boxes/webm/segments/duration.ts +0 -29
  130. package/src/boxes/webm/segments/info.ts +0 -34
  131. package/src/boxes/webm/segments/main.ts +0 -6
  132. package/src/boxes/webm/segments/muxing.ts +0 -18
  133. package/src/boxes/webm/segments/seek-head.ts +0 -34
  134. package/src/boxes/webm/segments/seek-position.ts +0 -18
  135. package/src/boxes/webm/segments/seek.ts +0 -55
  136. package/src/boxes/webm/segments/timestamp-scale.ts +0 -17
  137. package/src/boxes/webm/segments/tracks.ts +0 -32
  138. package/src/boxes/webm/segments/void.ts +0 -18
  139. package/src/boxes/webm/segments/writing.ts +0 -18
  140. package/src/combine-uint8array.ts +0 -13
  141. /package/dist/{get-samples.d.ts → boxes/webm/bitstream/av1/frame.d.ts} +0 -0
  142. /package/dist/{get-samples.js → boxes/webm/bitstream/av1/frame.js} +0 -0
  143. /package/dist/{sample-aspect-ratio.d.ts → boxes/webm/bitstream/h264/get-h264-descriptor.d.ts} +0 -0
  144. /package/dist/{sample-aspect-ratio.js → boxes/webm/bitstream/h264/get-h264-descriptor.js} +0 -0
@@ -1,43 +1,49 @@
1
+ import {getSamplePositionsFromTrack} from './boxes/iso-base-media/get-sample-positions-from-track';
2
+ import type {TrakBox} from './boxes/iso-base-media/trak/trak';
3
+ import type {DurationSegment} from './boxes/webm/segments/all-segments';
4
+ import {getTracks} from './get-tracks';
1
5
  import type {AnySegment} from './parse-result';
2
- import {getMoovBox, getMvhdBox} from './traversal';
6
+ import type {ParserState} from './parser-state';
7
+ import {getMoofBox, getMoovBox, getMvhdBox} from './traversal';
3
8
 
4
9
  const getDurationFromMatroska = (segments: AnySegment[]): number | null => {
5
- const mainSegment = segments.find((s) => s.type === 'main-segment');
6
- if (!mainSegment || mainSegment.type !== 'main-segment') {
10
+ const mainSegment = segments.find((s) => s.type === 'Segment');
11
+ if (!mainSegment || mainSegment.type !== 'Segment') {
7
12
  return null;
8
13
  }
9
14
 
10
- const {children} = mainSegment;
15
+ const {value: children} = mainSegment;
11
16
  if (!children) {
12
17
  return null;
13
18
  }
14
19
 
15
- const infoSegment = children.find((s) => s.type === 'info-segment');
20
+ const infoSegment = children.find((s) => s.type === 'Info');
16
21
 
17
22
  const relevantBoxes = [
18
- ...mainSegment.children,
19
- ...(infoSegment && infoSegment.type === 'info-segment'
20
- ? infoSegment.children
21
- : []),
23
+ ...mainSegment.value,
24
+ ...(infoSegment && infoSegment.type === 'Info' ? infoSegment.value : []),
22
25
  ];
23
26
 
24
- const timestampScale = relevantBoxes.find(
25
- (s) => s.type === 'timestamp-scale-segment',
26
- );
27
- if (!timestampScale || timestampScale.type !== 'timestamp-scale-segment') {
27
+ const timestampScale = relevantBoxes.find((s) => s.type === 'TimestampScale');
28
+ if (!timestampScale || timestampScale.type !== 'TimestampScale') {
28
29
  return null;
29
30
  }
30
31
 
31
- const duration = relevantBoxes.find((s) => s.type === 'duration-segment');
32
- if (!duration || duration.type !== 'duration-segment') {
32
+ const duration = relevantBoxes.find(
33
+ (s) => s.type === 'Duration',
34
+ ) as DurationSegment;
35
+ if (!duration || duration.type !== 'Duration') {
33
36
  return null;
34
37
  }
35
38
 
36
- return (duration.duration / timestampScale.timestampScale) * 1000;
39
+ return (duration.value.value / timestampScale.value.value) * 1000;
37
40
  };
38
41
 
39
- export const getDuration = (boxes: AnySegment[]): number | null => {
40
- const matroskaBox = boxes.find((b) => b.type === 'main-segment');
42
+ export const getDuration = (
43
+ boxes: AnySegment[],
44
+ parserState: ParserState,
45
+ ): number | null => {
46
+ const matroskaBox = boxes.find((b) => b.type === 'Segment');
41
47
  if (matroskaBox) {
42
48
  return getDurationFromMatroska(boxes);
43
49
  }
@@ -47,6 +53,7 @@ export const getDuration = (boxes: AnySegment[]): number | null => {
47
53
  return null;
48
54
  }
49
55
 
56
+ const moofBox = getMoofBox(boxes);
50
57
  const mvhdBox = getMvhdBox(moovBox);
51
58
 
52
59
  if (!mvhdBox) {
@@ -57,12 +64,39 @@ export const getDuration = (boxes: AnySegment[]): number | null => {
57
64
  throw new Error('Expected mvhd-box');
58
65
  }
59
66
 
60
- return mvhdBox.durationInSeconds;
67
+ if (mvhdBox.durationInSeconds > 0) {
68
+ return mvhdBox.durationInSeconds;
69
+ }
70
+
71
+ const tracks = getTracks(boxes, parserState);
72
+ const allTracks = [
73
+ ...tracks.videoTracks,
74
+ ...tracks.audioTracks,
75
+ ...tracks.otherTracks,
76
+ ];
77
+ const allSamples = allTracks.map((t) => {
78
+ const {timescale: ts} = t;
79
+ const samplePositions = getSamplePositionsFromTrack(
80
+ t.trakBox as TrakBox,
81
+ moofBox,
82
+ );
83
+
84
+ const highest = samplePositions
85
+ ?.map((sp) => (sp.cts + sp.duration) / ts)
86
+ .reduce((a, b) => Math.max(a, b), 0);
87
+ return highest ?? 0;
88
+ });
89
+ const highestTimestamp = Math.max(...allSamples);
90
+ return highestTimestamp;
61
91
  };
62
92
 
63
- export const hasDuration = (boxes: AnySegment[]): boolean => {
93
+ export const hasDuration = (
94
+ boxes: AnySegment[],
95
+ parserState: ParserState,
96
+ ): boolean => {
64
97
  try {
65
- return getDuration(boxes) !== null;
98
+ const duration = getDuration(boxes, parserState);
99
+ return getDuration(boxes, parserState) !== null && duration !== 0;
66
100
  } catch (err) {
67
101
  return false;
68
102
  }
package/src/get-tracks.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import {makeBaseMediaTrack} from './boxes/iso-base-media/make-track';
2
2
  import type {MoovBox} from './boxes/iso-base-media/moov/moov';
3
+ import type {TrakBox} from './boxes/iso-base-media/trak/trak';
3
4
  import {getTracksFromMatroska} from './boxes/webm/get-ready-tracks';
4
5
  import {getMainSegment} from './boxes/webm/traversal';
5
- import type {SamplePosition} from './get-sample-positions';
6
6
  import type {AnySegment} from './parse-result';
7
7
  import type {ParserState} from './parser-state';
8
8
  import {getMoovBox, getMvhdBox, getTracksSegment, getTraks} from './traversal';
@@ -14,7 +14,6 @@ type SampleAspectRatio = {
14
14
 
15
15
  export type VideoTrack = {
16
16
  type: 'video';
17
- samplePositions: SamplePosition[] | null;
18
17
  trackId: number;
19
18
  description: Uint8Array | undefined;
20
19
  timescale: number;
@@ -27,24 +26,25 @@ export type VideoTrack = {
27
26
  codedWidth: number;
28
27
  codedHeight: number;
29
28
  rotation: number;
29
+ trakBox: TrakBox | null;
30
30
  };
31
31
 
32
32
  export type AudioTrack = {
33
33
  type: 'audio';
34
- samplePositions: SamplePosition[] | null;
35
34
  trackId: number;
36
35
  timescale: number;
37
36
  codec: string;
38
37
  numberOfChannels: number;
39
38
  sampleRate: number;
40
39
  description: Uint8Array | undefined;
40
+ trakBox: TrakBox | null;
41
41
  };
42
42
 
43
43
  export type OtherTrack = {
44
44
  type: 'other';
45
- samplePositions: SamplePosition[] | null;
46
45
  trackId: number;
47
46
  timescale: number;
47
+ trakBox: TrakBox | null;
48
48
  };
49
49
 
50
50
  export type Track = VideoTrack | AudioTrack | OtherTrack;
@@ -89,8 +89,8 @@ export const getTracks = (
89
89
  const audioTracks: AudioTrack[] = [];
90
90
  const otherTracks: OtherTrack[] = [];
91
91
 
92
- const mainSegment = segments.find((s) => s.type === 'main-segment');
93
- if (mainSegment && mainSegment.type === 'main-segment') {
92
+ const mainSegment = segments.find((s) => s.type === 'Segment');
93
+ if (mainSegment && mainSegment.type === 'Segment') {
94
94
  const matroskaTracks = getTracksFromMatroska(
95
95
  mainSegment,
96
96
  state.getTimescale(),
@@ -76,39 +76,37 @@ export const getVideoCodec = (boxes: AnySegment[]): KnownVideoCodecs | null => {
76
76
  }
77
77
  }
78
78
 
79
- const mainSegment = boxes.find((b) => b.type === 'main-segment');
80
- if (!mainSegment || mainSegment.type !== 'main-segment') {
79
+ const mainSegment = boxes.find((b) => b.type === 'Segment');
80
+ if (!mainSegment || mainSegment.type !== 'Segment') {
81
81
  return null;
82
82
  }
83
83
 
84
- const tracksSegment = mainSegment.children.find(
85
- (b) => b.type === 'tracks-segment',
86
- );
87
- if (!tracksSegment || tracksSegment.type !== 'tracks-segment') {
84
+ const tracksSegment = mainSegment.value.find((b) => b.type === 'Tracks');
85
+ if (!tracksSegment || tracksSegment.type !== 'Tracks') {
88
86
  return null;
89
87
  }
90
88
 
91
- for (const track of tracksSegment.children) {
92
- if (track.type === 'track-entry-segment') {
93
- const trackType = track.children.find((b) => b.type === 'codec-segment');
94
- if (trackType && trackType.type === 'codec-segment') {
95
- if (trackType.codec === 'V_VP8') {
89
+ for (const track of tracksSegment.value) {
90
+ if (track.type === 'TrackEntry') {
91
+ const trackType = track.value.find((b) => b.type === 'CodecID');
92
+ if (trackType && trackType.type === 'CodecID') {
93
+ if (trackType.value === 'V_VP8') {
96
94
  return 'vp8';
97
95
  }
98
96
 
99
- if (trackType.codec === 'V_VP9') {
97
+ if (trackType.value === 'V_VP9') {
100
98
  return 'vp9';
101
99
  }
102
100
 
103
- if (trackType.codec === 'V_AV1') {
101
+ if (trackType.value === 'V_AV1') {
104
102
  return 'av1';
105
103
  }
106
104
 
107
- if (trackType.codec === 'V_MPEG4/ISO/AVC') {
105
+ if (trackType.value === 'V_MPEG4/ISO/AVC') {
108
106
  return 'h264';
109
107
  }
110
108
 
111
- if (trackType.codec === 'V_MPEGH/ISO/HEVC') {
109
+ if (trackType.value === 'V_MPEGH/ISO/HEVC') {
112
110
  return 'h265';
113
111
  }
114
112
  }
@@ -45,7 +45,7 @@ export const hasAllInfo = (
45
45
  }
46
46
 
47
47
  if (key === 'durationInSeconds') {
48
- return hasDuration(parseResult.segments);
48
+ return hasDuration(parseResult.segments, state);
49
49
  }
50
50
 
51
51
  if (
@@ -52,6 +52,11 @@ export const parseMedia: ParseMedia = async ({
52
52
  onAudioTrack: onAudioTrack ?? null,
53
53
  onVideoTrack: onVideoTrack ?? null,
54
54
  parserState: state,
55
+ nullifySamples: !(
56
+ typeof process !== 'undefined' &&
57
+ typeof process.env !== 'undefined' &&
58
+ process.env.KEEP_SAMPLES === 'true'
59
+ ),
55
60
  };
56
61
 
57
62
  while (parseResult === null || parseResult.status === 'incomplete') {
@@ -72,7 +77,7 @@ export const parseMedia: ParseMedia = async ({
72
77
 
73
78
  iterator = getArrayBufferIterator(
74
79
  result.value,
75
- contentLength ?? undefined,
80
+ contentLength ?? 1_000_000_000,
76
81
  );
77
82
  }
78
83
 
@@ -144,7 +149,7 @@ export const parseMedia: ParseMedia = async ({
144
149
  }
145
150
 
146
151
  if (fields?.durationInSeconds) {
147
- returnValue.durationInSeconds = getDuration(parseResult.segments);
152
+ returnValue.durationInSeconds = getDuration(parseResult.segments, state);
148
153
  }
149
154
 
150
155
  if (fields?.fps) {
@@ -19,8 +19,11 @@ import type {StsdBox} from './boxes/iso-base-media/stsd/stsd';
19
19
  import type {StssBox} from './boxes/iso-base-media/stsd/stss';
20
20
  import type {StszBox} from './boxes/iso-base-media/stsd/stsz';
21
21
  import type {SttsBox} from './boxes/iso-base-media/stsd/stts';
22
+ import type {TfdtBox} from './boxes/iso-base-media/tfdt';
23
+ import type {TfhdBox} from './boxes/iso-base-media/tfhd';
22
24
  import type {TkhdBox} from './boxes/iso-base-media/tkhd';
23
25
  import type {TrakBox} from './boxes/iso-base-media/trak/trak';
26
+ import type {TrunBox} from './boxes/iso-base-media/trun';
24
27
  import type {VoidBox} from './boxes/iso-base-media/void-box';
25
28
  import type {MatroskaSegment} from './boxes/webm/segments';
26
29
 
@@ -56,7 +59,10 @@ export type IsoBaseMediaBox =
56
59
  | PaspBox
57
60
  | CttsBox
58
61
  | Av1CBox
59
- | ColorParameterBox;
62
+ | TrunBox
63
+ | ColorParameterBox
64
+ | TfdtBox
65
+ | TfhdBox;
60
66
 
61
67
  export type AnySegment = MatroskaSegment | IsoBaseMediaBox;
62
68
 
@@ -6,4 +6,5 @@ export type ParserContext = {
6
6
  onVideoTrack: OnVideoTrack | null;
7
7
  canSkipVideoData: boolean;
8
8
  parserState: ParserState;
9
+ nullifySamples: boolean;
9
10
  };
@@ -40,8 +40,8 @@ export const makeParserState = ({
40
40
  const trackTimescale = getTrackTimestampScale(trackEntry);
41
41
 
42
42
  trackEntries[trackId] = {
43
- codec: codec.codec,
44
- trackTimescale,
43
+ codec: codec.value,
44
+ trackTimescale: trackTimescale?.value ?? null,
45
45
  };
46
46
  };
47
47
 
@@ -0,0 +1,101 @@
1
+ import type {SamplePosition} from './get-sample-positions';
2
+ import type {AnySegment, IsoBaseMediaBox} from './parse-result';
3
+ import {getTfdtBox, getTfhdBox, getTrunBoxes} from './traversal';
4
+
5
+ const getSamplesFromTraf = (
6
+ trafSegment: IsoBaseMediaBox,
7
+ moofOffset: number,
8
+ ): SamplePosition[] => {
9
+ if (trafSegment.type !== 'regular-box' || trafSegment.boxType !== 'traf') {
10
+ throw new Error('Expected traf-box');
11
+ }
12
+
13
+ const tfhdBox = getTfhdBox(trafSegment);
14
+ const defaultSampleDuration = tfhdBox?.defaultSampleDuration ?? null;
15
+ const defaultSampleSize = tfhdBox?.defaultSampleSize ?? null;
16
+ const defaultSampleFlags = tfhdBox?.defaultSampleFlags ?? null;
17
+
18
+ const tfdtBox = getTfdtBox(trafSegment);
19
+ const trunBoxes = getTrunBoxes(trafSegment);
20
+
21
+ let time = 0;
22
+ let offset = 0;
23
+
24
+ let dataOffset = 0;
25
+
26
+ const samples: SamplePosition[] = [];
27
+
28
+ for (const trunBox of trunBoxes) {
29
+ let i = -1;
30
+
31
+ if (dataOffset === 0 && trunBox.dataOffset) {
32
+ dataOffset = trunBox.dataOffset;
33
+ }
34
+
35
+ for (const sample of trunBox.samples) {
36
+ i++;
37
+ const duration = sample.sampleDuration ?? defaultSampleDuration;
38
+ if (duration === null) {
39
+ throw new Error('Expected duration');
40
+ }
41
+
42
+ const size = sample.sampleSize ?? defaultSampleSize;
43
+ if (size === null) {
44
+ throw new Error('Expected size');
45
+ }
46
+
47
+ const isFirstSample = i === 0;
48
+ const sampleFlags = sample.sampleFlags
49
+ ? sample.sampleFlags
50
+ : isFirstSample && trunBox.firstSampleFlags !== null
51
+ ? trunBox.firstSampleFlags
52
+ : defaultSampleFlags;
53
+ if (sampleFlags === null) {
54
+ throw new Error('Expected sample flags');
55
+ }
56
+
57
+ const keyframe = !((sampleFlags >> 16) & 0x1);
58
+
59
+ const dts = time + (tfdtBox?.baseMediaDecodeTime ?? 0);
60
+
61
+ const samplePosition: SamplePosition = {
62
+ offset: offset + (moofOffset ?? 0) + (dataOffset ?? 0),
63
+ dts,
64
+ cts: dts,
65
+ duration,
66
+ isKeyframe: keyframe,
67
+ size,
68
+ };
69
+ samples.push(samplePosition);
70
+ offset += size;
71
+ time += duration;
72
+ }
73
+ }
74
+
75
+ return samples;
76
+ };
77
+
78
+ export const getSamplesFromMoof = ({
79
+ moofBox,
80
+ trackId,
81
+ }: {
82
+ moofBox: AnySegment;
83
+ trackId: number;
84
+ }) => {
85
+ if (moofBox.type !== 'regular-box') {
86
+ throw new Error('Expected moof-box');
87
+ }
88
+
89
+ const trafs = moofBox.children.filter(
90
+ (c) => c.type === 'regular-box' && c.boxType === 'traf',
91
+ ) as IsoBaseMediaBox[];
92
+
93
+ const mapped = trafs.map((traf) => {
94
+ const tfhdBox = getTfhdBox(traf);
95
+
96
+ return tfhdBox?.trackId === trackId
97
+ ? getSamplesFromTraf(traf, moofBox.offset)
98
+ : [];
99
+ });
100
+ return mapped.flat(1);
101
+ };
@@ -1,29 +1,121 @@
1
- import {RenderInternals} from '@remotion/renderer';
1
+ /* eslint-disable padding-line-between-statements */
2
+ /* eslint-disable max-depth */
2
3
  import {expect, test} from 'bun:test';
3
- import {makeMatroskaHeader} from '../boxes/webm/make-header';
4
- import {
5
- matroskaHeader,
6
- seek,
7
- seekId,
4
+ import {combineUint8Arrays, makeMatroskaBytes} from '../boxes/webm/make-header';
5
+ import {parseEbml} from '../boxes/webm/parse-ebml';
6
+ import type {MatroskaSegment} from '../boxes/webm/segments';
7
+ import type {
8
+ MainSegment,
9
+ TrackEntry,
8
10
  } from '../boxes/webm/segments/all-segments';
11
+ import {getArrayBufferIterator} from '../buffer-iterator';
12
+ import {nodeReader} from '../from-node';
13
+ import {parseMedia} from '../parse-media';
14
+ import type {AnySegment} from '../parse-result';
15
+ import type {ParserContext} from '../parser-context';
16
+ import {makeParserState} from '../parser-state';
9
17
 
10
- test('Should make Matroska header that is same as input', async () => {
11
- const exampleVideo = RenderInternals.exampleVideos.matroskaMp3;
12
- const file = await Bun.file(exampleVideo).arrayBuffer();
18
+ const state = makeParserState({
19
+ hasAudioCallbacks: false,
20
+ hasVideoCallbacks: false,
21
+ signal: undefined,
22
+ });
13
23
 
14
- const headerInput = new Uint8Array(file.slice(0, 0x28));
24
+ const options: ParserContext = {
25
+ canSkipVideoData: true,
26
+ onAudioTrack: null,
27
+ onVideoTrack: null,
28
+ parserState: state,
29
+ nullifySamples: false,
30
+ };
15
31
 
16
- const headerOutput = makeMatroskaHeader(matroskaHeader, {
17
- DocType: 'matroska',
18
- DocTypeVersion: 4,
19
- DocTypeReadVersion: 2,
20
- EBMLMaxIDLength: 4,
21
- EBMLMaxSizeLength: 8,
22
- EBMLReadVersion: 1,
23
- EBMLVersion: 1,
32
+ test('Should make Matroska header that is same as input', async () => {
33
+ const headerOutput = makeMatroskaBytes({
34
+ type: 'Header',
35
+ minVintWidth: 1,
36
+ value: [
37
+ {
38
+ type: 'EBMLVersion',
39
+ value: {value: 1, byteLength: 1},
40
+ minVintWidth: 1,
41
+ },
42
+ {
43
+ type: 'EBMLReadVersion',
44
+ value: {value: 1, byteLength: 1},
45
+ minVintWidth: 1,
46
+ },
47
+ {
48
+ type: 'EBMLMaxIDLength',
49
+ value: {value: 4, byteLength: 1},
50
+ minVintWidth: 1,
51
+ },
52
+ {
53
+ type: 'EBMLMaxSizeLength',
54
+ value: {value: 8, byteLength: 1},
55
+ minVintWidth: 1,
56
+ },
57
+ {
58
+ type: 'DocType',
59
+ value: 'webm',
60
+ minVintWidth: 1,
61
+ },
62
+ {
63
+ type: 'DocTypeVersion',
64
+ value: {value: 4, byteLength: 1},
65
+ minVintWidth: 1,
66
+ },
67
+ {
68
+ type: 'DocTypeReadVersion',
69
+ value: {value: 2, byteLength: 1},
70
+ minVintWidth: 1,
71
+ },
72
+ ],
24
73
  });
25
74
 
26
- expect(headerInput).toEqual(headerOutput);
75
+ const iterator = getArrayBufferIterator(headerOutput, headerOutput.length);
76
+ const parsed = await parseEbml(iterator, options);
77
+
78
+ expect(parsed).toEqual({
79
+ type: 'Header',
80
+ minVintWidth: 1,
81
+ value: [
82
+ {
83
+ type: 'EBMLVersion',
84
+ value: {value: 1, byteLength: 1},
85
+ minVintWidth: 1,
86
+ },
87
+ {
88
+ type: 'EBMLReadVersion',
89
+ value: {value: 1, byteLength: 1},
90
+ minVintWidth: 1,
91
+ },
92
+ {
93
+ type: 'EBMLMaxIDLength',
94
+ value: {value: 4, byteLength: 1},
95
+ minVintWidth: 1,
96
+ },
97
+ {
98
+ type: 'EBMLMaxSizeLength',
99
+ value: {value: 8, byteLength: 1},
100
+ minVintWidth: 1,
101
+ },
102
+ {
103
+ type: 'DocType',
104
+ value: 'webm',
105
+ minVintWidth: 1,
106
+ },
107
+ {
108
+ type: 'DocTypeVersion',
109
+ value: {value: 4, byteLength: 1},
110
+ minVintWidth: 1,
111
+ },
112
+ {
113
+ type: 'DocTypeReadVersion',
114
+ value: {value: 2, byteLength: 1},
115
+ minVintWidth: 1,
116
+ },
117
+ ],
118
+ });
27
119
  });
28
120
 
29
121
  test('Should be able to create SeekIdBox', async () => {
@@ -31,7 +123,11 @@ test('Should be able to create SeekIdBox', async () => {
31
123
  await Bun.file('vp8-segments/56-0x53ab').arrayBuffer(),
32
124
  );
33
125
 
34
- const custom = makeMatroskaHeader(seekId, '0x1549a966');
126
+ const custom = makeMatroskaBytes({
127
+ type: 'SeekID',
128
+ value: '0x1549a966',
129
+ minVintWidth: 1,
130
+ });
35
131
  expect(custom).toEqual(file);
36
132
  });
37
133
 
@@ -39,10 +135,128 @@ test('Should be able to create Seek', async () => {
39
135
  const file = new Uint8Array(
40
136
  await Bun.file('vp8-segments/53-0x4dbb').arrayBuffer(),
41
137
  );
138
+ const parsed = await parseEbml(getArrayBufferIterator(file, null), options);
139
+ expect(parsed).toEqual({
140
+ type: 'Seek',
141
+ value: [
142
+ {
143
+ type: 'SeekID',
144
+ value: '0x1549a966',
145
+ minVintWidth: 1,
146
+ },
147
+ {
148
+ type: 'SeekPosition',
149
+ value: {value: 0x40, byteLength: 1},
150
+ minVintWidth: 1,
151
+ },
152
+ ],
153
+ minVintWidth: 1,
154
+ });
155
+
156
+ const custom = makeMatroskaBytes(parsed);
157
+ expect(custom).toEqual(file);
158
+ });
159
+
160
+ test('Should parse seekHead', async () => {
161
+ const file = new Uint8Array(
162
+ await Bun.file('vp8-segments/2165155-0x114d9b74').arrayBuffer(),
163
+ );
164
+
165
+ const seekPosition = makeMatroskaBytes({
166
+ type: 'SeekPosition',
167
+ value: {value: 3911, byteLength: 2},
168
+ minVintWidth: 1,
169
+ });
42
170
 
43
- const custom = makeMatroskaHeader(seek, {
44
- SeekID: '0x1549a966',
45
- SeekPosition: 0x40,
171
+ const custom = makeMatroskaBytes({
172
+ type: 'SeekHead',
173
+ value: [
174
+ {
175
+ type: 'Seek',
176
+ value: [
177
+ {
178
+ type: 'SeekID',
179
+ value: '0x1f43b675',
180
+ minVintWidth: 1,
181
+ },
182
+ seekPosition,
183
+ ],
184
+ minVintWidth: 1,
185
+ },
186
+ ],
187
+ minVintWidth: 1,
46
188
  });
47
189
  expect(custom).toEqual(file);
190
+
191
+ const iterator = getArrayBufferIterator(file, file.length);
192
+ const parsed = await parseEbml(iterator, options);
193
+
194
+ expect(parsed).toEqual({
195
+ minVintWidth: 1,
196
+ type: 'SeekHead',
197
+ value: [
198
+ {
199
+ minVintWidth: 1,
200
+ type: 'Seek',
201
+ value: [
202
+ {minVintWidth: 1, type: 'SeekID', value: '0x1f43b675'},
203
+ {
204
+ minVintWidth: 1,
205
+ type: 'SeekPosition',
206
+ value: {value: 3911, byteLength: 2},
207
+ },
208
+ ],
209
+ },
210
+ ],
211
+ });
212
+ });
213
+
214
+ const parseWebm = async (str: string) => {
215
+ const {boxes} = await parseMedia({
216
+ src: str,
217
+ fields: {
218
+ boxes: true,
219
+ },
220
+ reader: nodeReader,
221
+ });
222
+ return boxes;
223
+ };
224
+
225
+ const stringifyWebm = (boxes: AnySegment[]) => {
226
+ const buffers: Uint8Array[] = [];
227
+ for (const box of boxes) {
228
+ const bytes = makeMatroskaBytes(box as MatroskaSegment);
229
+ buffers.push(bytes);
230
+ }
231
+
232
+ const combined = combineUint8Arrays(buffers);
233
+ return combined;
234
+ };
235
+
236
+ test('Can we disassemble a Matroska file and assembled it again', async () => {
237
+ process.env.KEEP_SAMPLES = 'true';
238
+
239
+ const parsed = await parseWebm('input.webm');
240
+
241
+ const segment = parsed.find((s) => s.type === 'Segment') as MainSegment;
242
+ const tracks = segment.value.flatMap((s) =>
243
+ s.type === 'Tracks' ? s.value.filter((a) => a.type === 'TrackEntry') : [],
244
+ );
245
+ for (const track of tracks as TrackEntry[]) {
246
+ for (const child of track.value) {
247
+ if (child.type === 'Video') {
248
+ for (const attribute of child.value) {
249
+ if (attribute.type === 'DisplayHeight') {
250
+ attribute.value.value *= 2;
251
+ }
252
+ }
253
+ }
254
+ }
255
+ }
256
+
257
+ const bytes = await Bun.file('input.webm').arrayBuffer();
258
+ expect(stringifyWebm(parsed).byteLength).toEqual(
259
+ new Uint8Array(bytes).byteLength,
260
+ );
261
+ process.env.KEEP_SAMPLES = 'false';
48
262
  });