PageRenderTime 54ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/fluent-ffmpeg.js

https://github.com/jansmolders86/node-fluent-ffmpeg
JavaScript | 320 lines | 273 code | 19 blank | 28 comment | 48 complexity | 198f61accc6b341871a6b286ea0ada28 MD5 | raw file
Possible License(s): MIT
  1. var path = require('path'),
  2. async = require('../support/async.min.js'),
  3. exec = require('child_process').exec,
  4. spawn = require('child_process').spawn;
  5. /* options object consists of the following keys:
  6. * - source: either a ReadableStream or the path to a file (required)
  7. * - timeout: timeout in seconds for all ffmpeg sub-processes (optional, defaults to 30)
  8. * - priority: default-priority for all ffmpeg sub-processes (optional, defaults to 0)
  9. * - logger: add a winston logging instance (optional, default is clumsy console logging)
  10. * - nolog: completely disables any logging
  11. */
  12. function FfmpegCommand(args) {
  13. var source = args.source,
  14. timeout = args.timeout || 30,
  15. priority = args.priority || 0,
  16. logger = args.logger || null,
  17. nologging = args.nolog || false;
  18. if (!logger && !nologging) {
  19. // create new winston instance
  20. logger = require('winston');
  21. } else if (!logger && nologging) {
  22. // create fake object to route log calls
  23. logger = {
  24. debug: function() {},
  25. info: function() {},
  26. warn: function() {},
  27. error: function() {}
  28. };
  29. }
  30. // make sure execution is not killed on error
  31. logger.exitOnError = false;
  32. // check if argument is a stream
  33. var srcstream, srcfile;
  34. if (typeof source === 'object') {
  35. if (source.readable) {
  36. // streaming mode
  37. source.pause();
  38. srcstream = source;
  39. srcfile = source.path;
  40. } else {
  41. logger.error('Source is not a ReadableStream instance');
  42. throw new Error('Source is not a ReadableStream instance');
  43. }
  44. } else {
  45. // file mode
  46. srcfile = source;
  47. }
  48. this.options = {
  49. _isStreamable: true,
  50. _updateFlvMetadata: false,
  51. _useConstantVideoBitrate: false,
  52. _nice: { level: priority },
  53. keepPixelAspect: false,
  54. inputfile: srcfile,
  55. inputstream: srcstream,
  56. timeout: timeout,
  57. mergeList:[],
  58. video: {},
  59. audio: {},
  60. additional: [],
  61. otherInputs: [],
  62. informInputAudioCodec: null,
  63. informInputVideoCodec: null,
  64. logger: logger
  65. };
  66. // public chaining methods
  67. FfmpegCommand.prototype.usingPreset = function(preset) {
  68. // require preset (since require() works like a singleton, multiple calls generate no overhead)
  69. try {
  70. var module = require('./presets/' + preset);
  71. if (typeof module.load === 'function') {
  72. module.load(this);
  73. }
  74. return this;
  75. } catch (err) {
  76. throw new Error('preset ' + preset + ' could not be loaded');
  77. }
  78. return this;
  79. };
  80. FfmpegCommand.prototype.withNoVideo = function() {
  81. this.options.video.skip = true;
  82. return this;
  83. };
  84. FfmpegCommand.prototype.withNoAudio = function() {
  85. this.options.audio.skip = true;
  86. return this;
  87. };
  88. FfmpegCommand.prototype.withVideoBitrate = function(vbitrate, type) {
  89. if (typeof vbitrate === 'string' && vbitrate.indexOf('k') > 0) {
  90. vbitrate = vbitrate.replace('k', '');
  91. }
  92. if (type && type === exports.CONSTANT_BITRATE) {
  93. this.options._useConstantVideoBitrate = true;
  94. }
  95. this.options.video.bitrate = parseInt(vbitrate, 10);
  96. return this;
  97. };
  98. FfmpegCommand.prototype.withSize = function(sizeString) {
  99. this.options.video.size = sizeString;
  100. return this;
  101. };
  102. FfmpegCommand.prototype.applyAutopadding = function(autopad, color) {
  103. this.options._applyAutopad = autopad;
  104. if (!color) {
  105. this.options.video.padcolor = 'black';
  106. } else {
  107. this.options.video.padcolor = color;
  108. }
  109. return this;
  110. };
  111. FfmpegCommand.prototype.withFps = function(fps) {
  112. this.options.video.fps = fps;
  113. return this;
  114. };
  115. FfmpegCommand.prototype.withFpsInput = function(fps) {
  116. this.options.video.fpsInput = fps;
  117. return this;
  118. };
  119. FfmpegCommand.prototype.withFpsOutput = function(fps) {
  120. this.options.video.fpsOutput = fps;
  121. return this;
  122. };
  123. FfmpegCommand.prototype.withAspect = function(aspectRatio) {
  124. this.options.video.aspect = aspectRatio;
  125. return this;
  126. };
  127. FfmpegCommand.prototype.keepPixelAspect = function(bool) {
  128. this.options.keepPixelAspect = bool ? true : false;
  129. return this;
  130. };
  131. FfmpegCommand.prototype.withVideoCodec = function(codec) {
  132. this.options.video.codec = codec;
  133. return this;
  134. };
  135. FfmpegCommand.prototype.loop = function(duration) {
  136. this.options.video.loop = true;
  137. if (duration) {
  138. this.options.duration = duration;
  139. }
  140. return this;
  141. };
  142. FfmpegCommand.prototype.takeFrames = function(frameCount) {
  143. this.options.video.framecount = frameCount;
  144. return this;
  145. };
  146. FfmpegCommand.prototype.withAudioBitrate = function(abitrate) {
  147. if (typeof abitrate === 'string' && abitrate.indexOf('k') > 0) {
  148. abitrate = abitrate.replace('k', '');
  149. }
  150. this.options.audio.bitrate = parseInt(abitrate, 10);
  151. return this;
  152. };
  153. FfmpegCommand.prototype.withAudioCodec = function(audiocodec){
  154. this.options.audio.codec = audiocodec;
  155. return this;
  156. };
  157. FfmpegCommand.prototype.withAudioChannels = function(audiochannels) {
  158. this.options.audio.channels = audiochannels;
  159. return this;
  160. };
  161. FfmpegCommand.prototype.withAudioFrequency = function(frequency) {
  162. this.options.audio.frequency = frequency;
  163. return this;
  164. };
  165. FfmpegCommand.prototype.withAudioQuality = function(quality) {
  166. this.options.audio.quality = parseInt(quality, 10);
  167. return this;
  168. };
  169. FfmpegCommand.prototype.setStartTime = function(timestamp) {
  170. this.options.starttime = timestamp;
  171. return this;
  172. };
  173. FfmpegCommand.prototype.setDuration = function(duration) {
  174. this.options.duration = duration;
  175. return this;
  176. };
  177. FfmpegCommand.prototype.addInput = function(inputFile) {
  178. this.options.otherInputs.push(inputFile);
  179. return this;
  180. };
  181. FfmpegCommand.prototype.addOptions = function(optionArray) {
  182. if (typeof optionArray.length !== undefined) {
  183. var self = this;
  184. optionArray.forEach(function(el) {
  185. if (el.indexOf(' ') > 0) {
  186. var values = el.split(' ');
  187. self.options.additional.push(values[0], values[1]);
  188. } else {
  189. self.options.additional.push(el);
  190. }
  191. });
  192. }
  193. return this;
  194. };
  195. FfmpegCommand.prototype.addOption = function(option, value) {
  196. this.options.additional.push(option, value);
  197. return this;
  198. };
  199. FfmpegCommand.prototype.mergeAdd = function(path){
  200. this.options.mergeList.push(path)
  201. return this;
  202. };
  203. FfmpegCommand.prototype.toFormat = function(format) {
  204. this.options.format = format;
  205. // some muxers require the output stream to be seekable, disable streaming for those formats
  206. if (this.options.format === 'mp4') {
  207. this.options._isStreamable = false;
  208. }
  209. return this;
  210. };
  211. FfmpegCommand.prototype.updateFlvMetadata = function() {
  212. this.options._updateFlvMetadata = true;
  213. return this;
  214. };
  215. FfmpegCommand.prototype.renice = function(level) {
  216. if (!level) {
  217. // use 0 as default nice level (os default)
  218. level = 0;
  219. }
  220. // make sure niceness is within allowed boundaries
  221. if (level > 20 || level < -20) {
  222. this.options.logger.warn('niceness ' + level + ' is not valid, consider a value between -20 and +20 (whereas -20 is the highest priority)');
  223. level = 0;
  224. }
  225. this.options._nice.level = level;
  226. return this;
  227. };
  228. FfmpegCommand.prototype.onCodecData = function(callback) {
  229. this.options.onCodecData = callback;
  230. return this;
  231. };
  232. FfmpegCommand.prototype.onProgress = function(callback) {
  233. this.options.onProgress = callback;
  234. return this;
  235. };
  236. // private methods
  237. FfmpegCommand.prototype._prepare = function(callback) {
  238. var calcDimensions = false, calcPadding = false;
  239. // check for allowed sizestring formats and handle them accordingly
  240. var fixedWidth = /([0-9]+)x\?/.exec(this.options.video.size);
  241. var fixedHeight = /\?x([0-9]+)/.exec(this.options.video.size);
  242. var percentRatio = /\b([0-9]{1,2})%/.exec(this.options.video.size);
  243. if (!fixedWidth && !fixedHeight && !percentRatio) {
  244. // check for invalid size string
  245. var defaultSizestring = /([0-9]+)x([0-9]+)/.exec(this.options.video.size);
  246. if (this.options.video.size && !defaultSizestring) {
  247. callback(new Error('could not parse size string, aborting execution'));
  248. return;
  249. } else {
  250. // get width and height as integers (used for padding calculation)
  251. if (defaultSizestring) {
  252. this.options.video.width = parseInt(defaultSizestring[1], 10);
  253. this.options.video.height = parseInt(defaultSizestring[2], 10);
  254. }
  255. calcDimensions = false;
  256. }
  257. } else {
  258. calcDimensions = true;
  259. }
  260. // check if we have to check aspect ratio for changes and auto-pad the output
  261. if (this.options._applyAutopad) {
  262. calcPadding = true;
  263. }
  264. var self = this;
  265. this.getMetadata(this.options.inputfile, function(meta, err) {
  266. if (calcDimensions || calcPadding) {
  267. var dimErr, padErr;
  268. // calculate dimensions
  269. if (calcDimensions) {
  270. dimErr = self._calculateDimensions(meta);
  271. }
  272. // calculate padding
  273. if (calcPadding) {
  274. padErr = self._calculatePadding(meta);
  275. }
  276. if (dimErr || padErr) {
  277. callback(new Error('error while preparing: dimension -> ' + dimErr + ' padding -> ' + padErr));
  278. } else {
  279. callback(undefined, meta);
  280. }
  281. } else {
  282. callback(undefined, meta);
  283. }
  284. });
  285. };
  286. }
  287. // add module methods
  288. require('./extensions').apply(FfmpegCommand.prototype);
  289. require('./metadata').apply(FfmpegCommand.prototype);
  290. require('./processor').apply(FfmpegCommand.prototype);
  291. require('./calculate').apply(FfmpegCommand.prototype);
  292. require('./debug').apply(FfmpegCommand.prototype);
  293. // module exports
  294. exports = module.exports = function(args) {
  295. return new FfmpegCommand(args);
  296. };
  297. // export meta data discovery
  298. exports.Metadata = require('./metadata');
  299. exports.Calculate = require('./calculate');
  300. exports.CONSTANT_BITRATE = 1;
  301. exports.VARIABLE_BITRATE = 2;