@remotion/media-parser 4.0.191

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 (143) hide show
  1. package/.eslintrc +8 -0
  2. package/LICENSE.md +49 -0
  3. package/README.md +18 -0
  4. package/dist/boxes/iso-base-media/base-type.d.ts +4 -0
  5. package/dist/boxes/iso-base-media/base-type.js +2 -0
  6. package/dist/boxes/iso-base-media/ftyp.d.ts +13 -0
  7. package/dist/boxes/iso-base-media/ftyp.js +22 -0
  8. package/dist/boxes/iso-base-media/moov/moov.d.ts +12 -0
  9. package/dist/boxes/iso-base-media/moov/moov.js +22 -0
  10. package/dist/boxes/iso-base-media/mvhd.d.ts +30 -0
  11. package/dist/boxes/iso-base-media/mvhd.js +59 -0
  12. package/dist/boxes/iso-base-media/process-box.d.ts +8 -0
  13. package/dist/boxes/iso-base-media/process-box.js +174 -0
  14. package/dist/boxes/iso-base-media/stsd/keys.d.ts +5 -0
  15. package/dist/boxes/iso-base-media/stsd/keys.js +21 -0
  16. package/dist/boxes/iso-base-media/stsd/mebx.d.ts +14 -0
  17. package/dist/boxes/iso-base-media/stsd/mebx.js +27 -0
  18. package/dist/boxes/iso-base-media/stsd/samples.d.ts +48 -0
  19. package/dist/boxes/iso-base-media/stsd/samples.js +215 -0
  20. package/dist/boxes/iso-base-media/stsd/stsd.d.ts +13 -0
  21. package/dist/boxes/iso-base-media/stsd/stsd.js +26 -0
  22. package/dist/boxes/iso-base-media/stts/stts.d.ts +15 -0
  23. package/dist/boxes/iso-base-media/stts/stts.js +35 -0
  24. package/dist/boxes/iso-base-media/tkhd.d.ts +22 -0
  25. package/dist/boxes/iso-base-media/tkhd.js +63 -0
  26. package/dist/boxes/iso-base-media/to-date.d.ts +1 -0
  27. package/dist/boxes/iso-base-media/to-date.js +11 -0
  28. package/dist/boxes/iso-base-media/trak/trak.d.ts +12 -0
  29. package/dist/boxes/iso-base-media/trak/trak.js +22 -0
  30. package/dist/boxes/webm/parse-webm-header.d.ts +3 -0
  31. package/dist/boxes/webm/parse-webm-header.js +16 -0
  32. package/dist/boxes/webm/segments/duration.d.ts +6 -0
  33. package/dist/boxes/webm/segments/duration.js +15 -0
  34. package/dist/boxes/webm/segments/info.d.ts +8 -0
  35. package/dist/boxes/webm/segments/info.js +14 -0
  36. package/dist/boxes/webm/segments/main.d.ts +7 -0
  37. package/dist/boxes/webm/segments/main.js +13 -0
  38. package/dist/boxes/webm/segments/muxing.d.ts +6 -0
  39. package/dist/boxes/webm/segments/muxing.js +12 -0
  40. package/dist/boxes/webm/segments/parse-children.d.ts +3 -0
  41. package/dist/boxes/webm/segments/parse-children.js +17 -0
  42. package/dist/boxes/webm/segments/seek-head.d.ts +8 -0
  43. package/dist/boxes/webm/segments/seek-head.js +13 -0
  44. package/dist/boxes/webm/segments/seek-position.d.ts +6 -0
  45. package/dist/boxes/webm/segments/seek-position.js +12 -0
  46. package/dist/boxes/webm/segments/seek.d.ts +8 -0
  47. package/dist/boxes/webm/segments/seek.js +16 -0
  48. package/dist/boxes/webm/segments/timestamp-scale.d.ts +6 -0
  49. package/dist/boxes/webm/segments/timestamp-scale.js +11 -0
  50. package/dist/boxes/webm/segments/track-entry.d.ts +71 -0
  51. package/dist/boxes/webm/segments/track-entry.js +175 -0
  52. package/dist/boxes/webm/segments/tracks.d.ts +7 -0
  53. package/dist/boxes/webm/segments/tracks.js +13 -0
  54. package/dist/boxes/webm/segments/unknown.d.ts +6 -0
  55. package/dist/boxes/webm/segments/unknown.js +11 -0
  56. package/dist/boxes/webm/segments/void.d.ts +6 -0
  57. package/dist/boxes/webm/segments/void.js +12 -0
  58. package/dist/boxes/webm/segments/writing.d.ts +6 -0
  59. package/dist/boxes/webm/segments/writing.js +12 -0
  60. package/dist/boxes/webm/segments.d.ts +16 -0
  61. package/dist/boxes/webm/segments.js +106 -0
  62. package/dist/buffer-iterator.d.ts +36 -0
  63. package/dist/buffer-iterator.js +263 -0
  64. package/dist/from-node.d.ts +2 -0
  65. package/dist/from-node.js +19 -0
  66. package/dist/from-web.d.ts +2 -0
  67. package/dist/from-web.js +40 -0
  68. package/dist/get-dimensions.d.ts +7 -0
  69. package/dist/get-dimensions.js +104 -0
  70. package/dist/get-duration.d.ts +3 -0
  71. package/dist/get-duration.js +61 -0
  72. package/dist/get-fps.d.ts +3 -0
  73. package/dist/get-fps.js +70 -0
  74. package/dist/has-all-info.d.ts +3 -0
  75. package/dist/has-all-info.js +27 -0
  76. package/dist/index.d.ts +1 -0
  77. package/dist/index.js +5 -0
  78. package/dist/options.d.ts +19 -0
  79. package/dist/options.js +2 -0
  80. package/dist/parse-media.d.ts +2 -0
  81. package/dist/parse-media.js +44 -0
  82. package/dist/parse-result.d.ts +29 -0
  83. package/dist/parse-result.js +2 -0
  84. package/dist/parse-video.d.ts +10 -0
  85. package/dist/parse-video.js +29 -0
  86. package/dist/reader.d.ts +7 -0
  87. package/dist/reader.js +2 -0
  88. package/package.json +47 -0
  89. package/src/boxes/iso-base-media/base-type.ts +4 -0
  90. package/src/boxes/iso-base-media/ftyp.ts +39 -0
  91. package/src/boxes/iso-base-media/moov/moov.ts +37 -0
  92. package/src/boxes/iso-base-media/mvhd.ts +107 -0
  93. package/src/boxes/iso-base-media/process-box.ts +236 -0
  94. package/src/boxes/iso-base-media/stsd/keys.ts +25 -0
  95. package/src/boxes/iso-base-media/stsd/mebx.ts +46 -0
  96. package/src/boxes/iso-base-media/stsd/samples.ts +298 -0
  97. package/src/boxes/iso-base-media/stsd/stsd.ts +48 -0
  98. package/src/boxes/iso-base-media/stts/stts.ts +62 -0
  99. package/src/boxes/iso-base-media/tkhd.ts +105 -0
  100. package/src/boxes/iso-base-media/to-date.ts +9 -0
  101. package/src/boxes/iso-base-media/trak/trak.ts +37 -0
  102. package/src/boxes/webm/parse-webm-header.ts +18 -0
  103. package/src/boxes/webm/segments/duration.ts +22 -0
  104. package/src/boxes/webm/segments/info.ts +20 -0
  105. package/src/boxes/webm/segments/main.ts +19 -0
  106. package/src/boxes/webm/segments/muxing.ts +18 -0
  107. package/src/boxes/webm/segments/parse-children.ts +21 -0
  108. package/src/boxes/webm/segments/seek-head.ts +21 -0
  109. package/src/boxes/webm/segments/seek-position.ts +19 -0
  110. package/src/boxes/webm/segments/seek.ts +23 -0
  111. package/src/boxes/webm/segments/timestamp-scale.ts +17 -0
  112. package/src/boxes/webm/segments/track-entry.ts +295 -0
  113. package/src/boxes/webm/segments/tracks.ts +22 -0
  114. package/src/boxes/webm/segments/unknown.ts +19 -0
  115. package/src/boxes/webm/segments/void.ts +16 -0
  116. package/src/boxes/webm/segments/writing.ts +18 -0
  117. package/src/boxes/webm/segments.ts +206 -0
  118. package/src/buffer-iterator.ts +305 -0
  119. package/src/from-node.ts +22 -0
  120. package/src/from-web.ts +53 -0
  121. package/src/get-dimensions.ts +147 -0
  122. package/src/get-duration.ts +73 -0
  123. package/src/get-fps.ts +92 -0
  124. package/src/has-all-info.ts +34 -0
  125. package/src/index.ts +1 -0
  126. package/src/options.ts +38 -0
  127. package/src/parse-media.ts +57 -0
  128. package/src/parse-result.ts +44 -0
  129. package/src/parse-video.ts +41 -0
  130. package/src/reader.ts +10 -0
  131. package/src/test/duration.test.ts +34 -0
  132. package/src/test/keys.test.ts +47 -0
  133. package/src/test/matroska.test.ts +163 -0
  134. package/src/test/mvhd.test.ts +89 -0
  135. package/src/test/parse-stts.test.ts +38 -0
  136. package/src/test/parse-video.test.ts +113 -0
  137. package/src/test/parse-webm.test.ts +15 -0
  138. package/src/test/stream-local.test.ts +105 -0
  139. package/src/test/stream-remote.test.ts +12 -0
  140. package/src/test/stsd.test.ts +162 -0
  141. package/src/test/tkhd.test.ts +84 -0
  142. package/tsconfig.json +10 -0
  143. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,298 @@
1
+ import type {BufferIterator} from '../../../buffer-iterator';
2
+
3
+ type SampleBase = {
4
+ format: string;
5
+ offset: number;
6
+ dataReferenceIndex: number;
7
+ version: number;
8
+ revisionLevel: number;
9
+ vendor: number[];
10
+ size: number;
11
+ };
12
+
13
+ type AudioSample = SampleBase & {
14
+ type: 'audio';
15
+ numberOfChannels: number;
16
+ sampleSize: number;
17
+ compressionId: number;
18
+ packetSize: number;
19
+ sampleRate: number;
20
+ samplesPerPacket: number | null;
21
+ bytesPerPacket: number | null;
22
+ bytesPerFrame: number | null;
23
+ bitsPerSample: number | null;
24
+ };
25
+
26
+ type VideoSample = SampleBase & {
27
+ type: 'video';
28
+ temporalQuality: number;
29
+ spacialQuality: number;
30
+ width: number;
31
+ height: number;
32
+ compressorName: number[];
33
+ horizontalResolutionPpi: number;
34
+ verticalResolutionPpi: number;
35
+ dataSize: number;
36
+ frameCountPerSample: number;
37
+ depth: number;
38
+ colorTableId: number;
39
+ };
40
+
41
+ type UnknownSample = SampleBase & {
42
+ type: 'unknown';
43
+ };
44
+
45
+ export type Sample = AudioSample | VideoSample | UnknownSample;
46
+
47
+ type SampleAndNext = {
48
+ sample: Sample | null;
49
+ };
50
+
51
+ // https://developer.apple.com/documentation/quicktime-file-format/video_sample_description
52
+ const videoTags = [
53
+ 'cvid',
54
+ 'jpeg',
55
+ 'smc ',
56
+ 'rle ',
57
+ 'rpza',
58
+ 'kpcd',
59
+ 'png ',
60
+ 'mjpa',
61
+ 'mjpb',
62
+ 'SVQ1',
63
+ 'SVQ3',
64
+ 'mp4v',
65
+ 'avc1',
66
+ 'dvc ',
67
+ 'dvcp',
68
+ 'gif ',
69
+ 'h263',
70
+ 'tiff',
71
+ 'raw ',
72
+ '2vuY',
73
+ 'yuv2',
74
+ 'v308',
75
+ 'v408',
76
+ 'v216',
77
+ 'v410',
78
+ 'v210',
79
+ 'hvc1',
80
+ ];
81
+
82
+ // https://developer.apple.com/documentation/quicktime-file-format/sound_sample_descriptions
83
+ const audioTags = [
84
+ 0x00000000,
85
+ 'NONE',
86
+ 'raw ',
87
+ 'twos',
88
+ 'sowt',
89
+ 'MAC3 ',
90
+ 'MAC6 ',
91
+ 'ima4',
92
+ 'fl32',
93
+ 'fl64',
94
+ 'in24',
95
+ 'in32',
96
+ 'ulaw',
97
+ 'alaw',
98
+ 0x6d730002,
99
+ 0x6d730011,
100
+ 'dvca',
101
+ 'QDMC',
102
+ 'QDM2',
103
+ 'Qclp',
104
+ 0x6d730055,
105
+ '.mp3',
106
+ 'mp4a',
107
+ 'ac-3',
108
+ ];
109
+
110
+ export const processSample = ({
111
+ iterator,
112
+ }: {
113
+ iterator: BufferIterator;
114
+ }): SampleAndNext => {
115
+ const fileOffset = iterator.counter.getOffset();
116
+ const bytesRemaining = iterator.bytesRemaining();
117
+ const boxSize = iterator.getUint32();
118
+
119
+ if (bytesRemaining < boxSize) {
120
+ throw new Error(`Expected box size of ${bytesRemaining}, got ${boxSize}`);
121
+ }
122
+
123
+ const boxFormat = iterator.getAtom();
124
+
125
+ const isVideo = videoTags.includes(boxFormat);
126
+ const isAudio =
127
+ audioTags.includes(boxFormat) || audioTags.includes(Number(boxFormat));
128
+
129
+ // 6 reserved bytes
130
+ iterator.discard(6);
131
+
132
+ const dataReferenceIndex = iterator.getUint16();
133
+ const version = iterator.getUint16();
134
+ const revisionLevel = iterator.getUint16();
135
+ const vendor = iterator.getSlice(4);
136
+
137
+ if (!isVideo && !isAudio) {
138
+ const bytesRemainingInBox =
139
+ boxSize - (iterator.counter.getOffset() - fileOffset);
140
+ iterator.discard(bytesRemainingInBox);
141
+
142
+ return {
143
+ sample: {
144
+ type: 'unknown',
145
+ offset: fileOffset,
146
+ dataReferenceIndex,
147
+ version,
148
+ revisionLevel,
149
+ vendor: [...Array.from(new Uint8Array(vendor))],
150
+ size: boxSize,
151
+ format: boxFormat,
152
+ },
153
+ };
154
+ }
155
+
156
+ if (isAudio) {
157
+ if (version === 0) {
158
+ const numberOfChannels = iterator.getUint16();
159
+ const sampleSize = iterator.getUint16();
160
+ const compressionId = iterator.getUint16();
161
+ const packetSize = iterator.getUint16();
162
+ const sampleRate = iterator.getFixedPoint1616Number();
163
+
164
+ const bytesRemainingInBox =
165
+ boxSize - (iterator.counter.getOffset() - fileOffset);
166
+ iterator.discard(bytesRemainingInBox);
167
+
168
+ return {
169
+ sample: {
170
+ format: boxFormat,
171
+ offset: fileOffset,
172
+ dataReferenceIndex,
173
+ version,
174
+ revisionLevel,
175
+ vendor: [...Array.from(new Uint8Array(vendor))],
176
+ size: boxSize,
177
+ type: 'audio',
178
+ numberOfChannels,
179
+ sampleSize,
180
+ compressionId,
181
+ packetSize,
182
+ sampleRate,
183
+ samplesPerPacket: null,
184
+ bytesPerPacket: null,
185
+ bytesPerFrame: null,
186
+ bitsPerSample: null,
187
+ },
188
+ };
189
+ }
190
+
191
+ if (version === 1) {
192
+ const numberOfChannels = iterator.getUint16();
193
+ const sampleSize = iterator.getUint16();
194
+ const compressionId = iterator.getUint16();
195
+ const packetSize = iterator.getUint16();
196
+ const sampleRate = iterator.getFixedPoint1616Number();
197
+ const samplesPerPacket = iterator.getUint16();
198
+ const bytesPerPacket = iterator.getUint16();
199
+ const bytesPerFrame = iterator.getUint16();
200
+ const bitsPerSample = iterator.getUint16();
201
+
202
+ const bytesRemainingInBox =
203
+ boxSize - (iterator.counter.getOffset() - fileOffset);
204
+ iterator.discard(bytesRemainingInBox);
205
+
206
+ return {
207
+ sample: {
208
+ format: boxFormat,
209
+ offset: fileOffset,
210
+ dataReferenceIndex,
211
+ version,
212
+ revisionLevel,
213
+ vendor: [...Array.from(new Uint8Array(vendor))],
214
+ size: boxSize,
215
+ type: 'audio',
216
+ numberOfChannels,
217
+ sampleSize,
218
+ compressionId,
219
+ packetSize,
220
+ sampleRate,
221
+ samplesPerPacket,
222
+ bytesPerPacket,
223
+ bytesPerFrame,
224
+ bitsPerSample,
225
+ },
226
+ };
227
+ }
228
+
229
+ throw new Error(`Unsupported version ${version}`);
230
+ }
231
+
232
+ if (isVideo) {
233
+ const temporalQuality = iterator.getUint32();
234
+ const spacialQuality = iterator.getUint32();
235
+ const width = iterator.getUint16();
236
+ const height = iterator.getUint16();
237
+ const horizontalResolution = iterator.getFixedPoint1616Number();
238
+ const verticalResolution = iterator.getFixedPoint1616Number();
239
+ const dataSize = iterator.getUint32();
240
+ const frameCountPerSample = iterator.getUint16();
241
+ const compressorName = iterator.getPascalString();
242
+ const depth = iterator.getUint16();
243
+ const colorTableId = iterator.getInt16();
244
+
245
+ const bytesRemainingInBox =
246
+ boxSize - (iterator.counter.getOffset() - fileOffset);
247
+ iterator.discard(bytesRemainingInBox);
248
+
249
+ return {
250
+ sample: {
251
+ format: boxFormat,
252
+ offset: fileOffset,
253
+ dataReferenceIndex,
254
+ version,
255
+ revisionLevel,
256
+ vendor: [...Array.from(new Uint8Array(vendor))],
257
+ size: boxSize,
258
+ type: 'video',
259
+ width,
260
+ height,
261
+ horizontalResolutionPpi: horizontalResolution,
262
+ verticalResolutionPpi: verticalResolution,
263
+ spacialQuality,
264
+ temporalQuality,
265
+ dataSize,
266
+ frameCountPerSample,
267
+ compressorName,
268
+ depth,
269
+ colorTableId,
270
+ },
271
+ };
272
+ }
273
+
274
+ throw new Error(`Unknown sample format ${boxFormat}`);
275
+ };
276
+
277
+ export const parseSamples = (
278
+ iterator: BufferIterator,
279
+ maxBytes: number,
280
+ ): Sample[] => {
281
+ const samples: Sample[] = [];
282
+ const initialOffset = iterator.counter.getOffset();
283
+
284
+ while (
285
+ iterator.bytesRemaining() > 0 &&
286
+ iterator.counter.getOffset() - initialOffset < maxBytes
287
+ ) {
288
+ const {sample} = processSample({
289
+ iterator,
290
+ });
291
+
292
+ if (sample) {
293
+ samples.push(sample);
294
+ }
295
+ }
296
+
297
+ return samples;
298
+ };
@@ -0,0 +1,48 @@
1
+ import type {BufferIterator} from '../../../buffer-iterator';
2
+ import type {BaseBox} from '../base-type';
3
+ import type {Sample} from './samples';
4
+ import {parseSamples} from './samples';
5
+
6
+ export interface StsdBox extends BaseBox {
7
+ type: 'stsd-box';
8
+ numberOfEntries: number;
9
+ samples: Sample[];
10
+ }
11
+
12
+ export const parseStsd = ({
13
+ iterator,
14
+ offset,
15
+ size,
16
+ }: {
17
+ iterator: BufferIterator;
18
+ offset: number;
19
+ size: number;
20
+ }): StsdBox => {
21
+ const version = iterator.getUint8();
22
+ if (version !== 0) {
23
+ throw new Error(`Unsupported STSD version ${version}`);
24
+ }
25
+
26
+ // flags, we discard them
27
+ iterator.discard(3);
28
+
29
+ const numberOfEntries = iterator.getUint32();
30
+
31
+ const bytesRemainingInBox = size - (iterator.counter.getOffset() - offset);
32
+
33
+ const boxes = parseSamples(iterator, bytesRemainingInBox);
34
+
35
+ if (boxes.length !== numberOfEntries) {
36
+ throw new Error(
37
+ `Expected ${numberOfEntries} sample descriptions, got ${boxes.length}`,
38
+ );
39
+ }
40
+
41
+ return {
42
+ type: 'stsd-box',
43
+ boxSize: size,
44
+ offset,
45
+ numberOfEntries,
46
+ samples: boxes,
47
+ };
48
+ };
@@ -0,0 +1,62 @@
1
+ import type {BufferIterator} from '../../../buffer-iterator';
2
+
3
+ export interface SttsBox {
4
+ type: 'stts-box';
5
+ sampleDistribution: SampleDistribution[];
6
+ }
7
+
8
+ type SampleDistribution = {
9
+ sampleCount: number;
10
+ sampleDelta: number;
11
+ };
12
+
13
+ export const parseStts = ({
14
+ data,
15
+ size,
16
+ fileOffset,
17
+ }: {
18
+ data: BufferIterator;
19
+ size: number;
20
+ fileOffset: number;
21
+ }): SttsBox => {
22
+ const initialOffset = data.counter.getOffset();
23
+ const initialCounter = initialOffset - fileOffset;
24
+
25
+ const version = data.getUint8();
26
+ if (version !== 0) {
27
+ throw new Error(`Unsupported STTS version ${version}`);
28
+ }
29
+
30
+ // flags, we discard them
31
+ data.discard(3);
32
+
33
+ // entry count
34
+ const entryCount = data.getUint32();
35
+
36
+ const sampleDistributions: SampleDistribution[] = [];
37
+
38
+ // entries
39
+ for (let i = 0; i < entryCount; i++) {
40
+ const sampleCount = data.getUint32();
41
+ const sampleDelta = data.getUint32();
42
+
43
+ const sampleDistribution: SampleDistribution = {
44
+ sampleCount,
45
+ sampleDelta,
46
+ };
47
+ sampleDistributions.push(sampleDistribution);
48
+ }
49
+
50
+ const bytesUsed = data.counter.getOffset() - initialOffset + initialCounter;
51
+
52
+ if (bytesUsed !== size) {
53
+ throw new Error(
54
+ `Expected stts box to be ${size} bytes, but was ${bytesUsed} bytes`,
55
+ );
56
+ }
57
+
58
+ return {
59
+ type: 'stts-box',
60
+ sampleDistribution: sampleDistributions,
61
+ };
62
+ };
@@ -0,0 +1,105 @@
1
+ import type {BufferIterator} from '../../buffer-iterator';
2
+ import type {BaseBox} from './base-type';
3
+ import type {ThreeDMatrix} from './mvhd';
4
+ import {toUnixTimestamp} from './to-date';
5
+
6
+ export interface TkhdBox extends BaseBox {
7
+ type: 'tkhd-box';
8
+ alternateGroup: number;
9
+ creationTime: number | null;
10
+ duration: number;
11
+ modificationTime: number | null;
12
+ trackId: number;
13
+ version: number;
14
+ layer: number;
15
+ volume: number;
16
+ matrix: ThreeDMatrix;
17
+ width: number;
18
+ height: number;
19
+ }
20
+
21
+ export const parseTkhd = ({
22
+ iterator,
23
+ offset,
24
+ size,
25
+ }: {
26
+ iterator: BufferIterator;
27
+ offset: number;
28
+ size: number;
29
+ }): TkhdBox => {
30
+ if (size !== 92) {
31
+ throw new Error(`Expected tkhd size of version 0 to be 92, got ${size}`);
32
+ }
33
+
34
+ const version = iterator.getUint8();
35
+ if (version !== 0) {
36
+ throw new Error(`Unsupported TKHD version ${version}`);
37
+ }
38
+
39
+ // Flags, we discard them
40
+ iterator.discard(3);
41
+
42
+ const creationTime = iterator.getUint32();
43
+
44
+ const modificationTime = iterator.getUint32();
45
+
46
+ const trackId = iterator.getUint32();
47
+
48
+ // reserved
49
+ iterator.discard(4);
50
+
51
+ const duration = iterator.getUint32();
52
+
53
+ // reserved 2
54
+ iterator.discard(4);
55
+
56
+ // reserved 3
57
+ iterator.discard(4);
58
+
59
+ const layer = iterator.getUint16();
60
+
61
+ const alternateGroup = iterator.getUint16();
62
+
63
+ const volume = iterator.getUint16();
64
+
65
+ // reserved 4
66
+ iterator.discard(2);
67
+
68
+ const matrix = [
69
+ iterator.getUint32(),
70
+ iterator.getUint32(),
71
+ iterator.getUint32(),
72
+ iterator.getUint32(),
73
+ iterator.getUint32(),
74
+ iterator.getUint32(),
75
+ iterator.getUint32(),
76
+ iterator.getUint32(),
77
+ iterator.getUint32(),
78
+ ];
79
+ const widthWithoutRotationApplied =
80
+ iterator.getUint32() / (matrix[0] === 0 ? 1 : matrix[0]);
81
+ const heightWithoutRotationApplied =
82
+ iterator.getUint32() / (matrix[4] === 0 ? 1 : matrix[4]);
83
+
84
+ // TODO: This is not correct, HEVC videos with matrix is wrong
85
+ const width = widthWithoutRotationApplied / (matrix[1] === 0 ? 1 : matrix[1]);
86
+ const height =
87
+ heightWithoutRotationApplied / (matrix[1] === 0 ? 1 : matrix[1]);
88
+
89
+ return {
90
+ offset,
91
+ boxSize: size,
92
+ type: 'tkhd-box',
93
+ creationTime: toUnixTimestamp(creationTime),
94
+ modificationTime: toUnixTimestamp(modificationTime),
95
+ trackId,
96
+ duration,
97
+ layer,
98
+ alternateGroup,
99
+ volume,
100
+ matrix: matrix as ThreeDMatrix,
101
+ width,
102
+ height,
103
+ version,
104
+ };
105
+ };
@@ -0,0 +1,9 @@
1
+ export const toUnixTimestamp = (value: number): number | null => {
2
+ if (value === 0) {
3
+ return null;
4
+ }
5
+
6
+ const baseDate = new Date('1904-01-01T00:00:00Z');
7
+
8
+ return Math.floor(value + baseDate.getTime() / 1000) * 1000;
9
+ };
@@ -0,0 +1,37 @@
1
+ import type {BufferIterator} from '../../../buffer-iterator';
2
+ import type {AnySegment} from '../../../parse-result';
3
+ import type {BaseBox} from '../base-type';
4
+ import {parseBoxes} from '../process-box';
5
+
6
+ export interface TrakBox extends BaseBox {
7
+ type: 'trak-box';
8
+ children: AnySegment[];
9
+ }
10
+
11
+ export const parseTrak = ({
12
+ data,
13
+ size,
14
+ offsetAtStart,
15
+ }: {
16
+ data: BufferIterator;
17
+ size: number;
18
+ offsetAtStart: number;
19
+ }): TrakBox => {
20
+ const children = parseBoxes({
21
+ iterator: data,
22
+ maxBytes: size - (data.counter.getOffset() - offsetAtStart),
23
+ allowIncompleteBoxes: false,
24
+ initialBoxes: [],
25
+ });
26
+
27
+ if (children.status === 'incomplete') {
28
+ throw new Error('Incomplete boxes are not allowed');
29
+ }
30
+
31
+ return {
32
+ offset: offsetAtStart,
33
+ boxSize: size,
34
+ type: 'trak-box',
35
+ children: children.segments,
36
+ };
37
+ };
@@ -0,0 +1,18 @@
1
+ import type {BufferIterator} from '../../buffer-iterator';
2
+ import type {ParseResult} from '../../parse-result';
3
+ import {expectSegment} from './segments';
4
+
5
+ // Parsing according to https://darkcoding.net/software/reading-mediarecorders-webm-opus-output/
6
+ export const parseWebm = (counter: BufferIterator): ParseResult => {
7
+ counter.discard(4);
8
+ const length = counter.getEBML();
9
+
10
+ if (length !== 31) {
11
+ throw new Error(`Expected header length 31, got ${length}`);
12
+ }
13
+
14
+ // Discard header for now
15
+ counter.discard(31);
16
+
17
+ return {status: 'done', segments: [expectSegment(counter)]};
18
+ };
@@ -0,0 +1,22 @@
1
+ import type {BufferIterator} from '../../../buffer-iterator';
2
+
3
+ export type DurationSegment = {
4
+ type: 'duration-segment';
5
+ duration: number;
6
+ };
7
+
8
+ export const parseDurationSegment = (
9
+ iterator: BufferIterator,
10
+ ): DurationSegment => {
11
+ const length = iterator.getVint();
12
+ if (length !== 8) {
13
+ throw new Error('Expected duration segment to be 8 bytes');
14
+ }
15
+
16
+ const duration = iterator.getFloat64();
17
+
18
+ return {
19
+ type: 'duration-segment',
20
+ duration,
21
+ };
22
+ };
@@ -0,0 +1,20 @@
1
+ import type {BufferIterator} from '../../../buffer-iterator';
2
+ import type {MatroskaSegment} from '../segments';
3
+ import {expectChildren} from './parse-children';
4
+
5
+ export type InfoSegment = {
6
+ type: 'info-segment';
7
+ length: number;
8
+ children: MatroskaSegment[];
9
+ };
10
+
11
+ export const parseInfoSegment = (iterator: BufferIterator): InfoSegment => {
12
+ const length = iterator.getVint();
13
+ const children = expectChildren(iterator, length);
14
+
15
+ return {
16
+ type: 'info-segment',
17
+ length,
18
+ children,
19
+ };
20
+ };
@@ -0,0 +1,19 @@
1
+ import type {BufferIterator} from '../../../buffer-iterator';
2
+ import {type MatroskaSegment} from '../segments';
3
+ import {expectChildren} from './parse-children';
4
+
5
+ export type MainSegment = {
6
+ type: 'main-segment';
7
+ children: MatroskaSegment[];
8
+ };
9
+
10
+ export const parseMainSegment = (iterator: BufferIterator): MainSegment => {
11
+ const length = iterator.getVint();
12
+
13
+ const children = expectChildren(iterator, length);
14
+
15
+ return {
16
+ type: 'main-segment',
17
+ children,
18
+ };
19
+ };
@@ -0,0 +1,18 @@
1
+ import type {BufferIterator} from '../../../buffer-iterator';
2
+
3
+ export type MuxingAppSegment = {
4
+ type: 'muxing-app-segment';
5
+ value: string;
6
+ };
7
+
8
+ export const parseMuxingSegment = (
9
+ iterator: BufferIterator,
10
+ ): MuxingAppSegment => {
11
+ const length = iterator.getVint();
12
+ const value = iterator.getByteString(length);
13
+
14
+ return {
15
+ type: 'muxing-app-segment',
16
+ value,
17
+ };
18
+ };
@@ -0,0 +1,21 @@
1
+ import type {BufferIterator} from '../../../buffer-iterator';
2
+ import type {MatroskaSegment} from '../segments';
3
+ import {expectSegment} from '../segments';
4
+
5
+ export const expectChildren = (
6
+ iterator: BufferIterator,
7
+ length: number,
8
+ ): MatroskaSegment[] => {
9
+ const children: MatroskaSegment[] = [];
10
+ const startOffset = iterator.counter.getOffset();
11
+
12
+ while (iterator.counter.getOffset() < startOffset + length) {
13
+ const child = expectSegment(iterator);
14
+ children.push(child);
15
+ if (child.type === 'unknown-segment') {
16
+ break;
17
+ }
18
+ }
19
+
20
+ return children;
21
+ };
@@ -0,0 +1,21 @@
1
+ import type {BufferIterator} from '../../../buffer-iterator';
2
+ import {type MatroskaSegment} from '../segments';
3
+ import {expectChildren} from './parse-children';
4
+
5
+ export type SeekHeadSegment = {
6
+ type: 'seek-head-segment';
7
+ children: MatroskaSegment[];
8
+ length: number;
9
+ };
10
+
11
+ export const parseSeekHeadSegment = (
12
+ iterator: BufferIterator,
13
+ ): SeekHeadSegment => {
14
+ const length = iterator.getVint();
15
+
16
+ return {
17
+ type: 'seek-head-segment',
18
+ length,
19
+ children: expectChildren(iterator, length),
20
+ };
21
+ };