ffmpeg.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. var when = require('when')
  2. , fs = require('fs');
  3. var errors = require('./errors')
  4. , utils = require('./utils')
  5. , configs = require('./configs')
  6. , video = require('./video');
  7. var ffmpeg = function (/* inputFilepath, settings, callback */) {
  8. /**
  9. * Retrieve the list of the codec supported by the ffmpeg software
  10. */
  11. var _ffmpegInfoConfiguration = function (settings) {
  12. // New 'promise' instance
  13. var deferred = when.defer();
  14. // Instance the new arrays for the format
  15. var format = { modules : new Array(), encode : new Array(), decode : new Array() };
  16. // Make the call to retrieve information about the ffmpeg
  17. utils.exec(['ffmpeg','-formats','2>&1'], settings, function (error, stdout, stderr) {
  18. // Get the list of modules
  19. var configuration = /configuration:(.*)/.exec(stdout);
  20. // Check if exists the configuration
  21. if (configuration) {
  22. // Get the list of modules
  23. var modules = configuration[1].match(/--enable-([a-zA-Z0-9\-]+)/g);
  24. // Scan all modules
  25. for (var indexModule in modules) {
  26. // Add module to the list
  27. format.modules.push(/--enable-([a-zA-Z0-9\-]+)/.exec(modules[indexModule])[1]);
  28. }
  29. }
  30. // Get the codec list
  31. var codecList = stdout.match(/ (DE|D|E) (.*) {1,} (.*)/g);
  32. // Scan all codec
  33. for (var i in codecList) {
  34. // Get the match value
  35. var match = / (DE|D|E) (.*) {1,} (.*)/.exec(codecList[i]);
  36. // Check if match is valid
  37. if (match) {
  38. // Get the value from the match
  39. var scope = match[1].replace(/\s/g,'')
  40. , extension = match[2].replace(/\s/g,'');
  41. // Check which scope is best suited
  42. if (scope == 'D' || scope == 'DE')
  43. format.decode.push(extension);
  44. if (scope == 'E' || scope == 'DE')
  45. format.encode.push(extension);
  46. }
  47. }
  48. // Returns the list of supported formats
  49. deferred.resolve(format);
  50. });
  51. // Return 'promise' instance
  52. return deferred.promise;
  53. }
  54. /**
  55. * Get the video info
  56. */
  57. var _videoInfo = function (fileInput, settings) {
  58. // New 'promise' instance
  59. var deferred = when.defer();
  60. // Make the call to retrieve information about the ffmpeg
  61. utils.exec(['ffmpeg','-i',fileInput,'2>&1'], settings, function (error, stdout, stderr) {
  62. // Perse output for retrieve the file info
  63. var filename = /from \'(.*)\'/.exec(stdout) || []
  64. , title = /(INAM|title)\s+:\s(.+)/.exec(stdout) || []
  65. , artist = /artist\s+:\s(.+)/.exec(stdout) || []
  66. , album = /album\s+:\s(.+)/.exec(stdout) || []
  67. , track = /track\s+:\s(.+)/.exec(stdout) || []
  68. , date = /date\s+:\s(.+)/.exec(stdout) || []
  69. , is_synched = (/start: 0.000000/.exec(stdout) !== null)
  70. , duration = /Duration: (([0-9]+):([0-9]{2}):([0-9]{2}).([0-9]+))/.exec(stdout) || []
  71. , container = /Input #0, ([a-zA-Z0-9]+),/.exec(stdout) || []
  72. , video_bitrate = /bitrate: ([0-9]+) kb\/s/.exec(stdout) || []
  73. , video_stream = /Stream #([0-9\.]+)([a-z0-9\(\)\[\]]*)[:] Video/.exec(stdout) || []
  74. , video_codec = /Video: ([\w]+)/.exec(stdout) || []
  75. , resolution = /(([0-9]{2,5})x([0-9]{2,5}))/.exec(stdout) || []
  76. , pixel = /[SP]AR ([0-9\:]+)/.exec(stdout) || []
  77. , aspect = /DAR ([0-9\:]+)/.exec(stdout) || []
  78. , fps = /([0-9\.]+) (fps|tb\(r\))/.exec(stdout) || []
  79. , audio_stream = /Stream #([0-9\.]+)([a-z0-9\(\)\[\]]*)[:] Audio/.exec(stdout) || []
  80. , audio_codec = /Audio: ([\w]+)/.exec(stdout) || []
  81. , sample_rate = /([0-9]+) Hz/i.exec(stdout) || []
  82. , channels = /Audio:.* (stereo|mono)/.exec(stdout) || []
  83. , audio_bitrate = /Audio:.* ([0-9]+) kb\/s/.exec(stdout) || []
  84. , rotate = /rotate[\s]+:[\s]([\d]{2,3})/.exec(stdout) || [];
  85. // Build return object
  86. var ret = {
  87. filename : filename[1] || ''
  88. , title : title[2] || ''
  89. , artist : artist[1] || ''
  90. , album : album[1] || ''
  91. , track : track[1] || ''
  92. , date : date[1] || ''
  93. , synched : is_synched
  94. , duration : {
  95. raw : duration[1] || ''
  96. , seconds : duration[1] ? utils.durationToSeconds(duration[1]) : 0
  97. }
  98. , video : {
  99. container : container[1] || ''
  100. , bitrate : (video_bitrate.length > 1) ? parseInt(video_bitrate[1], 10) : 0
  101. , stream : video_stream.length > 1 ? parseFloat(video_stream[1]) : 0.0
  102. , codec : video_codec[1] || ''
  103. , resolution : {
  104. w : resolution.length > 2 ? parseInt(resolution[2], 10) : 0
  105. , h : resolution.length > 3 ? parseInt(resolution[3], 10) : 0
  106. }
  107. , resolutionSquare : {}
  108. , aspect : {}
  109. , rotate : rotate.length > 1 ? parseInt(rotate[1], 10) : 0
  110. , fps : fps.length > 1 ? parseFloat(fps[1]) : 0.0
  111. }
  112. , audio : {
  113. codec : audio_codec[1] || ''
  114. , bitrate : audio_bitrate[1] || ''
  115. , sample_rate : sample_rate.length > 1 ? parseInt(sample_rate[1], 10) : 0
  116. , stream : audio_stream.length > 1 ? parseFloat(audio_stream[1]) : 0.0
  117. , channels : {
  118. raw : channels[1] || ''
  119. , value : (channels.length > 0) ? ({ stereo : 2, mono : 1 }[channels[1]] || 0) : ''
  120. }
  121. }
  122. };
  123. // Check if exist aspect ratio
  124. if (aspect.length > 0) {
  125. var aspectValue = aspect[1].split(":");
  126. ret.video.aspect.x = parseInt(aspectValue[0], 10);
  127. ret.video.aspect.y = parseInt(aspectValue[1], 10);
  128. ret.video.aspect.string = aspect[1];
  129. ret.video.aspect.value = parseFloat((ret.video.aspect.x / ret.video.aspect.y));
  130. } else {
  131. // If exists horizontal resolution then calculate aspect ratio
  132. if(ret.video.resolution.w > 0) {
  133. var gcdValue = utils.gcd(ret.video.resolution.w, ret.video.resolution.h);
  134. // Calculate aspect ratio
  135. ret.video.aspect.x = ret.video.resolution.w / gcdValue;
  136. ret.video.aspect.y = ret.video.resolution.h / gcdValue;
  137. ret.video.aspect.string = ret.video.aspect.x + ':' + ret.video.aspect.y;
  138. ret.video.aspect.value = parseFloat((ret.video.aspect.x / ret.video.aspect.y));
  139. }
  140. }
  141. // Save pixel ratio for output size calculation
  142. if (pixel.length > 0) {
  143. ret.video.pixelString = pixel[1];
  144. var pixelValue = pixel[1].split(":");
  145. ret.video.pixel = parseFloat((parseInt(pixelValue[0], 10) / parseInt(pixelValue[1], 10)));
  146. } else {
  147. if (ret.video.resolution.w !== 0) {
  148. ret.video.pixelString = '1:1';
  149. ret.video.pixel = 1;
  150. } else {
  151. ret.video.pixelString = '';
  152. ret.video.pixel = 0.0;
  153. }
  154. }
  155. // Correct video.resolution when pixel aspectratio is not 1
  156. if (ret.video.pixel !== 1 || ret.video.pixel !== 0) {
  157. if( ret.video.pixel > 1 ) {
  158. ret.video.resolutionSquare.w = parseInt(ret.video.resolution.w * ret.video.pixel, 10);
  159. ret.video.resolutionSquare.h = ret.video.resolution.h;
  160. } else {
  161. ret.video.resolutionSquare.w = ret.video.resolution.w;
  162. ret.video.resolutionSquare.h = parseInt(ret.video.resolution.h / ret.video.pixel, 10);
  163. }
  164. }
  165. // Returns the list of supported formats
  166. deferred.resolve(ret);
  167. });
  168. // Return 'promise' instance
  169. return deferred.promise;
  170. }
  171. /**
  172. * Get the info about ffmpeg's codec and about file
  173. */
  174. var _getInformation = function (fileInput, settings) {
  175. var deferreds = [];
  176. // Add promise
  177. deferreds.push(_ffmpegInfoConfiguration(settings));
  178. deferreds.push(_videoInfo(fileInput, settings));
  179. // Return defer
  180. return when.all(deferreds);
  181. }
  182. var __constructor = function (args) {
  183. // Check if exist at least one option
  184. if (args.length == 0 || args[0] == undefined)
  185. throw errors.renderError('empty_input_filepath');
  186. // Check if first argument is a string
  187. if (typeof args[0] != 'string')
  188. throw errors.renderError('input_filepath_must_be_string');
  189. // Get the input filepath
  190. var inputFilepath = args[0];
  191. // Check if file exist
  192. if (!fs.existsSync(inputFilepath))
  193. throw errors.renderError('fileinput_not_exist');
  194. // New instance of the base configuration
  195. var settings = new configs();
  196. // Callback to call
  197. var callback = null;
  198. // Scan all arguments
  199. for (var i = 1; i < args.length; i++) {
  200. // Check the type of variable
  201. switch (typeof args[i]) {
  202. case 'object' :
  203. utils.mergeObject(settings, args[i]);
  204. break;
  205. case 'function' :
  206. callback = args[i];
  207. break;
  208. }
  209. }
  210. // 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
  211. var deferred = typeof callback != 'function' ? when.defer() : { promise : null };
  212. when(_getInformation(inputFilepath, settings), function (data) {
  213. // Check if the callback is a function
  214. if (typeof callback == 'function') {
  215. // Call the callback function e return the new instance of 'video' class
  216. callback(null, new video(inputFilepath, settings, data[0], data[1]));
  217. } else {
  218. // Positive response
  219. deferred.resolve(new video(inputFilepath, settings, data[0], data[1]));
  220. }
  221. }, function (error) {
  222. // Check if the callback is a function
  223. if (typeof callback == 'function') {
  224. // Call the callback function e return the error found
  225. callback(error, null);
  226. } else {
  227. // Negative response
  228. deferred.reject(error);
  229. }
  230. });
  231. // Return a possible promise instance
  232. return deferred.promise;
  233. }
  234. return __constructor.call(this, arguments);
  235. };
  236. module.exports = ffmpeg;