video.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863
  1. var fs = require('fs')
  2. , path = require('path')
  3. , when = require('when');
  4. var errors = require('./errors')
  5. , presets = require('./presets')
  6. , utils = require('./utils');
  7. module.exports = function (filePath, settings, infoConfiguration, infoFile) {
  8. // Public info about file and ffmpeg configuration
  9. this.file_path = filePath;
  10. this.info_configuration = infoConfiguration;
  11. this.metadata = infoFile;
  12. // Commands for building the ffmpeg string conversion
  13. var commands = new Array()
  14. , inputs = new Array()
  15. , filtersComlpex = new Array()
  16. , output = null;
  17. // List of options generated from setting functions
  18. var options = new Object();
  19. /*****************************************/
  20. /* FUNCTION FOR FILL THE COMMANDS OBJECT */
  21. /*****************************************/
  22. /**
  23. * Add a command to be bundled into the ffmpeg command call
  24. */
  25. this.addCommand = function (command, argument) {
  26. // Check if exists the current command
  27. if (utils.in_array(command, commands) === false) {
  28. // Add the new command
  29. commands.push(command);
  30. // Add the argument to new command
  31. if (argument != undefined)
  32. commands.push(argument);
  33. } else
  34. throw errors.renderError('command_already_exists', command);
  35. }
  36. /**
  37. * Add an input stream
  38. */
  39. this.addInput = function (argument) {
  40. inputs.push(argument);
  41. }
  42. /**
  43. * Add a filter complex
  44. */
  45. this.addFilterComplex = function (argument) {
  46. filtersComlpex.push(argument);
  47. }
  48. /**
  49. * Set the output path
  50. */
  51. var setOutput = function (path) {
  52. output = path;
  53. }
  54. /*********************/
  55. /* SETTING FUNCTIONS */
  56. /*********************/
  57. /**
  58. * Disables audio encoding
  59. */
  60. this.setDisableAudio = function () {
  61. if (options.audio == undefined)
  62. options.audio = new Object();
  63. // Set the new option
  64. options.audio.disabled = true;
  65. return this;
  66. }
  67. /**
  68. * Disables video encoding
  69. */
  70. this.setDisableVideo = function () {
  71. if (options.video == undefined)
  72. options.video = new Object();
  73. // Set the new option
  74. options.video.disabled = true;
  75. return this;
  76. }
  77. /**
  78. * Sets the new video format
  79. */
  80. this.setVideoFormat = function (format) {
  81. // Check if the format is supported by ffmpeg version
  82. if (this.info_configuration.encode.indexOf(format) != -1) {
  83. if (options.video == undefined)
  84. options.video = new Object();
  85. // Set the new option
  86. options.video.format = format;
  87. return this;
  88. } else
  89. throw errors.renderError('format_not_supported', format);
  90. }
  91. /**
  92. * Sets the new audio codec
  93. */
  94. this.setVideoCodec = function (codec) {
  95. // Check if the codec is supported by ffmpeg version
  96. if (this.info_configuration.encode.indexOf(codec) != -1) {
  97. if (options.video == undefined)
  98. options.video = new Object();
  99. // Set the new option
  100. options.video.codec = codec;
  101. return this;
  102. } else
  103. throw errors.renderError('codec_not_supported', codec);
  104. }
  105. /**
  106. * Sets the video bitrate
  107. */
  108. this.setVideoBitRate = function (bitrate) {
  109. if (options.video == undefined)
  110. options.video = new Object();
  111. // Set the new option
  112. options.video.bitrate = bitrate;
  113. return this;
  114. }
  115. /**
  116. * Sets the framerate of the video
  117. */
  118. this.setVideoFrameRate = function (framerate) {
  119. if (options.video == undefined)
  120. options.video = new Object();
  121. // Set the new option
  122. options.video.framerate = framerate;
  123. return this;
  124. }
  125. /**
  126. * Sets the start time
  127. */
  128. this.setVideoStartTime = function (time) {
  129. if (options.video == undefined)
  130. options.video = new Object();
  131. // Check if time is a string that contain: hours, minutes and seconds
  132. if (isNaN(time) && /([0-9]+):([0-9]{2}):([0-9]{2})/.exec(time)) {
  133. time = utils.durationToSeconds(time);
  134. } else if (!isNaN(time) && parseInt(time) == time) {
  135. time = parseInt(time, 10);
  136. } else {
  137. time = 0;
  138. }
  139. // Set the new option
  140. options.video.startTime = time;
  141. return this;
  142. }
  143. /**
  144. * Sets the duration
  145. */
  146. this.setVideoDuration = function (duration) {
  147. if (options.video == undefined)
  148. options.video = new Object();
  149. // Check if duration is a string that contain: hours, minutes and seconds
  150. if (isNaN(duration) && /([0-9]+):([0-9]{2}):([0-9]{2})/.exec(duration)) {
  151. duration = utils.durationToSeconds(duration);
  152. } else if (!isNaN(duration) && parseInt(duration) == duration) {
  153. duration = parseInt(duration, 10);
  154. } else {
  155. duration = 0;
  156. }
  157. // Set the new option
  158. options.video.duration = duration;
  159. return this;
  160. }
  161. /**
  162. * Sets the new aspetc ratio
  163. */
  164. this.setVideoAspectRatio = function (aspect) {
  165. // Check if aspect is a string
  166. if (isNaN(aspect)) {
  167. // Check if aspet is string xx:xx
  168. if (/([0-9]+):([0-9]+)/.exec(aspect)) {
  169. var check = /([0-9]+):([0-9]+)/.exec(aspect);
  170. aspect = parseFloat((check[1] / check[2]));
  171. } else {
  172. aspect = this.metadata.video.aspect.value;
  173. }
  174. }
  175. if (options.video == undefined)
  176. options.video = new Object();
  177. // Set the new option
  178. options.video.aspect = aspect;
  179. return this;
  180. }
  181. /**
  182. * Set the size of the video
  183. */
  184. this.setVideoSize = function (size, keepPixelAspectRatio, keepAspectRatio, paddingColor) {
  185. if (options.video == undefined)
  186. options.video = new Object();
  187. // Set the new option
  188. options.video.size = size;
  189. options.video.keepPixelAspectRatio = keepPixelAspectRatio;
  190. options.video.keepAspectRatio = keepAspectRatio;
  191. options.video.paddingColor = paddingColor;
  192. return this;
  193. }
  194. /**
  195. * Sets the new audio codec
  196. */
  197. this.setAudioCodec = function (codec) {
  198. // Check if the codec is supported by ffmpeg version
  199. if (this.info_configuration.encode.indexOf(codec) != -1) {
  200. // Check if codec is equal 'MP3' and check if the version of ffmpeg support the libmp3lame function
  201. if (codec == 'mp3' && this.info_configuration.modules.indexOf('libmp3lame') != -1)
  202. codec = 'libmp3lame';
  203. if (options.audio == undefined)
  204. options.audio = new Object();
  205. // Set the new option
  206. options.audio.codec = codec;
  207. return this;
  208. } else
  209. throw errors.renderError('codec_not_supported', codec);
  210. }
  211. /**
  212. * Sets the audio sample frequency for audio outputs
  213. */
  214. this.setAudioFrequency = function (frequency) {
  215. if (options.audio == undefined)
  216. options.audio = new Object();
  217. // Set the new option
  218. options.audio.frequency = frequency;
  219. return this;
  220. }
  221. /**
  222. * Sets the number of audio channels
  223. */
  224. this.setAudioChannels = function (channel) {
  225. // Check if the channel value is valid
  226. if (presets.audio_channel.stereo == channel || presets.audio_channel.mono == channel) {
  227. if (options.audio == undefined)
  228. options.audio = new Object();
  229. // Set the new option
  230. options.audio.channel = channel;
  231. return this;
  232. } else
  233. throw errors.renderError('audio_channel_is_invalid', channel);
  234. }
  235. /**
  236. * Sets the audio bitrate
  237. */
  238. this.setAudioBitRate = function (bitrate) {
  239. if (options.audio == undefined)
  240. options.audio = new Object();
  241. // Set the new option
  242. options.audio.bitrate = bitrate;
  243. return this;
  244. }
  245. /**
  246. * Sets the audio quality
  247. */
  248. this.setAudioQuality = function (quality) {
  249. if (options.audio == undefined)
  250. options.audio = new Object();
  251. // Set the new option
  252. options.audio.quality = quality;
  253. return this;
  254. }
  255. /**
  256. * Sets the watermark
  257. */
  258. this.setWatermark = function (watermarkPath, settings) {
  259. // Base settings
  260. var baseSettings = {
  261. position : "SW" // Position: NE NC NW SE SC SW C CE CW
  262. , margin_nord : null // Margin nord
  263. , margin_sud : null // Margin sud
  264. , margin_east : null // Margin east
  265. , margin_west : null // Margin west
  266. };
  267. // Check if watermark exists
  268. if (!fs.existsSync(watermarkPath))
  269. throw errors.renderError('invalid_watermark', watermarkPath);
  270. // Check if the settings are specified
  271. if (settings != null)
  272. utils.mergeObject(baseSettings, settings);
  273. // Check if position is valid
  274. if (baseSettings.position == null || utils.in_array(baseSettings.position, ['NE','NC','NW','SE','SC','SW','C','CE','CW']) === false)
  275. throw errors.renderError('invalid_watermark_position', baseSettings.position);
  276. // Check if margins are valid
  277. if (baseSettings.margin_nord == null || isNaN(baseSettings.margin_nord))
  278. baseSettings.margin_nord = 0;
  279. if (baseSettings.margin_sud == null || isNaN(baseSettings.margin_sud))
  280. baseSettings.margin_sud = 0;
  281. if (baseSettings.margin_east == null || isNaN(baseSettings.margin_east))
  282. baseSettings.margin_east = 0;
  283. if (baseSettings.margin_west == null || isNaN(baseSettings.margin_west))
  284. baseSettings.margin_west = 0;
  285. var overlay = '';
  286. var getSing = function (val, inverse) {
  287. return (val > 0 ? (inverse ? '-' : '+') : (inverse ? '+' : '-')).toString() + Math.abs(val).toString();
  288. }
  289. var getHorizontalMargins = function (east, west) {
  290. return getSing(east, false).toString() + getSing(west, true).toString();
  291. }
  292. var getVerticalMargins = function (nord, sud) {
  293. return getSing(nord, false).toString() + getSing(sud, true).toString();
  294. }
  295. // Calculate formula
  296. switch (baseSettings.position) {
  297. case 'NE':
  298. overlay = '0' + getHorizontalMargins(baseSettings.margin_east, baseSettings.margin_west) + ':0' + getVerticalMargins(baseSettings.margin_nord, baseSettings.margin_sud);
  299. break;
  300. case 'NC':
  301. overlay = 'main_w/2-overlay_w/2' + getHorizontalMargins(baseSettings.margin_east, baseSettings.margin_west) + ':0' + getVerticalMargins(baseSettings.margin_nord, baseSettings.margin_sud);
  302. break;
  303. case 'NW':
  304. overlay = 'main_w-overlay_w' + getHorizontalMargins(baseSettings.margin_east, baseSettings.margin_west) + ':0' + getVerticalMargins(baseSettings.margin_nord, baseSettings.margin_sud);
  305. break;
  306. case 'SE':
  307. overlay = '0' + getHorizontalMargins(baseSettings.margin_east, baseSettings.margin_west) + ':main_h-overlay_h' + getVerticalMargins(baseSettings.margin_nord, baseSettings.margin_sud);
  308. break;
  309. case 'SC':
  310. overlay = 'main_w/2-overlay_w/2' + getHorizontalMargins(baseSettings.margin_east, baseSettings.margin_west) + ':main_h-overlay_h' + getVerticalMargins(baseSettings.margin_nord, baseSettings.margin_sud);
  311. break;
  312. case 'SW':
  313. overlay = 'main_w-overlay_w' + getHorizontalMargins(baseSettings.margin_east, baseSettings.margin_west) + ':main_h-overlay_h' + getVerticalMargins(baseSettings.margin_nord, baseSettings.margin_sud);
  314. break;
  315. case 'CE':
  316. overlay = '0' + getHorizontalMargins(baseSettings.margin_east, baseSettings.margin_west) + ':main_h/2-overlay_h/2' + getVerticalMargins(baseSettings.margin_nord, baseSettings.margin_sud);
  317. break;
  318. case 'C':
  319. overlay = 'main_w/2-overlay_w/2' + getHorizontalMargins(baseSettings.margin_east, baseSettings.margin_west) + ':main_h/2-overlay_h/2' + getVerticalMargins(baseSettings.margin_nord, baseSettings.margin_sud);
  320. break;
  321. case 'CW':
  322. overlay = 'main_w-overlay_w' + getHorizontalMargins(baseSettings.margin_east, baseSettings.margin_west) + ':main_h/2-overlay_h/2' + getVerticalMargins(baseSettings.margin_nord, baseSettings.margin_sud);
  323. break;
  324. }
  325. // Check if the call comes from internal function
  326. if (arguments[2] == undefined || arguments[2] == null) {
  327. if (options.video == undefined)
  328. options.video = new Object();
  329. // Set the new option
  330. options.video.watermark = { path : watermarkPath, overlay : overlay };
  331. return this;
  332. } else if (arguments[2] != undefined && arguments[2] === true) {
  333. this.addInput(watermarkPath);
  334. this.addFilterComplex('overlay=' + overlay);
  335. }
  336. }
  337. /**
  338. * Save all set commands
  339. */
  340. this.save = function (destionationFileName, callback) {
  341. // Check if the 'video' is present in the options
  342. if (options.hasOwnProperty('video')) {
  343. // Check if video is disabled
  344. if (options.video.hasOwnProperty('disabled')) {
  345. this.addCommand('-vn');
  346. } else {
  347. // Check all video property
  348. if (options.video.hasOwnProperty('format'))
  349. this.addCommand('-f', options.video.format);
  350. if (options.video.hasOwnProperty('codec'))
  351. this.addCommand('-vcodec', options.video.codec);
  352. if (options.video.hasOwnProperty('bitrate'))
  353. this.addCommand('-b', parseInt(options.video.bitrate, 10) + 'kb');
  354. if (options.video.hasOwnProperty('framerate'))
  355. this.addCommand('-r', parseInt(options.video.framerate, 10));
  356. if (options.video.hasOwnProperty('startTime'))
  357. this.addCommand('-ss', parseInt(options.video.startTime, 10));
  358. if (options.video.hasOwnProperty('duration'))
  359. this.addCommand('-t', parseInt(options.video.duration, 10));
  360. if (options.video.hasOwnProperty('watermark')) {
  361. this.addInput(options.video.watermark.path);
  362. this.addFilterComplex('overlay=' + options.video.watermark.overlay);
  363. }
  364. // Check if the video should be scaled
  365. if (options.video.hasOwnProperty('size')) {
  366. var newDimension = _calculateNewDimension.call(this);
  367. if (newDimension.aspect != null) {
  368. this.addFilterComplex('scale=iw*sar:ih, pad=max(iw\\,ih*(' + newDimension.aspect.x + '/' + newDimension.aspect.y + ')):ow/(' + newDimension.aspect.x + '/' + newDimension.aspect.y + '):(ow-iw)/2:(oh-ih)/2' + (options.video.paddingColor != null ? ':' + options.video.paddingColor : ''));
  369. this.addCommand('-aspect', newDimension.aspect.string);
  370. }
  371. this.addCommand('-s', newDimension.width + 'x' + newDimension.height);
  372. }
  373. }
  374. }
  375. // Check if the 'audio' is present in the options
  376. if (options.hasOwnProperty('audio')) {
  377. // Check if audio is disabled
  378. if (options.audio.hasOwnProperty('disabled')) {
  379. this.addCommand('-an');
  380. } else {
  381. // Check all audio property
  382. if (options.audio.hasOwnProperty('codec'))
  383. this.addCommand('-acodec', options.audio.codec);
  384. if (options.audio.hasOwnProperty('frequency'))
  385. this.addCommand('-ar', parseInt(options.audio.frequency));
  386. if (options.audio.hasOwnProperty('channel'))
  387. this.addCommand('-ac', options.audio.channel);
  388. if (options.audio.hasOwnProperty('quality'))
  389. this.addCommand('-aq', options.audio.quality);
  390. if (options.audio.hasOwnProperty('bitrate'))
  391. this.addCommand('-ab', parseInt(options.audio.bitrate, 10) + 'k');
  392. }
  393. }
  394. setOutput(destionationFileName);
  395. return execCommand.call(this, callback);
  396. }
  397. /*********************/
  398. /* INTERNAL FUNCTION */
  399. /*********************/
  400. /**
  401. * Reset the list of commands
  402. */
  403. var resetCommands = function (self) {
  404. commands = new Array()
  405. inputs = [self.file_path];
  406. filtersComlpex = new Array();
  407. output = null;
  408. options = new Object();
  409. }
  410. /**
  411. * Calculate width, height and aspect ratio by the new dimension data
  412. */
  413. var _calculateNewDimension = function () {
  414. // Check if keepPixelAspectRatio is undefined
  415. var keepPixelAspectRatio = typeof options.video.keepPixelAspectRatio != 'boolean' ? false : options.video.keepPixelAspectRatio;
  416. // Check if keepAspectRatio is undefined
  417. var keepAspectRatio = typeof options.video.keepAspectRatio != 'boolean' ? false : options.video.keepAspectRatio;
  418. // Resolution to be taken as a reference
  419. var referrerResolution = this.metadata.video.resolution;
  420. // Check if is need keep pixel aspect ratio
  421. if (keepPixelAspectRatio) {
  422. // Check if exists resolution for pixel aspect ratio
  423. if (utils.isEmptyObj(this.metadata.video.resolutionSquare))
  424. throw errors.renderError('resolution_square_not_defined');
  425. // Apply the resolutionSquare
  426. referrerResolution = this.metadata.video.resolutionSquare;
  427. }
  428. // Final data
  429. var width = null
  430. , height = null
  431. , aspect = null;
  432. // Regex to check which type of dimension was specified
  433. var fixedWidth = /([0-9]+)x\?/.exec(options.video.size)
  434. , fixedHeight = /\?x([0-9]+)/.exec(options.video.size)
  435. , percentage = /([0-9]{1,2})%/.exec(options.video.size)
  436. , classicSize = /([0-9]+)x([0-9]+)/.exec(options.video.size);
  437. if (fixedWidth) {
  438. // Set the width dimension
  439. width = parseInt(fixedWidth[1], 10);
  440. // Check if the video has the aspect ratio setted
  441. if (!utils.isEmptyObj(this.metadata.video.aspect)) {
  442. height = Math.round((width / this.metadata.video.aspect.x) * this.metadata.video.aspect.y);
  443. } else {
  444. // Calculte the new height
  445. height = Math.round(referrerResolution.h / (referrerResolution.w / parseInt(fixedWidth[1], 10)));
  446. }
  447. } else if (fixedHeight) {
  448. // Set the width dimension
  449. height = parseInt(fixedHeight[1], 10);
  450. // Check if the video has the aspect ratio setted
  451. if (!utils.isEmptyObj(this.metadata.video.aspect)) {
  452. width = Math.round((height / this.metadata.video.aspect.y) * this.metadata.video.aspect.x);
  453. } else {
  454. // Calculte the new width
  455. width = Math.round(referrerResolution.w / (referrerResolution.h / parseInt(fixedHeight[1], 10)));
  456. }
  457. } else if (percentage) {
  458. // Calculte the ratio from percentage
  459. var ratio = parseInt(percentage[1], 10) / 100;
  460. // Calculate the new dimensions
  461. width = Math.round(referrerResolution.w * ratio);
  462. height = Math.round(referrerResolution.h * ratio);
  463. } else if (classicSize) {
  464. width = parseInt(classicSize[1], 10);
  465. height = parseInt(classicSize[2], 10);
  466. } else
  467. throw errors.renderError('size_format', options.video.size);
  468. // If the width or height are not multiples of 2 will be decremented by one unit
  469. if (width % 2 != 0) width -= 1;
  470. if (height % 2 != 0) height -= 1;
  471. if (keepAspectRatio) {
  472. // Calculate the new aspect ratio
  473. var gcdValue = utils.gcd(width, height);
  474. aspect = new Object();
  475. aspect.x = width / gcdValue;
  476. aspect.y = height / gcdValue;
  477. aspect.string = aspect.x + ':' + aspect.y;
  478. }
  479. return { width : width, height : height, aspect : aspect };
  480. }
  481. /**
  482. * Executing the commands list
  483. */
  484. var execCommand = function (callback, folder) {
  485. // Checking if folder is defined
  486. var onlyDestinationFile = folder != undefined ? false : true;
  487. // Building the value for return value. Check if the callback is not a function. In this case will created a new instance of the deferred class
  488. var deferred = typeof callback != 'function' ? when.defer() : { promise : null };
  489. // Create a copy of the commands list
  490. var finalCommands = ['ffmpeg -i']
  491. .concat(inputs.join(' -i '))
  492. .concat(commands.join(' '))
  493. .concat(filtersComlpex.length > 0 ? ['-filter_complex "'].concat(filtersComlpex.join(', ')).join('') + '"' : [])
  494. .concat([output]);
  495. // Reset commands
  496. resetCommands(this);
  497. // Execute the commands from the list
  498. utils.exec(finalCommands, settings, function (error, stdout, stderr) {
  499. // Building the result
  500. var result = null;
  501. if (!error) {
  502. // Check if show only destination filename or the complete file list
  503. if (onlyDestinationFile) {
  504. result = finalCommands[finalCommands.length-1];
  505. } else {
  506. // Clean possible "/" at the end of the string
  507. if (folder.charAt(folder.length-1) == "/")
  508. folder = folder.substr(0, folder.length-1);
  509. // Read file list inside the folder
  510. result = fs.readdirSync(folder);
  511. // Scan all file and prepend the folder path
  512. for (var i in result)
  513. result[i] = [folder, result[i]].join('/')
  514. }
  515. }
  516. // Check if the callback is a function
  517. if (typeof callback == 'function') {
  518. // Call the callback to return the info
  519. callback(error, result);
  520. } else {
  521. if (error) {
  522. // Negative response
  523. deferred.reject(error);
  524. } else {
  525. // Positive response
  526. deferred.resolve(result);
  527. }
  528. }
  529. });
  530. // Return a possible promise instance
  531. return deferred.promise;
  532. }
  533. /*******************/
  534. /* PRESET FUNCTION */
  535. /*******************/
  536. /**
  537. * Extracting sound from a video, and save it as Mp3
  538. */
  539. this.fnExtractSoundToMP3 = function (destionationFileName, callback) {
  540. // Check if file already exists. In this case will remove it
  541. if (fs.existsSync(destionationFileName))
  542. fs.unlinkSync(destionationFileName);
  543. // Building the final path
  544. var destinationDirName = path.dirname(destionationFileName)
  545. , destinationFileNameWE = path.basename(destionationFileName, path.extname(destionationFileName)) + '.mp3'
  546. , finalPath = path.join(destinationDirName, destinationFileNameWE);
  547. resetCommands(this);
  548. // Adding commands to the list
  549. this.addCommand('-vn');
  550. this.addCommand('-ar', 44100);
  551. this.addCommand('-ac', 2);
  552. this.addCommand('-ab', 192);
  553. this.addCommand('-f', 'mp3');
  554. // Add destination file path to the command list
  555. setOutput(finalPath);
  556. // Executing the commands list
  557. return execCommand.call(this, callback);
  558. }
  559. /**
  560. * Extract frame from video file
  561. */
  562. this.fnExtractFrameToJPG = function (/* destinationFolder, settings, callback */) {
  563. var destinationFolder = null
  564. , newSettings = null
  565. , callback = null;
  566. var settings = {
  567. start_time : null // Start time to recording
  568. , duration_time : null // Duration of recording
  569. , frame_rate : null // Number of the frames to capture in one second
  570. , size : null // Dimension each frame
  571. , number : null // Total frame to capture
  572. , every_n_frames : null // Frame to capture every N frames
  573. , every_n_seconds : null // Frame to capture every N seconds
  574. , every_n_percentage : null // Frame to capture every N percentage range
  575. , keep_pixel_aspect_ratio : true // Mantain the original pixel video aspect ratio
  576. , keep_aspect_ratio : true // Mantain the original aspect ratio
  577. , padding_color : 'black' // Padding color
  578. , file_name : null // File name
  579. };
  580. // Scan all arguments
  581. for (var i in arguments) {
  582. // Check the type of the argument
  583. switch (typeof arguments[i]) {
  584. case 'string':
  585. destinationFolder = arguments[i];
  586. break;
  587. case 'object':
  588. newSettings = arguments[i];
  589. break;
  590. case 'function':
  591. callback = arguments[i];
  592. break;
  593. }
  594. }
  595. // Check if the settings are specified
  596. if (newSettings !== null)
  597. utils.mergeObject(settings, newSettings);
  598. // Check if 'start_time' is in the format hours:minutes:seconds
  599. if (settings.start_time != null) {
  600. if (/([0-9]+):([0-9]{2}):([0-9]{2})/.exec(settings.start_time))
  601. settings.start_time = utils.durationToSeconds(settings.start_time);
  602. else if (!isNaN(settings.start_time))
  603. settings.start_time = parseInt(settings.start_time, 10);
  604. else
  605. settings.start_time = null;
  606. }
  607. // Check if 'duration_time' is in the format hours:minutes:seconds
  608. if (settings.duration_time != null) {
  609. if (/([0-9]+):([0-9]{2}):([0-9]{2})/.exec(settings.duration_time))
  610. settings.duration_time = utils.durationToSeconds(settings.duration_time);
  611. else if (!isNaN(settings.duration_time))
  612. settings.duration_time = parseInt(settings.duration_time, 10);
  613. else
  614. settings.duration_time = null;
  615. }
  616. // Check if the value of the framerate is number type
  617. if (settings.frame_rate != null && isNaN(settings.frame_rate))
  618. settings.frame_rate = null;
  619. // If the size is not settings then the size of the screenshots is equal to video size
  620. if (settings.size == null)
  621. settings.size = this.metadata.video.resolution.w + 'x' + this.metadata.video.resolution.h;
  622. // Check if the value of the 'number frame to capture' is number type
  623. if (settings.number != null && isNaN(settings.number))
  624. settings.number = null;
  625. var every_n_check = 0;
  626. // Check if the value of the 'every_n_frames' is number type
  627. if (settings.every_n_frames != null && isNaN(settings.every_n_frames)) {
  628. settings.every_n_frames = null;
  629. every_n_check++;
  630. }
  631. // Check if the value of the 'every_n_seconds' is number type
  632. if (settings.every_n_seconds != null && isNaN(settings.every_n_seconds)) {
  633. settings.every_n_seconds = null;
  634. every_n_check++;
  635. }
  636. // Check if the value of the 'every_n_percentage' is number type
  637. if (settings.every_n_percentage != null && (isNaN(settings.every_n_percentage) || settings.every_n_percentage > 100)) {
  638. settings.every_n_percentage = null;
  639. every_n_check++;
  640. }
  641. if (every_n_check >= 2) {
  642. if (callback) {
  643. callback(errors.renderError('extract_frame_invalid_everyN_options'));
  644. } else {
  645. throw errors.renderError('extract_frame_invalid_everyN_options');
  646. }
  647. }
  648. // If filename is null then his value is equal to original filename
  649. if (settings.file_name == null) {
  650. settings.file_name = path.basename(this.file_path, path.extname(this.file_path));
  651. } else {
  652. // Retrieve all possible replacements
  653. var replacements = settings.file_name.match(/(\%[a-zA-Z]{1})/g);
  654. // Check if exists replacements. The scan all replacements and build the final filename
  655. if (replacements) {
  656. for (var i in replacements) {
  657. switch (replacements[i]) {
  658. case '%t':
  659. settings.file_name = settings.file_name.replace('%t', new Date().getTime());
  660. break;
  661. case '%s':
  662. settings.file_name = settings.file_name.replace('%s', settings.size);
  663. break;
  664. case '%x':
  665. settings.file_name = settings.file_name.replace('%x', settings.size.split(':')[0]);
  666. break;
  667. case '%y':
  668. settings.file_name = settings.file_name.replace('%y', settings.size.split(':')[1]);
  669. break;
  670. default:
  671. settings.file_name = settings.file_name.replace(replacements[i], '');
  672. break;
  673. }
  674. }
  675. }
  676. }
  677. // At the filename will added the number of the frame
  678. settings.file_name = path.basename(settings.file_name, path.extname(settings.file_name)) + '_%d.jpg';
  679. // Create the directory to save the extracted frames
  680. utils.mkdir(destinationFolder, 0777);
  681. resetCommands(this);
  682. // Adding commands to the list
  683. if (settings.startTime)
  684. this.addCommand('-ss', settings.startTime);
  685. if (settings.duration_time)
  686. this.addCommand('-t', settings.duration_time);
  687. if (settings.frame_rate)
  688. this.addCommand('-r', settings.frame_rate);
  689. // Setting the size and padding settings
  690. this.setVideoSize(settings.size, settings.keep_pixel_aspect_ratio, settings.keep_aspect_ratio, settings.padding_color);
  691. // Get the dimensions
  692. var newDimension = _calculateNewDimension.call(this);
  693. // Apply the size and padding commands
  694. this.addCommand('-s', newDimension.width + 'x' + newDimension.height);
  695. // CHeck if isset aspect ratio options
  696. if (newDimension.aspect != null) {
  697. this.addFilterComplex('scale=iw*sar:ih, pad=max(iw\\,ih*(' + newDimension.aspect.x + '/' + newDimension.aspect.y + ')):ow/(' + newDimension.aspect.x + '/' + newDimension.aspect.y + '):(ow-iw)/2:(oh-ih)/2' + (settings.padding_color != null ? ':' + settings.padding_color : ''));
  698. this.addCommand('-aspect', newDimension.aspect.string);
  699. }
  700. if (settings.number)
  701. this.addCommand('-vframes', settings.number);
  702. if (settings.every_n_frames) {
  703. this.addCommand('-vsync', 0);
  704. this.addFilterComplex('select=not(mod(n\\,' + settings.every_n_frames + '))');
  705. }
  706. if (settings.every_n_seconds) {
  707. this.addCommand('-vsync', 0);
  708. this.addFilterComplex('select=not(mod(t\\,' + settings.every_n_seconds + '))');
  709. }
  710. if (settings.every_n_percentage) {
  711. this.addCommand('-vsync', 0);
  712. this.addFilterComplex('select=not(mod(t\\,' + parseInt((this.metadata.duration.seconds / 100) * settings.every_n_percentage) + '))');
  713. }
  714. // Add destination file path to the command list
  715. setOutput([destinationFolder,settings.file_name].join('/'));
  716. // Executing the commands list
  717. return execCommand.call(this, callback, destinationFolder);
  718. }
  719. /**
  720. * Add a watermark to the video and save it
  721. */
  722. this.fnAddWatermark = function (watermarkPath /* newFilepath , settings, callback */) {
  723. var newFilepath = null
  724. , newSettings = null
  725. , callback = null;
  726. // Scan all arguments
  727. for (var i = 1; i < arguments.length; i++) {
  728. // Check the type of the argument
  729. switch (typeof arguments[i]) {
  730. case 'string':
  731. newFilepath = arguments[i];
  732. break;
  733. case 'object':
  734. newSettings = arguments[i];
  735. break;
  736. case 'function':
  737. callback = arguments[i];
  738. break;
  739. }
  740. }
  741. resetCommands(this);
  742. // Call the function to add the watermark options
  743. this.setWatermark(watermarkPath, newSettings, true);
  744. if (newFilepath == null)
  745. newFilepath = path.dirname(this.file_path) + '/' +
  746. path.basename(this.file_path, path.extname(this.file_path)) + '_watermark_' +
  747. path.basename(watermarkPath, path.extname(watermarkPath)) +
  748. path.extname(this.file_path);
  749. // Add destination file path to the command list
  750. setOutput(newFilepath);
  751. // Executing the commands list
  752. return execCommand.call(this, callback);
  753. }
  754. /**
  755. * Constructor
  756. */
  757. var __constructor = function (self) {
  758. resetCommands(self);
  759. }(this);
  760. }