@remotion/media-parser 4.0.191 → 4.0.193
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/boxes.json +1 -0
- package/dist/boxes/iso-base-media/esds/esds-descriptors.d.ts +21 -0
- package/dist/boxes/iso-base-media/esds/esds-descriptors.js +62 -0
- package/dist/boxes/iso-base-media/esds/esds.d.ts +15 -0
- package/dist/boxes/iso-base-media/esds/esds.js +27 -0
- package/dist/boxes/iso-base-media/ftype.d.ts +9 -0
- package/dist/boxes/iso-base-media/ftype.js +31 -0
- 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 +30 -0
- package/dist/boxes/iso-base-media/stsd/samples.d.ts +2 -0
- package/dist/boxes/iso-base-media/stsd/samples.js +28 -8
- package/dist/boxes/webm/parse-webm-header.js +4 -4
- package/dist/boxes/webm/segments/track-entry.d.ts +30 -0
- package/dist/boxes/webm/segments/track-entry.js +59 -8
- package/dist/boxes/webm/segments.d.ts +2 -2
- package/dist/boxes/webm/segments.js +18 -0
- package/dist/buffer-iterator.d.ts +2 -1
- package/dist/buffer-iterator.js +29 -8
- package/dist/from-node.js +6 -2
- package/dist/from-web.js +6 -1
- package/dist/get-audio-codec.d.ts +4 -0
- package/dist/get-audio-codec.js +106 -0
- package/dist/get-dimensions.js +6 -2
- package/dist/get-fps.d.ts +8 -0
- package/dist/get-fps.js +117 -9
- package/dist/get-video-codec.d.ts +4 -0
- package/dist/get-video-codec.js +79 -0
- package/dist/get-video-metadata.d.ts +2 -0
- package/dist/get-video-metadata.js +44 -0
- package/dist/has-all-info.d.ts +1 -1
- package/dist/has-all-info.js +8 -0
- package/dist/options.d.ts +11 -3
- package/dist/parse-media.js +27 -6
- package/dist/parse-result.d.ts +3 -1
- package/dist/read-and-increment-offset.d.ts +28 -0
- package/dist/read-and-increment-offset.js +177 -0
- package/dist/reader.d.ts +5 -1
- package/package.json +2 -2
- package/src/boxes/iso-base-media/esds/esds-descriptors.ts +104 -0
- package/src/boxes/iso-base-media/esds/esds.ts +49 -0
- package/src/boxes/iso-base-media/mdhd.ts +56 -0
- package/src/boxes/iso-base-media/process-box.ts +35 -0
- package/src/boxes/iso-base-media/stsd/samples.ts +36 -8
- package/src/boxes/webm/parse-webm-header.ts +4 -4
- package/src/boxes/webm/segments/track-entry.ts +103 -11
- package/src/boxes/webm/segments.ts +43 -1
- package/src/buffer-iterator.ts +36 -10
- package/src/from-node.ts +6 -4
- package/src/from-web.ts +8 -1
- package/src/get-audio-codec.ts +143 -0
- package/src/get-dimensions.ts +11 -4
- package/src/get-fps.ts +175 -9
- package/src/get-video-codec.ts +104 -0
- package/src/has-all-info.ts +19 -2
- package/src/options.ts +43 -3
- package/src/parse-media.ts +35 -7
- package/src/parse-result.ts +5 -1
- package/src/reader.ts +5 -1
- package/src/test/matroska.test.ts +6 -7
- package/src/test/parse-esds.test.ts +75 -0
- package/src/test/parse-webm.test.ts +2 -0
- package/src/test/stream-local.test.ts +93 -5
- package/src/test/stream-remote.test.ts +41 -0
- package/src/test/stsd.test.ts +52 -5
- package/tsconfig.tsbuildinfo +1 -1
package/src/get-fps.ts
CHANGED
|
@@ -1,18 +1,183 @@
|
|
|
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 trakBoxContainsAudio = (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 === 'audio');
|
|
71
|
+
if (!videoSample || videoSample.type !== 'audio') {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return true;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const trakBoxContainsVideo = (trakBox: AnySegment): boolean => {
|
|
79
|
+
if (trakBox.type !== 'trak-box') {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const {children} = trakBox;
|
|
84
|
+
const mediaBoxes = children.filter(
|
|
85
|
+
(c) => c.type === 'regular-box' && c.boxType === 'mdia',
|
|
86
|
+
);
|
|
87
|
+
if (!mediaBoxes || mediaBoxes.length === 0) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const firstMediaBox = mediaBoxes[0];
|
|
92
|
+
if (
|
|
93
|
+
firstMediaBox.type !== 'regular-box' ||
|
|
94
|
+
firstMediaBox.boxType !== 'mdia'
|
|
95
|
+
) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const minf = firstMediaBox.children.find(
|
|
100
|
+
(c) => c.type === 'regular-box' && c.boxType === 'minf',
|
|
101
|
+
);
|
|
102
|
+
if (!minf || minf.type !== 'regular-box' || minf.boxType !== 'minf') {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const stbl = minf.children.find(
|
|
107
|
+
(c) => c.type === 'regular-box' && c.boxType === 'stbl',
|
|
108
|
+
);
|
|
109
|
+
if (!stbl || stbl.type !== 'regular-box' || stbl.boxType !== 'stbl') {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const stsd = stbl.children.find((c) => c.type === 'stsd-box');
|
|
114
|
+
if (!stsd || stsd.type !== 'stsd-box') {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const videoSample = stsd.samples.find((s) => s.type === 'video');
|
|
119
|
+
if (!videoSample || videoSample.type !== 'video') {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return true;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export const getTimescaleAndDuration = (
|
|
127
|
+
boxes: AnySegment[],
|
|
128
|
+
): TimescaleAndDuration | null => {
|
|
129
|
+
const moovBox = boxes.find((s) => s.type === 'moov-box');
|
|
130
|
+
if (!moovBox || moovBox.type !== 'moov-box') {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const {children} = moovBox;
|
|
135
|
+
const trackBoxes = children.filter((c) => c.type === 'trak-box');
|
|
136
|
+
if (!trackBoxes || trackBoxes.length === 0) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const trackBox = trackBoxes.find(trakBoxContainsVideo);
|
|
141
|
+
if (!trackBox || trackBox.type !== 'trak-box') {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const trackBoxChildren = trackBox.children;
|
|
146
|
+
if (!trackBoxChildren || trackBoxChildren.length === 0) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const mdiaBox = trackBoxChildren.find(
|
|
151
|
+
(c) => c.type === 'regular-box' && c.boxType === 'mdia',
|
|
152
|
+
);
|
|
153
|
+
if (
|
|
154
|
+
!mdiaBox ||
|
|
155
|
+
mdiaBox.type !== 'regular-box' ||
|
|
156
|
+
mdiaBox.boxType !== 'mdia'
|
|
157
|
+
) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const mdhdBox = mdiaBox?.children.find((c) => c.type === 'mdhd-box');
|
|
162
|
+
if (mdhdBox && mdhdBox.type === 'mdhd-box') {
|
|
163
|
+
return {timescale: mdhdBox.timescale, duration: mdhdBox.duration};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const mvhdBox = moovBox.children.find((c) => c.type === 'mvhd-box');
|
|
167
|
+
if (!mvhdBox || mvhdBox.type !== 'mvhd-box') {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const {timeScale, durationInUnits} = mvhdBox;
|
|
172
|
+
return {timescale: timeScale, duration: durationInUnits};
|
|
13
173
|
};
|
|
14
174
|
|
|
15
175
|
export const getFps = (segments: AnySegment[]) => {
|
|
176
|
+
const timescaleAndDuration = getTimescaleAndDuration(segments);
|
|
177
|
+
if (!timescaleAndDuration) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
16
181
|
const moovBox = segments.find((s) => s.type === 'moov-box');
|
|
17
182
|
if (!moovBox || moovBox.type !== 'moov-box') {
|
|
18
183
|
return null;
|
|
@@ -23,16 +188,13 @@ export const getFps = (segments: AnySegment[]) => {
|
|
|
23
188
|
return null;
|
|
24
189
|
}
|
|
25
190
|
|
|
26
|
-
const {timeScale} = mvhdBox;
|
|
27
|
-
|
|
28
191
|
const {children} = moovBox;
|
|
29
192
|
const trackBoxes = children.filter((c) => c.type === 'trak-box');
|
|
30
193
|
if (!trackBoxes || trackBoxes.length === 0) {
|
|
31
194
|
return null;
|
|
32
195
|
}
|
|
33
196
|
|
|
34
|
-
|
|
35
|
-
const trackBox = trackBoxes[0];
|
|
197
|
+
const trackBox = trackBoxes.find(trakBoxContainsVideo);
|
|
36
198
|
if (!trackBox || trackBox.type !== 'trak-box') {
|
|
37
199
|
return null;
|
|
38
200
|
}
|
|
@@ -80,7 +242,11 @@ export const getFps = (segments: AnySegment[]) => {
|
|
|
80
242
|
return null;
|
|
81
243
|
}
|
|
82
244
|
|
|
83
|
-
return calculateFps(
|
|
245
|
+
return calculateFps({
|
|
246
|
+
sttsBox,
|
|
247
|
+
timeScale: timescaleAndDuration.timescale,
|
|
248
|
+
durationInSamples: timescaleAndDuration.duration,
|
|
249
|
+
});
|
|
84
250
|
};
|
|
85
251
|
|
|
86
252
|
export const hasFps = (boxes: AnySegment[]): boolean => {
|
|
@@ -0,0 +1,104 @@
|
|
|
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
|
+
if (trackType.codec === 'V_MPEG4/ISO/AVC') {
|
|
97
|
+
return 'h264';
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return null;
|
|
104
|
+
};
|
package/src/has-all-info.ts
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
|
+
import {hasAudioCodec} from './get-audio-codec';
|
|
1
2
|
import {hasDimensions} from './get-dimensions';
|
|
2
3
|
import {hasDuration} from './get-duration';
|
|
3
4
|
import {hasFps} from './get-fps';
|
|
5
|
+
import {hasVideoCodec} from './get-video-codec';
|
|
4
6
|
import type {Options} from './options';
|
|
5
7
|
import type {ParseResult} from './parse-result';
|
|
6
8
|
|
|
7
9
|
export const hasAllInfo = (
|
|
8
|
-
options: Options<boolean, boolean, boolean, boolean>,
|
|
10
|
+
options: Options<boolean, boolean, boolean, boolean, boolean, boolean>,
|
|
9
11
|
parseResult: ParseResult,
|
|
10
12
|
) => {
|
|
11
13
|
const keys = Object.entries(options)
|
|
12
14
|
.filter(([, value]) => value)
|
|
13
|
-
.map(([key]) => key) as (keyof Options<
|
|
15
|
+
.map(([key]) => key) as (keyof Options<
|
|
16
|
+
true,
|
|
17
|
+
true,
|
|
18
|
+
true,
|
|
19
|
+
true,
|
|
20
|
+
true,
|
|
21
|
+
true
|
|
22
|
+
>)[];
|
|
14
23
|
|
|
15
24
|
return keys.every((key) => {
|
|
16
25
|
if (key === 'boxes') {
|
|
@@ -29,6 +38,14 @@ export const hasAllInfo = (
|
|
|
29
38
|
return hasFps(parseResult.segments) !== null;
|
|
30
39
|
}
|
|
31
40
|
|
|
41
|
+
if (key === 'videoCodec') {
|
|
42
|
+
return hasVideoCodec(parseResult.segments) !== null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (key === 'audioCodec') {
|
|
46
|
+
return hasAudioCodec(parseResult.segments) !== null;
|
|
47
|
+
}
|
|
48
|
+
|
|
32
49
|
throw new Error(`Unknown key: ${key satisfies never}`);
|
|
33
50
|
});
|
|
34
51
|
};
|
package/src/options.ts
CHANGED
|
@@ -2,16 +2,36 @@ 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
|
+
|
|
13
|
+
export type KnownAudioCodecs =
|
|
14
|
+
| 'aac'
|
|
15
|
+
| 'mp3'
|
|
16
|
+
| 'aiff'
|
|
17
|
+
| 'opus'
|
|
18
|
+
| 'pcm'
|
|
19
|
+
| 'unknown';
|
|
20
|
+
|
|
5
21
|
export type Options<
|
|
6
22
|
EnableDimensions extends boolean,
|
|
7
23
|
EnableDuration extends boolean,
|
|
8
24
|
EnableBoxes extends boolean,
|
|
9
25
|
EnableFps extends boolean,
|
|
26
|
+
EnableVideoCodec extends boolean,
|
|
27
|
+
EnableAudioCodec extends boolean,
|
|
10
28
|
> = {
|
|
11
29
|
dimensions?: EnableDimensions;
|
|
12
30
|
durationInSeconds?: EnableDuration;
|
|
13
31
|
boxes?: EnableBoxes;
|
|
14
32
|
fps?: EnableFps;
|
|
33
|
+
videoCodec?: EnableVideoCodec;
|
|
34
|
+
audioCodec?: EnableAudioCodec;
|
|
15
35
|
};
|
|
16
36
|
|
|
17
37
|
export type Metadata<
|
|
@@ -19,20 +39,40 @@ export type Metadata<
|
|
|
19
39
|
EnableDuration extends boolean,
|
|
20
40
|
EnableBoxes extends boolean,
|
|
21
41
|
EnableFps extends boolean,
|
|
42
|
+
EnableVideoCodec extends boolean,
|
|
43
|
+
EnableAudioCodec extends boolean,
|
|
22
44
|
> = (EnableDimensions extends true ? {dimensions: Dimensions} : {}) &
|
|
23
45
|
(EnableDuration extends true ? {durationInSeconds: number | null} : {}) &
|
|
24
46
|
(EnableBoxes extends true ? {boxes: AnySegment[]} : {}) &
|
|
25
|
-
(EnableFps extends true ? {fps: number | null} : {})
|
|
47
|
+
(EnableFps extends true ? {fps: number | null} : {}) &
|
|
48
|
+
(EnableVideoCodec extends true ? {videoCodec: KnownVideoCodecs | null} : {}) &
|
|
49
|
+
(EnableAudioCodec extends true ? {audioCodec: KnownAudioCodecs | null} : {});
|
|
26
50
|
|
|
27
51
|
export type ParseMedia = <
|
|
28
52
|
EnableDimensions extends boolean,
|
|
29
53
|
EnableDuration extends boolean,
|
|
30
54
|
EnableBoxes extends boolean,
|
|
31
55
|
EnableFps extends boolean,
|
|
56
|
+
EnableVideoCodec extends boolean,
|
|
57
|
+
EnableAudioCodec extends boolean,
|
|
32
58
|
>(
|
|
33
59
|
src: string,
|
|
34
|
-
options: Options<
|
|
60
|
+
options: Options<
|
|
61
|
+
EnableDimensions,
|
|
62
|
+
EnableDuration,
|
|
63
|
+
EnableBoxes,
|
|
64
|
+
EnableFps,
|
|
65
|
+
EnableVideoCodec,
|
|
66
|
+
EnableAudioCodec
|
|
67
|
+
>,
|
|
35
68
|
readerInterface?: ReaderInterface,
|
|
36
69
|
) => Promise<
|
|
37
|
-
Metadata<
|
|
70
|
+
Metadata<
|
|
71
|
+
EnableDimensions,
|
|
72
|
+
EnableDuration,
|
|
73
|
+
EnableBoxes,
|
|
74
|
+
EnableFps,
|
|
75
|
+
EnableVideoCodec,
|
|
76
|
+
EnableAudioCodec
|
|
77
|
+
>
|
|
38
78
|
>;
|
package/src/parse-media.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import type {BufferIterator} from './buffer-iterator';
|
|
1
2
|
import {getArrayBufferIterator} from './buffer-iterator';
|
|
2
3
|
import {webReader} from './from-web';
|
|
4
|
+
import {getAudioCodec} from './get-audio-codec';
|
|
3
5
|
import {getDimensions} from './get-dimensions';
|
|
4
6
|
import {getDuration} from './get-duration';
|
|
5
7
|
import {getFps} from './get-fps';
|
|
8
|
+
import {getVideoCodec} from './get-video-codec';
|
|
6
9
|
import {hasAllInfo} from './has-all-info';
|
|
7
10
|
import type {Metadata, ParseMedia} from './options';
|
|
11
|
+
import type {ParseResult} from './parse-result';
|
|
8
12
|
import {parseVideo} from './parse-video';
|
|
9
13
|
|
|
10
14
|
export const parseMedia: ParseMedia = async (
|
|
@@ -12,21 +16,33 @@ export const parseMedia: ParseMedia = async (
|
|
|
12
16
|
options,
|
|
13
17
|
readerInterface = webReader,
|
|
14
18
|
) => {
|
|
15
|
-
const reader = await readerInterface.read(src, null);
|
|
19
|
+
const {reader, contentLength} = await readerInterface.read(src, null);
|
|
16
20
|
|
|
17
|
-
const returnValue = {} as Metadata<true, true, true, true>;
|
|
21
|
+
const returnValue = {} as Metadata<true, true, true, true, true, true>;
|
|
18
22
|
|
|
19
|
-
|
|
20
|
-
let parseResult =
|
|
23
|
+
let iterator: BufferIterator | null = null;
|
|
24
|
+
let parseResult: ParseResult | null = null;
|
|
21
25
|
|
|
22
|
-
while (parseResult.status === 'incomplete') {
|
|
26
|
+
while (parseResult === null || parseResult.status === 'incomplete') {
|
|
23
27
|
const result = await reader.read();
|
|
24
28
|
if (result.done) {
|
|
25
29
|
break;
|
|
26
30
|
}
|
|
27
31
|
|
|
28
|
-
iterator
|
|
29
|
-
|
|
32
|
+
if (iterator) {
|
|
33
|
+
iterator.addData(result.value);
|
|
34
|
+
} else {
|
|
35
|
+
iterator = getArrayBufferIterator(
|
|
36
|
+
result.value,
|
|
37
|
+
contentLength ?? undefined,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (parseResult) {
|
|
42
|
+
parseResult = parseResult.continueParsing();
|
|
43
|
+
} else {
|
|
44
|
+
parseResult = parseVideo(iterator);
|
|
45
|
+
}
|
|
30
46
|
|
|
31
47
|
if (hasAllInfo(options, parseResult)) {
|
|
32
48
|
if (!reader.closed) {
|
|
@@ -37,6 +53,10 @@ export const parseMedia: ParseMedia = async (
|
|
|
37
53
|
}
|
|
38
54
|
}
|
|
39
55
|
|
|
56
|
+
if (!parseResult) {
|
|
57
|
+
throw new Error('Could not parse video');
|
|
58
|
+
}
|
|
59
|
+
|
|
40
60
|
if (options.dimensions) {
|
|
41
61
|
returnValue.dimensions = getDimensions(parseResult.segments);
|
|
42
62
|
}
|
|
@@ -49,6 +69,14 @@ export const parseMedia: ParseMedia = async (
|
|
|
49
69
|
returnValue.fps = getFps(parseResult.segments);
|
|
50
70
|
}
|
|
51
71
|
|
|
72
|
+
if (options.videoCodec) {
|
|
73
|
+
returnValue.videoCodec = getVideoCodec(parseResult.segments);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (options.audioCodec) {
|
|
77
|
+
returnValue.audioCodec = getAudioCodec(parseResult.segments);
|
|
78
|
+
}
|
|
79
|
+
|
|
52
80
|
if (options.boxes) {
|
|
53
81
|
returnValue.boxes = parseResult.segments;
|
|
54
82
|
}
|
package/src/parse-result.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type {BaseBox} from './boxes/iso-base-media/base-type';
|
|
2
|
+
import type {EsdsBox} from './boxes/iso-base-media/esds/esds';
|
|
2
3
|
import type {FtypBox} from './boxes/iso-base-media/ftyp';
|
|
4
|
+
import type {MdhdBox} from './boxes/iso-base-media/mdhd';
|
|
3
5
|
import type {MoovBox} from './boxes/iso-base-media/moov/moov';
|
|
4
6
|
import type {MvhdBox} from './boxes/iso-base-media/mvhd';
|
|
5
7
|
import type {KeysBox} from './boxes/iso-base-media/stsd/keys';
|
|
@@ -28,7 +30,9 @@ export type IsoBaseMediaBox =
|
|
|
28
30
|
| KeysBox
|
|
29
31
|
| MoovBox
|
|
30
32
|
| TrakBox
|
|
31
|
-
| SttsBox
|
|
33
|
+
| SttsBox
|
|
34
|
+
| MdhdBox
|
|
35
|
+
| EsdsBox;
|
|
32
36
|
|
|
33
37
|
export type AnySegment = MatroskaSegment | IsoBaseMediaBox;
|
|
34
38
|
|
package/src/reader.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
type ReadResult = {
|
|
2
|
+
reader: ReadableStreamDefaultReader<Uint8Array>;
|
|
3
|
+
contentLength: number | null;
|
|
4
|
+
};
|
|
1
5
|
type ReadContent = (
|
|
2
6
|
src: string,
|
|
3
7
|
range: [number, number] | null,
|
|
4
|
-
) => Promise<
|
|
8
|
+
) => Promise<ReadResult>;
|
|
5
9
|
type GetLength = (src: string) => Promise<number>;
|
|
6
10
|
|
|
7
11
|
export type ReaderInterface = {
|
|
@@ -140,19 +140,18 @@ test('Should get duration of AV1 video', async () => {
|
|
|
140
140
|
],
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
|
-
|
|
144
|
-
|
|
143
|
+
codecPrivateData: [
|
|
144
|
+
129, 8, 12, 0, 10, 14, 0, 0, 0, 66, 171, 191, 195, 118, 0,
|
|
145
|
+
8, 8, 8, 8, 32,
|
|
146
|
+
],
|
|
147
|
+
type: 'codec-private-segment',
|
|
145
148
|
},
|
|
146
149
|
],
|
|
147
150
|
},
|
|
148
|
-
{
|
|
149
|
-
id: '0x0c000a0e',
|
|
150
|
-
type: 'unknown-segment',
|
|
151
|
-
},
|
|
152
151
|
],
|
|
153
152
|
},
|
|
154
153
|
{
|
|
155
|
-
id: '
|
|
154
|
+
id: '0x1254c367',
|
|
156
155
|
type: 'unknown-segment',
|
|
157
156
|
},
|
|
158
157
|
],
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {expect, test} from 'bun:test';
|
|
2
|
+
import {parseEsds} from '../boxes/iso-base-media/esds/esds';
|
|
3
|
+
import {getArrayBufferIterator} from '../buffer-iterator';
|
|
4
|
+
|
|
5
|
+
test('Parse ESDS box', () => {
|
|
6
|
+
const buf = new Uint8Array([
|
|
7
|
+
// mock header
|
|
8
|
+
0, 0, 0, 0, 0, 0, 0, 0,
|
|
9
|
+
// actual box
|
|
10
|
+
0, 0, 0, 0, 3, 128, 128, 128, 27, 0, 2, 0, 4, 128, 128, 128, 13, 107, 21, 0,
|
|
11
|
+
0, 0, 0, 4, 226, 0, 0, 4, 226, 0, 6, 128, 128, 128, 1, 2,
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
const iter = getArrayBufferIterator(buf);
|
|
15
|
+
iter.counter.increment(8);
|
|
16
|
+
|
|
17
|
+
expect(
|
|
18
|
+
parseEsds({
|
|
19
|
+
data: iter,
|
|
20
|
+
fileOffset: 8,
|
|
21
|
+
size: buf.length - 8,
|
|
22
|
+
}),
|
|
23
|
+
).toEqual({
|
|
24
|
+
type: 'esds-box',
|
|
25
|
+
version: 0,
|
|
26
|
+
tag: 3,
|
|
27
|
+
sizeOfInstance: 27,
|
|
28
|
+
esId: 2,
|
|
29
|
+
descriptors: [
|
|
30
|
+
{
|
|
31
|
+
type: 'decoder-config-descriptor',
|
|
32
|
+
objectTypeIndication: 'mp3',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
type: 'sl-config-descriptor',
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('Parse two ESDS', () => {
|
|
42
|
+
const buf = new Uint8Array([
|
|
43
|
+
// mock header
|
|
44
|
+
0, 0, 0, 0, 0, 0, 0, 0,
|
|
45
|
+
// actual box
|
|
46
|
+
0, 0, 0, 0, 3, 25, 0, 0, 0, 4, 17, 64, 21, 0, 24, 0, 0, 4, 226, 0, 0, 4,
|
|
47
|
+
226, 0, 5, 2, 17, 144, 6, 1, 2, 0, 0, 0, 24, 115, 116, 116, 115,
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
const iter = getArrayBufferIterator(buf);
|
|
51
|
+
iter.counter.increment(8);
|
|
52
|
+
|
|
53
|
+
expect(
|
|
54
|
+
parseEsds({
|
|
55
|
+
data: iter,
|
|
56
|
+
fileOffset: 8,
|
|
57
|
+
size: buf.length - 8,
|
|
58
|
+
}),
|
|
59
|
+
).toEqual({
|
|
60
|
+
type: 'esds-box',
|
|
61
|
+
version: 0,
|
|
62
|
+
tag: 3,
|
|
63
|
+
sizeOfInstance: 25,
|
|
64
|
+
esId: 0,
|
|
65
|
+
descriptors: [
|
|
66
|
+
{
|
|
67
|
+
objectTypeIndication: 'aac',
|
|
68
|
+
type: 'decoder-config-descriptor',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
type: 'sl-config-descriptor',
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -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
|
});
|