summaryrefslogtreecommitdiff
path: root/node_modules/prism-media/src/core/FFmpeg.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/prism-media/src/core/FFmpeg.js')
-rw-r--r--node_modules/prism-media/src/core/FFmpeg.js147
1 files changed, 147 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;