@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.
- package/.eslintrc +8 -0
- package/LICENSE.md +49 -0
- package/README.md +18 -0
- package/dist/boxes/iso-base-media/base-type.d.ts +4 -0
- package/dist/boxes/iso-base-media/base-type.js +2 -0
- package/dist/boxes/iso-base-media/ftyp.d.ts +13 -0
- package/dist/boxes/iso-base-media/ftyp.js +22 -0
- package/dist/boxes/iso-base-media/moov/moov.d.ts +12 -0
- package/dist/boxes/iso-base-media/moov/moov.js +22 -0
- package/dist/boxes/iso-base-media/mvhd.d.ts +30 -0
- package/dist/boxes/iso-base-media/mvhd.js +59 -0
- package/dist/boxes/iso-base-media/process-box.d.ts +8 -0
- package/dist/boxes/iso-base-media/process-box.js +174 -0
- package/dist/boxes/iso-base-media/stsd/keys.d.ts +5 -0
- package/dist/boxes/iso-base-media/stsd/keys.js +21 -0
- package/dist/boxes/iso-base-media/stsd/mebx.d.ts +14 -0
- package/dist/boxes/iso-base-media/stsd/mebx.js +27 -0
- package/dist/boxes/iso-base-media/stsd/samples.d.ts +48 -0
- package/dist/boxes/iso-base-media/stsd/samples.js +215 -0
- package/dist/boxes/iso-base-media/stsd/stsd.d.ts +13 -0
- package/dist/boxes/iso-base-media/stsd/stsd.js +26 -0
- package/dist/boxes/iso-base-media/stts/stts.d.ts +15 -0
- package/dist/boxes/iso-base-media/stts/stts.js +35 -0
- package/dist/boxes/iso-base-media/tkhd.d.ts +22 -0
- package/dist/boxes/iso-base-media/tkhd.js +63 -0
- package/dist/boxes/iso-base-media/to-date.d.ts +1 -0
- package/dist/boxes/iso-base-media/to-date.js +11 -0
- package/dist/boxes/iso-base-media/trak/trak.d.ts +12 -0
- package/dist/boxes/iso-base-media/trak/trak.js +22 -0
- package/dist/boxes/webm/parse-webm-header.d.ts +3 -0
- package/dist/boxes/webm/parse-webm-header.js +16 -0
- package/dist/boxes/webm/segments/duration.d.ts +6 -0
- package/dist/boxes/webm/segments/duration.js +15 -0
- package/dist/boxes/webm/segments/info.d.ts +8 -0
- package/dist/boxes/webm/segments/info.js +14 -0
- package/dist/boxes/webm/segments/main.d.ts +7 -0
- package/dist/boxes/webm/segments/main.js +13 -0
- package/dist/boxes/webm/segments/muxing.d.ts +6 -0
- package/dist/boxes/webm/segments/muxing.js +12 -0
- package/dist/boxes/webm/segments/parse-children.d.ts +3 -0
- package/dist/boxes/webm/segments/parse-children.js +17 -0
- package/dist/boxes/webm/segments/seek-head.d.ts +8 -0
- package/dist/boxes/webm/segments/seek-head.js +13 -0
- package/dist/boxes/webm/segments/seek-position.d.ts +6 -0
- package/dist/boxes/webm/segments/seek-position.js +12 -0
- package/dist/boxes/webm/segments/seek.d.ts +8 -0
- package/dist/boxes/webm/segments/seek.js +16 -0
- package/dist/boxes/webm/segments/timestamp-scale.d.ts +6 -0
- package/dist/boxes/webm/segments/timestamp-scale.js +11 -0
- package/dist/boxes/webm/segments/track-entry.d.ts +71 -0
- package/dist/boxes/webm/segments/track-entry.js +175 -0
- package/dist/boxes/webm/segments/tracks.d.ts +7 -0
- package/dist/boxes/webm/segments/tracks.js +13 -0
- package/dist/boxes/webm/segments/unknown.d.ts +6 -0
- package/dist/boxes/webm/segments/unknown.js +11 -0
- package/dist/boxes/webm/segments/void.d.ts +6 -0
- package/dist/boxes/webm/segments/void.js +12 -0
- package/dist/boxes/webm/segments/writing.d.ts +6 -0
- package/dist/boxes/webm/segments/writing.js +12 -0
- package/dist/boxes/webm/segments.d.ts +16 -0
- package/dist/boxes/webm/segments.js +106 -0
- package/dist/buffer-iterator.d.ts +36 -0
- package/dist/buffer-iterator.js +263 -0
- package/dist/from-node.d.ts +2 -0
- package/dist/from-node.js +19 -0
- package/dist/from-web.d.ts +2 -0
- package/dist/from-web.js +40 -0
- package/dist/get-dimensions.d.ts +7 -0
- package/dist/get-dimensions.js +104 -0
- package/dist/get-duration.d.ts +3 -0
- package/dist/get-duration.js +61 -0
- package/dist/get-fps.d.ts +3 -0
- package/dist/get-fps.js +70 -0
- package/dist/has-all-info.d.ts +3 -0
- package/dist/has-all-info.js +27 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/options.d.ts +19 -0
- package/dist/options.js +2 -0
- package/dist/parse-media.d.ts +2 -0
- package/dist/parse-media.js +44 -0
- package/dist/parse-result.d.ts +29 -0
- package/dist/parse-result.js +2 -0
- package/dist/parse-video.d.ts +10 -0
- package/dist/parse-video.js +29 -0
- package/dist/reader.d.ts +7 -0
- package/dist/reader.js +2 -0
- package/package.json +47 -0
- package/src/boxes/iso-base-media/base-type.ts +4 -0
- package/src/boxes/iso-base-media/ftyp.ts +39 -0
- package/src/boxes/iso-base-media/moov/moov.ts +37 -0
- package/src/boxes/iso-base-media/mvhd.ts +107 -0
- package/src/boxes/iso-base-media/process-box.ts +236 -0
- package/src/boxes/iso-base-media/stsd/keys.ts +25 -0
- package/src/boxes/iso-base-media/stsd/mebx.ts +46 -0
- package/src/boxes/iso-base-media/stsd/samples.ts +298 -0
- package/src/boxes/iso-base-media/stsd/stsd.ts +48 -0
- package/src/boxes/iso-base-media/stts/stts.ts +62 -0
- package/src/boxes/iso-base-media/tkhd.ts +105 -0
- package/src/boxes/iso-base-media/to-date.ts +9 -0
- package/src/boxes/iso-base-media/trak/trak.ts +37 -0
- package/src/boxes/webm/parse-webm-header.ts +18 -0
- package/src/boxes/webm/segments/duration.ts +22 -0
- package/src/boxes/webm/segments/info.ts +20 -0
- package/src/boxes/webm/segments/main.ts +19 -0
- package/src/boxes/webm/segments/muxing.ts +18 -0
- package/src/boxes/webm/segments/parse-children.ts +21 -0
- package/src/boxes/webm/segments/seek-head.ts +21 -0
- package/src/boxes/webm/segments/seek-position.ts +19 -0
- package/src/boxes/webm/segments/seek.ts +23 -0
- package/src/boxes/webm/segments/timestamp-scale.ts +17 -0
- package/src/boxes/webm/segments/track-entry.ts +295 -0
- package/src/boxes/webm/segments/tracks.ts +22 -0
- package/src/boxes/webm/segments/unknown.ts +19 -0
- package/src/boxes/webm/segments/void.ts +16 -0
- package/src/boxes/webm/segments/writing.ts +18 -0
- package/src/boxes/webm/segments.ts +206 -0
- package/src/buffer-iterator.ts +305 -0
- package/src/from-node.ts +22 -0
- package/src/from-web.ts +53 -0
- package/src/get-dimensions.ts +147 -0
- package/src/get-duration.ts +73 -0
- package/src/get-fps.ts +92 -0
- package/src/has-all-info.ts +34 -0
- package/src/index.ts +1 -0
- package/src/options.ts +38 -0
- package/src/parse-media.ts +57 -0
- package/src/parse-result.ts +44 -0
- package/src/parse-video.ts +41 -0
- package/src/reader.ts +10 -0
- package/src/test/duration.test.ts +34 -0
- package/src/test/keys.test.ts +47 -0
- package/src/test/matroska.test.ts +163 -0
- package/src/test/mvhd.test.ts +89 -0
- package/src/test/parse-stts.test.ts +38 -0
- package/src/test/parse-video.test.ts +113 -0
- package/src/test/parse-webm.test.ts +15 -0
- package/src/test/stream-local.test.ts +105 -0
- package/src/test/stream-remote.test.ts +12 -0
- package/src/test/stsd.test.ts +162 -0
- package/src/test/tkhd.test.ts +84 -0
- package/tsconfig.json +10 -0
- 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,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
|
+
};
|