@remotion/media-parser 4.0.191
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/.eslintrc +8 -0
- package/LICENSE.md +49 -0
- package/README.md +18 -0
- package/dist/boxes/iso-base-media/base-type.d.ts +4 -0
- package/dist/boxes/iso-base-media/base-type.js +2 -0
- package/dist/boxes/iso-base-media/ftyp.d.ts +13 -0
- package/dist/boxes/iso-base-media/ftyp.js +22 -0
- package/dist/boxes/iso-base-media/moov/moov.d.ts +12 -0
- package/dist/boxes/iso-base-media/moov/moov.js +22 -0
- package/dist/boxes/iso-base-media/mvhd.d.ts +30 -0
- package/dist/boxes/iso-base-media/mvhd.js +59 -0
- package/dist/boxes/iso-base-media/process-box.d.ts +8 -0
- package/dist/boxes/iso-base-media/process-box.js +174 -0
- package/dist/boxes/iso-base-media/stsd/keys.d.ts +5 -0
- package/dist/boxes/iso-base-media/stsd/keys.js +21 -0
- package/dist/boxes/iso-base-media/stsd/mebx.d.ts +14 -0
- package/dist/boxes/iso-base-media/stsd/mebx.js +27 -0
- package/dist/boxes/iso-base-media/stsd/samples.d.ts +48 -0
- package/dist/boxes/iso-base-media/stsd/samples.js +215 -0
- package/dist/boxes/iso-base-media/stsd/stsd.d.ts +13 -0
- package/dist/boxes/iso-base-media/stsd/stsd.js +26 -0
- package/dist/boxes/iso-base-media/stts/stts.d.ts +15 -0
- package/dist/boxes/iso-base-media/stts/stts.js +35 -0
- package/dist/boxes/iso-base-media/tkhd.d.ts +22 -0
- package/dist/boxes/iso-base-media/tkhd.js +63 -0
- package/dist/boxes/iso-base-media/to-date.d.ts +1 -0
- package/dist/boxes/iso-base-media/to-date.js +11 -0
- package/dist/boxes/iso-base-media/trak/trak.d.ts +12 -0
- package/dist/boxes/iso-base-media/trak/trak.js +22 -0
- package/dist/boxes/webm/parse-webm-header.d.ts +3 -0
- package/dist/boxes/webm/parse-webm-header.js +16 -0
- package/dist/boxes/webm/segments/duration.d.ts +6 -0
- package/dist/boxes/webm/segments/duration.js +15 -0
- package/dist/boxes/webm/segments/info.d.ts +8 -0
- package/dist/boxes/webm/segments/info.js +14 -0
- package/dist/boxes/webm/segments/main.d.ts +7 -0
- package/dist/boxes/webm/segments/main.js +13 -0
- package/dist/boxes/webm/segments/muxing.d.ts +6 -0
- package/dist/boxes/webm/segments/muxing.js +12 -0
- package/dist/boxes/webm/segments/parse-children.d.ts +3 -0
- package/dist/boxes/webm/segments/parse-children.js +17 -0
- package/dist/boxes/webm/segments/seek-head.d.ts +8 -0
- package/dist/boxes/webm/segments/seek-head.js +13 -0
- package/dist/boxes/webm/segments/seek-position.d.ts +6 -0
- package/dist/boxes/webm/segments/seek-position.js +12 -0
- package/dist/boxes/webm/segments/seek.d.ts +8 -0
- package/dist/boxes/webm/segments/seek.js +16 -0
- package/dist/boxes/webm/segments/timestamp-scale.d.ts +6 -0
- package/dist/boxes/webm/segments/timestamp-scale.js +11 -0
- package/dist/boxes/webm/segments/track-entry.d.ts +71 -0
- package/dist/boxes/webm/segments/track-entry.js +175 -0
- package/dist/boxes/webm/segments/tracks.d.ts +7 -0
- package/dist/boxes/webm/segments/tracks.js +13 -0
- package/dist/boxes/webm/segments/unknown.d.ts +6 -0
- package/dist/boxes/webm/segments/unknown.js +11 -0
- package/dist/boxes/webm/segments/void.d.ts +6 -0
- package/dist/boxes/webm/segments/void.js +12 -0
- package/dist/boxes/webm/segments/writing.d.ts +6 -0
- package/dist/boxes/webm/segments/writing.js +12 -0
- package/dist/boxes/webm/segments.d.ts +16 -0
- package/dist/boxes/webm/segments.js +106 -0
- package/dist/buffer-iterator.d.ts +36 -0
- package/dist/buffer-iterator.js +263 -0
- package/dist/from-node.d.ts +2 -0
- package/dist/from-node.js +19 -0
- package/dist/from-web.d.ts +2 -0
- package/dist/from-web.js +40 -0
- package/dist/get-dimensions.d.ts +7 -0
- package/dist/get-dimensions.js +104 -0
- package/dist/get-duration.d.ts +3 -0
- package/dist/get-duration.js +61 -0
- package/dist/get-fps.d.ts +3 -0
- package/dist/get-fps.js +70 -0
- package/dist/has-all-info.d.ts +3 -0
- package/dist/has-all-info.js +27 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/options.d.ts +19 -0
- package/dist/options.js +2 -0
- package/dist/parse-media.d.ts +2 -0
- package/dist/parse-media.js +44 -0
- package/dist/parse-result.d.ts +29 -0
- package/dist/parse-result.js +2 -0
- package/dist/parse-video.d.ts +10 -0
- package/dist/parse-video.js +29 -0
- package/dist/reader.d.ts +7 -0
- package/dist/reader.js +2 -0
- package/package.json +47 -0
- package/src/boxes/iso-base-media/base-type.ts +4 -0
- package/src/boxes/iso-base-media/ftyp.ts +39 -0
- package/src/boxes/iso-base-media/moov/moov.ts +37 -0
- package/src/boxes/iso-base-media/mvhd.ts +107 -0
- package/src/boxes/iso-base-media/process-box.ts +236 -0
- package/src/boxes/iso-base-media/stsd/keys.ts +25 -0
- package/src/boxes/iso-base-media/stsd/mebx.ts +46 -0
- package/src/boxes/iso-base-media/stsd/samples.ts +298 -0
- package/src/boxes/iso-base-media/stsd/stsd.ts +48 -0
- package/src/boxes/iso-base-media/stts/stts.ts +62 -0
- package/src/boxes/iso-base-media/tkhd.ts +105 -0
- package/src/boxes/iso-base-media/to-date.ts +9 -0
- package/src/boxes/iso-base-media/trak/trak.ts +37 -0
- package/src/boxes/webm/parse-webm-header.ts +18 -0
- package/src/boxes/webm/segments/duration.ts +22 -0
- package/src/boxes/webm/segments/info.ts +20 -0
- package/src/boxes/webm/segments/main.ts +19 -0
- package/src/boxes/webm/segments/muxing.ts +18 -0
- package/src/boxes/webm/segments/parse-children.ts +21 -0
- package/src/boxes/webm/segments/seek-head.ts +21 -0
- package/src/boxes/webm/segments/seek-position.ts +19 -0
- package/src/boxes/webm/segments/seek.ts +23 -0
- package/src/boxes/webm/segments/timestamp-scale.ts +17 -0
- package/src/boxes/webm/segments/track-entry.ts +295 -0
- package/src/boxes/webm/segments/tracks.ts +22 -0
- package/src/boxes/webm/segments/unknown.ts +19 -0
- package/src/boxes/webm/segments/void.ts +16 -0
- package/src/boxes/webm/segments/writing.ts +18 -0
- package/src/boxes/webm/segments.ts +206 -0
- package/src/buffer-iterator.ts +305 -0
- package/src/from-node.ts +22 -0
- package/src/from-web.ts +53 -0
- package/src/get-dimensions.ts +147 -0
- package/src/get-duration.ts +73 -0
- package/src/get-fps.ts +92 -0
- package/src/has-all-info.ts +34 -0
- package/src/index.ts +1 -0
- package/src/options.ts +38 -0
- package/src/parse-media.ts +57 -0
- package/src/parse-result.ts +44 -0
- package/src/parse-video.ts +41 -0
- package/src/reader.ts +10 -0
- package/src/test/duration.test.ts +34 -0
- package/src/test/keys.test.ts +47 -0
- package/src/test/matroska.test.ts +163 -0
- package/src/test/mvhd.test.ts +89 -0
- package/src/test/parse-stts.test.ts +38 -0
- package/src/test/parse-video.test.ts +113 -0
- package/src/test/parse-webm.test.ts +15 -0
- package/src/test/stream-local.test.ts +105 -0
- package/src/test/stream-remote.test.ts +12 -0
- package/src/test/stsd.test.ts +162 -0
- package/src/test/tkhd.test.ts +84 -0
- package/tsconfig.json +10 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,263 @@
|
|
|
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, _OffsetCounter_discardedBytes;
|
|
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
|
+
_OffsetCounter_discardedBytes.set(this, void 0);
|
|
20
|
+
__classPrivateFieldSet(this, _OffsetCounter_offset, initial, "f");
|
|
21
|
+
__classPrivateFieldSet(this, _OffsetCounter_discardedBytes, 0, "f");
|
|
22
|
+
}
|
|
23
|
+
increment(amount) {
|
|
24
|
+
if (amount < 0) {
|
|
25
|
+
throw new Error('Cannot increment by a negative amount: ' + amount);
|
|
26
|
+
}
|
|
27
|
+
__classPrivateFieldSet(this, _OffsetCounter_offset, __classPrivateFieldGet(this, _OffsetCounter_offset, "f") + amount, "f");
|
|
28
|
+
}
|
|
29
|
+
getOffset() {
|
|
30
|
+
return __classPrivateFieldGet(this, _OffsetCounter_offset, "f");
|
|
31
|
+
}
|
|
32
|
+
getDiscardedOffset() {
|
|
33
|
+
return __classPrivateFieldGet(this, _OffsetCounter_offset, "f") - __classPrivateFieldGet(this, _OffsetCounter_discardedBytes, "f");
|
|
34
|
+
}
|
|
35
|
+
discardBytes(amount) {
|
|
36
|
+
__classPrivateFieldSet(this, _OffsetCounter_discardedBytes, __classPrivateFieldGet(this, _OffsetCounter_discardedBytes, "f") + amount, "f");
|
|
37
|
+
}
|
|
38
|
+
decrement(amount) {
|
|
39
|
+
if (amount < 0) {
|
|
40
|
+
throw new Error('Cannot decrement by a negative amount');
|
|
41
|
+
}
|
|
42
|
+
__classPrivateFieldSet(this, _OffsetCounter_offset, __classPrivateFieldGet(this, _OffsetCounter_offset, "f") - amount, "f");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.OffsetCounter = OffsetCounter;
|
|
46
|
+
_OffsetCounter_offset = new WeakMap(), _OffsetCounter_discardedBytes = new WeakMap();
|
|
47
|
+
const isoBaseMediaMp4Pattern = new TextEncoder().encode('ftyp');
|
|
48
|
+
const webmPattern = new Uint8Array([0x1a, 0x45, 0xdf, 0xa3]);
|
|
49
|
+
const matchesPattern = (pattern) => {
|
|
50
|
+
return (data) => {
|
|
51
|
+
return pattern.every((value, index) => data[index] === value);
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
const makeOffsetCounter = () => {
|
|
55
|
+
return new OffsetCounter(0);
|
|
56
|
+
};
|
|
57
|
+
const getArrayBufferIterator = (initialData) => {
|
|
58
|
+
let data = initialData;
|
|
59
|
+
let view = new DataView(data.buffer);
|
|
60
|
+
const counter = makeOffsetCounter();
|
|
61
|
+
const getSlice = (amount) => {
|
|
62
|
+
const value = data.slice(counter.getDiscardedOffset(), counter.getDiscardedOffset() + amount);
|
|
63
|
+
counter.increment(amount);
|
|
64
|
+
return value;
|
|
65
|
+
};
|
|
66
|
+
const getUint8 = () => {
|
|
67
|
+
const val = view.getUint8(counter.getDiscardedOffset());
|
|
68
|
+
counter.increment(1);
|
|
69
|
+
return val;
|
|
70
|
+
};
|
|
71
|
+
const getFourByteNumber = () => {
|
|
72
|
+
return ((getUint8() << 24) | (getUint8() << 16) | (getUint8() << 8) | getUint8());
|
|
73
|
+
};
|
|
74
|
+
const getUint32 = () => {
|
|
75
|
+
const val = view.getUint32(counter.getDiscardedOffset());
|
|
76
|
+
counter.increment(4);
|
|
77
|
+
return val;
|
|
78
|
+
};
|
|
79
|
+
const addData = (newData) => {
|
|
80
|
+
const newArray = new Uint8Array(data.buffer.byteLength + newData.byteLength);
|
|
81
|
+
newArray.set(data);
|
|
82
|
+
newArray.set(new Uint8Array(newData), data.byteLength);
|
|
83
|
+
data = newArray;
|
|
84
|
+
view = new DataView(data.buffer);
|
|
85
|
+
};
|
|
86
|
+
const byteLength = () => {
|
|
87
|
+
return data.byteLength;
|
|
88
|
+
};
|
|
89
|
+
const bytesRemaining = () => {
|
|
90
|
+
return data.byteLength - counter.getDiscardedOffset();
|
|
91
|
+
};
|
|
92
|
+
const isIsoBaseMedia = () => {
|
|
93
|
+
return matchesPattern(isoBaseMediaMp4Pattern)(data.subarray(4, 8));
|
|
94
|
+
};
|
|
95
|
+
const isWebm = () => {
|
|
96
|
+
return matchesPattern(webmPattern)(data.subarray(0, 4));
|
|
97
|
+
};
|
|
98
|
+
const removeBytesRead = () => {
|
|
99
|
+
const bytesToRemove = counter.getDiscardedOffset();
|
|
100
|
+
counter.discardBytes(bytesToRemove);
|
|
101
|
+
const newArray = new Uint8Array(data.buffer.byteLength - bytesToRemove);
|
|
102
|
+
newArray.set(data.slice(bytesToRemove));
|
|
103
|
+
data = newArray;
|
|
104
|
+
view = new DataView(data.buffer);
|
|
105
|
+
};
|
|
106
|
+
return {
|
|
107
|
+
addData,
|
|
108
|
+
counter,
|
|
109
|
+
byteLength,
|
|
110
|
+
bytesRemaining,
|
|
111
|
+
isIsoBaseMedia,
|
|
112
|
+
discardFirstBytes: removeBytesRead,
|
|
113
|
+
isWebm,
|
|
114
|
+
discard: (length) => {
|
|
115
|
+
counter.increment(length);
|
|
116
|
+
},
|
|
117
|
+
getFourByteNumber,
|
|
118
|
+
getSlice,
|
|
119
|
+
getAtom: () => {
|
|
120
|
+
const atom = getSlice(4);
|
|
121
|
+
return new TextDecoder().decode(atom);
|
|
122
|
+
},
|
|
123
|
+
getMatroskaSegmentId: () => {
|
|
124
|
+
const first = getSlice(1);
|
|
125
|
+
const firstOneString = `0x${Array.from(new Uint8Array(first))
|
|
126
|
+
.map((b) => {
|
|
127
|
+
return b.toString(16).padStart(2, '0');
|
|
128
|
+
})
|
|
129
|
+
.join('')}`;
|
|
130
|
+
// Catch void block
|
|
131
|
+
// https://www.matroska.org/technical/elements.html
|
|
132
|
+
const knownIdsWithOneLength = [
|
|
133
|
+
'0xec',
|
|
134
|
+
'0xae',
|
|
135
|
+
'0xd7',
|
|
136
|
+
'0x9c',
|
|
137
|
+
'0x86',
|
|
138
|
+
'0x83',
|
|
139
|
+
'0xe0',
|
|
140
|
+
'0xb0',
|
|
141
|
+
'0xba',
|
|
142
|
+
];
|
|
143
|
+
if (knownIdsWithOneLength.includes(firstOneString)) {
|
|
144
|
+
return firstOneString;
|
|
145
|
+
}
|
|
146
|
+
const firstTwo = getSlice(1);
|
|
147
|
+
const knownIdsWithTwoLength = [
|
|
148
|
+
'0x4dbb',
|
|
149
|
+
'0x53ac',
|
|
150
|
+
'0xec01',
|
|
151
|
+
'0x73c5',
|
|
152
|
+
'0x53c0',
|
|
153
|
+
'0x4d80',
|
|
154
|
+
'0x5741',
|
|
155
|
+
'0x4489',
|
|
156
|
+
'0x55ee',
|
|
157
|
+
'0x55b0',
|
|
158
|
+
];
|
|
159
|
+
const firstTwoString = `${firstOneString}${Array.from(new Uint8Array(firstTwo))
|
|
160
|
+
.map((b) => {
|
|
161
|
+
return b.toString(16).padStart(2, '0');
|
|
162
|
+
})
|
|
163
|
+
.join('')}`;
|
|
164
|
+
if (knownIdsWithTwoLength.includes(firstTwoString)) {
|
|
165
|
+
return firstTwoString;
|
|
166
|
+
}
|
|
167
|
+
const knownIdsWithThreeLength = [
|
|
168
|
+
'0x4d808c',
|
|
169
|
+
'0x57418c',
|
|
170
|
+
'0x448988',
|
|
171
|
+
'0x22b59c',
|
|
172
|
+
'0x23e383',
|
|
173
|
+
];
|
|
174
|
+
const firstThree = getSlice(1);
|
|
175
|
+
const firstThreeString = `${firstTwoString}${Array.from(new Uint8Array(firstThree))
|
|
176
|
+
.map((b) => {
|
|
177
|
+
return b.toString(16).padStart(2, '0');
|
|
178
|
+
})
|
|
179
|
+
.join('')}`;
|
|
180
|
+
if (knownIdsWithThreeLength.includes(firstThreeString)) {
|
|
181
|
+
return firstThreeString;
|
|
182
|
+
}
|
|
183
|
+
const segmentId = getSlice(1);
|
|
184
|
+
return `${firstThreeString}${Array.from(new Uint8Array(segmentId))
|
|
185
|
+
.map((b) => {
|
|
186
|
+
return b.toString(16).padStart(2, '0');
|
|
187
|
+
})
|
|
188
|
+
.join('')}`;
|
|
189
|
+
},
|
|
190
|
+
getVint: () => {
|
|
191
|
+
const firstByte = getUint8();
|
|
192
|
+
const totalLength = firstByte;
|
|
193
|
+
if (totalLength === 0) {
|
|
194
|
+
return 0;
|
|
195
|
+
}
|
|
196
|
+
// Calculate the actual length of the data based on the first set bit
|
|
197
|
+
let actualLength = 0;
|
|
198
|
+
while (((totalLength >> (7 - actualLength)) & 0x01) === 0) {
|
|
199
|
+
actualLength++;
|
|
200
|
+
}
|
|
201
|
+
const slice = getSlice(actualLength);
|
|
202
|
+
const d = [firstByte, ...Array.from(new Uint8Array(slice))];
|
|
203
|
+
actualLength += 1; // Include the first byte set as 1
|
|
204
|
+
// Combine the numbers to form the integer value
|
|
205
|
+
let value = 0;
|
|
206
|
+
// Mask the first byte properly then start combining
|
|
207
|
+
value = totalLength & (0xff >> actualLength);
|
|
208
|
+
for (let i = 1; i < actualLength; i++) {
|
|
209
|
+
value = (value << 8) | d[i];
|
|
210
|
+
}
|
|
211
|
+
return value;
|
|
212
|
+
},
|
|
213
|
+
getUint8,
|
|
214
|
+
getEBML: () => {
|
|
215
|
+
const val = getUint8();
|
|
216
|
+
// https://darkcoding.net/software/reading-mediarecorders-webm-opus-output/#:~:text=The%20first%20four%20bytes%20(%201A,%E2%80%93%20read%20on%20for%20why).
|
|
217
|
+
// 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).
|
|
218
|
+
const actualValue = val & 0x7f; // 0x7F is binary 01111111, which masks out the first bit
|
|
219
|
+
return actualValue;
|
|
220
|
+
},
|
|
221
|
+
getInt8: () => {
|
|
222
|
+
const val = view.getInt8(counter.getDiscardedOffset());
|
|
223
|
+
counter.increment(1);
|
|
224
|
+
return val;
|
|
225
|
+
},
|
|
226
|
+
getUint16: () => {
|
|
227
|
+
const val = view.getUint16(counter.getDiscardedOffset());
|
|
228
|
+
counter.increment(2);
|
|
229
|
+
return val;
|
|
230
|
+
},
|
|
231
|
+
getInt16: () => {
|
|
232
|
+
const val = view.getInt16(counter.getDiscardedOffset());
|
|
233
|
+
counter.increment(2);
|
|
234
|
+
return val;
|
|
235
|
+
},
|
|
236
|
+
getUint32,
|
|
237
|
+
// https://developer.apple.com/documentation/quicktime-file-format/sound_sample_description_version_1
|
|
238
|
+
// A 32-bit unsigned fixed-point number (16.16) that indicates the rate at which the sound samples were obtained.
|
|
239
|
+
getFixedPoint1616Number: () => {
|
|
240
|
+
const val = getUint32();
|
|
241
|
+
return val / 2 ** 16;
|
|
242
|
+
},
|
|
243
|
+
getPascalString: () => {
|
|
244
|
+
const val = getSlice(32);
|
|
245
|
+
return [...Array.from(new Uint8Array(val))];
|
|
246
|
+
},
|
|
247
|
+
getDecimalBytes(length) {
|
|
248
|
+
const bytes = getSlice(length);
|
|
249
|
+
const numbers = [...Array.from(new Uint8Array(bytes))];
|
|
250
|
+
return numbers.reduce((acc, byte, index) => acc + (byte << (8 * (numbers.length - index - 1))), 0);
|
|
251
|
+
},
|
|
252
|
+
getByteString(length) {
|
|
253
|
+
const bytes = getSlice(length);
|
|
254
|
+
return new TextDecoder().decode(bytes).trim();
|
|
255
|
+
},
|
|
256
|
+
getFloat64: () => {
|
|
257
|
+
const val = view.getFloat64(counter.getDiscardedOffset());
|
|
258
|
+
counter.increment(8);
|
|
259
|
+
return val;
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
};
|
|
263
|
+
exports.getArrayBufferIterator = getArrayBufferIterator;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.nodeReader = void 0;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const promises_1 = require("node:fs/promises");
|
|
6
|
+
const stream_1 = require("stream");
|
|
7
|
+
exports.nodeReader = {
|
|
8
|
+
read: (src, range) => {
|
|
9
|
+
const stream = (0, fs_1.createReadStream)(src, {
|
|
10
|
+
start: range === null ? 0 : range[0],
|
|
11
|
+
end: range === null ? Infinity : range[1],
|
|
12
|
+
});
|
|
13
|
+
return Promise.resolve(stream_1.Readable.toWeb(stream).getReader());
|
|
14
|
+
},
|
|
15
|
+
getLength: async (src) => {
|
|
16
|
+
const stats = await (0, promises_1.stat)(src);
|
|
17
|
+
return stats.size;
|
|
18
|
+
},
|
|
19
|
+
};
|
package/dist/from-web.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.webReader = void 0;
|
|
4
|
+
exports.webReader = {
|
|
5
|
+
read: async (src, range) => {
|
|
6
|
+
const resolvedUrl = typeof window !== 'undefined' && typeof window.location !== 'undefined'
|
|
7
|
+
? new URL(src, window.location.origin).toString()
|
|
8
|
+
: src;
|
|
9
|
+
if (!resolvedUrl.startsWith('https://') &&
|
|
10
|
+
!resolvedUrl.startsWith('http://')) {
|
|
11
|
+
return Promise.reject(new Error(resolvedUrl +
|
|
12
|
+
' is not a URL - needs to start with http:// or https://. If you want to read a local file, pass `nodeReader` to parseMedia().'));
|
|
13
|
+
}
|
|
14
|
+
const res = await fetch(resolvedUrl, {
|
|
15
|
+
headers: range === null
|
|
16
|
+
? {}
|
|
17
|
+
: {
|
|
18
|
+
Range: `bytes=${`${range[0]}-${range[1]}`}`,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
if (!res.body) {
|
|
22
|
+
throw new Error('No body');
|
|
23
|
+
}
|
|
24
|
+
const reader = res.body.getReader();
|
|
25
|
+
return reader;
|
|
26
|
+
},
|
|
27
|
+
getLength: async (src) => {
|
|
28
|
+
const res = await fetch(src, {
|
|
29
|
+
method: 'HEAD',
|
|
30
|
+
});
|
|
31
|
+
if (!res.body) {
|
|
32
|
+
throw new Error('No body');
|
|
33
|
+
}
|
|
34
|
+
const length = res.headers.get('content-length');
|
|
35
|
+
if (!length) {
|
|
36
|
+
throw new Error('No content-length');
|
|
37
|
+
}
|
|
38
|
+
return parseInt(length, 10);
|
|
39
|
+
},
|
|
40
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hasDimensions = exports.getDimensions = void 0;
|
|
4
|
+
const getDimensionsFromMatroska = (segments) => {
|
|
5
|
+
const tracksSegment = segments.children.find((b) => b.type === 'tracks-segment');
|
|
6
|
+
if (!tracksSegment || tracksSegment.type !== 'tracks-segment') {
|
|
7
|
+
throw new Error('No tracks segment');
|
|
8
|
+
}
|
|
9
|
+
// TODO: What if there are multiple video tracks, or audio track is first?
|
|
10
|
+
const trackEntrySegment = tracksSegment.children.find((b) => b.type === 'track-entry-segment');
|
|
11
|
+
if (!trackEntrySegment || trackEntrySegment.type !== 'track-entry-segment') {
|
|
12
|
+
throw new Error('No track entry segment');
|
|
13
|
+
}
|
|
14
|
+
const videoSegment = trackEntrySegment.children.find((b) => b.type === 'video-segment');
|
|
15
|
+
if (!videoSegment || videoSegment.type !== 'video-segment') {
|
|
16
|
+
throw new Error('No video segment');
|
|
17
|
+
}
|
|
18
|
+
const widthSegment = videoSegment.children.find((b) => b.type === 'width-segment');
|
|
19
|
+
if (!widthSegment || widthSegment.type !== 'width-segment') {
|
|
20
|
+
throw new Error('No width segment');
|
|
21
|
+
}
|
|
22
|
+
const heightSegment = videoSegment.children.find((b) => b.type === 'height-segment');
|
|
23
|
+
if (!heightSegment || heightSegment.type !== 'height-segment') {
|
|
24
|
+
throw new Error('No height segment');
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
width: widthSegment.width,
|
|
28
|
+
height: heightSegment.height,
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
const getDimensions = (boxes) => {
|
|
32
|
+
const matroskaBox = boxes.find((b) => b.type === 'main-segment');
|
|
33
|
+
if (matroskaBox && matroskaBox.type === 'main-segment') {
|
|
34
|
+
return getDimensionsFromMatroska(matroskaBox);
|
|
35
|
+
}
|
|
36
|
+
const moovBox = boxes.find((b) => b.type === 'moov-box');
|
|
37
|
+
if (!moovBox || moovBox.type !== 'moov-box') {
|
|
38
|
+
throw new Error('Expected moov box');
|
|
39
|
+
}
|
|
40
|
+
const { children } = moovBox;
|
|
41
|
+
if (!children) {
|
|
42
|
+
throw new Error('Expected moov box children');
|
|
43
|
+
}
|
|
44
|
+
const t = children.find((b) => b.type === 'trak-box');
|
|
45
|
+
if (!t || t.type !== 'trak-box') {
|
|
46
|
+
throw new Error('Expected trak box');
|
|
47
|
+
}
|
|
48
|
+
const mdiaBox = t.children.find((c) => c.type === 'regular-box' && c.boxType === 'mdia');
|
|
49
|
+
const tkhdBox = t.children.find((c) => c.type === 'tkhd-box');
|
|
50
|
+
if (tkhdBox && tkhdBox.type === 'tkhd-box') {
|
|
51
|
+
return {
|
|
52
|
+
width: tkhdBox.width,
|
|
53
|
+
height: tkhdBox.height,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (!mdiaBox) {
|
|
57
|
+
throw new Error('Expected mdia box');
|
|
58
|
+
}
|
|
59
|
+
if (mdiaBox.type !== 'regular-box') {
|
|
60
|
+
throw new Error('Expected mdia box');
|
|
61
|
+
}
|
|
62
|
+
const minfBox = mdiaBox.children.find((c) => c.type === 'regular-box' && c.boxType === 'minf');
|
|
63
|
+
if (!minfBox) {
|
|
64
|
+
throw new Error('Expected minf box');
|
|
65
|
+
}
|
|
66
|
+
if (minfBox.type !== 'regular-box') {
|
|
67
|
+
throw new Error('Expected minf box');
|
|
68
|
+
}
|
|
69
|
+
const stblBox = minfBox.children.find((c) => c.type === 'regular-box' && c.boxType === 'stbl');
|
|
70
|
+
if (!stblBox) {
|
|
71
|
+
throw new Error('Expected stbl box');
|
|
72
|
+
}
|
|
73
|
+
if (stblBox.type !== 'regular-box') {
|
|
74
|
+
throw new Error('Expected stbl box');
|
|
75
|
+
}
|
|
76
|
+
const stsdBox = stblBox.children.find((c) => c.type === 'stsd-box');
|
|
77
|
+
if (!stsdBox) {
|
|
78
|
+
throw new Error('Expected stsd box');
|
|
79
|
+
}
|
|
80
|
+
if (stsdBox.type !== 'stsd-box') {
|
|
81
|
+
throw new Error('Expected stsd box');
|
|
82
|
+
}
|
|
83
|
+
const videoSamples = stsdBox.samples.filter((s) => s.type === 'video');
|
|
84
|
+
if (videoSamples.length === 0) {
|
|
85
|
+
throw new Error('Has no video stream');
|
|
86
|
+
}
|
|
87
|
+
const [firstTrack] = videoSamples;
|
|
88
|
+
if (firstTrack.type !== 'video') {
|
|
89
|
+
throw new Error('Expected video track');
|
|
90
|
+
}
|
|
91
|
+
return { width: firstTrack.width, height: firstTrack.height };
|
|
92
|
+
};
|
|
93
|
+
exports.getDimensions = getDimensions;
|
|
94
|
+
// TODO: An audio track should return 'hasDimensions' = true on an audio file
|
|
95
|
+
// and stop parsing
|
|
96
|
+
const hasDimensions = (boxes) => {
|
|
97
|
+
try {
|
|
98
|
+
return (0, exports.getDimensions)(boxes) !== null;
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
exports.hasDimensions = hasDimensions;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hasDuration = exports.getDuration = void 0;
|
|
4
|
+
const getDurationFromMatroska = (segments) => {
|
|
5
|
+
const mainSegment = segments.find((s) => s.type === 'main-segment');
|
|
6
|
+
if (!mainSegment || mainSegment.type !== 'main-segment') {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
const { children } = mainSegment;
|
|
10
|
+
if (!children) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
const infoSegment = children.find((s) => s.type === 'info-segment');
|
|
14
|
+
const relevantBoxes = [
|
|
15
|
+
...mainSegment.children,
|
|
16
|
+
...(infoSegment && infoSegment.type === 'info-segment'
|
|
17
|
+
? infoSegment.children
|
|
18
|
+
: []),
|
|
19
|
+
];
|
|
20
|
+
const timestampScale = relevantBoxes.find((s) => s.type === 'timestamp-scale-segment');
|
|
21
|
+
if (!timestampScale || timestampScale.type !== 'timestamp-scale-segment') {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const duration = relevantBoxes.find((s) => s.type === 'duration-segment');
|
|
25
|
+
if (!duration || duration.type !== 'duration-segment') {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
return (duration.duration / timestampScale.timestampScale) * 1000;
|
|
29
|
+
};
|
|
30
|
+
const getDuration = (boxes) => {
|
|
31
|
+
const matroskaBox = boxes.find((b) => b.type === 'main-segment');
|
|
32
|
+
if (matroskaBox) {
|
|
33
|
+
return getDurationFromMatroska(boxes);
|
|
34
|
+
}
|
|
35
|
+
const moovBox = boxes.find((b) => b.type === 'moov-box');
|
|
36
|
+
if (!moovBox || moovBox.type !== 'moov-box') {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const { children } = moovBox;
|
|
40
|
+
if (!children) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const mvhdBox = children.find((b) => b.type === 'mvhd-box');
|
|
44
|
+
if (!mvhdBox) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
if (mvhdBox.type !== 'mvhd-box') {
|
|
48
|
+
throw new Error('Expected mvhd-box');
|
|
49
|
+
}
|
|
50
|
+
return mvhdBox.durationInSeconds;
|
|
51
|
+
};
|
|
52
|
+
exports.getDuration = getDuration;
|
|
53
|
+
const hasDuration = (boxes) => {
|
|
54
|
+
try {
|
|
55
|
+
return (0, exports.getDuration)(boxes) !== null;
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
exports.hasDuration = hasDuration;
|
package/dist/get-fps.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hasFps = exports.getFps = void 0;
|
|
4
|
+
const calculateFps = (sttsBox, timeScale) => {
|
|
5
|
+
let sum = 0;
|
|
6
|
+
let totalSamples = 0;
|
|
7
|
+
for (const sample of sttsBox.sampleDistribution) {
|
|
8
|
+
sum += sample.sampleCount * sample.sampleDelta;
|
|
9
|
+
totalSamples += sample.sampleCount;
|
|
10
|
+
}
|
|
11
|
+
return timeScale / (sum / totalSamples);
|
|
12
|
+
};
|
|
13
|
+
const getFps = (segments) => {
|
|
14
|
+
const moovBox = segments.find((s) => s.type === 'moov-box');
|
|
15
|
+
if (!moovBox || moovBox.type !== 'moov-box') {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const mvhdBox = moovBox.children.find((c) => c.type === 'mvhd-box');
|
|
19
|
+
if (!mvhdBox || mvhdBox.type !== 'mvhd-box') {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const { timeScale } = mvhdBox;
|
|
23
|
+
const { children } = moovBox;
|
|
24
|
+
const trackBoxes = children.filter((c) => c.type === 'trak-box');
|
|
25
|
+
if (!trackBoxes || trackBoxes.length === 0) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
// TODO: What if the video track is not the first track?
|
|
29
|
+
const trackBox = trackBoxes[0];
|
|
30
|
+
if (!trackBox || trackBox.type !== 'trak-box') {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const trackBoxChildren = trackBox.children;
|
|
34
|
+
if (!trackBoxChildren || trackBoxChildren.length === 0) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const mdiaBox = trackBoxChildren.find((c) => c.type === 'regular-box' && c.boxType === 'mdia');
|
|
38
|
+
if (!mdiaBox ||
|
|
39
|
+
mdiaBox.type !== 'regular-box' ||
|
|
40
|
+
mdiaBox.boxType !== 'mdia') {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const minfBox = mdiaBox.children.find((c) => c.type === 'regular-box' && c.boxType === 'minf');
|
|
44
|
+
if (!minfBox ||
|
|
45
|
+
minfBox.type !== 'regular-box' ||
|
|
46
|
+
minfBox.boxType !== 'minf') {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const stblBox = minfBox.children.find((c) => c.type === 'regular-box' && c.boxType === 'stbl');
|
|
50
|
+
if (!stblBox ||
|
|
51
|
+
stblBox.type !== 'regular-box' ||
|
|
52
|
+
stblBox.boxType !== 'stbl') {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
const sttsBox = stblBox.children.find((c) => c.type === 'stts-box');
|
|
56
|
+
if (!sttsBox || sttsBox.type !== 'stts-box') {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
return calculateFps(sttsBox, timeScale);
|
|
60
|
+
};
|
|
61
|
+
exports.getFps = getFps;
|
|
62
|
+
const hasFps = (boxes) => {
|
|
63
|
+
try {
|
|
64
|
+
return (0, exports.getFps)(boxes) !== null;
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
exports.hasFps = hasFps;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hasAllInfo = void 0;
|
|
4
|
+
const get_dimensions_1 = require("./get-dimensions");
|
|
5
|
+
const get_duration_1 = require("./get-duration");
|
|
6
|
+
const get_fps_1 = require("./get-fps");
|
|
7
|
+
const hasAllInfo = (options, parseResult) => {
|
|
8
|
+
const keys = Object.entries(options)
|
|
9
|
+
.filter(([, value]) => value)
|
|
10
|
+
.map(([key]) => key);
|
|
11
|
+
return keys.every((key) => {
|
|
12
|
+
if (key === 'boxes') {
|
|
13
|
+
return parseResult.status === 'done';
|
|
14
|
+
}
|
|
15
|
+
if (key === 'durationInSeconds') {
|
|
16
|
+
return (0, get_duration_1.hasDuration)(parseResult.segments);
|
|
17
|
+
}
|
|
18
|
+
if (key === 'dimensions') {
|
|
19
|
+
return (0, get_dimensions_1.hasDimensions)(parseResult.segments);
|
|
20
|
+
}
|
|
21
|
+
if (key === 'fps') {
|
|
22
|
+
return (0, get_fps_1.hasFps)(parseResult.segments) !== null;
|
|
23
|
+
}
|
|
24
|
+
throw new Error(`Unknown key: ${key}`);
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
exports.hasAllInfo = hasAllInfo;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { parseMedia } from './parse-media';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseMedia = void 0;
|
|
4
|
+
var parse_media_1 = require("./parse-media");
|
|
5
|
+
Object.defineProperty(exports, "parseMedia", { enumerable: true, get: function () { return parse_media_1.parseMedia; } });
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Dimensions } from './get-dimensions';
|
|
2
|
+
import type { AnySegment } from './parse-result';
|
|
3
|
+
import type { ReaderInterface } from './reader';
|
|
4
|
+
export type Options<EnableDimensions extends boolean, EnableDuration extends boolean, EnableBoxes extends boolean, EnableFps extends boolean> = {
|
|
5
|
+
dimensions?: EnableDimensions;
|
|
6
|
+
durationInSeconds?: EnableDuration;
|
|
7
|
+
boxes?: EnableBoxes;
|
|
8
|
+
fps?: EnableFps;
|
|
9
|
+
};
|
|
10
|
+
export type Metadata<EnableDimensions extends boolean, EnableDuration extends boolean, EnableBoxes extends boolean, EnableFps extends boolean> = (EnableDimensions extends true ? {
|
|
11
|
+
dimensions: Dimensions;
|
|
12
|
+
} : {}) & (EnableDuration extends true ? {
|
|
13
|
+
durationInSeconds: number | null;
|
|
14
|
+
} : {}) & (EnableBoxes extends true ? {
|
|
15
|
+
boxes: AnySegment[];
|
|
16
|
+
} : {}) & (EnableFps extends true ? {
|
|
17
|
+
fps: number | null;
|
|
18
|
+
} : {});
|
|
19
|
+
export type ParseMedia = <EnableDimensions extends boolean, EnableDuration extends boolean, EnableBoxes extends boolean, EnableFps extends boolean>(src: string, options: Options<EnableDimensions, EnableDuration, EnableBoxes, EnableFps>, readerInterface?: ReaderInterface) => Promise<Metadata<EnableDimensions, EnableDuration, EnableBoxes, EnableFps>>;
|
package/dist/options.js
ADDED