506 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			HTML
		
	
	
	
			
		
		
	
	
			506 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			HTML
		
	
	
	
<!DOCTYPE html>
 | 
						|
<html lang="en">
 | 
						|
<head>
 | 
						|
    <meta charset="utf-8">
 | 
						|
    <title>JSDoc: Source: utils.js</title>
 | 
						|
 | 
						|
    <script src="scripts/prettify/prettify.js"> </script>
 | 
						|
    <script src="scripts/prettify/lang-css.js"> </script>
 | 
						|
    <!--[if lt IE 9]>
 | 
						|
      <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
 | 
						|
    <![endif]-->
 | 
						|
    <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
 | 
						|
    <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
 | 
						|
</head>
 | 
						|
 | 
						|
<body>
 | 
						|
 | 
						|
<div id="main">
 | 
						|
 | 
						|
    <h1 class="page-title">Source: utils.js</h1>
 | 
						|
 | 
						|
    
 | 
						|
 | 
						|
 | 
						|
    
 | 
						|
    <section>
 | 
						|
        <article>
 | 
						|
            <pre class="prettyprint source linenums"><code>/*jshint node:true*/
 | 
						|
'use strict';
 | 
						|
 | 
						|
var exec = require('child_process').exec;
 | 
						|
var isWindows = require('os').platform().match(/win(32|64)/);
 | 
						|
var which = require('which');
 | 
						|
 | 
						|
var nlRegexp = /\r\n|\r|\n/g;
 | 
						|
var streamRegexp = /^\[?(.*?)\]?$/;
 | 
						|
var filterEscapeRegexp = /[,]/;
 | 
						|
var whichCache = {};
 | 
						|
 | 
						|
/**
 | 
						|
 * Parse progress line from ffmpeg stderr
 | 
						|
 *
 | 
						|
 * @param {String} line progress line
 | 
						|
 * @return progress object
 | 
						|
 * @private
 | 
						|
 */
 | 
						|
function parseProgressLine(line) {
 | 
						|
  var progress = {};
 | 
						|
 | 
						|
  // Remove all spaces after = and trim
 | 
						|
  line  = line.replace(/=\s+/g, '=').trim();
 | 
						|
  var progressParts = line.split(' ');
 | 
						|
 | 
						|
  // Split every progress part by "=" to get key and value
 | 
						|
  for(var i = 0; i < progressParts.length; i++) {
 | 
						|
    var progressSplit = progressParts[i].split('=', 2);
 | 
						|
    var key = progressSplit[0];
 | 
						|
    var value = progressSplit[1];
 | 
						|
 | 
						|
    // This is not a progress line
 | 
						|
    if(typeof value === 'undefined')
 | 
						|
      return null;
 | 
						|
 | 
						|
    progress[key] = value;
 | 
						|
  }
 | 
						|
 | 
						|
  return progress;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
var utils = module.exports = {
 | 
						|
  isWindows: isWindows,
 | 
						|
  streamRegexp: streamRegexp,
 | 
						|
 | 
						|
 | 
						|
  /**
 | 
						|
   * Copy an object keys into another one
 | 
						|
   *
 | 
						|
   * @param {Object} source source object
 | 
						|
   * @param {Object} dest destination object
 | 
						|
   * @private
 | 
						|
   */
 | 
						|
  copy: function(source, dest) {
 | 
						|
    Object.keys(source).forEach(function(key) {
 | 
						|
      dest[key] = source[key];
 | 
						|
    });
 | 
						|
  },
 | 
						|
 | 
						|
 | 
						|
  /**
 | 
						|
   * Create an argument list
 | 
						|
   *
 | 
						|
   * Returns a function that adds new arguments to the list.
 | 
						|
   * It also has the following methods:
 | 
						|
   * - clear() empties the argument list
 | 
						|
   * - get() returns the argument list
 | 
						|
   * - find(arg, count) finds 'arg' in the list and return the following 'count' items, or undefined if not found
 | 
						|
   * - remove(arg, count) remove 'arg' in the list as well as the following 'count' items
 | 
						|
   *
 | 
						|
   * @private
 | 
						|
   */
 | 
						|
  args: function() {
 | 
						|
    var list = [];
 | 
						|
 | 
						|
    // Append argument(s) to the list
 | 
						|
    var argfunc = function() {
 | 
						|
      if (arguments.length === 1 && Array.isArray(arguments[0])) {
 | 
						|
        list = list.concat(arguments[0]);
 | 
						|
      } else {
 | 
						|
        list = list.concat([].slice.call(arguments));
 | 
						|
      }
 | 
						|
    };
 | 
						|
 | 
						|
    // Clear argument list
 | 
						|
    argfunc.clear = function() {
 | 
						|
      list = [];
 | 
						|
    };
 | 
						|
 | 
						|
    // Return argument list
 | 
						|
    argfunc.get = function() {
 | 
						|
      return list;
 | 
						|
    };
 | 
						|
 | 
						|
    // Find argument 'arg' in list, and if found, return an array of the 'count' items that follow it
 | 
						|
    argfunc.find = function(arg, count) {
 | 
						|
      var index = list.indexOf(arg);
 | 
						|
      if (index !== -1) {
 | 
						|
        return list.slice(index + 1, index + 1 + (count || 0));
 | 
						|
      }
 | 
						|
    };
 | 
						|
 | 
						|
    // Find argument 'arg' in list, and if found, remove it as well as the 'count' items that follow it
 | 
						|
    argfunc.remove = function(arg, count) {
 | 
						|
      var index = list.indexOf(arg);
 | 
						|
      if (index !== -1) {
 | 
						|
        list.splice(index, (count || 0) + 1);
 | 
						|
      }
 | 
						|
    };
 | 
						|
 | 
						|
    // Clone argument list
 | 
						|
    argfunc.clone = function() {
 | 
						|
      var cloned = utils.args();
 | 
						|
      cloned(list);
 | 
						|
      return cloned;
 | 
						|
    };
 | 
						|
 | 
						|
    return argfunc;
 | 
						|
  },
 | 
						|
 | 
						|
 | 
						|
  /**
 | 
						|
   * Generate filter strings
 | 
						|
   *
 | 
						|
   * @param {String[]|Object[]} filters filter specifications. When using objects,
 | 
						|
   *   each must have the following properties:
 | 
						|
   * @param {String} filters.filter filter name
 | 
						|
   * @param {String|Array} [filters.inputs] (array of) input stream specifier(s) for the filter,
 | 
						|
   *   defaults to ffmpeg automatically choosing the first unused matching streams
 | 
						|
   * @param {String|Array} [filters.outputs] (array of) output stream specifier(s) for the filter,
 | 
						|
   *   defaults to ffmpeg automatically assigning the output to the output file
 | 
						|
   * @param {Object|String|Array} [filters.options] filter options, can be omitted to not set any options
 | 
						|
   * @return String[]
 | 
						|
   * @private
 | 
						|
   */
 | 
						|
  makeFilterStrings: function(filters) {
 | 
						|
    return filters.map(function(filterSpec) {
 | 
						|
      if (typeof filterSpec === 'string') {
 | 
						|
        return filterSpec;
 | 
						|
      }
 | 
						|
 | 
						|
      var filterString = '';
 | 
						|
 | 
						|
      // Filter string format is:
 | 
						|
      // [input1][input2]...filter[output1][output2]...
 | 
						|
      // The 'filter' part can optionaly have arguments:
 | 
						|
      //   filter=arg1:arg2:arg3
 | 
						|
      //   filter=arg1=v1:arg2=v2:arg3=v3
 | 
						|
 | 
						|
      // Add inputs
 | 
						|
      if (Array.isArray(filterSpec.inputs)) {
 | 
						|
        filterString += filterSpec.inputs.map(function(streamSpec) {
 | 
						|
          return streamSpec.replace(streamRegexp, '[$1]');
 | 
						|
        }).join('');
 | 
						|
      } else if (typeof filterSpec.inputs === 'string') {
 | 
						|
        filterString += filterSpec.inputs.replace(streamRegexp, '[$1]');
 | 
						|
      }
 | 
						|
 | 
						|
      // Add filter
 | 
						|
      filterString += filterSpec.filter;
 | 
						|
 | 
						|
      // Add options
 | 
						|
      if (filterSpec.options) {
 | 
						|
        if (typeof filterSpec.options === 'string' || typeof filterSpec.options === 'number') {
 | 
						|
          // Option string
 | 
						|
          filterString += '=' + filterSpec.options;
 | 
						|
        } else if (Array.isArray(filterSpec.options)) {
 | 
						|
          // Option array (unnamed options)
 | 
						|
          filterString += '=' + filterSpec.options.map(function(option) {
 | 
						|
            if (typeof option === 'string' && option.match(filterEscapeRegexp)) {
 | 
						|
              return '\'' + option + '\'';
 | 
						|
            } else {
 | 
						|
              return option;
 | 
						|
            }
 | 
						|
          }).join(':');
 | 
						|
        } else if (Object.keys(filterSpec.options).length) {
 | 
						|
          // Option object (named options)
 | 
						|
          filterString += '=' + Object.keys(filterSpec.options).map(function(option) {
 | 
						|
            var value = filterSpec.options[option];
 | 
						|
 | 
						|
            if (typeof value === 'string' && value.match(filterEscapeRegexp)) {
 | 
						|
              value = '\'' + value + '\'';
 | 
						|
            }
 | 
						|
 | 
						|
            return option + '=' + value;
 | 
						|
          }).join(':');
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      // Add outputs
 | 
						|
      if (Array.isArray(filterSpec.outputs)) {
 | 
						|
        filterString += filterSpec.outputs.map(function(streamSpec) {
 | 
						|
          return streamSpec.replace(streamRegexp, '[$1]');
 | 
						|
        }).join('');
 | 
						|
      } else if (typeof filterSpec.outputs === 'string') {
 | 
						|
        filterString += filterSpec.outputs.replace(streamRegexp, '[$1]');
 | 
						|
      }
 | 
						|
 | 
						|
      return filterString;
 | 
						|
    });
 | 
						|
  },
 | 
						|
 | 
						|
 | 
						|
  /**
 | 
						|
   * Search for an executable
 | 
						|
   *
 | 
						|
   * Uses 'which' or 'where' depending on platform
 | 
						|
   *
 | 
						|
   * @param {String} name executable name
 | 
						|
   * @param {Function} callback callback with signature (err, path)
 | 
						|
   * @private
 | 
						|
   */
 | 
						|
  which: function(name, callback) {
 | 
						|
    if (name in whichCache) {
 | 
						|
      return callback(null, whichCache[name]);
 | 
						|
    }
 | 
						|
 | 
						|
    which(name, function(err, result){
 | 
						|
      if (err) {
 | 
						|
        // Treat errors as not found
 | 
						|
        return callback(null, whichCache[name] = '');
 | 
						|
      }
 | 
						|
      callback(null, whichCache[name] = result);
 | 
						|
    });
 | 
						|
  },
 | 
						|
 | 
						|
 | 
						|
  /**
 | 
						|
   * Convert a [[hh:]mm:]ss[.xxx] timemark into seconds
 | 
						|
   *
 | 
						|
   * @param {String} timemark timemark string
 | 
						|
   * @return Number
 | 
						|
   * @private
 | 
						|
   */
 | 
						|
  timemarkToSeconds: function(timemark) {
 | 
						|
    if (typeof timemark === 'number') {
 | 
						|
      return timemark;
 | 
						|
    }
 | 
						|
 | 
						|
    if (timemark.indexOf(':') === -1 && timemark.indexOf('.') >= 0) {
 | 
						|
      return Number(timemark);
 | 
						|
    }
 | 
						|
 | 
						|
    var parts = timemark.split(':');
 | 
						|
 | 
						|
    // add seconds
 | 
						|
    var secs = Number(parts.pop());
 | 
						|
 | 
						|
    if (parts.length) {
 | 
						|
      // add minutes
 | 
						|
      secs += Number(parts.pop()) * 60;
 | 
						|
    }
 | 
						|
 | 
						|
    if (parts.length) {
 | 
						|
      // add hours
 | 
						|
      secs += Number(parts.pop()) * 3600;
 | 
						|
    }
 | 
						|
 | 
						|
    return secs;
 | 
						|
  },
 | 
						|
 | 
						|
 | 
						|
  /**
 | 
						|
   * Extract codec data from ffmpeg stderr and emit 'codecData' event if appropriate
 | 
						|
   * Call it with an initially empty codec object once with each line of stderr output until it returns true
 | 
						|
   *
 | 
						|
   * @param {FfmpegCommand} command event emitter
 | 
						|
   * @param {String} stderrLine ffmpeg stderr output line
 | 
						|
   * @param {Object} codecObject object used to accumulate codec data between calls
 | 
						|
   * @return {Boolean} true if codec data is complete (and event was emitted), false otherwise
 | 
						|
   * @private
 | 
						|
   */
 | 
						|
  extractCodecData: function(command, stderrLine, codecsObject) {
 | 
						|
    var inputPattern = /Input #[0-9]+, ([^ ]+),/;
 | 
						|
    var durPattern = /Duration\: ([^,]+)/;
 | 
						|
    var audioPattern = /Audio\: (.*)/;
 | 
						|
    var videoPattern = /Video\: (.*)/;
 | 
						|
 | 
						|
    if (!('inputStack' in codecsObject)) {
 | 
						|
      codecsObject.inputStack = [];
 | 
						|
      codecsObject.inputIndex = -1;
 | 
						|
      codecsObject.inInput = false;
 | 
						|
    }
 | 
						|
 | 
						|
    var inputStack = codecsObject.inputStack;
 | 
						|
    var inputIndex = codecsObject.inputIndex;
 | 
						|
    var inInput = codecsObject.inInput;
 | 
						|
 | 
						|
    var format, dur, audio, video;
 | 
						|
 | 
						|
    if (format = stderrLine.match(inputPattern)) {
 | 
						|
      inInput = codecsObject.inInput = true;
 | 
						|
      inputIndex = codecsObject.inputIndex = codecsObject.inputIndex + 1;
 | 
						|
 | 
						|
      inputStack[inputIndex] = { format: format[1], audio: '', video: '', duration: '' };
 | 
						|
    } else if (inInput && (dur = stderrLine.match(durPattern))) {
 | 
						|
      inputStack[inputIndex].duration = dur[1];
 | 
						|
    } else if (inInput && (audio = stderrLine.match(audioPattern))) {
 | 
						|
      audio = audio[1].split(', ');
 | 
						|
      inputStack[inputIndex].audio = audio[0];
 | 
						|
      inputStack[inputIndex].audio_details = audio;
 | 
						|
    } else if (inInput && (video = stderrLine.match(videoPattern))) {
 | 
						|
      video = video[1].split(', ');
 | 
						|
      inputStack[inputIndex].video = video[0];
 | 
						|
      inputStack[inputIndex].video_details = video;
 | 
						|
    } else if (/Output #\d+/.test(stderrLine)) {
 | 
						|
      inInput = codecsObject.inInput = false;
 | 
						|
    } else if (/Stream mapping:|Press (\[q\]|ctrl-c) to stop/.test(stderrLine)) {
 | 
						|
      command.emit.apply(command, ['codecData'].concat(inputStack));
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
 | 
						|
    return false;
 | 
						|
  },
 | 
						|
 | 
						|
 | 
						|
  /**
 | 
						|
   * Extract progress data from ffmpeg stderr and emit 'progress' event if appropriate
 | 
						|
   *
 | 
						|
   * @param {FfmpegCommand} command event emitter
 | 
						|
   * @param {String} stderrLine ffmpeg stderr data
 | 
						|
   * @param {Number} [duration=0] expected output duration in seconds
 | 
						|
   * @private
 | 
						|
   */
 | 
						|
  extractProgress: function(command, stderrLine, duration) {
 | 
						|
    var progress = parseProgressLine(stderrLine);
 | 
						|
 | 
						|
    if (progress) {
 | 
						|
      // build progress report object
 | 
						|
      var ret = {
 | 
						|
        frames: parseInt(progress.frame, 10),
 | 
						|
        currentFps: parseInt(progress.fps, 10),
 | 
						|
        currentKbps: progress.bitrate ? parseFloat(progress.bitrate.replace('kbits/s', '')) : 0,
 | 
						|
        targetSize: parseInt(progress.size, 10),
 | 
						|
        timemark: progress.time
 | 
						|
      };
 | 
						|
 | 
						|
      // calculate percent progress using duration
 | 
						|
      if (duration && duration > 0) {
 | 
						|
        ret.percent = (utils.timemarkToSeconds(ret.timemark) / duration) * 100;
 | 
						|
      }
 | 
						|
 | 
						|
      command.emit('progress', ret);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
 | 
						|
  /**
 | 
						|
   * Extract error message(s) from ffmpeg stderr
 | 
						|
   *
 | 
						|
   * @param {String} stderr ffmpeg stderr data
 | 
						|
   * @return {String}
 | 
						|
   * @private
 | 
						|
   */
 | 
						|
  extractError: function(stderr) {
 | 
						|
    // Only return the last stderr lines that don't start with a space or a square bracket
 | 
						|
    return stderr.split(nlRegexp).reduce(function(messages, message) {
 | 
						|
      if (message.charAt(0) === ' ' || message.charAt(0) === '[') {
 | 
						|
        return [];
 | 
						|
      } else {
 | 
						|
        messages.push(message);
 | 
						|
        return messages;
 | 
						|
      }
 | 
						|
    }, []).join('\n');
 | 
						|
  },
 | 
						|
 | 
						|
 | 
						|
  /**
 | 
						|
   * Creates a line ring buffer object with the following methods:
 | 
						|
   * - append(str) : appends a string or buffer
 | 
						|
   * - get() : returns the whole string
 | 
						|
   * - close() : prevents further append() calls and does a last call to callbacks
 | 
						|
   * - callback(cb) : calls cb for each line (incl. those already in the ring)
 | 
						|
   *
 | 
						|
   * @param {Numebr} maxLines maximum number of lines to store (<= 0 for unlimited)
 | 
						|
   */
 | 
						|
  linesRing: function(maxLines) {
 | 
						|
    var cbs = [];
 | 
						|
    var lines = [];
 | 
						|
    var current = null;
 | 
						|
    var closed = false
 | 
						|
    var max = maxLines - 1;
 | 
						|
 | 
						|
    function emit(line) {
 | 
						|
      cbs.forEach(function(cb) { cb(line); });
 | 
						|
    }
 | 
						|
 | 
						|
    return {
 | 
						|
      callback: function(cb) {
 | 
						|
        lines.forEach(function(l) { cb(l); });
 | 
						|
        cbs.push(cb);
 | 
						|
      },
 | 
						|
 | 
						|
      append: function(str) {
 | 
						|
        if (closed) return;
 | 
						|
        if (str instanceof Buffer) str = '' + str;
 | 
						|
        if (!str || str.length === 0) return;
 | 
						|
 | 
						|
        var newLines = str.split(nlRegexp);
 | 
						|
 | 
						|
        if (newLines.length === 1) {
 | 
						|
          if (current !== null) {
 | 
						|
            current = current + newLines.shift();
 | 
						|
          } else {
 | 
						|
            current = newLines.shift();
 | 
						|
          }
 | 
						|
        } else {
 | 
						|
          if (current !== null) {
 | 
						|
            current = current + newLines.shift();
 | 
						|
            emit(current);
 | 
						|
            lines.push(current);
 | 
						|
          }
 | 
						|
 | 
						|
          current = newLines.pop();
 | 
						|
 | 
						|
          newLines.forEach(function(l) {
 | 
						|
            emit(l);
 | 
						|
            lines.push(l);
 | 
						|
          });
 | 
						|
 | 
						|
          if (max > -1 && lines.length > max) {
 | 
						|
            lines.splice(0, lines.length - max);
 | 
						|
          }
 | 
						|
        }
 | 
						|
      },
 | 
						|
 | 
						|
      get: function() {
 | 
						|
        if (current !== null) {
 | 
						|
          return lines.concat([current]).join('\n');
 | 
						|
        } else {
 | 
						|
          return lines.join('\n');
 | 
						|
        }
 | 
						|
      },
 | 
						|
 | 
						|
      close: function() {
 | 
						|
        if (closed) return;
 | 
						|
 | 
						|
        if (current !== null) {
 | 
						|
          emit(current);
 | 
						|
          lines.push(current);
 | 
						|
 | 
						|
          if (max > -1 && lines.length > max) {
 | 
						|
            lines.shift();
 | 
						|
          }
 | 
						|
 | 
						|
          current = null;
 | 
						|
        }
 | 
						|
 | 
						|
        closed = true;
 | 
						|
      }
 | 
						|
    };
 | 
						|
  }
 | 
						|
};
 | 
						|
</code></pre>
 | 
						|
        </article>
 | 
						|
    </section>
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
</div>
 | 
						|
 | 
						|
<nav>
 | 
						|
    <h2><a href="index.html">Index</a></h2><ul><li><a href="index.html#installation">Installation</a></li><ul></ul><li><a href="index.html#usage">Usage</a></li><ul><li><a href="index.html#prerequisites">Prerequisites</a></li><li><a href="index.html#creating-an-ffmpeg-command">Creating an FFmpeg command</a></li><li><a href="index.html#specifying-inputs">Specifying inputs</a></li><li><a href="index.html#input-options">Input options</a></li><li><a href="index.html#audio-options">Audio options</a></li><li><a href="index.html#video-options">Video options</a></li><li><a href="index.html#video-frame-size-options">Video frame size options</a></li><li><a href="index.html#specifying-multiple-outputs">Specifying multiple outputs</a></li><li><a href="index.html#output-options">Output options</a></li><li><a href="index.html#miscellaneous-options">Miscellaneous options</a></li><li><a href="index.html#setting-event-handlers">Setting event handlers</a></li><li><a href="index.html#starting-ffmpeg-processing">Starting FFmpeg processing</a></li><li><a href="index.html#controlling-the-ffmpeg-process">Controlling the FFmpeg process</a></li><li><a href="index.html#reading-video-metadata">Reading video metadata</a></li><li><a href="index.html#querying-ffmpeg-capabilities">Querying ffmpeg capabilities</a></li><li><a href="index.html#cloning-an-ffmpegcommand">Cloning an FfmpegCommand</a></li></ul><li><a href="index.html#contributing">Contributing</a></li><ul><li><a href="index.html#code-contributions">Code contributions</a></li><li><a href="index.html#documentation-contributions">Documentation contributions</a></li><li><a href="index.html#updating-the-documentation">Updating the documentation</a></li><li><a href="index.html#running-tests">Running tests</a></li></ul><li><a href="index.html#main-contributors">Main contributors</a></li><ul></ul><li><a href="index.html#license">License</a></li><ul></ul></ul><h3>Classes</h3><ul><li><a href="FfmpegCommand.html">FfmpegCommand</a></li><ul><li> <a href="FfmpegCommand.html#audio-methods">Audio methods</a></li><li> <a href="FfmpegCommand.html#capabilities-methods">Capabilities methods</a></li><li> <a href="FfmpegCommand.html#custom-options-methods">Custom options methods</a></li><li> <a href="FfmpegCommand.html#input-methods">Input methods</a></li><li> <a href="FfmpegCommand.html#metadata-methods">Metadata methods</a></li><li> <a href="FfmpegCommand.html#miscellaneous-methods">Miscellaneous methods</a></li><li> <a href="FfmpegCommand.html#other-methods">Other methods</a></li><li> <a href="FfmpegCommand.html#output-methods">Output methods</a></li><li> <a href="FfmpegCommand.html#processing-methods">Processing methods</a></li><li> <a href="FfmpegCommand.html#video-methods">Video methods</a></li><li> <a href="FfmpegCommand.html#video-size-methods">Video size methods</a></li></ul></ul>
 | 
						|
</nav>
 | 
						|
 | 
						|
<br clear="both">
 | 
						|
 | 
						|
<footer>
 | 
						|
    Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.0</a> on Sun May 01 2016 12:10:37 GMT+0200 (CEST)
 | 
						|
</footer>
 | 
						|
 | 
						|
<script> prettyPrint(); </script>
 | 
						|
<script src="scripts/linenumber.js"> </script>
 | 
						|
</body>
 | 
						|
</html>
 |