@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/dist/parse-media.js
CHANGED
|
@@ -3,23 +3,35 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.parseMedia = void 0;
|
|
4
4
|
const buffer_iterator_1 = require("./buffer-iterator");
|
|
5
5
|
const from_web_1 = require("./from-web");
|
|
6
|
+
const get_audio_codec_1 = require("./get-audio-codec");
|
|
6
7
|
const get_dimensions_1 = require("./get-dimensions");
|
|
7
8
|
const get_duration_1 = require("./get-duration");
|
|
8
9
|
const get_fps_1 = require("./get-fps");
|
|
10
|
+
const get_video_codec_1 = require("./get-video-codec");
|
|
9
11
|
const has_all_info_1 = require("./has-all-info");
|
|
10
12
|
const parse_video_1 = require("./parse-video");
|
|
11
13
|
const parseMedia = async (src, options, readerInterface = from_web_1.webReader) => {
|
|
12
|
-
const reader = await readerInterface.read(src, null);
|
|
14
|
+
const { reader, contentLength } = await readerInterface.read(src, null);
|
|
13
15
|
const returnValue = {};
|
|
14
|
-
|
|
15
|
-
let parseResult =
|
|
16
|
-
while (parseResult.status === 'incomplete') {
|
|
16
|
+
let iterator = null;
|
|
17
|
+
let parseResult = null;
|
|
18
|
+
while (parseResult === null || parseResult.status === 'incomplete') {
|
|
17
19
|
const result = await reader.read();
|
|
18
20
|
if (result.done) {
|
|
19
21
|
break;
|
|
20
22
|
}
|
|
21
|
-
iterator
|
|
22
|
-
|
|
23
|
+
if (iterator) {
|
|
24
|
+
iterator.addData(result.value);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
iterator = (0, buffer_iterator_1.getArrayBufferIterator)(result.value, contentLength !== null && contentLength !== void 0 ? contentLength : undefined);
|
|
28
|
+
}
|
|
29
|
+
if (parseResult) {
|
|
30
|
+
parseResult = parseResult.continueParsing();
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
parseResult = (0, parse_video_1.parseVideo)(iterator);
|
|
34
|
+
}
|
|
23
35
|
if ((0, has_all_info_1.hasAllInfo)(options, parseResult)) {
|
|
24
36
|
if (!reader.closed) {
|
|
25
37
|
reader.cancel(new Error('has all information'));
|
|
@@ -27,6 +39,9 @@ const parseMedia = async (src, options, readerInterface = from_web_1.webReader)
|
|
|
27
39
|
break;
|
|
28
40
|
}
|
|
29
41
|
}
|
|
42
|
+
if (!parseResult) {
|
|
43
|
+
throw new Error('Could not parse video');
|
|
44
|
+
}
|
|
30
45
|
if (options.dimensions) {
|
|
31
46
|
returnValue.dimensions = (0, get_dimensions_1.getDimensions)(parseResult.segments);
|
|
32
47
|
}
|
|
@@ -36,6 +51,12 @@ const parseMedia = async (src, options, readerInterface = from_web_1.webReader)
|
|
|
36
51
|
if (options.fps) {
|
|
37
52
|
returnValue.fps = (0, get_fps_1.getFps)(parseResult.segments);
|
|
38
53
|
}
|
|
54
|
+
if (options.videoCodec) {
|
|
55
|
+
returnValue.videoCodec = (0, get_video_codec_1.getVideoCodec)(parseResult.segments);
|
|
56
|
+
}
|
|
57
|
+
if (options.audioCodec) {
|
|
58
|
+
returnValue.audioCodec = (0, get_audio_codec_1.getAudioCodec)(parseResult.segments);
|
|
59
|
+
}
|
|
39
60
|
if (options.boxes) {
|
|
40
61
|
returnValue.boxes = parseResult.segments;
|
|
41
62
|
}
|
package/dist/parse-result.d.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';
|
|
@@ -16,7 +18,7 @@ interface RegularBox extends BaseBox {
|
|
|
16
18
|
offset: number;
|
|
17
19
|
type: 'regular-box';
|
|
18
20
|
}
|
|
19
|
-
export type IsoBaseMediaBox = RegularBox | FtypBox | MvhdBox | TkhdBox | StsdBox | MebxBox | KeysBox | MoovBox | TrakBox | SttsBox;
|
|
21
|
+
export type IsoBaseMediaBox = RegularBox | FtypBox | MvhdBox | TkhdBox | StsdBox | MebxBox | KeysBox | MoovBox | TrakBox | SttsBox | MdhdBox | EsdsBox;
|
|
20
22
|
export type AnySegment = MatroskaSegment | IsoBaseMediaBox;
|
|
21
23
|
export type ParseResult = {
|
|
22
24
|
status: 'done';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare class OffsetCounter {
|
|
2
|
+
#private;
|
|
3
|
+
constructor(initial: number);
|
|
4
|
+
increment(amount: number): void;
|
|
5
|
+
getOffset(): number;
|
|
6
|
+
}
|
|
7
|
+
export declare const getArrayBufferIterator: (data: ArrayBuffer, initialOffset: number) => {
|
|
8
|
+
view: DataView;
|
|
9
|
+
counter: OffsetCounter;
|
|
10
|
+
data: ArrayBuffer;
|
|
11
|
+
discard: (length: number) => void;
|
|
12
|
+
getSlice: (amount: number) => ArrayBuffer;
|
|
13
|
+
getAtom: () => string;
|
|
14
|
+
getMatroskaSegmentId: () => string;
|
|
15
|
+
getVint: (bytes: number) => number;
|
|
16
|
+
getUint8: () => number;
|
|
17
|
+
getEBML: () => number;
|
|
18
|
+
getInt8: () => number;
|
|
19
|
+
getUint16: () => number;
|
|
20
|
+
getInt16: () => number;
|
|
21
|
+
getUint32: () => number;
|
|
22
|
+
getFixedPoint1616Number: () => number;
|
|
23
|
+
getPascalString: () => number[];
|
|
24
|
+
getDecimalBytes(length: number): number;
|
|
25
|
+
getByteString(length: number): string;
|
|
26
|
+
getFloat64: () => number;
|
|
27
|
+
};
|
|
28
|
+
export type BufferIterator = ReturnType<typeof getArrayBufferIterator>;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
3
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
4
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
5
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
6
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
7
|
+
};
|
|
8
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
9
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
10
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
11
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12
|
+
};
|
|
13
|
+
var _OffsetCounter_offset;
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.getArrayBufferIterator = exports.OffsetCounter = void 0;
|
|
16
|
+
class OffsetCounter {
|
|
17
|
+
constructor(initial) {
|
|
18
|
+
_OffsetCounter_offset.set(this, void 0);
|
|
19
|
+
__classPrivateFieldSet(this, _OffsetCounter_offset, initial, "f");
|
|
20
|
+
}
|
|
21
|
+
increment(amount) {
|
|
22
|
+
if (amount < 0) {
|
|
23
|
+
throw new Error('Cannot increment by a negative amount');
|
|
24
|
+
}
|
|
25
|
+
__classPrivateFieldSet(this, _OffsetCounter_offset, __classPrivateFieldGet(this, _OffsetCounter_offset, "f") + amount, "f");
|
|
26
|
+
}
|
|
27
|
+
getOffset() {
|
|
28
|
+
return __classPrivateFieldGet(this, _OffsetCounter_offset, "f");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.OffsetCounter = OffsetCounter;
|
|
32
|
+
_OffsetCounter_offset = new WeakMap();
|
|
33
|
+
const makeOffsetCounter = (initial) => {
|
|
34
|
+
return new OffsetCounter(initial);
|
|
35
|
+
};
|
|
36
|
+
const getArrayBufferIterator = (data, initialOffset) => {
|
|
37
|
+
const view = new DataView(data);
|
|
38
|
+
const counter = makeOffsetCounter(initialOffset);
|
|
39
|
+
const getSlice = (amount) => {
|
|
40
|
+
const value = data.slice(counter.getOffset(), counter.getOffset() + amount);
|
|
41
|
+
counter.increment(amount);
|
|
42
|
+
return value;
|
|
43
|
+
};
|
|
44
|
+
const getUint8 = () => {
|
|
45
|
+
const val = view.getUint8(counter.getOffset());
|
|
46
|
+
counter.increment(1);
|
|
47
|
+
return val;
|
|
48
|
+
};
|
|
49
|
+
const getUint32 = () => {
|
|
50
|
+
const val = view.getUint32(counter.getOffset());
|
|
51
|
+
counter.increment(4);
|
|
52
|
+
return val;
|
|
53
|
+
};
|
|
54
|
+
return {
|
|
55
|
+
view,
|
|
56
|
+
counter,
|
|
57
|
+
data,
|
|
58
|
+
discard: (length) => {
|
|
59
|
+
counter.increment(length);
|
|
60
|
+
},
|
|
61
|
+
getSlice,
|
|
62
|
+
getAtom: () => {
|
|
63
|
+
const atom = getSlice(4);
|
|
64
|
+
return new TextDecoder().decode(atom);
|
|
65
|
+
},
|
|
66
|
+
getMatroskaSegmentId: () => {
|
|
67
|
+
const first = getSlice(1);
|
|
68
|
+
const firstOneString = `0x${Array.from(new Uint8Array(first))
|
|
69
|
+
.map((b) => {
|
|
70
|
+
return b.toString(16).padStart(2, '0');
|
|
71
|
+
})
|
|
72
|
+
.join('')}`;
|
|
73
|
+
// Catch void block
|
|
74
|
+
const knownIdsWithOneLength = ['0xec', '0xae'];
|
|
75
|
+
if (knownIdsWithOneLength.includes(firstOneString)) {
|
|
76
|
+
return firstOneString;
|
|
77
|
+
}
|
|
78
|
+
const firstTwo = getSlice(1);
|
|
79
|
+
const knownIdsWithTwoLength = ['0x4dbb', '0x53ac', '0xec01'];
|
|
80
|
+
const firstTwoString = `${firstOneString}${Array.from(new Uint8Array(firstTwo))
|
|
81
|
+
.map((b) => {
|
|
82
|
+
return b.toString(16).padStart(2, '0');
|
|
83
|
+
})
|
|
84
|
+
.join('')}`;
|
|
85
|
+
if (knownIdsWithTwoLength.includes(firstTwoString)) {
|
|
86
|
+
return firstTwoString;
|
|
87
|
+
}
|
|
88
|
+
const knownIdsWithThreeLength = ['0x4d808c', '0x57418c', '0x448988'];
|
|
89
|
+
const firstThree = getSlice(1);
|
|
90
|
+
const firstThreeString = `${firstTwoString}${Array.from(new Uint8Array(firstThree))
|
|
91
|
+
.map((b) => {
|
|
92
|
+
return b.toString(16).padStart(2, '0');
|
|
93
|
+
})
|
|
94
|
+
.join('')}`;
|
|
95
|
+
if (knownIdsWithThreeLength.includes(firstThreeString)) {
|
|
96
|
+
return firstThreeString;
|
|
97
|
+
}
|
|
98
|
+
const segmentId = getSlice(1);
|
|
99
|
+
return `${firstThreeString}${Array.from(new Uint8Array(segmentId))
|
|
100
|
+
.map((b) => {
|
|
101
|
+
return b.toString(16).padStart(2, '0');
|
|
102
|
+
})
|
|
103
|
+
.join('')}`;
|
|
104
|
+
},
|
|
105
|
+
getVint: (bytes) => {
|
|
106
|
+
const slice = getSlice(bytes);
|
|
107
|
+
const d = [...Array.from(new Uint8Array(slice))];
|
|
108
|
+
const totalLength = d[0];
|
|
109
|
+
if (totalLength === 0) {
|
|
110
|
+
return 0;
|
|
111
|
+
}
|
|
112
|
+
// Calculate the actual length of the data based on the first set bit
|
|
113
|
+
let actualLength = 0;
|
|
114
|
+
while (((totalLength >> (7 - actualLength)) & 0x01) === 0) {
|
|
115
|
+
actualLength++;
|
|
116
|
+
}
|
|
117
|
+
actualLength += 1; // Include the first byte set as 1
|
|
118
|
+
// Combine the numbers to form the integer value
|
|
119
|
+
let value = 0;
|
|
120
|
+
// Mask the first byte properly then start combining
|
|
121
|
+
value = totalLength & (0xff >> actualLength);
|
|
122
|
+
for (let i = 1; i < actualLength; i++) {
|
|
123
|
+
value = (value << 8) | d[i];
|
|
124
|
+
}
|
|
125
|
+
return value;
|
|
126
|
+
},
|
|
127
|
+
getUint8,
|
|
128
|
+
getEBML: () => {
|
|
129
|
+
const val = getUint8();
|
|
130
|
+
// https://darkcoding.net/software/reading-mediarecorders-webm-opus-output/#:~:text=The%20first%20four%20bytes%20(%201A,%E2%80%93%20read%20on%20for%20why).
|
|
131
|
+
// You drop the initial 0 bits and the first 1 bit to get the value. 0x81 is 0b10000001, so there are zero inital 0 bits, meaning length one byte, and the value is 1. The 0x9F value for length of the EBML header we saw earlier is 0b10011111, still one byte, value is 0b0011111, which is 31 (the python repl is very helpful for these conversions).
|
|
132
|
+
const actualValue = val & 0x7f; // 0x7F is binary 01111111, which masks out the first bit
|
|
133
|
+
return actualValue;
|
|
134
|
+
},
|
|
135
|
+
getInt8: () => {
|
|
136
|
+
const val = view.getInt8(counter.getOffset());
|
|
137
|
+
counter.increment(1);
|
|
138
|
+
return val;
|
|
139
|
+
},
|
|
140
|
+
getUint16: () => {
|
|
141
|
+
const val = view.getUint16(counter.getOffset());
|
|
142
|
+
counter.increment(2);
|
|
143
|
+
return val;
|
|
144
|
+
},
|
|
145
|
+
getInt16: () => {
|
|
146
|
+
const val = view.getInt16(counter.getOffset());
|
|
147
|
+
counter.increment(2);
|
|
148
|
+
return val;
|
|
149
|
+
},
|
|
150
|
+
getUint32,
|
|
151
|
+
// https://developer.apple.com/documentation/quicktime-file-format/sound_sample_description_version_1
|
|
152
|
+
// A 32-bit unsigned fixed-point number (16.16) that indicates the rate at which the sound samples were obtained.
|
|
153
|
+
getFixedPoint1616Number: () => {
|
|
154
|
+
const val = getUint32();
|
|
155
|
+
return val / 2 ** 16;
|
|
156
|
+
},
|
|
157
|
+
getPascalString: () => {
|
|
158
|
+
const val = getSlice(32);
|
|
159
|
+
return [...Array.from(new Uint8Array(val))];
|
|
160
|
+
},
|
|
161
|
+
getDecimalBytes(length) {
|
|
162
|
+
const bytes = getSlice(length);
|
|
163
|
+
const numbers = [...Array.from(new Uint8Array(bytes))];
|
|
164
|
+
return numbers.reduce((acc, byte, index) => acc + (byte << (8 * (numbers.length - index - 1))), 0);
|
|
165
|
+
},
|
|
166
|
+
getByteString(length) {
|
|
167
|
+
const bytes = getSlice(length);
|
|
168
|
+
return new TextDecoder().decode(bytes);
|
|
169
|
+
},
|
|
170
|
+
getFloat64: () => {
|
|
171
|
+
const val = view.getFloat64(counter.getOffset());
|
|
172
|
+
counter.increment(8);
|
|
173
|
+
return val;
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
exports.getArrayBufferIterator = getArrayBufferIterator;
|
package/dist/reader.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
type
|
|
1
|
+
type ReadResult = {
|
|
2
|
+
reader: ReadableStreamDefaultReader<Uint8Array>;
|
|
3
|
+
contentLength: number | null;
|
|
4
|
+
};
|
|
5
|
+
type ReadContent = (src: string, range: [number, number] | null) => Promise<ReadResult>;
|
|
2
6
|
type GetLength = (src: string) => Promise<number>;
|
|
3
7
|
export type ReaderInterface = {
|
|
4
8
|
read: ReadContent;
|
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.193",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"sideEffects": false,
|
|
9
9
|
"devDependencies": {
|
|
10
|
-
"@remotion/renderer": "4.0.
|
|
10
|
+
"@remotion/renderer": "4.0.193"
|
|
11
11
|
},
|
|
12
12
|
"publishConfig": {
|
|
13
13
|
"access": "public"
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type {BufferIterator} from '../../../buffer-iterator';
|
|
2
|
+
|
|
3
|
+
type AudioObjectType = 'aac' | 'mp3' | 'unknown';
|
|
4
|
+
|
|
5
|
+
type DecoderConfigDescriptor = {
|
|
6
|
+
type: 'decoder-config-descriptor';
|
|
7
|
+
objectTypeIndication: AudioObjectType;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const mapToObjectAudioIndicator = (num: number): AudioObjectType => {
|
|
11
|
+
// https://chromium.googlesource.com/chromium/src/media/+/master/formats/mp4/es_descriptor.h
|
|
12
|
+
// http://netmedia.zju.edu.cn/multimedia2013/mpeg-4/ISO%20IEC%2014496-1%20MPEG-4%20System%20Standard.pdf
|
|
13
|
+
// Page 42, table 8
|
|
14
|
+
if (num === 0x40) {
|
|
15
|
+
return 'aac';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (num === 0x6b) {
|
|
19
|
+
return 'mp3';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return 'unknown';
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type SlConfigDescriptor = {
|
|
26
|
+
type: 'sl-config-descriptor';
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type UnknownDescriptor = {
|
|
30
|
+
type: 'unknown-descriptor';
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type Descriptor =
|
|
34
|
+
| DecoderConfigDescriptor
|
|
35
|
+
| SlConfigDescriptor
|
|
36
|
+
| UnknownDescriptor;
|
|
37
|
+
|
|
38
|
+
type DescriptorAndNext = {
|
|
39
|
+
descriptor: Descriptor | null;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const processDescriptor = ({
|
|
43
|
+
iterator,
|
|
44
|
+
}: {
|
|
45
|
+
iterator: BufferIterator;
|
|
46
|
+
}): DescriptorAndNext => {
|
|
47
|
+
const tag = iterator.getUint8();
|
|
48
|
+
|
|
49
|
+
if (tag === 4) {
|
|
50
|
+
const size = iterator.getPaddedFourByteNumber();
|
|
51
|
+
const initialOffset = iterator.counter.getOffset();
|
|
52
|
+
|
|
53
|
+
const objectTypeIndication = iterator.getUint8();
|
|
54
|
+
|
|
55
|
+
const remaining = size - (iterator.counter.getOffset() - initialOffset);
|
|
56
|
+
iterator.discard(remaining);
|
|
57
|
+
return {
|
|
58
|
+
descriptor: {
|
|
59
|
+
type: 'decoder-config-descriptor',
|
|
60
|
+
objectTypeIndication: mapToObjectAudioIndicator(objectTypeIndication),
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (tag === 6) {
|
|
66
|
+
const size = iterator.getPaddedFourByteNumber();
|
|
67
|
+
|
|
68
|
+
iterator.discard(size);
|
|
69
|
+
return {
|
|
70
|
+
descriptor: {
|
|
71
|
+
type: 'sl-config-descriptor',
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
descriptor: null,
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const parseDescriptors = (
|
|
82
|
+
iterator: BufferIterator,
|
|
83
|
+
maxBytes: number,
|
|
84
|
+
): Descriptor[] => {
|
|
85
|
+
const descriptors: Descriptor[] = [];
|
|
86
|
+
const initialOffset = iterator.counter.getOffset();
|
|
87
|
+
|
|
88
|
+
while (
|
|
89
|
+
iterator.bytesRemaining() > 0 &&
|
|
90
|
+
iterator.counter.getOffset() - initialOffset < maxBytes
|
|
91
|
+
) {
|
|
92
|
+
const {descriptor} = processDescriptor({
|
|
93
|
+
iterator,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (descriptor) {
|
|
97
|
+
descriptors.push(descriptor);
|
|
98
|
+
} else {
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return descriptors;
|
|
104
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type {BufferIterator} from '../../../buffer-iterator';
|
|
2
|
+
import type {Descriptor} from './esds-descriptors';
|
|
3
|
+
import {parseDescriptors} from './esds-descriptors';
|
|
4
|
+
|
|
5
|
+
export interface EsdsBox {
|
|
6
|
+
type: 'esds-box';
|
|
7
|
+
version: number;
|
|
8
|
+
tag: number;
|
|
9
|
+
sizeOfInstance: number;
|
|
10
|
+
esId: number;
|
|
11
|
+
descriptors: Descriptor[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const parseEsds = ({
|
|
15
|
+
data,
|
|
16
|
+
size,
|
|
17
|
+
fileOffset,
|
|
18
|
+
}: {
|
|
19
|
+
data: BufferIterator;
|
|
20
|
+
size: number;
|
|
21
|
+
fileOffset: number;
|
|
22
|
+
}): EsdsBox => {
|
|
23
|
+
const version = data.getUint8();
|
|
24
|
+
// Flags, we discard them
|
|
25
|
+
data.discard(3);
|
|
26
|
+
const tag = data.getUint8();
|
|
27
|
+
|
|
28
|
+
const sizeOfInstance = data.getPaddedFourByteNumber();
|
|
29
|
+
const esId = data.getUint16();
|
|
30
|
+
|
|
31
|
+
// disard 1 byte, currently unknown
|
|
32
|
+
data.discard(1);
|
|
33
|
+
|
|
34
|
+
const remaining = size - (data.counter.getOffset() - fileOffset);
|
|
35
|
+
const descriptors = parseDescriptors(data, remaining);
|
|
36
|
+
|
|
37
|
+
const remainingNow = size - (data.counter.getOffset() - fileOffset);
|
|
38
|
+
|
|
39
|
+
data.discard(remainingNow);
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
type: 'esds-box',
|
|
43
|
+
version,
|
|
44
|
+
tag,
|
|
45
|
+
sizeOfInstance,
|
|
46
|
+
esId,
|
|
47
|
+
descriptors,
|
|
48
|
+
};
|
|
49
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type {BufferIterator} from '../../buffer-iterator';
|
|
2
|
+
|
|
3
|
+
export interface MdhdBox {
|
|
4
|
+
type: 'mdhd-box';
|
|
5
|
+
version: number;
|
|
6
|
+
timescale: number;
|
|
7
|
+
duration: number;
|
|
8
|
+
language: number;
|
|
9
|
+
quality: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const parseMdhd = ({
|
|
13
|
+
data,
|
|
14
|
+
size,
|
|
15
|
+
fileOffset,
|
|
16
|
+
}: {
|
|
17
|
+
data: BufferIterator;
|
|
18
|
+
size: number;
|
|
19
|
+
fileOffset: number;
|
|
20
|
+
}): MdhdBox => {
|
|
21
|
+
const version = data.getUint8();
|
|
22
|
+
if (version !== 0) {
|
|
23
|
+
throw new Error(`Unsupported MDHD version ${version}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// flags, we discard them
|
|
27
|
+
data.discard(3);
|
|
28
|
+
|
|
29
|
+
// creation time
|
|
30
|
+
data.discard(4);
|
|
31
|
+
|
|
32
|
+
// modification time
|
|
33
|
+
data.discard(4);
|
|
34
|
+
|
|
35
|
+
const timescale = data.getUint32();
|
|
36
|
+
const duration = data.getUint32();
|
|
37
|
+
|
|
38
|
+
const language = data.getUint16();
|
|
39
|
+
|
|
40
|
+
// quality
|
|
41
|
+
const quality = data.getUint16();
|
|
42
|
+
|
|
43
|
+
const remaining = size - (data.counter.getOffset() - fileOffset);
|
|
44
|
+
if (remaining !== 0) {
|
|
45
|
+
throw new Error(`Expected remaining bytes to be 0, got ${remaining}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
type: 'mdhd-box',
|
|
50
|
+
duration,
|
|
51
|
+
timescale,
|
|
52
|
+
version,
|
|
53
|
+
language,
|
|
54
|
+
quality,
|
|
55
|
+
};
|
|
56
|
+
};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type {BufferIterator} from '../../buffer-iterator';
|
|
2
2
|
import type {IsoBaseMediaBox, ParseResult} from '../../parse-result';
|
|
3
3
|
import type {BoxAndNext} from '../../parse-video';
|
|
4
|
+
import {parseEsds} from './esds/esds';
|
|
4
5
|
import {parseFtyp} from './ftyp';
|
|
6
|
+
import {parseMdhd} from './mdhd';
|
|
5
7
|
import {parseMoov} from './moov/moov';
|
|
6
8
|
import {parseMvhd} from './mvhd';
|
|
7
9
|
import {parseMebx} from './stsd/mebx';
|
|
@@ -24,6 +26,7 @@ const getChildren = ({
|
|
|
24
26
|
boxType === 'minf' ||
|
|
25
27
|
boxType === 'stbl' ||
|
|
26
28
|
boxType === 'dims' ||
|
|
29
|
+
boxType === 'wave' ||
|
|
27
30
|
boxType === 'stsb';
|
|
28
31
|
|
|
29
32
|
if (parseChildren) {
|
|
@@ -41,6 +44,10 @@ const getChildren = ({
|
|
|
41
44
|
return parsed.segments;
|
|
42
45
|
}
|
|
43
46
|
|
|
47
|
+
if (bytesRemainingInBox < 0) {
|
|
48
|
+
throw new Error('Box size is too big ' + JSON.stringify({boxType}));
|
|
49
|
+
}
|
|
50
|
+
|
|
44
51
|
iterator.discard(bytesRemainingInBox);
|
|
45
52
|
return [];
|
|
46
53
|
};
|
|
@@ -162,6 +169,34 @@ const processBox = ({
|
|
|
162
169
|
};
|
|
163
170
|
}
|
|
164
171
|
|
|
172
|
+
if (boxType === 'mdhd') {
|
|
173
|
+
const box = parseMdhd({
|
|
174
|
+
data: iterator,
|
|
175
|
+
size: boxSize,
|
|
176
|
+
fileOffset,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
type: 'complete',
|
|
181
|
+
box,
|
|
182
|
+
size: boxSize,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (boxType === 'esds') {
|
|
187
|
+
const box = parseEsds({
|
|
188
|
+
data: iterator,
|
|
189
|
+
size: boxSize,
|
|
190
|
+
fileOffset,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
type: 'complete',
|
|
195
|
+
box,
|
|
196
|
+
size: boxSize,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
165
200
|
const bytesRemainingInBox =
|
|
166
201
|
boxSize - (iterator.counter.getOffset() - fileOffset);
|
|
167
202
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type {BufferIterator} from '../../../buffer-iterator';
|
|
2
|
+
import type {AnySegment} from '../../../parse-result';
|
|
3
|
+
import {parseBoxes} from '../process-box';
|
|
2
4
|
|
|
3
5
|
type SampleBase = {
|
|
4
6
|
format: string;
|
|
@@ -21,6 +23,7 @@ type AudioSample = SampleBase & {
|
|
|
21
23
|
bytesPerPacket: number | null;
|
|
22
24
|
bytesPerFrame: number | null;
|
|
23
25
|
bitsPerSample: number | null;
|
|
26
|
+
children: AnySegment[];
|
|
24
27
|
};
|
|
25
28
|
|
|
26
29
|
type VideoSample = SampleBase & {
|
|
@@ -77,6 +80,7 @@ const videoTags = [
|
|
|
77
80
|
'v410',
|
|
78
81
|
'v210',
|
|
79
82
|
'hvc1',
|
|
83
|
+
'ap4h',
|
|
80
84
|
];
|
|
81
85
|
|
|
82
86
|
// https://developer.apple.com/documentation/quicktime-file-format/sound_sample_descriptions
|
|
@@ -163,7 +167,16 @@ export const processSample = ({
|
|
|
163
167
|
|
|
164
168
|
const bytesRemainingInBox =
|
|
165
169
|
boxSize - (iterator.counter.getOffset() - fileOffset);
|
|
166
|
-
|
|
170
|
+
const children = parseBoxes({
|
|
171
|
+
iterator,
|
|
172
|
+
allowIncompleteBoxes: false,
|
|
173
|
+
maxBytes: bytesRemainingInBox,
|
|
174
|
+
initialBoxes: [],
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
if (children.status === 'incomplete') {
|
|
178
|
+
throw new Error('Incomplete boxes are not allowed');
|
|
179
|
+
}
|
|
167
180
|
|
|
168
181
|
return {
|
|
169
182
|
sample: {
|
|
@@ -184,6 +197,7 @@ export const processSample = ({
|
|
|
184
197
|
bytesPerPacket: null,
|
|
185
198
|
bytesPerFrame: null,
|
|
186
199
|
bitsPerSample: null,
|
|
200
|
+
children: children.segments,
|
|
187
201
|
},
|
|
188
202
|
};
|
|
189
203
|
}
|
|
@@ -191,17 +205,29 @@ export const processSample = ({
|
|
|
191
205
|
if (version === 1) {
|
|
192
206
|
const numberOfChannels = iterator.getUint16();
|
|
193
207
|
const sampleSize = iterator.getUint16();
|
|
194
|
-
const compressionId = iterator.
|
|
208
|
+
const compressionId = iterator.getInt16();
|
|
195
209
|
const packetSize = iterator.getUint16();
|
|
196
210
|
const sampleRate = iterator.getFixedPoint1616Number();
|
|
197
|
-
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
const
|
|
211
|
+
|
|
212
|
+
const samplesPerPacket = iterator.getUint32();
|
|
213
|
+
|
|
214
|
+
const bytesPerPacket = iterator.getUint32();
|
|
215
|
+
const bytesPerFrame = iterator.getUint32();
|
|
216
|
+
const bytesPerSample = iterator.getUint32();
|
|
201
217
|
|
|
202
218
|
const bytesRemainingInBox =
|
|
203
219
|
boxSize - (iterator.counter.getOffset() - fileOffset);
|
|
204
|
-
|
|
220
|
+
|
|
221
|
+
const children = parseBoxes({
|
|
222
|
+
iterator,
|
|
223
|
+
allowIncompleteBoxes: false,
|
|
224
|
+
maxBytes: bytesRemainingInBox,
|
|
225
|
+
initialBoxes: [],
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
if (children.status === 'incomplete') {
|
|
229
|
+
throw new Error('Incomplete boxes are not allowed');
|
|
230
|
+
}
|
|
205
231
|
|
|
206
232
|
return {
|
|
207
233
|
sample: {
|
|
@@ -221,7 +247,8 @@ export const processSample = ({
|
|
|
221
247
|
samplesPerPacket,
|
|
222
248
|
bytesPerPacket,
|
|
223
249
|
bytesPerFrame,
|
|
224
|
-
bitsPerSample,
|
|
250
|
+
bitsPerSample: bytesPerSample,
|
|
251
|
+
children: children.segments,
|
|
225
252
|
},
|
|
226
253
|
};
|
|
227
254
|
}
|
|
@@ -244,6 +271,7 @@ export const processSample = ({
|
|
|
244
271
|
|
|
245
272
|
const bytesRemainingInBox =
|
|
246
273
|
boxSize - (iterator.counter.getOffset() - fileOffset);
|
|
274
|
+
|
|
247
275
|
iterator.discard(bytesRemainingInBox);
|
|
248
276
|
|
|
249
277
|
return {
|