UNPKG

webm-duration-fix

Version:

based on ts-ebml and support large file(than 2GB) and optimize memory usage during repair

265 lines (243 loc) 6.75 kB
import { Buffer } from "./tools"; export default class Tools { /** * read variable length integer per * https://www.matroska.org/technical/specs/index.html#EBML_ex * @static * @param {Buffer} buffer containing input * @param {Number} [start=0] position in buffer * @returns {{length: Number, value: number}} value / length object */ static readVint(buffer, start = 0) { const length = 8 - Math.floor(Math.log2(buffer[start])); if (length > 8) { const number = Tools.readHexString(buffer, start, start + length); throw new Error(`Unrepresentable length: ${length} ${number}`); } if (start + length > buffer.length) { return null; } let value = buffer[start] & ((1 << (8 - length)) - 1); for (let i = 1; i < length; i += 1) { if (i === 7) { if (value >= 2 ** 8 && buffer[start + 7] > 0) { return { length, value: -1 }; } } value *= 2 ** 8; value += buffer[start + i]; } return { length, value }; } /** * write variable length integer * @static * @param {Number} value to store into buffer * @returns {Buffer} containing the value */ static writeVint(value) { if (value < 0 || value > 2 ** 53) { throw new Error(`Unrepresentable value: ${value}`); } let length = 1; for (length = 1; length <= 8; length += 1) { if (value < 2 ** (7 * length) - 1) { break; } } const buffer = Buffer.alloc(length); let val = value; for (let i = 1; i <= length; i += 1) { const b = val & 0xff; buffer[length - i] = b; val -= b; val /= 2 ** 8; } buffer[0] |= 1 << (8 - length); return buffer; } /** * * * concatenate two arrays of bytes * @static * @param {Buffer} a1 First array * @param {Buffer} a2 Second array * @returns {Buffer} concatenated arrays */ static concatenate(a1, a2) { // both null or undefined if (!a1 && !a2) { return Buffer.from([]); } if (!a1 || a1.byteLength === 0) { return a2; } if (!a2 || a2.byteLength === 0) { return a1; } return Buffer.from([...a1, ...a2]); } /** * get a hex text string from Buff[start,end) * @param {Buffer} buff from which to read the string * @param {Number} [start=0] starting point (default 0) * @param {Number} [end=buff.byteLength] ending point (default the whole buffer) * @returns {string} the hex string */ static readHexString(buff, start = 0, end = buff.byteLength) { return Array.from(buff.slice(start, end)) .map(q => Number(q).toString(16)) .reduce((acc, current) => `${acc}${current.padStart(2, '0')}`, ''); } /** * tries to read out a UTF-8 encoded string * @param {Buffer} buff the buffer to attempt to read from * @return {string|null} the decoded text, or null if unable to */ static readUtf8(buff) { try { return Buffer.from(buff).toString('utf8'); } catch (exception) { return null; } } /** * get an unsigned number from a buffer * @param {Buffer} buff from which to read variable-length unsigned number * @returns {number|string} result (in hex for lengths > 6) */ static readUnsigned(buff) { const b = new DataView(buff.buffer, buff.byteOffset, buff.byteLength); switch (buff.byteLength) { case 1: return b.getUint8(0); case 2: return b.getUint16(0); case 4: return b.getUint32(0); default: break; } if (buff.byteLength <= 6) { return buff.reduce((acc, current) => acc * 256 + current, 0); } return Tools.readHexString(buff, 0, buff.byteLength); } /** * get an signed number from a buffer * @static * @param {Buffer} buff from which to read variable-length signed number * @returns {number} result */ static readSigned(buff) { const b = new DataView(buff.buffer, buff.byteOffset, buff.byteLength); switch (buff.byteLength) { case 1: return b.getInt8(0); case 2: return b.getInt16(0); case 4: return b.getInt32(0); default: return NaN; } } /** * get an floating-point number from a buffer * @static * @param {Buffer} buff from which to read variable-length floating-point number * @returns {number} result */ static readFloat(buff) { const b = new DataView(buff.buffer, buff.byteOffset, buff.byteLength); switch (buff.byteLength) { case 4: return b.getFloat32(0); case 8: return b.getFloat64(0); default: return NaN; } } /** * get a date from a buffer * @static * @param {Buffer} buff from which to read the date * @return {Date} result */ static readDate(buff) { const b = new DataView(buff.buffer, buff.byteOffset, buff.byteLength); switch (buff.byteLength) { case 1: return new Date(b.getUint8(0)); case 2: return new Date(b.getUint16(0)); case 4: return new Date(b.getUint32(0)); case 8: return new Date(Number.parseInt(Tools.readHexString(buff), 16)); default: return new Date(0); } } /** * Reads the data from a tag * @static * @param {TagData} tagObj The tag object to be read * @param {Buffer} data Data to be transformed * @return {Tag} result */ static readDataFromTag(tagObj, data) { const { type, name } = tagObj; let { track } = tagObj; let discardable = tagObj.discardable || false; let keyframe = tagObj.keyframe || false; let payload = null; let value; switch (type) { case 'u': value = Tools.readUnsigned(data); break; case 'f': value = Tools.readFloat(data); break; case 'i': value = Tools.readSigned(data); break; case 's': value = String.fromCharCode(...data); break; case '8': value = Tools.readUtf8(data); break; case 'd': value = Tools.readDate(data); break; default: break; } if (name === 'SimpleBlock' || name === 'Block') { let p = 0; const { length, value: trak } = Tools.readVint(data, p) as any; p += length; track = trak; value = Tools.readSigned(data.subarray(p, p + 2)); p += 2; if (name === 'SimpleBlock') { keyframe = Boolean(data[length + 2] & 0x80); discardable = Boolean(data[length + 2] & 0x01); } p += 1; payload = data.subarray(p); } return { ...tagObj, data, discardable, keyframe, payload, track, value, }; } }