First release
This commit is contained in:
commit
fa6c85266e
2339 changed files with 761050 additions and 0 deletions
518
node_modules/@videojs/vhs-utils/cjs/ebml-helpers.js
generated
vendored
Normal file
518
node_modules/@videojs/vhs-utils/cjs/ebml-helpers.js
generated
vendored
Normal file
|
@ -0,0 +1,518 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.parseData = exports.parseTracks = exports.decodeBlock = exports.findEbml = exports.EBML_TAGS = void 0;
|
||||
|
||||
var _byteHelpers = require("./byte-helpers");
|
||||
|
||||
var _codecHelpers = require("./codec-helpers.js");
|
||||
|
||||
// relevant specs for this parser:
|
||||
// https://matroska-org.github.io/libebml/specs.html
|
||||
// https://www.matroska.org/technical/elements.html
|
||||
// https://www.webmproject.org/docs/container/
|
||||
var EBML_TAGS = {
|
||||
EBML: (0, _byteHelpers.toUint8)([0x1A, 0x45, 0xDF, 0xA3]),
|
||||
DocType: (0, _byteHelpers.toUint8)([0x42, 0x82]),
|
||||
Segment: (0, _byteHelpers.toUint8)([0x18, 0x53, 0x80, 0x67]),
|
||||
SegmentInfo: (0, _byteHelpers.toUint8)([0x15, 0x49, 0xA9, 0x66]),
|
||||
Tracks: (0, _byteHelpers.toUint8)([0x16, 0x54, 0xAE, 0x6B]),
|
||||
Track: (0, _byteHelpers.toUint8)([0xAE]),
|
||||
TrackNumber: (0, _byteHelpers.toUint8)([0xd7]),
|
||||
DefaultDuration: (0, _byteHelpers.toUint8)([0x23, 0xe3, 0x83]),
|
||||
TrackEntry: (0, _byteHelpers.toUint8)([0xAE]),
|
||||
TrackType: (0, _byteHelpers.toUint8)([0x83]),
|
||||
FlagDefault: (0, _byteHelpers.toUint8)([0x88]),
|
||||
CodecID: (0, _byteHelpers.toUint8)([0x86]),
|
||||
CodecPrivate: (0, _byteHelpers.toUint8)([0x63, 0xA2]),
|
||||
VideoTrack: (0, _byteHelpers.toUint8)([0xe0]),
|
||||
AudioTrack: (0, _byteHelpers.toUint8)([0xe1]),
|
||||
// Not used yet, but will be used for live webm/mkv
|
||||
// see https://www.matroska.org/technical/basics.html#block-structure
|
||||
// see https://www.matroska.org/technical/basics.html#simpleblock-structure
|
||||
Cluster: (0, _byteHelpers.toUint8)([0x1F, 0x43, 0xB6, 0x75]),
|
||||
Timestamp: (0, _byteHelpers.toUint8)([0xE7]),
|
||||
TimestampScale: (0, _byteHelpers.toUint8)([0x2A, 0xD7, 0xB1]),
|
||||
BlockGroup: (0, _byteHelpers.toUint8)([0xA0]),
|
||||
BlockDuration: (0, _byteHelpers.toUint8)([0x9B]),
|
||||
Block: (0, _byteHelpers.toUint8)([0xA1]),
|
||||
SimpleBlock: (0, _byteHelpers.toUint8)([0xA3])
|
||||
};
|
||||
/**
|
||||
* This is a simple table to determine the length
|
||||
* of things in ebml. The length is one based (starts at 1,
|
||||
* rather than zero) and for every zero bit before a one bit
|
||||
* we add one to length. We also need this table because in some
|
||||
* case we have to xor all the length bits from another value.
|
||||
*/
|
||||
|
||||
exports.EBML_TAGS = EBML_TAGS;
|
||||
var LENGTH_TABLE = [128, 64, 32, 16, 8, 4, 2, 1];
|
||||
|
||||
var getLength = function getLength(byte) {
|
||||
var len = 1;
|
||||
|
||||
for (var i = 0; i < LENGTH_TABLE.length; i++) {
|
||||
if (byte & LENGTH_TABLE[i]) {
|
||||
break;
|
||||
}
|
||||
|
||||
len++;
|
||||
}
|
||||
|
||||
return len;
|
||||
}; // length in ebml is stored in the first 4 to 8 bits
|
||||
// of the first byte. 4 for the id length and 8 for the
|
||||
// data size length. Length is measured by converting the number to binary
|
||||
// then 1 + the number of zeros before a 1 is encountered starting
|
||||
// from the left.
|
||||
|
||||
|
||||
var getvint = function getvint(bytes, offset, removeLength, signed) {
|
||||
if (removeLength === void 0) {
|
||||
removeLength = true;
|
||||
}
|
||||
|
||||
if (signed === void 0) {
|
||||
signed = false;
|
||||
}
|
||||
|
||||
var length = getLength(bytes[offset]);
|
||||
var valueBytes = bytes.subarray(offset, offset + length); // NOTE that we do **not** subarray here because we need to copy these bytes
|
||||
// as they will be modified below to remove the dataSizeLen bits and we do not
|
||||
// want to modify the original data. normally we could just call slice on
|
||||
// uint8array but ie 11 does not support that...
|
||||
|
||||
if (removeLength) {
|
||||
valueBytes = Array.prototype.slice.call(bytes, offset, offset + length);
|
||||
valueBytes[0] ^= LENGTH_TABLE[length - 1];
|
||||
}
|
||||
|
||||
return {
|
||||
length: length,
|
||||
value: (0, _byteHelpers.bytesToNumber)(valueBytes, {
|
||||
signed: signed
|
||||
}),
|
||||
bytes: valueBytes
|
||||
};
|
||||
};
|
||||
|
||||
var normalizePath = function normalizePath(path) {
|
||||
if (typeof path === 'string') {
|
||||
return path.match(/.{1,2}/g).map(function (p) {
|
||||
return normalizePath(p);
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof path === 'number') {
|
||||
return (0, _byteHelpers.numberToBytes)(path);
|
||||
}
|
||||
|
||||
return path;
|
||||
};
|
||||
|
||||
var normalizePaths = function normalizePaths(paths) {
|
||||
if (!Array.isArray(paths)) {
|
||||
return [normalizePath(paths)];
|
||||
}
|
||||
|
||||
return paths.map(function (p) {
|
||||
return normalizePath(p);
|
||||
});
|
||||
};
|
||||
|
||||
var getInfinityDataSize = function getInfinityDataSize(id, bytes, offset) {
|
||||
if (offset >= bytes.length) {
|
||||
return bytes.length;
|
||||
}
|
||||
|
||||
var innerid = getvint(bytes, offset, false);
|
||||
|
||||
if ((0, _byteHelpers.bytesMatch)(id.bytes, innerid.bytes)) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
var dataHeader = getvint(bytes, offset + innerid.length);
|
||||
return getInfinityDataSize(id, bytes, offset + dataHeader.length + dataHeader.value + innerid.length);
|
||||
};
|
||||
/**
|
||||
* Notes on the EBLM format.
|
||||
*
|
||||
* EBLM uses "vints" tags. Every vint tag contains
|
||||
* two parts
|
||||
*
|
||||
* 1. The length from the first byte. You get this by
|
||||
* converting the byte to binary and counting the zeros
|
||||
* before a 1. Then you add 1 to that. Examples
|
||||
* 00011111 = length 4 because there are 3 zeros before a 1.
|
||||
* 00100000 = length 3 because there are 2 zeros before a 1.
|
||||
* 00000011 = length 7 because there are 6 zeros before a 1.
|
||||
*
|
||||
* 2. The bits used for length are removed from the first byte
|
||||
* Then all the bytes are merged into a value. NOTE: this
|
||||
* is not the case for id ebml tags as there id includes
|
||||
* length bits.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
var findEbml = function findEbml(bytes, paths) {
|
||||
paths = normalizePaths(paths);
|
||||
bytes = (0, _byteHelpers.toUint8)(bytes);
|
||||
var results = [];
|
||||
|
||||
if (!paths.length) {
|
||||
return results;
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
|
||||
while (i < bytes.length) {
|
||||
var id = getvint(bytes, i, false);
|
||||
var dataHeader = getvint(bytes, i + id.length);
|
||||
var dataStart = i + id.length + dataHeader.length; // dataSize is unknown or this is a live stream
|
||||
|
||||
if (dataHeader.value === 0x7f) {
|
||||
dataHeader.value = getInfinityDataSize(id, bytes, dataStart);
|
||||
|
||||
if (dataHeader.value !== bytes.length) {
|
||||
dataHeader.value -= dataStart;
|
||||
}
|
||||
}
|
||||
|
||||
var dataEnd = dataStart + dataHeader.value > bytes.length ? bytes.length : dataStart + dataHeader.value;
|
||||
var data = bytes.subarray(dataStart, dataEnd);
|
||||
|
||||
if ((0, _byteHelpers.bytesMatch)(paths[0], id.bytes)) {
|
||||
if (paths.length === 1) {
|
||||
// this is the end of the paths and we've found the tag we were
|
||||
// looking for
|
||||
results.push(data);
|
||||
} else {
|
||||
// recursively search for the next tag inside of the data
|
||||
// of this one
|
||||
results = results.concat(findEbml(data, paths.slice(1)));
|
||||
}
|
||||
}
|
||||
|
||||
var totalLength = id.length + dataHeader.length + data.length; // move past this tag entirely, we are not looking for it
|
||||
|
||||
i += totalLength;
|
||||
}
|
||||
|
||||
return results;
|
||||
}; // see https://www.matroska.org/technical/basics.html#block-structure
|
||||
|
||||
|
||||
exports.findEbml = findEbml;
|
||||
|
||||
var decodeBlock = function decodeBlock(block, type, timestampScale, clusterTimestamp) {
|
||||
var duration;
|
||||
|
||||
if (type === 'group') {
|
||||
duration = findEbml(block, [EBML_TAGS.BlockDuration])[0];
|
||||
|
||||
if (duration) {
|
||||
duration = (0, _byteHelpers.bytesToNumber)(duration);
|
||||
duration = 1 / timestampScale * duration * timestampScale / 1000;
|
||||
}
|
||||
|
||||
block = findEbml(block, [EBML_TAGS.Block])[0];
|
||||
type = 'block'; // treat data as a block after this point
|
||||
}
|
||||
|
||||
var dv = new DataView(block.buffer, block.byteOffset, block.byteLength);
|
||||
var trackNumber = getvint(block, 0);
|
||||
var timestamp = dv.getInt16(trackNumber.length, false);
|
||||
var flags = block[trackNumber.length + 2];
|
||||
var data = block.subarray(trackNumber.length + 3); // pts/dts in seconds
|
||||
|
||||
var ptsdts = 1 / timestampScale * (clusterTimestamp + timestamp) * timestampScale / 1000; // return the frame
|
||||
|
||||
var parsed = {
|
||||
duration: duration,
|
||||
trackNumber: trackNumber.value,
|
||||
keyframe: type === 'simple' && flags >> 7 === 1,
|
||||
invisible: (flags & 0x08) >> 3 === 1,
|
||||
lacing: (flags & 0x06) >> 1,
|
||||
discardable: type === 'simple' && (flags & 0x01) === 1,
|
||||
frames: [],
|
||||
pts: ptsdts,
|
||||
dts: ptsdts,
|
||||
timestamp: timestamp
|
||||
};
|
||||
|
||||
if (!parsed.lacing) {
|
||||
parsed.frames.push(data);
|
||||
return parsed;
|
||||
}
|
||||
|
||||
var numberOfFrames = data[0] + 1;
|
||||
var frameSizes = [];
|
||||
var offset = 1; // Fixed
|
||||
|
||||
if (parsed.lacing === 2) {
|
||||
var sizeOfFrame = (data.length - offset) / numberOfFrames;
|
||||
|
||||
for (var i = 0; i < numberOfFrames; i++) {
|
||||
frameSizes.push(sizeOfFrame);
|
||||
}
|
||||
} // xiph
|
||||
|
||||
|
||||
if (parsed.lacing === 1) {
|
||||
for (var _i = 0; _i < numberOfFrames - 1; _i++) {
|
||||
var size = 0;
|
||||
|
||||
do {
|
||||
size += data[offset];
|
||||
offset++;
|
||||
} while (data[offset - 1] === 0xFF);
|
||||
|
||||
frameSizes.push(size);
|
||||
}
|
||||
} // ebml
|
||||
|
||||
|
||||
if (parsed.lacing === 3) {
|
||||
// first vint is unsinged
|
||||
// after that vints are singed and
|
||||
// based on a compounding size
|
||||
var _size = 0;
|
||||
|
||||
for (var _i2 = 0; _i2 < numberOfFrames - 1; _i2++) {
|
||||
var vint = _i2 === 0 ? getvint(data, offset) : getvint(data, offset, true, true);
|
||||
_size += vint.value;
|
||||
frameSizes.push(_size);
|
||||
offset += vint.length;
|
||||
}
|
||||
}
|
||||
|
||||
frameSizes.forEach(function (size) {
|
||||
parsed.frames.push(data.subarray(offset, offset + size));
|
||||
offset += size;
|
||||
});
|
||||
return parsed;
|
||||
}; // VP9 Codec Feature Metadata (CodecPrivate)
|
||||
// https://www.webmproject.org/docs/container/
|
||||
|
||||
|
||||
exports.decodeBlock = decodeBlock;
|
||||
|
||||
var parseVp9Private = function parseVp9Private(bytes) {
|
||||
var i = 0;
|
||||
var params = {};
|
||||
|
||||
while (i < bytes.length) {
|
||||
var id = bytes[i] & 0x7f;
|
||||
var len = bytes[i + 1];
|
||||
var val = void 0;
|
||||
|
||||
if (len === 1) {
|
||||
val = bytes[i + 2];
|
||||
} else {
|
||||
val = bytes.subarray(i + 2, i + 2 + len);
|
||||
}
|
||||
|
||||
if (id === 1) {
|
||||
params.profile = val;
|
||||
} else if (id === 2) {
|
||||
params.level = val;
|
||||
} else if (id === 3) {
|
||||
params.bitDepth = val;
|
||||
} else if (id === 4) {
|
||||
params.chromaSubsampling = val;
|
||||
} else {
|
||||
params[id] = val;
|
||||
}
|
||||
|
||||
i += 2 + len;
|
||||
}
|
||||
|
||||
return params;
|
||||
};
|
||||
|
||||
var parseTracks = function parseTracks(bytes) {
|
||||
bytes = (0, _byteHelpers.toUint8)(bytes);
|
||||
var decodedTracks = [];
|
||||
var tracks = findEbml(bytes, [EBML_TAGS.Segment, EBML_TAGS.Tracks, EBML_TAGS.Track]);
|
||||
|
||||
if (!tracks.length) {
|
||||
tracks = findEbml(bytes, [EBML_TAGS.Tracks, EBML_TAGS.Track]);
|
||||
}
|
||||
|
||||
if (!tracks.length) {
|
||||
tracks = findEbml(bytes, [EBML_TAGS.Track]);
|
||||
}
|
||||
|
||||
if (!tracks.length) {
|
||||
return decodedTracks;
|
||||
}
|
||||
|
||||
tracks.forEach(function (track) {
|
||||
var trackType = findEbml(track, EBML_TAGS.TrackType)[0];
|
||||
|
||||
if (!trackType || !trackType.length) {
|
||||
return;
|
||||
} // 1 is video, 2 is audio, 17 is subtitle
|
||||
// other values are unimportant in this context
|
||||
|
||||
|
||||
if (trackType[0] === 1) {
|
||||
trackType = 'video';
|
||||
} else if (trackType[0] === 2) {
|
||||
trackType = 'audio';
|
||||
} else if (trackType[0] === 17) {
|
||||
trackType = 'subtitle';
|
||||
} else {
|
||||
return;
|
||||
} // todo parse language
|
||||
|
||||
|
||||
var decodedTrack = {
|
||||
rawCodec: (0, _byteHelpers.bytesToString)(findEbml(track, [EBML_TAGS.CodecID])[0]),
|
||||
type: trackType,
|
||||
codecPrivate: findEbml(track, [EBML_TAGS.CodecPrivate])[0],
|
||||
number: (0, _byteHelpers.bytesToNumber)(findEbml(track, [EBML_TAGS.TrackNumber])[0]),
|
||||
defaultDuration: (0, _byteHelpers.bytesToNumber)(findEbml(track, [EBML_TAGS.DefaultDuration])[0]),
|
||||
default: findEbml(track, [EBML_TAGS.FlagDefault])[0],
|
||||
rawData: track
|
||||
};
|
||||
var codec = '';
|
||||
|
||||
if (/V_MPEG4\/ISO\/AVC/.test(decodedTrack.rawCodec)) {
|
||||
codec = "avc1." + (0, _codecHelpers.getAvcCodec)(decodedTrack.codecPrivate);
|
||||
} else if (/V_MPEGH\/ISO\/HEVC/.test(decodedTrack.rawCodec)) {
|
||||
codec = "hev1." + (0, _codecHelpers.getHvcCodec)(decodedTrack.codecPrivate);
|
||||
} else if (/V_MPEG4\/ISO\/ASP/.test(decodedTrack.rawCodec)) {
|
||||
if (decodedTrack.codecPrivate) {
|
||||
codec = 'mp4v.20.' + decodedTrack.codecPrivate[4].toString();
|
||||
} else {
|
||||
codec = 'mp4v.20.9';
|
||||
}
|
||||
} else if (/^V_THEORA/.test(decodedTrack.rawCodec)) {
|
||||
codec = 'theora';
|
||||
} else if (/^V_VP8/.test(decodedTrack.rawCodec)) {
|
||||
codec = 'vp8';
|
||||
} else if (/^V_VP9/.test(decodedTrack.rawCodec)) {
|
||||
if (decodedTrack.codecPrivate) {
|
||||
var _parseVp9Private = parseVp9Private(decodedTrack.codecPrivate),
|
||||
profile = _parseVp9Private.profile,
|
||||
level = _parseVp9Private.level,
|
||||
bitDepth = _parseVp9Private.bitDepth,
|
||||
chromaSubsampling = _parseVp9Private.chromaSubsampling;
|
||||
|
||||
codec = 'vp09.';
|
||||
codec += (0, _byteHelpers.padStart)(profile, 2, '0') + ".";
|
||||
codec += (0, _byteHelpers.padStart)(level, 2, '0') + ".";
|
||||
codec += (0, _byteHelpers.padStart)(bitDepth, 2, '0') + ".";
|
||||
codec += "" + (0, _byteHelpers.padStart)(chromaSubsampling, 2, '0'); // Video -> Colour -> Ebml name
|
||||
|
||||
var matrixCoefficients = findEbml(track, [0xE0, [0x55, 0xB0], [0x55, 0xB1]])[0] || [];
|
||||
var videoFullRangeFlag = findEbml(track, [0xE0, [0x55, 0xB0], [0x55, 0xB9]])[0] || [];
|
||||
var transferCharacteristics = findEbml(track, [0xE0, [0x55, 0xB0], [0x55, 0xBA]])[0] || [];
|
||||
var colourPrimaries = findEbml(track, [0xE0, [0x55, 0xB0], [0x55, 0xBB]])[0] || []; // if we find any optional codec parameter specify them all.
|
||||
|
||||
if (matrixCoefficients.length || videoFullRangeFlag.length || transferCharacteristics.length || colourPrimaries.length) {
|
||||
codec += "." + (0, _byteHelpers.padStart)(colourPrimaries[0], 2, '0');
|
||||
codec += "." + (0, _byteHelpers.padStart)(transferCharacteristics[0], 2, '0');
|
||||
codec += "." + (0, _byteHelpers.padStart)(matrixCoefficients[0], 2, '0');
|
||||
codec += "." + (0, _byteHelpers.padStart)(videoFullRangeFlag[0], 2, '0');
|
||||
}
|
||||
} else {
|
||||
codec = 'vp9';
|
||||
}
|
||||
} else if (/^V_AV1/.test(decodedTrack.rawCodec)) {
|
||||
codec = "av01." + (0, _codecHelpers.getAv1Codec)(decodedTrack.codecPrivate);
|
||||
} else if (/A_ALAC/.test(decodedTrack.rawCodec)) {
|
||||
codec = 'alac';
|
||||
} else if (/A_MPEG\/L2/.test(decodedTrack.rawCodec)) {
|
||||
codec = 'mp2';
|
||||
} else if (/A_MPEG\/L3/.test(decodedTrack.rawCodec)) {
|
||||
codec = 'mp3';
|
||||
} else if (/^A_AAC/.test(decodedTrack.rawCodec)) {
|
||||
if (decodedTrack.codecPrivate) {
|
||||
codec = 'mp4a.40.' + (decodedTrack.codecPrivate[0] >>> 3).toString();
|
||||
} else {
|
||||
codec = 'mp4a.40.2';
|
||||
}
|
||||
} else if (/^A_AC3/.test(decodedTrack.rawCodec)) {
|
||||
codec = 'ac-3';
|
||||
} else if (/^A_PCM/.test(decodedTrack.rawCodec)) {
|
||||
codec = 'pcm';
|
||||
} else if (/^A_MS\/ACM/.test(decodedTrack.rawCodec)) {
|
||||
codec = 'speex';
|
||||
} else if (/^A_EAC3/.test(decodedTrack.rawCodec)) {
|
||||
codec = 'ec-3';
|
||||
} else if (/^A_VORBIS/.test(decodedTrack.rawCodec)) {
|
||||
codec = 'vorbis';
|
||||
} else if (/^A_FLAC/.test(decodedTrack.rawCodec)) {
|
||||
codec = 'flac';
|
||||
} else if (/^A_OPUS/.test(decodedTrack.rawCodec)) {
|
||||
codec = 'opus';
|
||||
}
|
||||
|
||||
decodedTrack.codec = codec;
|
||||
decodedTracks.push(decodedTrack);
|
||||
});
|
||||
return decodedTracks.sort(function (a, b) {
|
||||
return a.number - b.number;
|
||||
});
|
||||
};
|
||||
|
||||
exports.parseTracks = parseTracks;
|
||||
|
||||
var parseData = function parseData(data, tracks) {
|
||||
var allBlocks = [];
|
||||
var segment = findEbml(data, [EBML_TAGS.Segment])[0];
|
||||
var timestampScale = findEbml(segment, [EBML_TAGS.SegmentInfo, EBML_TAGS.TimestampScale])[0]; // in nanoseconds, defaults to 1ms
|
||||
|
||||
if (timestampScale && timestampScale.length) {
|
||||
timestampScale = (0, _byteHelpers.bytesToNumber)(timestampScale);
|
||||
} else {
|
||||
timestampScale = 1000000;
|
||||
}
|
||||
|
||||
var clusters = findEbml(segment, [EBML_TAGS.Cluster]);
|
||||
|
||||
if (!tracks) {
|
||||
tracks = parseTracks(segment);
|
||||
}
|
||||
|
||||
clusters.forEach(function (cluster, ci) {
|
||||
var simpleBlocks = findEbml(cluster, [EBML_TAGS.SimpleBlock]).map(function (b) {
|
||||
return {
|
||||
type: 'simple',
|
||||
data: b
|
||||
};
|
||||
});
|
||||
var blockGroups = findEbml(cluster, [EBML_TAGS.BlockGroup]).map(function (b) {
|
||||
return {
|
||||
type: 'group',
|
||||
data: b
|
||||
};
|
||||
});
|
||||
var timestamp = findEbml(cluster, [EBML_TAGS.Timestamp])[0] || 0;
|
||||
|
||||
if (timestamp && timestamp.length) {
|
||||
timestamp = (0, _byteHelpers.bytesToNumber)(timestamp);
|
||||
} // get all blocks then sort them into the correct order
|
||||
|
||||
|
||||
var blocks = simpleBlocks.concat(blockGroups).sort(function (a, b) {
|
||||
return a.data.byteOffset - b.data.byteOffset;
|
||||
});
|
||||
blocks.forEach(function (block, bi) {
|
||||
var decoded = decodeBlock(block.data, block.type, timestampScale, timestamp);
|
||||
allBlocks.push(decoded);
|
||||
});
|
||||
});
|
||||
return {
|
||||
tracks: tracks,
|
||||
blocks: allBlocks
|
||||
};
|
||||
};
|
||||
|
||||
exports.parseData = parseData;
|
Loading…
Add table
Add a link
Reference in a new issue