buddy

node MVC discord bot
Log | Files | Refs | README

WebmBase.js (6617B)


      1 const { Transform } = require('stream');
      2 
      3 /**
      4  * Base class for WebmOpusDemuxer and WebmVorbisDemuxer.
      5  * **You shouldn't directly instantiate this class, use the opus.WebmDemuxer and vorbis.WebmDemuxer
      6  * implementations instead!**
      7  * @memberof core
      8  * @protected
      9  * @extends TransformStream
     10  */
     11 class WebmBaseDemuxer extends Transform {
     12   /**
     13    * Creates a new Webm demuxer.
     14    * @private
     15    * @memberof core
     16    * @param {Object} [options] options that you would pass to a regular Transform stream.
     17    */
     18   constructor(options = {}) {
     19     super(Object.assign({ readableObjectMode: true }, options));
     20     this._remainder = null;
     21     this._length = 0;
     22     this._count = 0;
     23     this._skipUntil = null;
     24     this._track = null;
     25     this._incompleteTrack = {};
     26     this._ebmlFound = false;
     27   }
     28 
     29   _transform(chunk, encoding, done) {
     30     this._length += chunk.length;
     31     if (this._remainder) {
     32       chunk = Buffer.concat([this._remainder, chunk]);
     33       this._remainder = null;
     34     }
     35     let offset = 0;
     36     if (this._skipUntil && this._length > this._skipUntil) {
     37       offset = this._skipUntil - this._count;
     38       this._skipUntil = null;
     39     } else if (this._skipUntil) {
     40       this._count += chunk.length;
     41       return done();
     42     }
     43     let result;
     44     while (result !== TOO_SHORT) {
     45       result = this._readTag(chunk, offset);
     46       if (result === TOO_SHORT) break;
     47       if (result._skipUntil) {
     48         this._skipUntil = result._skipUntil;
     49         break;
     50       }
     51       if (result.offset) offset = result.offset;
     52       else break;
     53     }
     54     this._count += offset;
     55     this._remainder = chunk.slice(offset);
     56     return done();
     57   }
     58 
     59   /**
     60    * Reads an EBML ID from a buffer.
     61    * @private
     62    * @param {Buffer} chunk the buffer to read from.
     63    * @param {number} offset the offset in the buffer.
     64    * @returns {Object|Symbol} contains an `id` property (buffer) and the new `offset` (number).
     65    * Returns the TOO_SHORT symbol if the data wasn't big enough to facilitate the request.
     66    */
     67   _readEBMLId(chunk, offset) {
     68     const idLength = vintLength(chunk, offset);
     69     if (idLength === TOO_SHORT) return TOO_SHORT;
     70     return {
     71       id: chunk.slice(offset, offset + idLength),
     72       offset: offset + idLength,
     73     };
     74   }
     75 
     76   /**
     77    * Reads a size variable-integer to calculate the length of the data of a tag.
     78    * @private
     79    * @param {Buffer} chunk the buffer to read from.
     80    * @param {number} offset the offset in the buffer.
     81    * @returns {Object|Symbol} contains property `offset` (number), `dataLength` (number) and `sizeLength` (number).
     82    * Returns the TOO_SHORT symbol if the data wasn't big enough to facilitate the request.
     83    */
     84   _readTagDataSize(chunk, offset) {
     85     const sizeLength = vintLength(chunk, offset);
     86     if (sizeLength === TOO_SHORT) return TOO_SHORT;
     87     const dataLength = expandVint(chunk, offset, offset + sizeLength);
     88     return { offset: offset + sizeLength, dataLength, sizeLength };
     89   }
     90 
     91   /**
     92    * Takes a buffer and attempts to read and process a tag.
     93    * @private
     94    * @param {Buffer} chunk the buffer to read from.
     95    * @param {number} offset the offset in the buffer.
     96    * @returns {Object|Symbol} contains the new `offset` (number) and optionally the `_skipUntil` property,
     97    * indicating that the stream should ignore any data until a certain length is reached.
     98    * Returns the TOO_SHORT symbol if the data wasn't big enough to facilitate the request.
     99    */
    100   _readTag(chunk, offset) {
    101     const idData = this._readEBMLId(chunk, offset);
    102     if (idData === TOO_SHORT) return TOO_SHORT;
    103     const ebmlID = idData.id.toString('hex');
    104     if (!this._ebmlFound) {
    105       if (ebmlID === '1a45dfa3') this._ebmlFound = true;
    106       else throw Error('Did not find the EBML tag at the start of the stream');
    107     }
    108     offset = idData.offset;
    109     const sizeData = this._readTagDataSize(chunk, offset);
    110     if (sizeData === TOO_SHORT) return TOO_SHORT;
    111     const { dataLength } = sizeData;
    112     offset = sizeData.offset;
    113     // If this tag isn't useful, tell the stream to stop processing data until the tag ends
    114     if (typeof TAGS[ebmlID] === 'undefined') {
    115       if (chunk.length > offset + dataLength) {
    116         return { offset: offset + dataLength };
    117       }
    118       return { offset, _skipUntil: this._count + offset + dataLength };
    119     }
    120 
    121     const tagHasChildren = TAGS[ebmlID];
    122     if (tagHasChildren) {
    123       return { offset };
    124     }
    125 
    126     if (offset + dataLength > chunk.length) return TOO_SHORT;
    127     const data = chunk.slice(offset, offset + dataLength);
    128     if (!this._track) {
    129       if (ebmlID === 'ae') this._incompleteTrack = {};
    130       if (ebmlID === 'd7') this._incompleteTrack.number = data[0];
    131       if (ebmlID === '83') this._incompleteTrack.type = data[0];
    132       if (this._incompleteTrack.type === 2 && typeof this._incompleteTrack.number !== 'undefined') {
    133         this._track = this._incompleteTrack;
    134       }
    135     }
    136     if (ebmlID === '63a2') {
    137       this._checkHead(data);
    138     } else if (ebmlID === 'a3') {
    139       if (!this._track) throw Error('No audio track in this webm!');
    140       if ((data[0] & 0xF) === this._track.number) {
    141         this.push(data.slice(4));
    142       }
    143     }
    144     return { offset: offset + dataLength };
    145   }
    146 }
    147 
    148 /**
    149  * A symbol that is returned by some functions that indicates the buffer it has been provided is not large enough
    150  * to facilitate a request.
    151  * @name WebmBaseDemuxer#TOO_SHORT
    152  * @memberof core
    153  * @private
    154  * @type {Symbol}
    155  */
    156 const TOO_SHORT = WebmBaseDemuxer.TOO_SHORT = Symbol('TOO_SHORT');
    157 
    158 /**
    159  * A map that takes a value of an EBML ID in hex string form, with the value being a boolean that indicates whether
    160  * this tag has children.
    161  * @name WebmBaseDemuxer#TAGS
    162  * @memberof core
    163  * @private
    164  * @type {Object}
    165  */
    166 const TAGS = WebmBaseDemuxer.TAGS = { // value is true if the element has children
    167   '1a45dfa3': true, // EBML
    168   '18538067': true, // Segment
    169   '1f43b675': true, // Cluster
    170   '1654ae6b': true, // Tracks
    171   'ae': true, // TrackEntry
    172   'd7': false, // TrackNumber
    173   '83': false, // TrackType
    174   'a3': false, // SimpleBlock
    175   '63a2': false,
    176 };
    177 
    178 module.exports = WebmBaseDemuxer;
    179 
    180 function vintLength(buffer, index) {
    181   let i = 0;
    182   for (; i < 8; i++) if ((1 << (7 - i)) & buffer[index]) break;
    183   i++;
    184   if (index + i > buffer.length) {
    185     return TOO_SHORT;
    186   }
    187   return i;
    188 }
    189 
    190 function expandVint(buffer, start, end) {
    191   const length = vintLength(buffer, start);
    192   if (end > buffer.length || length === TOO_SHORT) return TOO_SHORT;
    193   let mask = (1 << (8 - length)) - 1;
    194   let value = buffer[start] & mask;
    195   for (let i = start + 1; i < end; i++) {
    196     value = (value << 8) + buffer[i];
    197   }
    198   return value;
    199 }