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