@remotion/media-parser 4.0.191 → 4.0.192
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/dist/boxes/iso-base-media/mdhd.d.ts +14 -0
- package/dist/boxes/iso-base-media/mdhd.js +33 -0
- package/dist/boxes/iso-base-media/process-box.js +13 -0
- package/dist/boxes/iso-base-media/stsd/samples.js +1 -0
- package/dist/boxes/webm/parse-webm-header.js +1 -1
- package/dist/boxes/webm/segments/track-entry.d.ts +10 -0
- package/dist/boxes/webm/segments/track-entry.js +23 -7
- package/dist/boxes/webm/segments.d.ts +2 -2
- package/dist/boxes/webm/segments.js +6 -0
- package/dist/buffer-iterator.js +2 -0
- package/dist/get-codec.d.ts +4 -0
- package/dist/get-codec.js +22 -0
- package/dist/get-fps.d.ts +7 -0
- package/dist/get-fps.js +84 -9
- package/dist/get-video-codec.d.ts +4 -0
- package/dist/get-video-codec.js +76 -0
- package/dist/has-all-info.d.ts +1 -1
- package/dist/has-all-info.js +4 -0
- package/dist/options.d.ts +7 -3
- package/dist/parse-media.js +4 -0
- package/dist/parse-result.d.ts +2 -1
- package/package.json +2 -2
- package/src/boxes/iso-base-media/mdhd.ts +56 -0
- package/src/boxes/iso-base-media/process-box.ts +15 -0
- package/src/boxes/iso-base-media/stsd/samples.ts +1 -0
- package/src/boxes/webm/parse-webm-header.ts +1 -1
- package/src/boxes/webm/segments/track-entry.ts +37 -10
- package/src/boxes/webm/segments.ts +15 -1
- package/src/buffer-iterator.ts +2 -0
- package/src/get-fps.ts +127 -9
- package/src/get-video-codec.ts +100 -0
- package/src/has-all-info.ts +7 -2
- package/src/options.ts +28 -3
- package/src/parse-media.ts +6 -1
- package/src/parse-result.ts +3 -1
- package/src/test/matroska.test.ts +5 -5
- package/src/test/parse-webm.test.ts +2 -0
- package/src/test/stream-local.test.ts +16 -5
- package/src/test/stream-remote.test.ts +41 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -5,7 +5,7 @@ import {expectSegment} from './segments';
|
|
|
5
5
|
// Parsing according to https://darkcoding.net/software/reading-mediarecorders-webm-opus-output/
|
|
6
6
|
export const parseWebm = (counter: BufferIterator): ParseResult => {
|
|
7
7
|
counter.discard(4);
|
|
8
|
-
const length = counter.
|
|
8
|
+
const length = counter.getVint();
|
|
9
9
|
|
|
10
10
|
if (length !== 31) {
|
|
11
11
|
throw new Error(`Expected header length 31, got ${length}`);
|
|
@@ -51,10 +51,6 @@ export type TrackUIDSegment = {
|
|
|
51
51
|
|
|
52
52
|
export const parseTrackUID = (iterator: BufferIterator): TrackUIDSegment => {
|
|
53
53
|
const length = iterator.getVint();
|
|
54
|
-
// Observation: AV1 has 8 bytes, WebM has 7
|
|
55
|
-
if (length !== 8 && length !== 7) {
|
|
56
|
-
throw new Error('Expected track number to be 8 byte');
|
|
57
|
-
}
|
|
58
54
|
|
|
59
55
|
const bytes = iterator.getSlice(length);
|
|
60
56
|
|
|
@@ -187,16 +183,11 @@ export type VideoSegment = {
|
|
|
187
183
|
};
|
|
188
184
|
|
|
189
185
|
export const parseVideoSegment = (iterator: BufferIterator): VideoSegment => {
|
|
190
|
-
const offset = iterator.counter.getOffset();
|
|
191
|
-
|
|
192
186
|
const length = iterator.getVint();
|
|
193
187
|
|
|
194
188
|
return {
|
|
195
189
|
type: 'video-segment',
|
|
196
|
-
children: expectChildren(
|
|
197
|
-
iterator,
|
|
198
|
-
length - (iterator.counter.getOffset() - offset),
|
|
199
|
-
),
|
|
190
|
+
children: expectChildren(iterator, length),
|
|
200
191
|
};
|
|
201
192
|
};
|
|
202
193
|
|
|
@@ -293,3 +284,39 @@ export const parseColorSegment = (iterator: BufferIterator): ColorSegment => {
|
|
|
293
284
|
type: 'color-segment',
|
|
294
285
|
};
|
|
295
286
|
};
|
|
287
|
+
|
|
288
|
+
export type TitleSegment = {
|
|
289
|
+
type: 'title-segment';
|
|
290
|
+
title: string;
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
export const parseTitleSegment = (iterator: BufferIterator): TitleSegment => {
|
|
294
|
+
const length = iterator.getVint();
|
|
295
|
+
const title = iterator.getByteString(length);
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
type: 'title-segment',
|
|
299
|
+
title,
|
|
300
|
+
};
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
export type InterlacedSegment = {
|
|
304
|
+
type: 'interlaced-segment';
|
|
305
|
+
interlaced: boolean;
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
export const parseInterlacedSegment = (
|
|
309
|
+
iterator: BufferIterator,
|
|
310
|
+
): InterlacedSegment => {
|
|
311
|
+
const length = iterator.getVint();
|
|
312
|
+
if (length !== 1) {
|
|
313
|
+
throw new Error('Expected interlaced segment to be 1 byte');
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const interlaced = iterator.getUint8();
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
type: 'interlaced-segment',
|
|
320
|
+
interlaced: Boolean(interlaced),
|
|
321
|
+
};
|
|
322
|
+
};
|
|
@@ -22,8 +22,10 @@ import type {
|
|
|
22
22
|
DefaultDurationSegment,
|
|
23
23
|
FlagLacingSegment,
|
|
24
24
|
HeightSegment,
|
|
25
|
+
InterlacedSegment,
|
|
25
26
|
LanguageSegment,
|
|
26
27
|
MaxBlockAdditionId,
|
|
28
|
+
TitleSegment,
|
|
27
29
|
TrackEntrySegment,
|
|
28
30
|
TrackNumberSegment,
|
|
29
31
|
TrackTypeSegment,
|
|
@@ -38,8 +40,10 @@ import {
|
|
|
38
40
|
parseDefaultDurationSegment,
|
|
39
41
|
parseFlagLacing,
|
|
40
42
|
parseHeightSegment,
|
|
43
|
+
parseInterlacedSegment,
|
|
41
44
|
parseLanguageSegment,
|
|
42
45
|
parseMaxBlockAdditionId,
|
|
46
|
+
parseTitleSegment,
|
|
43
47
|
parseTrackEntry,
|
|
44
48
|
parseTrackNumber,
|
|
45
49
|
parseTrackTypeSegment,
|
|
@@ -82,7 +86,9 @@ export type MatroskaSegment =
|
|
|
82
86
|
| HeightSegment
|
|
83
87
|
| AlphaModeSegment
|
|
84
88
|
| MaxBlockAdditionId
|
|
85
|
-
| ColorSegment
|
|
89
|
+
| ColorSegment
|
|
90
|
+
| TitleSegment
|
|
91
|
+
| InterlacedSegment;
|
|
86
92
|
|
|
87
93
|
export const expectSegment = (iterator: BufferIterator): MatroskaSegment => {
|
|
88
94
|
const segmentId = iterator.getMatroskaSegmentId();
|
|
@@ -189,10 +195,18 @@ export const expectSegment = (iterator: BufferIterator): MatroskaSegment => {
|
|
|
189
195
|
return parseHeightSegment(iterator);
|
|
190
196
|
}
|
|
191
197
|
|
|
198
|
+
if (segmentId === '0x9a') {
|
|
199
|
+
return parseInterlacedSegment(iterator);
|
|
200
|
+
}
|
|
201
|
+
|
|
192
202
|
if (segmentId === '0x53c0') {
|
|
193
203
|
return parseAlphaModeSegment(iterator);
|
|
194
204
|
}
|
|
195
205
|
|
|
206
|
+
if (segmentId === '0x7ba9') {
|
|
207
|
+
return parseTitleSegment(iterator);
|
|
208
|
+
}
|
|
209
|
+
|
|
196
210
|
const length = iterator.getVint();
|
|
197
211
|
|
|
198
212
|
const bytesRemaining = iterator.byteLength() - iterator.counter.getOffset();
|
package/src/buffer-iterator.ts
CHANGED
|
@@ -154,6 +154,7 @@ export const getArrayBufferIterator = (initialData: Uint8Array) => {
|
|
|
154
154
|
'0xe0',
|
|
155
155
|
'0xb0',
|
|
156
156
|
'0xba',
|
|
157
|
+
'0x9a',
|
|
157
158
|
];
|
|
158
159
|
if (knownIdsWithOneLength.includes(firstOneString)) {
|
|
159
160
|
return firstOneString;
|
|
@@ -172,6 +173,7 @@ export const getArrayBufferIterator = (initialData: Uint8Array) => {
|
|
|
172
173
|
'0x4489',
|
|
173
174
|
'0x55ee',
|
|
174
175
|
'0x55b0',
|
|
176
|
+
'0x7ba9',
|
|
175
177
|
];
|
|
176
178
|
|
|
177
179
|
const firstTwoString = `${firstOneString}${Array.from(
|
package/src/get-fps.ts
CHANGED
|
@@ -1,18 +1,135 @@
|
|
|
1
1
|
import type {SttsBox} from './boxes/iso-base-media/stts/stts';
|
|
2
2
|
import type {AnySegment} from './parse-result';
|
|
3
3
|
|
|
4
|
-
const calculateFps = (
|
|
5
|
-
|
|
4
|
+
const calculateFps = ({
|
|
5
|
+
sttsBox,
|
|
6
|
+
timeScale,
|
|
7
|
+
durationInSamples,
|
|
8
|
+
}: {
|
|
9
|
+
sttsBox: SttsBox;
|
|
10
|
+
timeScale: number;
|
|
11
|
+
durationInSamples: number;
|
|
12
|
+
}) => {
|
|
6
13
|
let totalSamples = 0;
|
|
14
|
+
|
|
7
15
|
for (const sample of sttsBox.sampleDistribution) {
|
|
8
|
-
sum += sample.sampleCount * sample.sampleDelta;
|
|
9
16
|
totalSamples += sample.sampleCount;
|
|
10
17
|
}
|
|
11
18
|
|
|
12
|
-
|
|
19
|
+
const durationInSeconds = durationInSamples / timeScale;
|
|
20
|
+
const fps = totalSamples / durationInSeconds;
|
|
21
|
+
|
|
22
|
+
return fps;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type TimescaleAndDuration = {
|
|
26
|
+
timescale: number;
|
|
27
|
+
duration: number;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const trakBoxContainsVideo = (trakBox: AnySegment): boolean => {
|
|
31
|
+
if (trakBox.type !== 'trak-box') {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const {children} = trakBox;
|
|
36
|
+
const mediaBoxes = children.filter(
|
|
37
|
+
(c) => c.type === 'regular-box' && c.boxType === 'mdia',
|
|
38
|
+
);
|
|
39
|
+
if (!mediaBoxes || mediaBoxes.length === 0) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const firstMediaBox = mediaBoxes[0];
|
|
44
|
+
if (
|
|
45
|
+
firstMediaBox.type !== 'regular-box' ||
|
|
46
|
+
firstMediaBox.boxType !== 'mdia'
|
|
47
|
+
) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const minf = firstMediaBox.children.find(
|
|
52
|
+
(c) => c.type === 'regular-box' && c.boxType === 'minf',
|
|
53
|
+
);
|
|
54
|
+
if (!minf || minf.type !== 'regular-box' || minf.boxType !== 'minf') {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const stbl = minf.children.find(
|
|
59
|
+
(c) => c.type === 'regular-box' && c.boxType === 'stbl',
|
|
60
|
+
);
|
|
61
|
+
if (!stbl || stbl.type !== 'regular-box' || stbl.boxType !== 'stbl') {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const stsd = stbl.children.find((c) => c.type === 'stsd-box');
|
|
66
|
+
if (!stsd || stsd.type !== 'stsd-box') {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const videoSample = stsd.samples.find((s) => s.type === 'video');
|
|
71
|
+
if (!videoSample || videoSample.type !== 'video') {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return true;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const getTimescaleAndDuration = (
|
|
79
|
+
boxes: AnySegment[],
|
|
80
|
+
): TimescaleAndDuration | null => {
|
|
81
|
+
const moovBox = boxes.find((s) => s.type === 'moov-box');
|
|
82
|
+
if (!moovBox || moovBox.type !== 'moov-box') {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const {children} = moovBox;
|
|
87
|
+
const trackBoxes = children.filter((c) => c.type === 'trak-box');
|
|
88
|
+
if (!trackBoxes || trackBoxes.length === 0) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const trackBox = trackBoxes.find(trakBoxContainsVideo);
|
|
93
|
+
if (!trackBox || trackBox.type !== 'trak-box') {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const trackBoxChildren = trackBox.children;
|
|
98
|
+
if (!trackBoxChildren || trackBoxChildren.length === 0) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const mdiaBox = trackBoxChildren.find(
|
|
103
|
+
(c) => c.type === 'regular-box' && c.boxType === 'mdia',
|
|
104
|
+
);
|
|
105
|
+
if (
|
|
106
|
+
!mdiaBox ||
|
|
107
|
+
mdiaBox.type !== 'regular-box' ||
|
|
108
|
+
mdiaBox.boxType !== 'mdia'
|
|
109
|
+
) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const mdhdBox = mdiaBox?.children.find((c) => c.type === 'mdhd-box');
|
|
114
|
+
if (mdhdBox && mdhdBox.type === 'mdhd-box') {
|
|
115
|
+
return {timescale: mdhdBox.timescale, duration: mdhdBox.duration};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const mvhdBox = moovBox.children.find((c) => c.type === 'mvhd-box');
|
|
119
|
+
if (!mvhdBox || mvhdBox.type !== 'mvhd-box') {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const {timeScale, durationInUnits} = mvhdBox;
|
|
124
|
+
return {timescale: timeScale, duration: durationInUnits};
|
|
13
125
|
};
|
|
14
126
|
|
|
15
127
|
export const getFps = (segments: AnySegment[]) => {
|
|
128
|
+
const timescaleAndDuration = getTimescaleAndDuration(segments);
|
|
129
|
+
if (!timescaleAndDuration) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
16
133
|
const moovBox = segments.find((s) => s.type === 'moov-box');
|
|
17
134
|
if (!moovBox || moovBox.type !== 'moov-box') {
|
|
18
135
|
return null;
|
|
@@ -23,16 +140,13 @@ export const getFps = (segments: AnySegment[]) => {
|
|
|
23
140
|
return null;
|
|
24
141
|
}
|
|
25
142
|
|
|
26
|
-
const {timeScale} = mvhdBox;
|
|
27
|
-
|
|
28
143
|
const {children} = moovBox;
|
|
29
144
|
const trackBoxes = children.filter((c) => c.type === 'trak-box');
|
|
30
145
|
if (!trackBoxes || trackBoxes.length === 0) {
|
|
31
146
|
return null;
|
|
32
147
|
}
|
|
33
148
|
|
|
34
|
-
|
|
35
|
-
const trackBox = trackBoxes[0];
|
|
149
|
+
const trackBox = trackBoxes.find(trakBoxContainsVideo);
|
|
36
150
|
if (!trackBox || trackBox.type !== 'trak-box') {
|
|
37
151
|
return null;
|
|
38
152
|
}
|
|
@@ -80,7 +194,11 @@ export const getFps = (segments: AnySegment[]) => {
|
|
|
80
194
|
return null;
|
|
81
195
|
}
|
|
82
196
|
|
|
83
|
-
return calculateFps(
|
|
197
|
+
return calculateFps({
|
|
198
|
+
sttsBox,
|
|
199
|
+
timeScale: timescaleAndDuration.timescale,
|
|
200
|
+
durationInSamples: timescaleAndDuration.duration,
|
|
201
|
+
});
|
|
84
202
|
};
|
|
85
203
|
|
|
86
204
|
export const hasFps = (boxes: AnySegment[]): boolean => {
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/* eslint-disable max-depth */
|
|
2
|
+
import {trakBoxContainsVideo} from './get-fps';
|
|
3
|
+
import type {KnownVideoCodecs} from './options';
|
|
4
|
+
import type {AnySegment} from './parse-result';
|
|
5
|
+
|
|
6
|
+
export const hasVideoCodec = (boxes: AnySegment[]): boolean => {
|
|
7
|
+
try {
|
|
8
|
+
return getVideoCodec(boxes) !== null;
|
|
9
|
+
} catch (e) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const getVideoCodec = (boxes: AnySegment[]): KnownVideoCodecs | null => {
|
|
15
|
+
const moovBox = boxes.find((b) => b.type === 'moov-box');
|
|
16
|
+
if (moovBox && moovBox.type === 'moov-box') {
|
|
17
|
+
const trakBox = moovBox.children.find(
|
|
18
|
+
(b) => b.type === 'trak-box' && trakBoxContainsVideo(b),
|
|
19
|
+
);
|
|
20
|
+
if (trakBox && trakBox.type === 'trak-box') {
|
|
21
|
+
const mdiaBox = trakBox.children.find(
|
|
22
|
+
(b) => b.type === 'regular-box' && b.boxType === 'mdia',
|
|
23
|
+
);
|
|
24
|
+
if (
|
|
25
|
+
mdiaBox &&
|
|
26
|
+
mdiaBox.type === 'regular-box' &&
|
|
27
|
+
mdiaBox.boxType === 'mdia'
|
|
28
|
+
) {
|
|
29
|
+
const minfBox = mdiaBox?.children.find(
|
|
30
|
+
(b) => b.type === 'regular-box' && b.boxType === 'minf',
|
|
31
|
+
);
|
|
32
|
+
if (
|
|
33
|
+
minfBox &&
|
|
34
|
+
minfBox.type === 'regular-box' &&
|
|
35
|
+
minfBox.boxType === 'minf'
|
|
36
|
+
) {
|
|
37
|
+
const stblBox = minfBox?.children.find(
|
|
38
|
+
(b) => b.type === 'regular-box' && b.boxType === 'stbl',
|
|
39
|
+
);
|
|
40
|
+
if (stblBox && stblBox.type === 'regular-box') {
|
|
41
|
+
const stsdBox = stblBox?.children.find(
|
|
42
|
+
(b) => b.type === 'stsd-box',
|
|
43
|
+
);
|
|
44
|
+
if (stsdBox && stsdBox.type === 'stsd-box') {
|
|
45
|
+
const videoSample = stsdBox.samples.find(
|
|
46
|
+
(s) => s.type === 'video',
|
|
47
|
+
);
|
|
48
|
+
if (videoSample && videoSample.type === 'video') {
|
|
49
|
+
if (videoSample.format === 'hvc1') {
|
|
50
|
+
return 'h265';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (videoSample.format === 'avc1') {
|
|
54
|
+
return 'h264';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (videoSample.format === 'ap4h') {
|
|
58
|
+
return 'prores';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const mainSegment = boxes.find((b) => b.type === 'main-segment');
|
|
69
|
+
if (!mainSegment || mainSegment.type !== 'main-segment') {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const tracksSegment = mainSegment.children.find(
|
|
74
|
+
(b) => b.type === 'tracks-segment',
|
|
75
|
+
);
|
|
76
|
+
if (!tracksSegment || tracksSegment.type !== 'tracks-segment') {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
for (const track of tracksSegment.children) {
|
|
81
|
+
if (track.type === 'track-entry-segment') {
|
|
82
|
+
const trackType = track.children.find((b) => b.type === 'codec-segment');
|
|
83
|
+
if (trackType && trackType.type === 'codec-segment') {
|
|
84
|
+
if (trackType.codec === 'V_VP8') {
|
|
85
|
+
return 'vp8';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (trackType.codec === 'V_VP9') {
|
|
89
|
+
return 'vp9';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (trackType.codec === 'V_AV1') {
|
|
93
|
+
return 'av1';
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return null;
|
|
100
|
+
};
|
package/src/has-all-info.ts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import {hasDimensions} from './get-dimensions';
|
|
2
2
|
import {hasDuration} from './get-duration';
|
|
3
3
|
import {hasFps} from './get-fps';
|
|
4
|
+
import {hasVideoCodec} from './get-video-codec';
|
|
4
5
|
import type {Options} from './options';
|
|
5
6
|
import type {ParseResult} from './parse-result';
|
|
6
7
|
|
|
7
8
|
export const hasAllInfo = (
|
|
8
|
-
options: Options<boolean, boolean, boolean, boolean>,
|
|
9
|
+
options: Options<boolean, boolean, boolean, boolean, boolean>,
|
|
9
10
|
parseResult: ParseResult,
|
|
10
11
|
) => {
|
|
11
12
|
const keys = Object.entries(options)
|
|
12
13
|
.filter(([, value]) => value)
|
|
13
|
-
.map(([key]) => key) as (keyof Options<true, true, true, true>)[];
|
|
14
|
+
.map(([key]) => key) as (keyof Options<true, true, true, true, true>)[];
|
|
14
15
|
|
|
15
16
|
return keys.every((key) => {
|
|
16
17
|
if (key === 'boxes') {
|
|
@@ -29,6 +30,10 @@ export const hasAllInfo = (
|
|
|
29
30
|
return hasFps(parseResult.segments) !== null;
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
if (key === 'videoCodec') {
|
|
34
|
+
return hasVideoCodec(parseResult.segments) !== null;
|
|
35
|
+
}
|
|
36
|
+
|
|
32
37
|
throw new Error(`Unknown key: ${key satisfies never}`);
|
|
33
38
|
});
|
|
34
39
|
};
|
package/src/options.ts
CHANGED
|
@@ -2,16 +2,26 @@ import type {Dimensions} from './get-dimensions';
|
|
|
2
2
|
import type {AnySegment} from './parse-result';
|
|
3
3
|
import type {ReaderInterface} from './reader';
|
|
4
4
|
|
|
5
|
+
export type KnownVideoCodecs =
|
|
6
|
+
| 'h264'
|
|
7
|
+
| 'h265'
|
|
8
|
+
| 'vp8'
|
|
9
|
+
| 'vp9'
|
|
10
|
+
| 'av1'
|
|
11
|
+
| 'prores';
|
|
12
|
+
|
|
5
13
|
export type Options<
|
|
6
14
|
EnableDimensions extends boolean,
|
|
7
15
|
EnableDuration extends boolean,
|
|
8
16
|
EnableBoxes extends boolean,
|
|
9
17
|
EnableFps extends boolean,
|
|
18
|
+
EnableVideoCodec extends boolean,
|
|
10
19
|
> = {
|
|
11
20
|
dimensions?: EnableDimensions;
|
|
12
21
|
durationInSeconds?: EnableDuration;
|
|
13
22
|
boxes?: EnableBoxes;
|
|
14
23
|
fps?: EnableFps;
|
|
24
|
+
videoCodec?: EnableVideoCodec;
|
|
15
25
|
};
|
|
16
26
|
|
|
17
27
|
export type Metadata<
|
|
@@ -19,20 +29,35 @@ export type Metadata<
|
|
|
19
29
|
EnableDuration extends boolean,
|
|
20
30
|
EnableBoxes extends boolean,
|
|
21
31
|
EnableFps extends boolean,
|
|
32
|
+
EnableVideoCodec extends boolean,
|
|
22
33
|
> = (EnableDimensions extends true ? {dimensions: Dimensions} : {}) &
|
|
23
34
|
(EnableDuration extends true ? {durationInSeconds: number | null} : {}) &
|
|
24
35
|
(EnableBoxes extends true ? {boxes: AnySegment[]} : {}) &
|
|
25
|
-
(EnableFps extends true ? {fps: number | null} : {})
|
|
36
|
+
(EnableFps extends true ? {fps: number | null} : {}) &
|
|
37
|
+
(EnableVideoCodec extends true ? {videoCodec: KnownVideoCodecs | null} : {});
|
|
26
38
|
|
|
27
39
|
export type ParseMedia = <
|
|
28
40
|
EnableDimensions extends boolean,
|
|
29
41
|
EnableDuration extends boolean,
|
|
30
42
|
EnableBoxes extends boolean,
|
|
31
43
|
EnableFps extends boolean,
|
|
44
|
+
EnableVideoCodec extends boolean,
|
|
32
45
|
>(
|
|
33
46
|
src: string,
|
|
34
|
-
options: Options<
|
|
47
|
+
options: Options<
|
|
48
|
+
EnableDimensions,
|
|
49
|
+
EnableDuration,
|
|
50
|
+
EnableBoxes,
|
|
51
|
+
EnableFps,
|
|
52
|
+
EnableVideoCodec
|
|
53
|
+
>,
|
|
35
54
|
readerInterface?: ReaderInterface,
|
|
36
55
|
) => Promise<
|
|
37
|
-
Metadata<
|
|
56
|
+
Metadata<
|
|
57
|
+
EnableDimensions,
|
|
58
|
+
EnableDuration,
|
|
59
|
+
EnableBoxes,
|
|
60
|
+
EnableFps,
|
|
61
|
+
EnableVideoCodec
|
|
62
|
+
>
|
|
38
63
|
>;
|
package/src/parse-media.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {webReader} from './from-web';
|
|
|
3
3
|
import {getDimensions} from './get-dimensions';
|
|
4
4
|
import {getDuration} from './get-duration';
|
|
5
5
|
import {getFps} from './get-fps';
|
|
6
|
+
import {getVideoCodec} from './get-video-codec';
|
|
6
7
|
import {hasAllInfo} from './has-all-info';
|
|
7
8
|
import type {Metadata, ParseMedia} from './options';
|
|
8
9
|
import {parseVideo} from './parse-video';
|
|
@@ -14,7 +15,7 @@ export const parseMedia: ParseMedia = async (
|
|
|
14
15
|
) => {
|
|
15
16
|
const reader = await readerInterface.read(src, null);
|
|
16
17
|
|
|
17
|
-
const returnValue = {} as Metadata<true, true, true, true>;
|
|
18
|
+
const returnValue = {} as Metadata<true, true, true, true, true>;
|
|
18
19
|
|
|
19
20
|
const iterator = getArrayBufferIterator(new Uint8Array([]));
|
|
20
21
|
let parseResult = parseVideo(iterator);
|
|
@@ -49,6 +50,10 @@ export const parseMedia: ParseMedia = async (
|
|
|
49
50
|
returnValue.fps = getFps(parseResult.segments);
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
if (options.videoCodec) {
|
|
54
|
+
returnValue.videoCodec = getVideoCodec(parseResult.segments);
|
|
55
|
+
}
|
|
56
|
+
|
|
52
57
|
if (options.boxes) {
|
|
53
58
|
returnValue.boxes = parseResult.segments;
|
|
54
59
|
}
|
package/src/parse-result.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {BaseBox} from './boxes/iso-base-media/base-type';
|
|
2
2
|
import type {FtypBox} from './boxes/iso-base-media/ftyp';
|
|
3
|
+
import type {MdhdBox} from './boxes/iso-base-media/mdhd';
|
|
3
4
|
import type {MoovBox} from './boxes/iso-base-media/moov/moov';
|
|
4
5
|
import type {MvhdBox} from './boxes/iso-base-media/mvhd';
|
|
5
6
|
import type {KeysBox} from './boxes/iso-base-media/stsd/keys';
|
|
@@ -28,7 +29,8 @@ export type IsoBaseMediaBox =
|
|
|
28
29
|
| KeysBox
|
|
29
30
|
| MoovBox
|
|
30
31
|
| TrakBox
|
|
31
|
-
| SttsBox
|
|
32
|
+
| SttsBox
|
|
33
|
+
| MdhdBox;
|
|
32
34
|
|
|
33
35
|
export type AnySegment = MatroskaSegment | IsoBaseMediaBox;
|
|
34
36
|
|
|
@@ -137,18 +137,18 @@ test('Should get duration of AV1 video', async () => {
|
|
|
137
137
|
{
|
|
138
138
|
type: 'color-segment',
|
|
139
139
|
},
|
|
140
|
+
{
|
|
141
|
+
id: '0x0163a294',
|
|
142
|
+
type: 'unknown-segment',
|
|
143
|
+
},
|
|
140
144
|
],
|
|
141
145
|
},
|
|
142
146
|
{
|
|
143
|
-
id: '
|
|
147
|
+
id: '0x0c000a0e',
|
|
144
148
|
type: 'unknown-segment',
|
|
145
149
|
},
|
|
146
150
|
],
|
|
147
151
|
},
|
|
148
|
-
{
|
|
149
|
-
id: '0x0c000a0e',
|
|
150
|
-
type: 'unknown-segment',
|
|
151
|
-
},
|
|
152
152
|
],
|
|
153
153
|
},
|
|
154
154
|
{
|
|
@@ -8,8 +8,10 @@ test('should be able to parse a WebM', async () => {
|
|
|
8
8
|
RenderInternals.exampleVideos.transparentWebm,
|
|
9
9
|
{
|
|
10
10
|
durationInSeconds: true,
|
|
11
|
+
videoCodec: true,
|
|
11
12
|
},
|
|
12
13
|
nodeReader,
|
|
13
14
|
);
|
|
14
15
|
expect(webm.durationInSeconds).toBe(5);
|
|
16
|
+
expect(webm.videoCodec).toBe('vp8');
|
|
15
17
|
});
|