utils.js.html 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <title>JSDoc: Source: utils.js</title>
  6. <script src="scripts/prettify/prettify.js"> </script>
  7. <script src="scripts/prettify/lang-css.js"> </script>
  8. <!--[if lt IE 9]>
  9. <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
  10. <![endif]-->
  11. <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
  12. <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
  13. </head>
  14. <body>
  15. <div id="main">
  16. <h1 class="page-title">Source: utils.js</h1>
  17. <section>
  18. <article>
  19. <pre class="prettyprint source linenums"><code>/*jshint node:true*/
  20. 'use strict';
  21. var exec = require('child_process').exec;
  22. var isWindows = require('os').platform().match(/win(32|64)/);
  23. var which = require('which');
  24. var nlRegexp = /\r\n|\r|\n/g;
  25. var streamRegexp = /^\[?(.*?)\]?$/;
  26. var filterEscapeRegexp = /[,]/;
  27. var whichCache = {};
  28. /**
  29. * Parse progress line from ffmpeg stderr
  30. *
  31. * @param {String} line progress line
  32. * @return progress object
  33. * @private
  34. */
  35. function parseProgressLine(line) {
  36. var progress = {};
  37. // Remove all spaces after = and trim
  38. line = line.replace(/=\s+/g, '=').trim();
  39. var progressParts = line.split(' ');
  40. // Split every progress part by "=" to get key and value
  41. for(var i = 0; i &lt; progressParts.length; i++) {
  42. var progressSplit = progressParts[i].split('=', 2);
  43. var key = progressSplit[0];
  44. var value = progressSplit[1];
  45. // This is not a progress line
  46. if(typeof value === 'undefined')
  47. return null;
  48. progress[key] = value;
  49. }
  50. return progress;
  51. }
  52. var utils = module.exports = {
  53. isWindows: isWindows,
  54. streamRegexp: streamRegexp,
  55. /**
  56. * Copy an object keys into another one
  57. *
  58. * @param {Object} source source object
  59. * @param {Object} dest destination object
  60. * @private
  61. */
  62. copy: function(source, dest) {
  63. Object.keys(source).forEach(function(key) {
  64. dest[key] = source[key];
  65. });
  66. },
  67. /**
  68. * Create an argument list
  69. *
  70. * Returns a function that adds new arguments to the list.
  71. * It also has the following methods:
  72. * - clear() empties the argument list
  73. * - get() returns the argument list
  74. * - find(arg, count) finds 'arg' in the list and return the following 'count' items, or undefined if not found
  75. * - remove(arg, count) remove 'arg' in the list as well as the following 'count' items
  76. *
  77. * @private
  78. */
  79. args: function() {
  80. var list = [];
  81. // Append argument(s) to the list
  82. var argfunc = function() {
  83. if (arguments.length === 1 &amp;&amp; Array.isArray(arguments[0])) {
  84. list = list.concat(arguments[0]);
  85. } else {
  86. list = list.concat([].slice.call(arguments));
  87. }
  88. };
  89. // Clear argument list
  90. argfunc.clear = function() {
  91. list = [];
  92. };
  93. // Return argument list
  94. argfunc.get = function() {
  95. return list;
  96. };
  97. // Find argument 'arg' in list, and if found, return an array of the 'count' items that follow it
  98. argfunc.find = function(arg, count) {
  99. var index = list.indexOf(arg);
  100. if (index !== -1) {
  101. return list.slice(index + 1, index + 1 + (count || 0));
  102. }
  103. };
  104. // Find argument 'arg' in list, and if found, remove it as well as the 'count' items that follow it
  105. argfunc.remove = function(arg, count) {
  106. var index = list.indexOf(arg);
  107. if (index !== -1) {
  108. list.splice(index, (count || 0) + 1);
  109. }
  110. };
  111. // Clone argument list
  112. argfunc.clone = function() {
  113. var cloned = utils.args();
  114. cloned(list);
  115. return cloned;
  116. };
  117. return argfunc;
  118. },
  119. /**
  120. * Generate filter strings
  121. *
  122. * @param {String[]|Object[]} filters filter specifications. When using objects,
  123. * each must have the following properties:
  124. * @param {String} filters.filter filter name
  125. * @param {String|Array} [filters.inputs] (array of) input stream specifier(s) for the filter,
  126. * defaults to ffmpeg automatically choosing the first unused matching streams
  127. * @param {String|Array} [filters.outputs] (array of) output stream specifier(s) for the filter,
  128. * defaults to ffmpeg automatically assigning the output to the output file
  129. * @param {Object|String|Array} [filters.options] filter options, can be omitted to not set any options
  130. * @return String[]
  131. * @private
  132. */
  133. makeFilterStrings: function(filters) {
  134. return filters.map(function(filterSpec) {
  135. if (typeof filterSpec === 'string') {
  136. return filterSpec;
  137. }
  138. var filterString = '';
  139. // Filter string format is:
  140. // [input1][input2]...filter[output1][output2]...
  141. // The 'filter' part can optionaly have arguments:
  142. // filter=arg1:arg2:arg3
  143. // filter=arg1=v1:arg2=v2:arg3=v3
  144. // Add inputs
  145. if (Array.isArray(filterSpec.inputs)) {
  146. filterString += filterSpec.inputs.map(function(streamSpec) {
  147. return streamSpec.replace(streamRegexp, '[$1]');
  148. }).join('');
  149. } else if (typeof filterSpec.inputs === 'string') {
  150. filterString += filterSpec.inputs.replace(streamRegexp, '[$1]');
  151. }
  152. // Add filter
  153. filterString += filterSpec.filter;
  154. // Add options
  155. if (filterSpec.options) {
  156. if (typeof filterSpec.options === 'string' || typeof filterSpec.options === 'number') {
  157. // Option string
  158. filterString += '=' + filterSpec.options;
  159. } else if (Array.isArray(filterSpec.options)) {
  160. // Option array (unnamed options)
  161. filterString += '=' + filterSpec.options.map(function(option) {
  162. if (typeof option === 'string' &amp;&amp; option.match(filterEscapeRegexp)) {
  163. return '\'' + option + '\'';
  164. } else {
  165. return option;
  166. }
  167. }).join(':');
  168. } else if (Object.keys(filterSpec.options).length) {
  169. // Option object (named options)
  170. filterString += '=' + Object.keys(filterSpec.options).map(function(option) {
  171. var value = filterSpec.options[option];
  172. if (typeof value === 'string' &amp;&amp; value.match(filterEscapeRegexp)) {
  173. value = '\'' + value + '\'';
  174. }
  175. return option + '=' + value;
  176. }).join(':');
  177. }
  178. }
  179. // Add outputs
  180. if (Array.isArray(filterSpec.outputs)) {
  181. filterString += filterSpec.outputs.map(function(streamSpec) {
  182. return streamSpec.replace(streamRegexp, '[$1]');
  183. }).join('');
  184. } else if (typeof filterSpec.outputs === 'string') {
  185. filterString += filterSpec.outputs.replace(streamRegexp, '[$1]');
  186. }
  187. return filterString;
  188. });
  189. },
  190. /**
  191. * Search for an executable
  192. *
  193. * Uses 'which' or 'where' depending on platform
  194. *
  195. * @param {String} name executable name
  196. * @param {Function} callback callback with signature (err, path)
  197. * @private
  198. */
  199. which: function(name, callback) {
  200. if (name in whichCache) {
  201. return callback(null, whichCache[name]);
  202. }
  203. which(name, function(err, result){
  204. if (err) {
  205. // Treat errors as not found
  206. return callback(null, whichCache[name] = '');
  207. }
  208. callback(null, whichCache[name] = result);
  209. });
  210. },
  211. /**
  212. * Convert a [[hh:]mm:]ss[.xxx] timemark into seconds
  213. *
  214. * @param {String} timemark timemark string
  215. * @return Number
  216. * @private
  217. */
  218. timemarkToSeconds: function(timemark) {
  219. if (typeof timemark === 'number') {
  220. return timemark;
  221. }
  222. if (timemark.indexOf(':') === -1 &amp;&amp; timemark.indexOf('.') >= 0) {
  223. return Number(timemark);
  224. }
  225. var parts = timemark.split(':');
  226. // add seconds
  227. var secs = Number(parts.pop());
  228. if (parts.length) {
  229. // add minutes
  230. secs += Number(parts.pop()) * 60;
  231. }
  232. if (parts.length) {
  233. // add hours
  234. secs += Number(parts.pop()) * 3600;
  235. }
  236. return secs;
  237. },
  238. /**
  239. * Extract codec data from ffmpeg stderr and emit 'codecData' event if appropriate
  240. * Call it with an initially empty codec object once with each line of stderr output until it returns true
  241. *
  242. * @param {FfmpegCommand} command event emitter
  243. * @param {String} stderrLine ffmpeg stderr output line
  244. * @param {Object} codecObject object used to accumulate codec data between calls
  245. * @return {Boolean} true if codec data is complete (and event was emitted), false otherwise
  246. * @private
  247. */
  248. extractCodecData: function(command, stderrLine, codecsObject) {
  249. var inputPattern = /Input #[0-9]+, ([^ ]+),/;
  250. var durPattern = /Duration\: ([^,]+)/;
  251. var audioPattern = /Audio\: (.*)/;
  252. var videoPattern = /Video\: (.*)/;
  253. if (!('inputStack' in codecsObject)) {
  254. codecsObject.inputStack = [];
  255. codecsObject.inputIndex = -1;
  256. codecsObject.inInput = false;
  257. }
  258. var inputStack = codecsObject.inputStack;
  259. var inputIndex = codecsObject.inputIndex;
  260. var inInput = codecsObject.inInput;
  261. var format, dur, audio, video;
  262. if (format = stderrLine.match(inputPattern)) {
  263. inInput = codecsObject.inInput = true;
  264. inputIndex = codecsObject.inputIndex = codecsObject.inputIndex + 1;
  265. inputStack[inputIndex] = { format: format[1], audio: '', video: '', duration: '' };
  266. } else if (inInput &amp;&amp; (dur = stderrLine.match(durPattern))) {
  267. inputStack[inputIndex].duration = dur[1];
  268. } else if (inInput &amp;&amp; (audio = stderrLine.match(audioPattern))) {
  269. audio = audio[1].split(', ');
  270. inputStack[inputIndex].audio = audio[0];
  271. inputStack[inputIndex].audio_details = audio;
  272. } else if (inInput &amp;&amp; (video = stderrLine.match(videoPattern))) {
  273. video = video[1].split(', ');
  274. inputStack[inputIndex].video = video[0];
  275. inputStack[inputIndex].video_details = video;
  276. } else if (/Output #\d+/.test(stderrLine)) {
  277. inInput = codecsObject.inInput = false;
  278. } else if (/Stream mapping:|Press (\[q\]|ctrl-c) to stop/.test(stderrLine)) {
  279. command.emit.apply(command, ['codecData'].concat(inputStack));
  280. return true;
  281. }
  282. return false;
  283. },
  284. /**
  285. * Extract progress data from ffmpeg stderr and emit 'progress' event if appropriate
  286. *
  287. * @param {FfmpegCommand} command event emitter
  288. * @param {String} stderrLine ffmpeg stderr data
  289. * @param {Number} [duration=0] expected output duration in seconds
  290. * @private
  291. */
  292. extractProgress: function(command, stderrLine, duration) {
  293. var progress = parseProgressLine(stderrLine);
  294. if (progress) {
  295. // build progress report object
  296. var ret = {
  297. frames: parseInt(progress.frame, 10),
  298. currentFps: parseInt(progress.fps, 10),
  299. currentKbps: progress.bitrate ? parseFloat(progress.bitrate.replace('kbits/s', '')) : 0,
  300. targetSize: parseInt(progress.size, 10),
  301. timemark: progress.time
  302. };
  303. // calculate percent progress using duration
  304. if (duration &amp;&amp; duration > 0) {
  305. ret.percent = (utils.timemarkToSeconds(ret.timemark) / duration) * 100;
  306. }
  307. command.emit('progress', ret);
  308. }
  309. },
  310. /**
  311. * Extract error message(s) from ffmpeg stderr
  312. *
  313. * @param {String} stderr ffmpeg stderr data
  314. * @return {String}
  315. * @private
  316. */
  317. extractError: function(stderr) {
  318. // Only return the last stderr lines that don't start with a space or a square bracket
  319. return stderr.split(nlRegexp).reduce(function(messages, message) {
  320. if (message.charAt(0) === ' ' || message.charAt(0) === '[') {
  321. return [];
  322. } else {
  323. messages.push(message);
  324. return messages;
  325. }
  326. }, []).join('\n');
  327. },
  328. /**
  329. * Creates a line ring buffer object with the following methods:
  330. * - append(str) : appends a string or buffer
  331. * - get() : returns the whole string
  332. * - close() : prevents further append() calls and does a last call to callbacks
  333. * - callback(cb) : calls cb for each line (incl. those already in the ring)
  334. *
  335. * @param {Numebr} maxLines maximum number of lines to store (&lt;= 0 for unlimited)
  336. */
  337. linesRing: function(maxLines) {
  338. var cbs = [];
  339. var lines = [];
  340. var current = null;
  341. var closed = false
  342. var max = maxLines - 1;
  343. function emit(line) {
  344. cbs.forEach(function(cb) { cb(line); });
  345. }
  346. return {
  347. callback: function(cb) {
  348. lines.forEach(function(l) { cb(l); });
  349. cbs.push(cb);
  350. },
  351. append: function(str) {
  352. if (closed) return;
  353. if (str instanceof Buffer) str = '' + str;
  354. if (!str || str.length === 0) return;
  355. var newLines = str.split(nlRegexp);
  356. if (newLines.length === 1) {
  357. if (current !== null) {
  358. current = current + newLines.shift();
  359. } else {
  360. current = newLines.shift();
  361. }
  362. } else {
  363. if (current !== null) {
  364. current = current + newLines.shift();
  365. emit(current);
  366. lines.push(current);
  367. }
  368. current = newLines.pop();
  369. newLines.forEach(function(l) {
  370. emit(l);
  371. lines.push(l);
  372. });
  373. if (max > -1 &amp;&amp; lines.length > max) {
  374. lines.splice(0, lines.length - max);
  375. }
  376. }
  377. },
  378. get: function() {
  379. if (current !== null) {
  380. return lines.concat([current]).join('\n');
  381. } else {
  382. return lines.join('\n');
  383. }
  384. },
  385. close: function() {
  386. if (closed) return;
  387. if (current !== null) {
  388. emit(current);
  389. lines.push(current);
  390. if (max > -1 &amp;&amp; lines.length > max) {
  391. lines.shift();
  392. }
  393. current = null;
  394. }
  395. closed = true;
  396. }
  397. };
  398. }
  399. };
  400. </code></pre>
  401. </article>
  402. </section>
  403. </div>
  404. <nav>
  405. <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>
  406. </nav>
  407. <br clear="both">
  408. <footer>
  409. 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)
  410. </footer>
  411. <script> prettyPrint(); </script>
  412. <script src="scripts/linenumber.js"> </script>
  413. </body>
  414. </html>