@remotion/media-parser 4.0.199 → 4.0.200
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/mdat/mdat.js +0 -1
- package/dist/boxes/webm/ebml.d.ts +2 -0
- package/dist/boxes/webm/ebml.js +72 -0
- package/dist/boxes/webm/make-header.d.ts +2 -0
- package/dist/boxes/webm/make-header.js +44 -0
- package/dist/boxes/webm/segments/all-segments.d.ts +5 -0
- package/dist/boxes/webm/segments/all-segments.js +5 -0
- package/dist/boxes/webm/segments/block-simple-block-flags.d.ts +9 -0
- package/dist/boxes/webm/segments/block-simple-block-flags.js +38 -0
- package/dist/boxes/webm/segments/track-entry.d.ts +20 -8
- package/dist/boxes/webm/segments/track-entry.js +64 -23
- package/dist/boxes/webm/segments.d.ts +2 -2
- package/dist/boxes/webm/segments.js +35 -6
- package/dist/buffer-iterator.d.ts +1 -0
- package/dist/buffer-iterator.js +9 -2
- package/dist/get-audio-codec.d.ts +1 -1
- package/dist/parser-state.d.ts +4 -6
- package/dist/parser-state.js +24 -16
- package/dist/webcodec-sample-types.d.ts +0 -1
- package/package.json +2 -2
- package/src/boxes/iso-base-media/mdat/mdat.ts +0 -1
- package/src/boxes/webm/ebml.ts +78 -0
- package/src/boxes/webm/make-header.ts +48 -0
- package/src/boxes/webm/segments/all-segments.ts +5 -0
- package/src/boxes/webm/segments/block-simple-block-flags.ts +52 -0
- package/src/boxes/webm/segments/track-entry.ts +108 -29
- package/src/boxes/webm/segments.ts +71 -9
- package/src/buffer-iterator.ts +8 -1
- package/src/parser-state.ts +30 -20
- package/src/test/create-matroska.test.ts +14 -0
- package/src/test/matroska.test.ts +75 -100
- package/src/test/stream-local.test.ts +47 -5
- package/src/webcodec-sample-types.ts +0 -1
- package/tsconfig.tsbuildinfo +1 -1
package/dist/parser-state.js
CHANGED
|
@@ -21,7 +21,6 @@ const makeParserState = ({ hasAudioCallbacks, hasVideoCallbacks, }) => {
|
|
|
21
21
|
};
|
|
22
22
|
const videoSampleCallbacks = {};
|
|
23
23
|
const audioSampleCallbacks = {};
|
|
24
|
-
let samplesThatHadToBeQueued = 0;
|
|
25
24
|
const queuedAudioSamples = {};
|
|
26
25
|
const queuedVideoSamples = {};
|
|
27
26
|
const declinedTrackNumbers = [];
|
|
@@ -35,6 +34,25 @@ const makeParserState = ({ hasAudioCallbacks, hasVideoCallbacks, }) => {
|
|
|
35
34
|
const setTimescale = (newTimescale) => {
|
|
36
35
|
timescale = newTimescale;
|
|
37
36
|
};
|
|
37
|
+
const timestampMap = new Map();
|
|
38
|
+
const setTimestampOffset = (byteOffset, timestamp) => {
|
|
39
|
+
timestampMap.set(byteOffset, timestamp);
|
|
40
|
+
};
|
|
41
|
+
const getTimestampOffsetForByteOffset = (byteOffset) => {
|
|
42
|
+
const entries = Array.from(timestampMap.entries());
|
|
43
|
+
const sortedByByteOffset = entries
|
|
44
|
+
.sort((a, b) => {
|
|
45
|
+
return a[0] - b[0];
|
|
46
|
+
})
|
|
47
|
+
.reverse();
|
|
48
|
+
for (const [offset, timestamp] of sortedByByteOffset) {
|
|
49
|
+
if (offset >= byteOffset) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
return timestamp;
|
|
53
|
+
}
|
|
54
|
+
return timestampMap.get(byteOffset);
|
|
55
|
+
};
|
|
38
56
|
return {
|
|
39
57
|
onTrackEntrySegment,
|
|
40
58
|
getTrackInfoByNumber: (id) => trackEntries[id],
|
|
@@ -51,6 +69,8 @@ const makeParserState = ({ hasAudioCallbacks, hasVideoCallbacks, }) => {
|
|
|
51
69
|
}
|
|
52
70
|
queuedVideoSamples[id] = [];
|
|
53
71
|
},
|
|
72
|
+
setTimestampOffset,
|
|
73
|
+
getTimestampOffsetForByteOffset,
|
|
54
74
|
registerAudioSampleCallback: async (id, callback) => {
|
|
55
75
|
var _a;
|
|
56
76
|
if (callback === null) {
|
|
@@ -65,7 +85,6 @@ const makeParserState = ({ hasAudioCallbacks, hasVideoCallbacks, }) => {
|
|
|
65
85
|
queuedAudioSamples[id] = [];
|
|
66
86
|
},
|
|
67
87
|
onAudioSample: async (trackId, audioSample) => {
|
|
68
|
-
var _a;
|
|
69
88
|
const callback = audioSampleCallbacks[trackId];
|
|
70
89
|
if (callback) {
|
|
71
90
|
await callback(audioSample);
|
|
@@ -75,15 +94,11 @@ const makeParserState = ({ hasAudioCallbacks, hasVideoCallbacks, }) => {
|
|
|
75
94
|
return;
|
|
76
95
|
}
|
|
77
96
|
if (!hasAudioCallbacks) {
|
|
78
|
-
|
|
97
|
+
throw new Error('No audio callbacks registered');
|
|
79
98
|
}
|
|
80
|
-
(_a = queuedAudioSamples[trackId]) !== null && _a !== void 0 ? _a : (queuedAudioSamples[trackId] = []);
|
|
81
|
-
queuedAudioSamples[trackId].push(audioSample);
|
|
82
|
-
samplesThatHadToBeQueued++;
|
|
83
99
|
}
|
|
84
100
|
},
|
|
85
101
|
onVideoSample: async (trackId, videoSample) => {
|
|
86
|
-
var _a;
|
|
87
102
|
const callback = videoSampleCallbacks[trackId];
|
|
88
103
|
if (callback) {
|
|
89
104
|
await callback(videoSample);
|
|
@@ -93,18 +108,11 @@ const makeParserState = ({ hasAudioCallbacks, hasVideoCallbacks, }) => {
|
|
|
93
108
|
return;
|
|
94
109
|
}
|
|
95
110
|
if (!hasVideoCallbacks) {
|
|
96
|
-
|
|
111
|
+
throw new Error('No video callbacks registered');
|
|
97
112
|
}
|
|
98
|
-
(_a = queuedVideoSamples[trackId]) !== null && _a !== void 0 ? _a : (queuedVideoSamples[trackId] = []);
|
|
99
|
-
queuedVideoSamples[trackId].push(videoSample);
|
|
100
|
-
samplesThatHadToBeQueued++;
|
|
101
113
|
}
|
|
102
114
|
},
|
|
103
|
-
getInternalStats: () => {
|
|
104
|
-
return {
|
|
105
|
-
samplesThatHadToBeQueued,
|
|
106
|
-
};
|
|
107
|
-
},
|
|
115
|
+
getInternalStats: () => ({}),
|
|
108
116
|
getTimescale,
|
|
109
117
|
setTimescale,
|
|
110
118
|
};
|
package/package.json
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
"url": "https://github.com/remotion-dev/remotion/tree/main/packages/media-parser"
|
|
4
4
|
},
|
|
5
5
|
"name": "@remotion/media-parser",
|
|
6
|
-
"version": "4.0.
|
|
6
|
+
"version": "4.0.200",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"sideEffects": false,
|
|
9
9
|
"devDependencies": {
|
|
10
|
-
"@remotion/renderer": "4.0.
|
|
10
|
+
"@remotion/renderer": "4.0.200"
|
|
11
11
|
},
|
|
12
12
|
"publishConfig": {
|
|
13
13
|
"access": "public"
|
|
@@ -91,7 +91,6 @@ export const parseMdat = async ({
|
|
|
91
91
|
await options.parserState.onAudioSample(sampleWithIndex.track.trackId, {
|
|
92
92
|
data: bytes,
|
|
93
93
|
timestamp: sampleWithIndex.samplePosition.offset,
|
|
94
|
-
offset: data.counter.getOffset(),
|
|
95
94
|
trackId: sampleWithIndex.track.trackId,
|
|
96
95
|
type: sampleWithIndex.samplePosition.isKeyframe ? 'key' : 'delta',
|
|
97
96
|
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// https://github.com/Vanilagy/webm-muxer/blob/main/src/ebml.ts#L101
|
|
2
|
+
|
|
3
|
+
export const measureEBMLVarInt = (value: number) => {
|
|
4
|
+
if (value < (1 << 7) - 1) {
|
|
5
|
+
/** Top bit is set, leaving 7 bits to hold the integer, but we can't store
|
|
6
|
+
* 127 because "all bits set to one" is a reserved value. Same thing for the
|
|
7
|
+
* other cases below:
|
|
8
|
+
*/
|
|
9
|
+
return 1;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (value < (1 << 14) - 1) {
|
|
13
|
+
return 2;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (value < (1 << 21) - 1) {
|
|
17
|
+
return 3;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (value < (1 << 28) - 1) {
|
|
21
|
+
return 4;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (value < 2 ** 35 - 1) {
|
|
25
|
+
return 5;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (value < 2 ** 42 - 1) {
|
|
29
|
+
return 6;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
throw new Error('EBML VINT size not supported ' + value);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const getVariableInt = (
|
|
36
|
+
value: number,
|
|
37
|
+
width: number = measureEBMLVarInt(value),
|
|
38
|
+
) => {
|
|
39
|
+
switch (width) {
|
|
40
|
+
case 1:
|
|
41
|
+
return new Uint8Array([(1 << 7) | value]);
|
|
42
|
+
case 2:
|
|
43
|
+
return new Uint8Array([(1 << 6) | (value >> 8), value]);
|
|
44
|
+
case 3:
|
|
45
|
+
return new Uint8Array([(1 << 5) | (value >> 16), value >> 8, value]);
|
|
46
|
+
case 4:
|
|
47
|
+
return new Uint8Array([
|
|
48
|
+
(1 << 4) | (value >> 24),
|
|
49
|
+
value >> 16,
|
|
50
|
+
value >> 8,
|
|
51
|
+
value,
|
|
52
|
+
]);
|
|
53
|
+
case 5:
|
|
54
|
+
/**
|
|
55
|
+
* JavaScript converts its doubles to 32-bit integers for bitwise
|
|
56
|
+
* operations, so we need to do a division by 2^32 instead of a
|
|
57
|
+
* right-shift of 32 to retain those top 3 bits
|
|
58
|
+
*/
|
|
59
|
+
return new Uint8Array([
|
|
60
|
+
(1 << 3) | ((value / 2 ** 32) & 0x7),
|
|
61
|
+
value >> 24,
|
|
62
|
+
value >> 16,
|
|
63
|
+
value >> 8,
|
|
64
|
+
value,
|
|
65
|
+
]);
|
|
66
|
+
case 6:
|
|
67
|
+
return new Uint8Array([
|
|
68
|
+
(1 << 2) | ((value / 2 ** 40) & 0x3),
|
|
69
|
+
(value / 2 ** 32) | 0,
|
|
70
|
+
value >> 24,
|
|
71
|
+
value >> 16,
|
|
72
|
+
value >> 8,
|
|
73
|
+
value,
|
|
74
|
+
]);
|
|
75
|
+
default:
|
|
76
|
+
throw new Error('Bad EBML VINT size ' + width);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {getVariableInt} from './ebml';
|
|
2
|
+
import {matroskaElements} from './segments/all-segments';
|
|
3
|
+
|
|
4
|
+
export const webmPattern = new Uint8Array([0x1a, 0x45, 0xdf, 0xa3]);
|
|
5
|
+
|
|
6
|
+
const matroskaToHex = (
|
|
7
|
+
matrId: (typeof matroskaElements)[keyof typeof matroskaElements],
|
|
8
|
+
) => {
|
|
9
|
+
const numbers: number[] = [];
|
|
10
|
+
for (let i = 2; i < matrId.length; i += 2) {
|
|
11
|
+
const hex = matrId.substr(i, 2);
|
|
12
|
+
numbers.push(parseInt(hex, 16));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return numbers;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const makeMatroskaHeader = () => {
|
|
19
|
+
const size = 0x23;
|
|
20
|
+
|
|
21
|
+
const array = new Uint8Array([
|
|
22
|
+
...webmPattern,
|
|
23
|
+
...getVariableInt(size),
|
|
24
|
+
...matroskaToHex(matroskaElements.EBMLVersion),
|
|
25
|
+
...getVariableInt(1),
|
|
26
|
+
1,
|
|
27
|
+
...matroskaToHex(matroskaElements.EBMLReadVersion),
|
|
28
|
+
...getVariableInt(1),
|
|
29
|
+
1,
|
|
30
|
+
...matroskaToHex(matroskaElements.EBMLMaxIDLength),
|
|
31
|
+
...getVariableInt(1),
|
|
32
|
+
4,
|
|
33
|
+
...matroskaToHex(matroskaElements.EBMLMaxSizeLength),
|
|
34
|
+
...getVariableInt(1),
|
|
35
|
+
8,
|
|
36
|
+
...matroskaToHex(matroskaElements.DocType),
|
|
37
|
+
...getVariableInt(8),
|
|
38
|
+
...new TextEncoder().encode('matroska'),
|
|
39
|
+
...matroskaToHex(matroskaElements.DocTypeVersion),
|
|
40
|
+
...getVariableInt(1),
|
|
41
|
+
4,
|
|
42
|
+
...matroskaToHex(matroskaElements.DocTypeReadVersion),
|
|
43
|
+
...getVariableInt(1),
|
|
44
|
+
2,
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
return array;
|
|
48
|
+
};
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
export const matroskaElements = {
|
|
2
2
|
EBMLMaxIDLength: '0x42f2',
|
|
3
|
+
EBMLVersion: '0x4286',
|
|
4
|
+
EBMLReadVersion: '0x42F7',
|
|
3
5
|
EBMLMaxSizeLength: '0x42f3',
|
|
6
|
+
DocType: '0x4282',
|
|
7
|
+
DocTypeVersion: '0x4287',
|
|
8
|
+
DocTypeReadVersion: '0x4285',
|
|
4
9
|
Segment: '0x18538067',
|
|
5
10
|
SeekHead: '0x114d9b74',
|
|
6
11
|
Seek: '0x4dbb',
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type {BufferIterator} from '../../../buffer-iterator';
|
|
2
|
+
import {matroskaElements} from './all-segments';
|
|
3
|
+
|
|
4
|
+
type BlockFlags = {
|
|
5
|
+
invisible: boolean;
|
|
6
|
+
lacing: number;
|
|
7
|
+
keyframe: boolean | null;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const parseBlockFlags = (
|
|
11
|
+
iterator: BufferIterator,
|
|
12
|
+
type:
|
|
13
|
+
| (typeof matroskaElements)['Block']
|
|
14
|
+
| (typeof matroskaElements)['SimpleBlock'],
|
|
15
|
+
): BlockFlags => {
|
|
16
|
+
if (type === matroskaElements.Block) {
|
|
17
|
+
iterator.startReadingBits();
|
|
18
|
+
// Reserved
|
|
19
|
+
iterator.getBits(4);
|
|
20
|
+
const invisible = Boolean(iterator.getBits(1));
|
|
21
|
+
const lacing = iterator.getBits(2);
|
|
22
|
+
// unused
|
|
23
|
+
iterator.getBits(1);
|
|
24
|
+
iterator.stopReadingBits();
|
|
25
|
+
return {
|
|
26
|
+
invisible,
|
|
27
|
+
lacing,
|
|
28
|
+
keyframe: null,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (type === matroskaElements.SimpleBlock) {
|
|
33
|
+
iterator.startReadingBits();
|
|
34
|
+
|
|
35
|
+
const keyframe = Boolean(iterator.getBits(1));
|
|
36
|
+
// Reserved
|
|
37
|
+
iterator.getBits(3);
|
|
38
|
+
const invisible = Boolean(iterator.getBits(1));
|
|
39
|
+
const lacing = iterator.getBits(2);
|
|
40
|
+
iterator.getBits(1);
|
|
41
|
+
|
|
42
|
+
iterator.stopReadingBits();
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
invisible,
|
|
46
|
+
lacing,
|
|
47
|
+
keyframe,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
throw new Error('Unexpected type');
|
|
52
|
+
};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type {BufferIterator} from '../../../buffer-iterator';
|
|
2
2
|
import type {ParserContext} from '../../../parser-context';
|
|
3
|
+
import type {VideoSample} from '../../../webcodec-sample-types';
|
|
3
4
|
import type {MatroskaSegment} from '../segments';
|
|
5
|
+
import type {matroskaElements} from './all-segments';
|
|
6
|
+
import {parseBlockFlags} from './block-simple-block-flags';
|
|
4
7
|
import {expectChildren} from './parse-children';
|
|
5
8
|
|
|
6
9
|
export type TrackEntrySegment = {
|
|
@@ -566,8 +569,18 @@ export const parseTimestampSegment = (
|
|
|
566
569
|
iterator: BufferIterator,
|
|
567
570
|
length: number,
|
|
568
571
|
): TimestampSegment => {
|
|
569
|
-
if (length >
|
|
570
|
-
throw new Error(
|
|
572
|
+
if (length > 3) {
|
|
573
|
+
throw new Error(
|
|
574
|
+
'Expected timestamp segment to be 1 byte or 2 bytes, but is ' + length,
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (length === 3) {
|
|
579
|
+
const val = iterator.getUint24();
|
|
580
|
+
return {
|
|
581
|
+
type: 'timestamp-segment',
|
|
582
|
+
timestamp: val,
|
|
583
|
+
};
|
|
571
584
|
}
|
|
572
585
|
|
|
573
586
|
const value = length === 2 ? iterator.getUint16() : iterator.getUint8();
|
|
@@ -578,66 +591,86 @@ export const parseTimestampSegment = (
|
|
|
578
591
|
};
|
|
579
592
|
};
|
|
580
593
|
|
|
581
|
-
export type
|
|
582
|
-
type: 'simple-block-segment';
|
|
594
|
+
export type SimpleBlockOrBlockSegment = {
|
|
595
|
+
type: 'simple-block-or-block-segment';
|
|
583
596
|
length: number;
|
|
584
597
|
trackNumber: number;
|
|
585
598
|
timecode: number;
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
lacing: [number, number];
|
|
599
|
+
keyframe: boolean | null;
|
|
600
|
+
lacing: number;
|
|
589
601
|
invisible: boolean;
|
|
590
|
-
|
|
602
|
+
videoSample: Omit<VideoSample, 'type'> | null;
|
|
591
603
|
};
|
|
592
604
|
|
|
593
605
|
export type GetTracks = () => TrackEntrySegment[];
|
|
594
606
|
|
|
595
|
-
export const
|
|
607
|
+
export const parseSimpleBlockOrBlockSegment = async ({
|
|
596
608
|
iterator,
|
|
597
609
|
length,
|
|
598
610
|
parserContext,
|
|
611
|
+
type,
|
|
599
612
|
}: {
|
|
600
613
|
iterator: BufferIterator;
|
|
601
614
|
length: number;
|
|
602
615
|
parserContext: ParserContext;
|
|
603
|
-
|
|
616
|
+
type:
|
|
617
|
+
| (typeof matroskaElements)['Block']
|
|
618
|
+
| (typeof matroskaElements)['SimpleBlock'];
|
|
619
|
+
}): Promise<SimpleBlockOrBlockSegment> => {
|
|
604
620
|
const start = iterator.counter.getOffset();
|
|
605
621
|
const trackNumber = iterator.getVint();
|
|
606
|
-
const
|
|
607
|
-
const headerFlags = iterator.getUint8();
|
|
622
|
+
const timecodeRelativeToCluster = iterator.getUint16();
|
|
608
623
|
|
|
609
|
-
const invisible =
|
|
610
|
-
const pos6 = (headerFlags >> 6) & 1;
|
|
611
|
-
const pos7 = (headerFlags >> 6) & 1;
|
|
612
|
-
const keyframe = Boolean((headerFlags >> 7) & 1);
|
|
624
|
+
const {invisible, lacing, keyframe} = parseBlockFlags(iterator, type);
|
|
613
625
|
|
|
614
626
|
const codec = parserContext.parserState.getTrackInfoByNumber(trackNumber);
|
|
627
|
+
const clusterOffset =
|
|
628
|
+
parserContext.parserState.getTimestampOffsetForByteOffset(
|
|
629
|
+
iterator.counter.getOffset(),
|
|
630
|
+
);
|
|
631
|
+
|
|
632
|
+
if (clusterOffset === undefined) {
|
|
633
|
+
throw new Error(
|
|
634
|
+
'Could not find offset for byte offset ' + iterator.counter.getOffset(),
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const timecode = timecodeRelativeToCluster + clusterOffset;
|
|
615
639
|
|
|
616
640
|
if (!codec) {
|
|
617
641
|
throw new Error('Could not find codec for track ' + trackNumber);
|
|
618
642
|
}
|
|
619
643
|
|
|
620
|
-
const
|
|
644
|
+
const remainingNow = length - (iterator.counter.getOffset() - start);
|
|
621
645
|
|
|
622
|
-
|
|
623
|
-
const remainingNow = length - (iterator.counter.getOffset() - start);
|
|
646
|
+
let videoSample: Omit<VideoSample, 'type'> | null = null;
|
|
624
647
|
|
|
625
|
-
|
|
648
|
+
if (codec.codec.startsWith('V_')) {
|
|
649
|
+
const partialVideoSample: Omit<VideoSample, 'type'> = {
|
|
626
650
|
data: iterator.getSlice(remainingNow),
|
|
627
651
|
cts: null,
|
|
628
652
|
dts: null,
|
|
629
653
|
duration: undefined,
|
|
630
|
-
type: keyframe ? 'key' : 'delta',
|
|
631
654
|
trackId: trackNumber,
|
|
632
655
|
timestamp: timecode,
|
|
633
|
-
}
|
|
656
|
+
};
|
|
657
|
+
|
|
658
|
+
if (keyframe === null) {
|
|
659
|
+
// If we don't know if this is a keyframe, we know after we emit the BlockGroup
|
|
660
|
+
videoSample = partialVideoSample;
|
|
661
|
+
} else {
|
|
662
|
+
const sample: VideoSample = {
|
|
663
|
+
...partialVideoSample,
|
|
664
|
+
type: keyframe ? 'key' : 'delta',
|
|
665
|
+
};
|
|
666
|
+
|
|
667
|
+
await parserContext.parserState.onVideoSample(trackNumber, sample);
|
|
668
|
+
}
|
|
634
669
|
}
|
|
635
670
|
|
|
636
671
|
if (codec.codec.startsWith('A_')) {
|
|
637
|
-
const vorbisRemaining = length - (iterator.counter.getOffset() - start);
|
|
638
672
|
await parserContext.parserState.onAudioSample(trackNumber, {
|
|
639
|
-
data: iterator.getSlice(
|
|
640
|
-
offset: timecode,
|
|
673
|
+
data: iterator.getSlice(remainingNow),
|
|
641
674
|
trackId: trackNumber,
|
|
642
675
|
timestamp: timecode,
|
|
643
676
|
type: 'key',
|
|
@@ -650,15 +683,14 @@ export const parseSimpleBlockSegment = async ({
|
|
|
650
683
|
}
|
|
651
684
|
|
|
652
685
|
return {
|
|
653
|
-
type: 'simple-block-segment',
|
|
686
|
+
type: 'simple-block-or-block-segment',
|
|
654
687
|
length,
|
|
655
688
|
trackNumber,
|
|
656
689
|
timecode,
|
|
657
|
-
headerFlags,
|
|
658
690
|
keyframe,
|
|
659
|
-
lacing
|
|
691
|
+
lacing,
|
|
660
692
|
invisible,
|
|
661
|
-
|
|
693
|
+
videoSample,
|
|
662
694
|
};
|
|
663
695
|
};
|
|
664
696
|
|
|
@@ -705,6 +737,53 @@ export const parseBlockGroupSegment = async (
|
|
|
705
737
|
};
|
|
706
738
|
};
|
|
707
739
|
|
|
740
|
+
export type ReferenceBlockSegment = {
|
|
741
|
+
type: 'reference-block-segment';
|
|
742
|
+
referenceBlock: number;
|
|
743
|
+
};
|
|
744
|
+
|
|
745
|
+
export const parseReferenceBlockSegment = (
|
|
746
|
+
iterator: BufferIterator,
|
|
747
|
+
length: number,
|
|
748
|
+
): ReferenceBlockSegment => {
|
|
749
|
+
if (length > 4) {
|
|
750
|
+
throw new Error(
|
|
751
|
+
`Expected reference block segment to be 4 bytes, but got ${length}`,
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
const referenceBlock =
|
|
756
|
+
length === 4
|
|
757
|
+
? iterator.getUint32()
|
|
758
|
+
: length === 3
|
|
759
|
+
? iterator.getUint24()
|
|
760
|
+
: length === 2
|
|
761
|
+
? iterator.getUint16()
|
|
762
|
+
: iterator.getUint8();
|
|
763
|
+
|
|
764
|
+
return {
|
|
765
|
+
type: 'reference-block-segment',
|
|
766
|
+
referenceBlock,
|
|
767
|
+
};
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
export type BlockAdditionsSegment = {
|
|
771
|
+
type: 'block-additions-segment';
|
|
772
|
+
blockAdditions: Uint8Array;
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
export const parseBlockAdditionsSegment = (
|
|
776
|
+
iterator: BufferIterator,
|
|
777
|
+
length: number,
|
|
778
|
+
): BlockAdditionsSegment => {
|
|
779
|
+
const blockAdditions = iterator.getSlice(length);
|
|
780
|
+
|
|
781
|
+
return {
|
|
782
|
+
type: 'block-additions-segment',
|
|
783
|
+
blockAdditions,
|
|
784
|
+
};
|
|
785
|
+
};
|
|
786
|
+
|
|
708
787
|
export type BlockElement = {
|
|
709
788
|
type: 'block-element-segment';
|
|
710
789
|
length: number;
|
|
@@ -2,6 +2,7 @@ import {registerTrack} from '../../add-new-matroska-tracks';
|
|
|
2
2
|
import type {BufferIterator} from '../../buffer-iterator';
|
|
3
3
|
import type {ParseResult} from '../../parse-result';
|
|
4
4
|
import type {ParserContext} from '../../parser-context';
|
|
5
|
+
import type {VideoSample} from '../../webcodec-sample-types';
|
|
5
6
|
import {getTrack} from './get-track';
|
|
6
7
|
import {matroskaElements} from './segments/all-segments';
|
|
7
8
|
import type {DurationSegment} from './segments/duration';
|
|
@@ -29,6 +30,7 @@ import type {
|
|
|
29
30
|
AlphaModeSegment,
|
|
30
31
|
AudioSegment,
|
|
31
32
|
BitDepthSegment,
|
|
33
|
+
BlockAdditionsSegment,
|
|
32
34
|
BlockElement,
|
|
33
35
|
BlockGroupSegment,
|
|
34
36
|
ChannelsSegment,
|
|
@@ -46,9 +48,10 @@ import type {
|
|
|
46
48
|
InterlacedSegment,
|
|
47
49
|
LanguageSegment,
|
|
48
50
|
MaxBlockAdditionId,
|
|
51
|
+
ReferenceBlockSegment,
|
|
49
52
|
SamplingFrequencySegment,
|
|
50
53
|
SegmentUUIDSegment,
|
|
51
|
-
|
|
54
|
+
SimpleBlockOrBlockSegment,
|
|
52
55
|
TagSegment,
|
|
53
56
|
TagsSegment,
|
|
54
57
|
TimestampSegment,
|
|
@@ -64,6 +67,7 @@ import {
|
|
|
64
67
|
parseAlphaModeSegment,
|
|
65
68
|
parseAudioSegment,
|
|
66
69
|
parseBitDepthSegment,
|
|
70
|
+
parseBlockAdditionsSegment,
|
|
67
71
|
parseBlockElementSegment,
|
|
68
72
|
parseBlockGroupSegment,
|
|
69
73
|
parseChannelsSegment,
|
|
@@ -80,9 +84,10 @@ import {
|
|
|
80
84
|
parseInterlacedSegment,
|
|
81
85
|
parseLanguageSegment,
|
|
82
86
|
parseMaxBlockAdditionId,
|
|
87
|
+
parseReferenceBlockSegment,
|
|
83
88
|
parseSamplingFrequencySegment,
|
|
84
89
|
parseSegmentUUIDSegment,
|
|
85
|
-
|
|
90
|
+
parseSimpleBlockOrBlockSegment,
|
|
86
91
|
parseTagSegment,
|
|
87
92
|
parseTagsSegment,
|
|
88
93
|
parseTimestampSegment,
|
|
@@ -142,14 +147,16 @@ export type MatroskaSegment =
|
|
|
142
147
|
| TagSegment
|
|
143
148
|
| ClusterSegment
|
|
144
149
|
| TimestampSegment
|
|
145
|
-
|
|
|
150
|
+
| SimpleBlockOrBlockSegment
|
|
146
151
|
| BlockGroupSegment
|
|
147
152
|
| BlockElement
|
|
148
153
|
| SeekIdSegment
|
|
149
154
|
| AudioSegment
|
|
150
155
|
| SamplingFrequencySegment
|
|
151
156
|
| ChannelsSegment
|
|
152
|
-
| BitDepthSegment
|
|
157
|
+
| BitDepthSegment
|
|
158
|
+
| ReferenceBlockSegment
|
|
159
|
+
| BlockAdditionsSegment;
|
|
153
160
|
|
|
154
161
|
export type OnTrackEntrySegment = (trackEntry: TrackEntrySegment) => void;
|
|
155
162
|
|
|
@@ -360,20 +367,75 @@ const parseSegment = async ({
|
|
|
360
367
|
return parseBitDepthSegment(iterator, length);
|
|
361
368
|
}
|
|
362
369
|
|
|
363
|
-
if (segmentId ===
|
|
364
|
-
|
|
370
|
+
if (segmentId === matroskaElements.Timestamp) {
|
|
371
|
+
const offset = iterator.counter.getOffset();
|
|
372
|
+
const timestampSegment = parseTimestampSegment(iterator, length);
|
|
373
|
+
|
|
374
|
+
parserContext.parserState.setTimestampOffset(
|
|
375
|
+
offset,
|
|
376
|
+
timestampSegment.timestamp,
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
return timestampSegment;
|
|
365
380
|
}
|
|
366
381
|
|
|
367
|
-
if (
|
|
368
|
-
|
|
382
|
+
if (
|
|
383
|
+
segmentId === matroskaElements.SimpleBlock ||
|
|
384
|
+
segmentId === matroskaElements.Block
|
|
385
|
+
) {
|
|
386
|
+
return parseSimpleBlockOrBlockSegment({
|
|
369
387
|
iterator,
|
|
370
388
|
length,
|
|
371
389
|
parserContext,
|
|
390
|
+
type: segmentId,
|
|
372
391
|
});
|
|
373
392
|
}
|
|
374
393
|
|
|
394
|
+
if (segmentId === matroskaElements.ReferenceBlock) {
|
|
395
|
+
return parseReferenceBlockSegment(iterator, length);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (segmentId === matroskaElements.BlockAdditions) {
|
|
399
|
+
return parseBlockAdditionsSegment(iterator, length);
|
|
400
|
+
}
|
|
401
|
+
|
|
375
402
|
if (segmentId === '0xa0') {
|
|
376
|
-
|
|
403
|
+
const blockGroup = await parseBlockGroupSegment(
|
|
404
|
+
iterator,
|
|
405
|
+
length,
|
|
406
|
+
parserContext,
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
// Blocks don't have information about keyframes.
|
|
410
|
+
// https://ffmpeg.org/pipermail/ffmpeg-devel/2015-June/173825.html
|
|
411
|
+
// "For Blocks, keyframes is
|
|
412
|
+
// inferred by the absence of ReferenceBlock element (as done by matroskadec).""
|
|
413
|
+
|
|
414
|
+
const block = blockGroup.children.find(
|
|
415
|
+
(c) => c.type === 'simple-block-or-block-segment',
|
|
416
|
+
);
|
|
417
|
+
if (!block || block.type !== 'simple-block-or-block-segment') {
|
|
418
|
+
throw new Error('Expected block segment');
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const hasReferenceBlock = blockGroup.children.find(
|
|
422
|
+
(c) => c.type === 'reference-block-segment',
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
const partialVideoSample = block.videoSample;
|
|
426
|
+
|
|
427
|
+
if (partialVideoSample) {
|
|
428
|
+
const completeFrame: VideoSample = {
|
|
429
|
+
...partialVideoSample,
|
|
430
|
+
type: hasReferenceBlock ? 'delta' : 'key',
|
|
431
|
+
};
|
|
432
|
+
await parserContext.parserState.onVideoSample(
|
|
433
|
+
partialVideoSample.trackId,
|
|
434
|
+
completeFrame,
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return blockGroup;
|
|
377
439
|
}
|
|
378
440
|
|
|
379
441
|
if (segmentId === '0xa1') {
|
package/src/buffer-iterator.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {webmPattern} from './boxes/webm/make-header';
|
|
1
2
|
import {
|
|
2
3
|
knownIdsWithOneLength,
|
|
3
4
|
knownIdsWithThreeLength,
|
|
@@ -46,7 +47,6 @@ export class OffsetCounter {
|
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
const isoBaseMediaMp4Pattern = new TextEncoder().encode('ftyp');
|
|
49
|
-
const webmPattern = new Uint8Array([0x1a, 0x45, 0xdf, 0xa3]);
|
|
50
50
|
const mpegPattern = new Uint8Array([0xff, 0xf3, 0xe4, 0x64]);
|
|
51
51
|
|
|
52
52
|
const matchesPattern = (pattern: Uint8Array) => {
|
|
@@ -388,6 +388,13 @@ export const getArrayBufferIterator = (
|
|
|
388
388
|
counter.increment(2);
|
|
389
389
|
return val;
|
|
390
390
|
},
|
|
391
|
+
getUint24: () => {
|
|
392
|
+
const val1 = view.getUint8(counter.getDiscardedOffset());
|
|
393
|
+
const val2 = view.getUint8(counter.getDiscardedOffset());
|
|
394
|
+
const val3 = view.getUint8(counter.getDiscardedOffset());
|
|
395
|
+
counter.increment(3);
|
|
396
|
+
return (val1 << 16) | (val2 << 8) | val3;
|
|
397
|
+
},
|
|
391
398
|
|
|
392
399
|
getInt16: () => {
|
|
393
400
|
const val = view.getInt16(counter.getDiscardedOffset());
|