summaryrefslogtreecommitdiff
path: root/node_modules/prism-media/src/core
diff options
context:
space:
mode:
author8cy <[email protected]>2020-04-30 15:46:16 -0700
committer8cy <[email protected]>2020-04-30 15:46:16 -0700
commit3a4deac89054021b56ad5bd8005b2044cc085c98 (patch)
tree3dd6af8503e497e46180b6b5231674f36bdce9f2 /node_modules/prism-media/src/core
downloaduppity-3a4deac89054021b56ad5bd8005b2044cc085c98.tar.xz
uppity-3a4deac89054021b56ad5bd8005b2044cc085c98.zip
Up, up, uppity.
Diffstat (limited to 'node_modules/prism-media/src/core')
-rw-r--r--node_modules/prism-media/src/core/FFmpeg.js147
-rw-r--r--node_modules/prism-media/src/core/VolumeTransformer.js129
-rw-r--r--node_modules/prism-media/src/core/WebmBase.js199
-rw-r--r--node_modules/prism-media/src/core/index.js9
4 files changed, 484 insertions, 0 deletions
diff --git a/node_modules/prism-media/src/core/FFmpeg.js b/node_modules/prism-media/src/core/FFmpeg.js
new file mode 100644
index 0000000..fa156cc
--- /dev/null
+++ b/node_modules/prism-media/src/core/FFmpeg.js
@@ -0,0 +1,147 @@
+const ChildProcess = require('child_process');
+const { Duplex } = require('stream');
+
+let FFMPEG = {
+ command: null,
+ output: null,
+};
+
+const VERSION_REGEX = /version (.+) Copyright/mi;
+
+Object.defineProperty(FFMPEG, 'version', {
+ get() {
+ return VERSION_REGEX.exec(FFMPEG.output)[1];
+ },
+ enumerable: true,
+});
+
+/**
+ * An FFmpeg transform stream that provides an interface to FFmpeg.
+ * @memberof core
+ */
+class FFmpeg extends Duplex {
+ /**
+ * Creates a new FFmpeg transform stream
+ * @memberof core
+ * @param {Object} options Options you would pass to a regular Transform stream, plus an `args` option
+ * @param {Array<string>} options.args Arguments to pass to FFmpeg
+ * @example
+ * // By default, if you don't specify an input (`-i ...`) prism will assume you're piping a stream into it.
+ * const transcoder = new prism.FFmpeg({
+ * args: [
+ * '-analyzeduration', '0',
+ * '-loglevel', '0',
+ * '-f', 's16le',
+ * '-ar', '48000',
+ * '-ac', '2',
+ * ]
+ * });
+ * const s16le = mp3File.pipe(transcoder);
+ * const opus = s16le.pipe(new prism.opus.Encoder({ rate: 48000, channels: 2, frameSize: 960 }));
+ */
+ constructor(options = {}) {
+ super();
+ this.process = FFmpeg.create(options);
+ const EVENTS = {
+ readable: this._reader,
+ data: this._reader,
+ end: this._reader,
+ unpipe: this._reader,
+ finish: this._writer,
+ drain: this._writer,
+ };
+
+ this._readableState = this._reader._readableState;
+ this._writableState = this._writer._writableState;
+
+ this._copy(['write', 'end'], this._writer);
+ this._copy(['read', 'setEncoding', 'pipe', 'unpipe'], this._reader);
+
+ for (const method of ['on', 'once', 'removeListener', 'removeListeners', 'listeners']) {
+ this[method] = (ev, fn) => EVENTS[ev] ? EVENTS[ev][method](ev, fn) : Duplex.prototype[method].call(this, ev, fn);
+ }
+
+ const processError = error => this.emit('error', error);
+ this._reader.on('error', processError);
+ this._writer.on('error', processError);
+ }
+
+ get _reader() { return this.process.stdout; }
+ get _writer() { return this.process.stdin; }
+
+ _copy(methods, target) {
+ for (const method of methods) {
+ this[method] = target[method].bind(target);
+ }
+ }
+
+ _destroy(err, cb) {
+ super._destroy(err, cb);
+ this.once('error', () => {});
+ this.process.kill('SIGKILL');
+ }
+
+
+ /**
+ * The available FFmpeg information
+ * @typedef {Object} FFmpegInfo
+ * @memberof core
+ * @property {string} command The command used to launch FFmpeg
+ * @property {string} output The output from running `ffmpeg -h`
+ * @property {string} version The version of FFmpeg being used, determined from `output`.
+ */
+
+ /**
+ * Finds a suitable FFmpeg command and obtains the debug information from it.
+ * @param {boolean} [force=false] If true, will ignore any cached results and search for the command again
+ * @returns {FFmpegInfo}
+ * @throws Will throw an error if FFmpeg cannot be found.
+ * @example
+ * const ffmpeg = prism.FFmpeg.getInfo();
+ *
+ * console.log(`Using FFmpeg version ${ffmpeg.version}`);
+ *
+ * if (ffmpeg.output.includes('--enable-libopus')) {
+ * console.log('libopus is available!');
+ * } else {
+ * console.log('libopus is unavailable!');
+ * }
+ */
+ static getInfo(force = false) {
+ if (FFMPEG.command && !force) return FFMPEG;
+ const sources = [() => {
+ const ffmpegStatic = require('ffmpeg-static');
+ return ffmpegStatic.path || ffmpegStatic;
+ }, 'ffmpeg', 'avconv', './ffmpeg', './avconv'];
+ for (let source of sources) {
+ try {
+ if (typeof source === 'function') source = source();
+ const result = ChildProcess.spawnSync(source, ['-h'], { windowsHide: true });
+ if (result.error) throw result.error;
+ Object.assign(FFMPEG, {
+ command: source,
+ output: Buffer.concat(result.output.filter(Boolean)).toString(),
+ });
+ return FFMPEG;
+ } catch (error) {
+ // Do nothing
+ }
+ }
+ throw new Error('FFmpeg/avconv not found!');
+ }
+
+ /**
+ * Creates a new FFmpeg instance. If you do not include `-i ...` it will be assumed that `-i -` should be prepended
+ * to the options and that you'll be piping data into the process.
+ * @param {String[]} [args=[]] Arguments to pass to FFmpeg
+ * @returns {ChildProcess}
+ * @private
+ * @throws Will throw an error if FFmpeg cannot be found.
+ */
+ static create({ args = [] } = {}) {
+ if (!args.includes('-i')) args.unshift('-i', '-');
+ return ChildProcess.spawn(FFmpeg.getInfo().command, args.concat(['pipe:1']), { windowsHide: true });
+ }
+}
+
+module.exports = FFmpeg;
diff --git a/node_modules/prism-media/src/core/VolumeTransformer.js b/node_modules/prism-media/src/core/VolumeTransformer.js
new file mode 100644
index 0000000..c0084d4
--- /dev/null
+++ b/node_modules/prism-media/src/core/VolumeTransformer.js
@@ -0,0 +1,129 @@
+// Based on discord.js' old volume system
+
+const { Transform } = require('stream');
+
+/**
+ * Transforms a stream of PCM volume.
+ * @memberof core
+ * @extends TransformStream
+ */
+class VolumeTransformer extends Transform {
+ /**
+ * @memberof core
+ * @param {Object} options Any optional TransformStream options plus some extra:
+ * @param {string} options.type The type of transformer: s16le (signed 16-bit little-endian), s16be, s32le, s32be
+ * @param {number} [options.volume=1] The output volume of the stream
+ * @example
+ * // Half the volume of a signed 16-bit little-endian PCM stream
+ * input
+ * .pipe(new prism.VolumeTransformer({ type: 's16le', volume: 0.5 }))
+ * .pipe(writeStream);
+ */
+ constructor(options = {}) {
+ super(options);
+ switch (options.type) {
+ case 's16le':
+ this._readInt = (buffer, index) => buffer.readInt16LE(index);
+ this._writeInt = (buffer, int, index) => buffer.writeInt16LE(int, index);
+ this._bits = 16;
+ break;
+ case 's16be':
+ this._readInt = (buffer, index) => buffer.readInt16BE(index);
+ this._writeInt = (buffer, int, index) => buffer.writeInt16BE(int, index);
+ this._bits = 16;
+ break;
+ case 's32le':
+ this._readInt = (buffer, index) => buffer.readInt32LE(index);
+ this._writeInt = (buffer, int, index) => buffer.writeInt32LE(int, index);
+ this._bits = 32;
+ break;
+ case 's32be':
+ this._readInt = (buffer, index) => buffer.readInt32BE(index);
+ this._writeInt = (buffer, int, index) => buffer.writeInt32BE(int, index);
+ this._bits = 32;
+ break;
+ default:
+ throw new Error('VolumeTransformer type should be one of s16le, s16be, s32le, s32be');
+ }
+ this._bytes = this._bits / 8;
+ this._extremum = Math.pow(2, this._bits - 1);
+ this.volume = typeof options.volume === 'undefined' ? 1 : options.volume;
+ this._chunk = Buffer.alloc(0);
+ }
+
+ _readInt(buffer, index) { return index; }
+ _writeInt(buffer, int, index) { return index; }
+
+ _transform(chunk, encoding, done) {
+ // If the volume is 1, act like a passthrough stream
+ if (this.volume === 1) {
+ this.push(chunk);
+ return done();
+ }
+
+ const { _bytes, _extremum } = this;
+
+ chunk = this._chunk = Buffer.concat([this._chunk, chunk]);
+ if (chunk.length < _bytes) return done();
+
+ const transformed = Buffer.allocUnsafe(chunk.length);
+ const complete = Math.floor(chunk.length / _bytes) * _bytes;
+
+ for (let i = 0; i < complete; i += _bytes) {
+ const int = Math.min(_extremum - 1, Math.max(-_extremum, Math.floor(this.volume * this._readInt(chunk, i))));
+ this._writeInt(transformed, int, i);
+ }
+
+ this._chunk = chunk.slice(complete);
+ this.push(transformed);
+ return done();
+ }
+
+ _destroy(err, cb) {
+ super._destroy(err, cb);
+ this._chunk = null;
+ }
+
+ /**
+ * Sets the volume relative to the input stream - i.e. 1 is normal, 0.5 is half, 2 is double.
+ * @param {number} volume The volume that you want to set
+ */
+ setVolume(volume) {
+ this.volume = volume;
+ }
+
+ /**
+ * Sets the volume in decibels.
+ * @param {number} db The decibels
+ */
+ setVolumeDecibels(db) {
+ this.setVolume(Math.pow(10, db / 20));
+ }
+
+ /**
+ * Sets the volume so that a perceived value of 0.5 is half the perceived volume etc.
+ * @param {number} value The value for the volume
+ */
+ setVolumeLogarithmic(value) {
+ this.setVolume(Math.pow(value, 1.660964));
+ }
+
+ /**
+ * The current volume of the stream in decibels
+ * @readonly
+ * @type {number}
+ */
+ get volumeDecibels() {
+ return Math.log10(this._volume) * 20;
+ }
+ /**
+ * The current volume of the stream from a logarithmic scale
+ * @readonly
+ * @type {number}
+ */
+ get volumeLogarithmic() {
+ return Math.pow(this._volume, 1 / 1.660964);
+ }
+}
+
+module.exports = VolumeTransformer;
diff --git a/node_modules/prism-media/src/core/WebmBase.js b/node_modules/prism-media/src/core/WebmBase.js
new file mode 100644
index 0000000..76dcdca
--- /dev/null
+++ b/node_modules/prism-media/src/core/WebmBase.js
@@ -0,0 +1,199 @@
+const { Transform } = require('stream');
+
+/**
+ * Base class for WebmOpusDemuxer and WebmVorbisDemuxer.
+ * **You shouldn't directly instantiate this class, use the opus.WebmDemuxer and vorbis.WebmDemuxer
+ * implementations instead!**
+ * @memberof core
+ * @protected
+ * @extends TransformStream
+ */
+class WebmBaseDemuxer extends Transform {
+ /**
+ * Creates a new Webm demuxer.
+ * @private
+ * @memberof core
+ * @param {Object} [options] options that you would pass to a regular Transform stream.
+ */
+ constructor(options = {}) {
+ super(Object.assign({ readableObjectMode: true }, options));
+ this._remainder = null;
+ this._length = 0;
+ this._count = 0;
+ this._skipUntil = null;
+ this._track = null;
+ this._incompleteTrack = {};
+ this._ebmlFound = false;
+ }
+
+ _transform(chunk, encoding, done) {
+ this._length += chunk.length;
+ if (this._remainder) {
+ chunk = Buffer.concat([this._remainder, chunk]);
+ this._remainder = null;
+ }
+ let offset = 0;
+ if (this._skipUntil && this._length > this._skipUntil) {
+ offset = this._skipUntil - this._count;
+ this._skipUntil = null;
+ } else if (this._skipUntil) {
+ this._count += chunk.length;
+ return done();
+ }
+ let result;
+ while (result !== TOO_SHORT) {
+ result = this._readTag(chunk, offset);
+ if (result === TOO_SHORT) break;
+ if (result._skipUntil) {
+ this._skipUntil = result._skipUntil;
+ break;
+ }
+ if (result.offset) offset = result.offset;
+ else break;
+ }
+ this._count += offset;
+ this._remainder = chunk.slice(offset);
+ return done();
+ }
+
+ /**
+ * Reads an EBML ID from a buffer.
+ * @private
+ * @param {Buffer} chunk the buffer to read from.
+ * @param {number} offset the offset in the buffer.
+ * @returns {Object|Symbol} contains an `id` property (buffer) and the new `offset` (number).
+ * Returns the TOO_SHORT symbol if the data wasn't big enough to facilitate the request.
+ */
+ _readEBMLId(chunk, offset) {
+ const idLength = vintLength(chunk, offset);
+ if (idLength === TOO_SHORT) return TOO_SHORT;
+ return {
+ id: chunk.slice(offset, offset + idLength),
+ offset: offset + idLength,
+ };
+ }
+
+ /**
+ * Reads a size variable-integer to calculate the length of the data of a tag.
+ * @private
+ * @param {Buffer} chunk the buffer to read from.
+ * @param {number} offset the offset in the buffer.
+ * @returns {Object|Symbol} contains property `offset` (number), `dataLength` (number) and `sizeLength` (number).
+ * Returns the TOO_SHORT symbol if the data wasn't big enough to facilitate the request.
+ */
+ _readTagDataSize(chunk, offset) {
+ const sizeLength = vintLength(chunk, offset);
+ if (sizeLength === TOO_SHORT) return TOO_SHORT;
+ const dataLength = expandVint(chunk, offset, offset + sizeLength);
+ return { offset: offset + sizeLength, dataLength, sizeLength };
+ }
+
+ /**
+ * Takes a buffer and attempts to read and process a tag.
+ * @private
+ * @param {Buffer} chunk the buffer to read from.
+ * @param {number} offset the offset in the buffer.
+ * @returns {Object|Symbol} contains the new `offset` (number) and optionally the `_skipUntil` property,
+ * indicating that the stream should ignore any data until a certain length is reached.
+ * Returns the TOO_SHORT symbol if the data wasn't big enough to facilitate the request.
+ */
+ _readTag(chunk, offset) {
+ const idData = this._readEBMLId(chunk, offset);
+ if (idData === TOO_SHORT) return TOO_SHORT;
+ const ebmlID = idData.id.toString('hex');
+ if (!this._ebmlFound) {
+ if (ebmlID === '1a45dfa3') this._ebmlFound = true;
+ else throw Error('Did not find the EBML tag at the start of the stream');
+ }
+ offset = idData.offset;
+ const sizeData = this._readTagDataSize(chunk, offset);
+ if (sizeData === TOO_SHORT) return TOO_SHORT;
+ const { dataLength } = sizeData;
+ offset = sizeData.offset;
+ // If this tag isn't useful, tell the stream to stop processing data until the tag ends
+ if (typeof TAGS[ebmlID] === 'undefined') {
+ if (chunk.length > offset + dataLength) {
+ return { offset: offset + dataLength };
+ }
+ return { offset, _skipUntil: this._count + offset + dataLength };
+ }
+
+ const tagHasChildren = TAGS[ebmlID];
+ if (tagHasChildren) {
+ return { offset };
+ }
+
+ if (offset + dataLength > chunk.length) return TOO_SHORT;
+ const data = chunk.slice(offset, offset + dataLength);
+ if (!this._track) {
+ if (ebmlID === 'ae') this._incompleteTrack = {};
+ if (ebmlID === 'd7') this._incompleteTrack.number = data[0];
+ if (ebmlID === '83') this._incompleteTrack.type = data[0];
+ if (this._incompleteTrack.type === 2 && typeof this._incompleteTrack.number !== 'undefined') {
+ this._track = this._incompleteTrack;
+ }
+ }
+ if (ebmlID === '63a2') {
+ this._checkHead(data);
+ } else if (ebmlID === 'a3') {
+ if (!this._track) throw Error('No audio track in this webm!');
+ if ((data[0] & 0xF) === this._track.number) {
+ this.push(data.slice(4));
+ }
+ }
+ return { offset: offset + dataLength };
+ }
+}
+
+/**
+ * A symbol that is returned by some functions that indicates the buffer it has been provided is not large enough
+ * to facilitate a request.
+ * @name WebmBaseDemuxer#TOO_SHORT
+ * @memberof core
+ * @private
+ * @type {Symbol}
+ */
+const TOO_SHORT = WebmBaseDemuxer.TOO_SHORT = Symbol('TOO_SHORT');
+
+/**
+ * A map that takes a value of an EBML ID in hex string form, with the value being a boolean that indicates whether
+ * this tag has children.
+ * @name WebmBaseDemuxer#TAGS
+ * @memberof core
+ * @private
+ * @type {Object}
+ */
+const TAGS = WebmBaseDemuxer.TAGS = { // value is true if the element has children
+ '1a45dfa3': true, // EBML
+ '18538067': true, // Segment
+ '1f43b675': true, // Cluster
+ '1654ae6b': true, // Tracks
+ 'ae': true, // TrackEntry
+ 'd7': false, // TrackNumber
+ '83': false, // TrackType
+ 'a3': false, // SimpleBlock
+ '63a2': false,
+};
+
+module.exports = WebmBaseDemuxer;
+
+function vintLength(buffer, index) {
+ let i = 0;
+ for (; i < 8; i++) if ((1 << (7 - i)) & buffer[index]) break;
+ i++;
+ if (index + i > buffer.length) {
+ return TOO_SHORT;
+ }
+ return i;
+}
+
+function expandVint(buffer, start, end) {
+ const length = vintLength(buffer, start);
+ if (end > buffer.length || length === TOO_SHORT) return TOO_SHORT;
+ let mask = (1 << (8 - length)) - 1;
+ let value = buffer[start] & mask;
+ for (let i = start + 1; i < end; i++) {
+ value = (value << 8) + buffer[i];
+ }
+ return value;
+}
diff --git a/node_modules/prism-media/src/core/index.js b/node_modules/prism-media/src/core/index.js
new file mode 100644
index 0000000..ae9967f
--- /dev/null
+++ b/node_modules/prism-media/src/core/index.js
@@ -0,0 +1,9 @@
+/**
+ * Core features.
+ * **You shouldn't prefix imports from this namespace with `core`.**
+ * @namespace core
+ */
+module.exports = {
+ FFmpeg: require('./FFmpeg'),
+ VolumeTransformer: require('./VolumeTransformer'),
+};