mirror of
https://github.com/musix-org/musix-oss
synced 2024-12-23 22:33:17 +00:00
863 lines
28 KiB
JavaScript
863 lines
28 KiB
JavaScript
|
var fs = require('fs')
|
||
|
, path = require('path')
|
||
|
, when = require('when');
|
||
|
|
||
|
var errors = require('./errors')
|
||
|
, presets = require('./presets')
|
||
|
, utils = require('./utils');
|
||
|
|
||
|
module.exports = function (filePath, settings, infoConfiguration, infoFile) {
|
||
|
|
||
|
// Public info about file and ffmpeg configuration
|
||
|
this.file_path = filePath;
|
||
|
this.info_configuration = infoConfiguration;
|
||
|
this.metadata = infoFile;
|
||
|
|
||
|
// Commands for building the ffmpeg string conversion
|
||
|
var commands = new Array()
|
||
|
, inputs = new Array()
|
||
|
, filtersComlpex = new Array()
|
||
|
, output = null;
|
||
|
|
||
|
// List of options generated from setting functions
|
||
|
var options = new Object();
|
||
|
|
||
|
/*****************************************/
|
||
|
/* FUNCTION FOR FILL THE COMMANDS OBJECT */
|
||
|
/*****************************************/
|
||
|
|
||
|
/**
|
||
|
* Add a command to be bundled into the ffmpeg command call
|
||
|
*/
|
||
|
this.addCommand = function (command, argument) {
|
||
|
// Check if exists the current command
|
||
|
if (utils.in_array(command, commands) === false) {
|
||
|
// Add the new command
|
||
|
commands.push(command);
|
||
|
// Add the argument to new command
|
||
|
if (argument != undefined)
|
||
|
commands.push(argument);
|
||
|
} else
|
||
|
throw errors.renderError('command_already_exists', command);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Add an input stream
|
||
|
*/
|
||
|
this.addInput = function (argument) {
|
||
|
inputs.push(argument);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Add a filter complex
|
||
|
*/
|
||
|
this.addFilterComplex = function (argument) {
|
||
|
filtersComlpex.push(argument);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the output path
|
||
|
*/
|
||
|
var setOutput = function (path) {
|
||
|
output = path;
|
||
|
}
|
||
|
|
||
|
/*********************/
|
||
|
/* SETTING FUNCTIONS */
|
||
|
/*********************/
|
||
|
|
||
|
/**
|
||
|
* Disables audio encoding
|
||
|
*/
|
||
|
this.setDisableAudio = function () {
|
||
|
if (options.audio == undefined)
|
||
|
options.audio = new Object();
|
||
|
// Set the new option
|
||
|
options.audio.disabled = true;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Disables video encoding
|
||
|
*/
|
||
|
this.setDisableVideo = function () {
|
||
|
if (options.video == undefined)
|
||
|
options.video = new Object();
|
||
|
// Set the new option
|
||
|
options.video.disabled = true;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the new video format
|
||
|
*/
|
||
|
this.setVideoFormat = function (format) {
|
||
|
// Check if the format is supported by ffmpeg version
|
||
|
if (this.info_configuration.encode.indexOf(format) != -1) {
|
||
|
if (options.video == undefined)
|
||
|
options.video = new Object();
|
||
|
// Set the new option
|
||
|
options.video.format = format;
|
||
|
return this;
|
||
|
} else
|
||
|
throw errors.renderError('format_not_supported', format);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the new audio codec
|
||
|
*/
|
||
|
this.setVideoCodec = function (codec) {
|
||
|
// Check if the codec is supported by ffmpeg version
|
||
|
if (this.info_configuration.encode.indexOf(codec) != -1) {
|
||
|
if (options.video == undefined)
|
||
|
options.video = new Object();
|
||
|
// Set the new option
|
||
|
options.video.codec = codec;
|
||
|
return this;
|
||
|
} else
|
||
|
throw errors.renderError('codec_not_supported', codec);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the video bitrate
|
||
|
*/
|
||
|
this.setVideoBitRate = function (bitrate) {
|
||
|
if (options.video == undefined)
|
||
|
options.video = new Object();
|
||
|
// Set the new option
|
||
|
options.video.bitrate = bitrate;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the framerate of the video
|
||
|
*/
|
||
|
this.setVideoFrameRate = function (framerate) {
|
||
|
if (options.video == undefined)
|
||
|
options.video = new Object();
|
||
|
// Set the new option
|
||
|
options.video.framerate = framerate;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the start time
|
||
|
*/
|
||
|
this.setVideoStartTime = function (time) {
|
||
|
if (options.video == undefined)
|
||
|
options.video = new Object();
|
||
|
|
||
|
// Check if time is a string that contain: hours, minutes and seconds
|
||
|
if (isNaN(time) && /([0-9]+):([0-9]{2}):([0-9]{2})/.exec(time)) {
|
||
|
time = utils.durationToSeconds(time);
|
||
|
} else if (!isNaN(time) && parseInt(time) == time) {
|
||
|
time = parseInt(time, 10);
|
||
|
} else {
|
||
|
time = 0;
|
||
|
}
|
||
|
|
||
|
// Set the new option
|
||
|
options.video.startTime = time;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the duration
|
||
|
*/
|
||
|
this.setVideoDuration = function (duration) {
|
||
|
if (options.video == undefined)
|
||
|
options.video = new Object();
|
||
|
|
||
|
// Check if duration is a string that contain: hours, minutes and seconds
|
||
|
if (isNaN(duration) && /([0-9]+):([0-9]{2}):([0-9]{2})/.exec(duration)) {
|
||
|
duration = utils.durationToSeconds(duration);
|
||
|
} else if (!isNaN(duration) && parseInt(duration) == duration) {
|
||
|
duration = parseInt(duration, 10);
|
||
|
} else {
|
||
|
duration = 0;
|
||
|
}
|
||
|
|
||
|
// Set the new option
|
||
|
options.video.duration = duration;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the new aspetc ratio
|
||
|
*/
|
||
|
this.setVideoAspectRatio = function (aspect) {
|
||
|
// Check if aspect is a string
|
||
|
if (isNaN(aspect)) {
|
||
|
// Check if aspet is string xx:xx
|
||
|
if (/([0-9]+):([0-9]+)/.exec(aspect)) {
|
||
|
var check = /([0-9]+):([0-9]+)/.exec(aspect);
|
||
|
aspect = parseFloat((check[1] / check[2]));
|
||
|
} else {
|
||
|
aspect = this.metadata.video.aspect.value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (options.video == undefined)
|
||
|
options.video = new Object();
|
||
|
// Set the new option
|
||
|
options.video.aspect = aspect;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the size of the video
|
||
|
*/
|
||
|
this.setVideoSize = function (size, keepPixelAspectRatio, keepAspectRatio, paddingColor) {
|
||
|
if (options.video == undefined)
|
||
|
options.video = new Object();
|
||
|
// Set the new option
|
||
|
options.video.size = size;
|
||
|
options.video.keepPixelAspectRatio = keepPixelAspectRatio;
|
||
|
options.video.keepAspectRatio = keepAspectRatio;
|
||
|
options.video.paddingColor = paddingColor;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the new audio codec
|
||
|
*/
|
||
|
this.setAudioCodec = function (codec) {
|
||
|
// Check if the codec is supported by ffmpeg version
|
||
|
if (this.info_configuration.encode.indexOf(codec) != -1) {
|
||
|
// Check if codec is equal 'MP3' and check if the version of ffmpeg support the libmp3lame function
|
||
|
if (codec == 'mp3' && this.info_configuration.modules.indexOf('libmp3lame') != -1)
|
||
|
codec = 'libmp3lame';
|
||
|
|
||
|
if (options.audio == undefined)
|
||
|
options.audio = new Object();
|
||
|
// Set the new option
|
||
|
options.audio.codec = codec;
|
||
|
return this;
|
||
|
} else
|
||
|
throw errors.renderError('codec_not_supported', codec);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the audio sample frequency for audio outputs
|
||
|
*/
|
||
|
this.setAudioFrequency = function (frequency) {
|
||
|
if (options.audio == undefined)
|
||
|
options.audio = new Object();
|
||
|
// Set the new option
|
||
|
options.audio.frequency = frequency;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the number of audio channels
|
||
|
*/
|
||
|
this.setAudioChannels = function (channel) {
|
||
|
// Check if the channel value is valid
|
||
|
if (presets.audio_channel.stereo == channel || presets.audio_channel.mono == channel) {
|
||
|
if (options.audio == undefined)
|
||
|
options.audio = new Object();
|
||
|
// Set the new option
|
||
|
options.audio.channel = channel;
|
||
|
return this;
|
||
|
} else
|
||
|
throw errors.renderError('audio_channel_is_invalid', channel);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the audio bitrate
|
||
|
*/
|
||
|
this.setAudioBitRate = function (bitrate) {
|
||
|
if (options.audio == undefined)
|
||
|
options.audio = new Object();
|
||
|
// Set the new option
|
||
|
options.audio.bitrate = bitrate;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the audio quality
|
||
|
*/
|
||
|
this.setAudioQuality = function (quality) {
|
||
|
if (options.audio == undefined)
|
||
|
options.audio = new Object();
|
||
|
// Set the new option
|
||
|
options.audio.quality = quality;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the watermark
|
||
|
*/
|
||
|
this.setWatermark = function (watermarkPath, settings) {
|
||
|
// Base settings
|
||
|
var baseSettings = {
|
||
|
position : "SW" // Position: NE NC NW SE SC SW C CE CW
|
||
|
, margin_nord : null // Margin nord
|
||
|
, margin_sud : null // Margin sud
|
||
|
, margin_east : null // Margin east
|
||
|
, margin_west : null // Margin west
|
||
|
};
|
||
|
|
||
|
// Check if watermark exists
|
||
|
if (!fs.existsSync(watermarkPath))
|
||
|
throw errors.renderError('invalid_watermark', watermarkPath);
|
||
|
|
||
|
// Check if the settings are specified
|
||
|
if (settings != null)
|
||
|
utils.mergeObject(baseSettings, settings);
|
||
|
|
||
|
// Check if position is valid
|
||
|
if (baseSettings.position == null || utils.in_array(baseSettings.position, ['NE','NC','NW','SE','SC','SW','C','CE','CW']) === false)
|
||
|
throw errors.renderError('invalid_watermark_position', baseSettings.position);
|
||
|
|
||
|
// Check if margins are valid
|
||
|
|
||
|
if (baseSettings.margin_nord == null || isNaN(baseSettings.margin_nord))
|
||
|
baseSettings.margin_nord = 0;
|
||
|
if (baseSettings.margin_sud == null || isNaN(baseSettings.margin_sud))
|
||
|
baseSettings.margin_sud = 0;
|
||
|
if (baseSettings.margin_east == null || isNaN(baseSettings.margin_east))
|
||
|
baseSettings.margin_east = 0;
|
||
|
if (baseSettings.margin_west == null || isNaN(baseSettings.margin_west))
|
||
|
baseSettings.margin_west = 0;
|
||
|
|
||
|
var overlay = '';
|
||
|
|
||
|
var getSing = function (val, inverse) {
|
||
|
return (val > 0 ? (inverse ? '-' : '+') : (inverse ? '+' : '-')).toString() + Math.abs(val).toString();
|
||
|
}
|
||
|
|
||
|
var getHorizontalMargins = function (east, west) {
|
||
|
return getSing(east, false).toString() + getSing(west, true).toString();
|
||
|
}
|
||
|
|
||
|
var getVerticalMargins = function (nord, sud) {
|
||
|
return getSing(nord, false).toString() + getSing(sud, true).toString();
|
||
|
}
|
||
|
|
||
|
// Calculate formula
|
||
|
switch (baseSettings.position) {
|
||
|
case 'NE':
|
||
|
overlay = '0' + getHorizontalMargins(baseSettings.margin_east, baseSettings.margin_west) + ':0' + getVerticalMargins(baseSettings.margin_nord, baseSettings.margin_sud);
|
||
|
break;
|
||
|
case 'NC':
|
||
|
overlay = 'main_w/2-overlay_w/2' + getHorizontalMargins(baseSettings.margin_east, baseSettings.margin_west) + ':0' + getVerticalMargins(baseSettings.margin_nord, baseSettings.margin_sud);
|
||
|
break;
|
||
|
case 'NW':
|
||
|
overlay = 'main_w-overlay_w' + getHorizontalMargins(baseSettings.margin_east, baseSettings.margin_west) + ':0' + getVerticalMargins(baseSettings.margin_nord, baseSettings.margin_sud);
|
||
|
break;
|
||
|
case 'SE':
|
||
|
overlay = '0' + getHorizontalMargins(baseSettings.margin_east, baseSettings.margin_west) + ':main_h-overlay_h' + getVerticalMargins(baseSettings.margin_nord, baseSettings.margin_sud);
|
||
|
break;
|
||
|
case 'SC':
|
||
|
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);
|
||
|
break;
|
||
|
case 'SW':
|
||
|
overlay = 'main_w-overlay_w' + getHorizontalMargins(baseSettings.margin_east, baseSettings.margin_west) + ':main_h-overlay_h' + getVerticalMargins(baseSettings.margin_nord, baseSettings.margin_sud);
|
||
|
break;
|
||
|
case 'CE':
|
||
|
overlay = '0' + getHorizontalMargins(baseSettings.margin_east, baseSettings.margin_west) + ':main_h/2-overlay_h/2' + getVerticalMargins(baseSettings.margin_nord, baseSettings.margin_sud);
|
||
|
break;
|
||
|
case 'C':
|
||
|
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);
|
||
|
break;
|
||
|
case 'CW':
|
||
|
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);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Check if the call comes from internal function
|
||
|
if (arguments[2] == undefined || arguments[2] == null) {
|
||
|
if (options.video == undefined)
|
||
|
options.video = new Object();
|
||
|
// Set the new option
|
||
|
options.video.watermark = { path : watermarkPath, overlay : overlay };
|
||
|
return this;
|
||
|
} else if (arguments[2] != undefined && arguments[2] === true) {
|
||
|
this.addInput(watermarkPath);
|
||
|
this.addFilterComplex('overlay=' + overlay);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Save all set commands
|
||
|
*/
|
||
|
this.save = function (destionationFileName, callback) {
|
||
|
// Check if the 'video' is present in the options
|
||
|
if (options.hasOwnProperty('video')) {
|
||
|
// Check if video is disabled
|
||
|
if (options.video.hasOwnProperty('disabled')) {
|
||
|
this.addCommand('-vn');
|
||
|
} else {
|
||
|
// Check all video property
|
||
|
if (options.video.hasOwnProperty('format'))
|
||
|
this.addCommand('-f', options.video.format);
|
||
|
if (options.video.hasOwnProperty('codec'))
|
||
|
this.addCommand('-vcodec', options.video.codec);
|
||
|
if (options.video.hasOwnProperty('bitrate'))
|
||
|
this.addCommand('-b', parseInt(options.video.bitrate, 10) + 'kb');
|
||
|
if (options.video.hasOwnProperty('framerate'))
|
||
|
this.addCommand('-r', parseInt(options.video.framerate, 10));
|
||
|
if (options.video.hasOwnProperty('startTime'))
|
||
|
this.addCommand('-ss', parseInt(options.video.startTime, 10));
|
||
|
if (options.video.hasOwnProperty('duration'))
|
||
|
this.addCommand('-t', parseInt(options.video.duration, 10));
|
||
|
|
||
|
if (options.video.hasOwnProperty('watermark')) {
|
||
|
this.addInput(options.video.watermark.path);
|
||
|
this.addFilterComplex('overlay=' + options.video.watermark.overlay);
|
||
|
}
|
||
|
|
||
|
// Check if the video should be scaled
|
||
|
if (options.video.hasOwnProperty('size')) {
|
||
|
var newDimension = _calculateNewDimension.call(this);
|
||
|
|
||
|
if (newDimension.aspect != null) {
|
||
|
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 : ''));
|
||
|
this.addCommand('-aspect', newDimension.aspect.string);
|
||
|
}
|
||
|
|
||
|
this.addCommand('-s', newDimension.width + 'x' + newDimension.height);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// Check if the 'audio' is present in the options
|
||
|
if (options.hasOwnProperty('audio')) {
|
||
|
// Check if audio is disabled
|
||
|
if (options.audio.hasOwnProperty('disabled')) {
|
||
|
this.addCommand('-an');
|
||
|
} else {
|
||
|
// Check all audio property
|
||
|
if (options.audio.hasOwnProperty('codec'))
|
||
|
this.addCommand('-acodec', options.audio.codec);
|
||
|
if (options.audio.hasOwnProperty('frequency'))
|
||
|
this.addCommand('-ar', parseInt(options.audio.frequency));
|
||
|
if (options.audio.hasOwnProperty('channel'))
|
||
|
this.addCommand('-ac', options.audio.channel);
|
||
|
if (options.audio.hasOwnProperty('quality'))
|
||
|
this.addCommand('-aq', options.audio.quality);
|
||
|
if (options.audio.hasOwnProperty('bitrate'))
|
||
|
this.addCommand('-ab', parseInt(options.audio.bitrate, 10) + 'k');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
setOutput(destionationFileName);
|
||
|
|
||
|
return execCommand.call(this, callback);
|
||
|
}
|
||
|
|
||
|
/*********************/
|
||
|
/* INTERNAL FUNCTION */
|
||
|
/*********************/
|
||
|
|
||
|
/**
|
||
|
* Reset the list of commands
|
||
|
*/
|
||
|
var resetCommands = function (self) {
|
||
|
commands = new Array()
|
||
|
inputs = [self.file_path];
|
||
|
filtersComlpex = new Array();
|
||
|
output = null;
|
||
|
options = new Object();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Calculate width, height and aspect ratio by the new dimension data
|
||
|
*/
|
||
|
var _calculateNewDimension = function () {
|
||
|
// Check if keepPixelAspectRatio is undefined
|
||
|
var keepPixelAspectRatio = typeof options.video.keepPixelAspectRatio != 'boolean' ? false : options.video.keepPixelAspectRatio;
|
||
|
// Check if keepAspectRatio is undefined
|
||
|
var keepAspectRatio = typeof options.video.keepAspectRatio != 'boolean' ? false : options.video.keepAspectRatio;
|
||
|
|
||
|
// Resolution to be taken as a reference
|
||
|
var referrerResolution = this.metadata.video.resolution;
|
||
|
// Check if is need keep pixel aspect ratio
|
||
|
if (keepPixelAspectRatio) {
|
||
|
// Check if exists resolution for pixel aspect ratio
|
||
|
if (utils.isEmptyObj(this.metadata.video.resolutionSquare))
|
||
|
throw errors.renderError('resolution_square_not_defined');
|
||
|
|
||
|
// Apply the resolutionSquare
|
||
|
referrerResolution = this.metadata.video.resolutionSquare;
|
||
|
}
|
||
|
|
||
|
// Final data
|
||
|
var width = null
|
||
|
, height = null
|
||
|
, aspect = null;
|
||
|
|
||
|
// Regex to check which type of dimension was specified
|
||
|
var fixedWidth = /([0-9]+)x\?/.exec(options.video.size)
|
||
|
, fixedHeight = /\?x([0-9]+)/.exec(options.video.size)
|
||
|
, percentage = /([0-9]{1,2})%/.exec(options.video.size)
|
||
|
, classicSize = /([0-9]+)x([0-9]+)/.exec(options.video.size);
|
||
|
|
||
|
if (fixedWidth) {
|
||
|
// Set the width dimension
|
||
|
width = parseInt(fixedWidth[1], 10);
|
||
|
// Check if the video has the aspect ratio setted
|
||
|
if (!utils.isEmptyObj(this.metadata.video.aspect)) {
|
||
|
height = Math.round((width / this.metadata.video.aspect.x) * this.metadata.video.aspect.y);
|
||
|
} else {
|
||
|
// Calculte the new height
|
||
|
height = Math.round(referrerResolution.h / (referrerResolution.w / parseInt(fixedWidth[1], 10)));
|
||
|
}
|
||
|
} else if (fixedHeight) {
|
||
|
// Set the width dimension
|
||
|
height = parseInt(fixedHeight[1], 10);
|
||
|
// Check if the video has the aspect ratio setted
|
||
|
if (!utils.isEmptyObj(this.metadata.video.aspect)) {
|
||
|
width = Math.round((height / this.metadata.video.aspect.y) * this.metadata.video.aspect.x);
|
||
|
} else {
|
||
|
// Calculte the new width
|
||
|
width = Math.round(referrerResolution.w / (referrerResolution.h / parseInt(fixedHeight[1], 10)));
|
||
|
}
|
||
|
} else if (percentage) {
|
||
|
// Calculte the ratio from percentage
|
||
|
var ratio = parseInt(percentage[1], 10) / 100;
|
||
|
// Calculate the new dimensions
|
||
|
width = Math.round(referrerResolution.w * ratio);
|
||
|
height = Math.round(referrerResolution.h * ratio);
|
||
|
} else if (classicSize) {
|
||
|
width = parseInt(classicSize[1], 10);
|
||
|
height = parseInt(classicSize[2], 10);
|
||
|
} else
|
||
|
throw errors.renderError('size_format', options.video.size);
|
||
|
|
||
|
// If the width or height are not multiples of 2 will be decremented by one unit
|
||
|
if (width % 2 != 0) width -= 1;
|
||
|
if (height % 2 != 0) height -= 1;
|
||
|
|
||
|
if (keepAspectRatio) {
|
||
|
// Calculate the new aspect ratio
|
||
|
var gcdValue = utils.gcd(width, height);
|
||
|
|
||
|
aspect = new Object();
|
||
|
aspect.x = width / gcdValue;
|
||
|
aspect.y = height / gcdValue;
|
||
|
aspect.string = aspect.x + ':' + aspect.y;
|
||
|
}
|
||
|
|
||
|
return { width : width, height : height, aspect : aspect };
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Executing the commands list
|
||
|
*/
|
||
|
var execCommand = function (callback, folder) {
|
||
|
// Checking if folder is defined
|
||
|
var onlyDestinationFile = folder != undefined ? false : true;
|
||
|
// 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
|
||
|
var deferred = typeof callback != 'function' ? when.defer() : { promise : null };
|
||
|
// Create a copy of the commands list
|
||
|
var finalCommands = ['ffmpeg -i']
|
||
|
.concat(inputs.join(' -i '))
|
||
|
.concat(commands.join(' '))
|
||
|
.concat(filtersComlpex.length > 0 ? ['-filter_complex "'].concat(filtersComlpex.join(', ')).join('') + '"' : [])
|
||
|
.concat([output]);
|
||
|
// Reset commands
|
||
|
resetCommands(this);
|
||
|
// Execute the commands from the list
|
||
|
utils.exec(finalCommands, settings, function (error, stdout, stderr) {
|
||
|
// Building the result
|
||
|
var result = null;
|
||
|
if (!error) {
|
||
|
// Check if show only destination filename or the complete file list
|
||
|
if (onlyDestinationFile) {
|
||
|
result = finalCommands[finalCommands.length-1];
|
||
|
} else {
|
||
|
// Clean possible "/" at the end of the string
|
||
|
if (folder.charAt(folder.length-1) == "/")
|
||
|
folder = folder.substr(0, folder.length-1);
|
||
|
// Read file list inside the folder
|
||
|
result = fs.readdirSync(folder);
|
||
|
// Scan all file and prepend the folder path
|
||
|
for (var i in result)
|
||
|
result[i] = [folder, result[i]].join('/')
|
||
|
}
|
||
|
}
|
||
|
// Check if the callback is a function
|
||
|
if (typeof callback == 'function') {
|
||
|
// Call the callback to return the info
|
||
|
callback(error, result);
|
||
|
} else {
|
||
|
if (error) {
|
||
|
// Negative response
|
||
|
deferred.reject(error);
|
||
|
} else {
|
||
|
// Positive response
|
||
|
deferred.resolve(result);
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
// Return a possible promise instance
|
||
|
return deferred.promise;
|
||
|
}
|
||
|
|
||
|
/*******************/
|
||
|
/* PRESET FUNCTION */
|
||
|
/*******************/
|
||
|
|
||
|
/**
|
||
|
* Extracting sound from a video, and save it as Mp3
|
||
|
*/
|
||
|
this.fnExtractSoundToMP3 = function (destionationFileName, callback) {
|
||
|
// Check if file already exists. In this case will remove it
|
||
|
if (fs.existsSync(destionationFileName))
|
||
|
fs.unlinkSync(destionationFileName);
|
||
|
|
||
|
// Building the final path
|
||
|
var destinationDirName = path.dirname(destionationFileName)
|
||
|
, destinationFileNameWE = path.basename(destionationFileName, path.extname(destionationFileName)) + '.mp3'
|
||
|
, finalPath = path.join(destinationDirName, destinationFileNameWE);
|
||
|
|
||
|
resetCommands(this);
|
||
|
|
||
|
// Adding commands to the list
|
||
|
this.addCommand('-vn');
|
||
|
this.addCommand('-ar', 44100);
|
||
|
this.addCommand('-ac', 2);
|
||
|
this.addCommand('-ab', 192);
|
||
|
this.addCommand('-f', 'mp3');
|
||
|
|
||
|
// Add destination file path to the command list
|
||
|
setOutput(finalPath);
|
||
|
|
||
|
// Executing the commands list
|
||
|
return execCommand.call(this, callback);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Extract frame from video file
|
||
|
*/
|
||
|
this.fnExtractFrameToJPG = function (/* destinationFolder, settings, callback */) {
|
||
|
|
||
|
var destinationFolder = null
|
||
|
, newSettings = null
|
||
|
, callback = null;
|
||
|
|
||
|
var settings = {
|
||
|
start_time : null // Start time to recording
|
||
|
, duration_time : null // Duration of recording
|
||
|
, frame_rate : null // Number of the frames to capture in one second
|
||
|
, size : null // Dimension each frame
|
||
|
, number : null // Total frame to capture
|
||
|
, every_n_frames : null // Frame to capture every N frames
|
||
|
, every_n_seconds : null // Frame to capture every N seconds
|
||
|
, every_n_percentage : null // Frame to capture every N percentage range
|
||
|
, keep_pixel_aspect_ratio : true // Mantain the original pixel video aspect ratio
|
||
|
, keep_aspect_ratio : true // Mantain the original aspect ratio
|
||
|
, padding_color : 'black' // Padding color
|
||
|
, file_name : null // File name
|
||
|
};
|
||
|
|
||
|
// Scan all arguments
|
||
|
for (var i in arguments) {
|
||
|
// Check the type of the argument
|
||
|
switch (typeof arguments[i]) {
|
||
|
case 'string':
|
||
|
destinationFolder = arguments[i];
|
||
|
break;
|
||
|
case 'object':
|
||
|
newSettings = arguments[i];
|
||
|
break;
|
||
|
case 'function':
|
||
|
callback = arguments[i];
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Check if the settings are specified
|
||
|
if (newSettings !== null)
|
||
|
utils.mergeObject(settings, newSettings);
|
||
|
|
||
|
// Check if 'start_time' is in the format hours:minutes:seconds
|
||
|
if (settings.start_time != null) {
|
||
|
if (/([0-9]+):([0-9]{2}):([0-9]{2})/.exec(settings.start_time))
|
||
|
settings.start_time = utils.durationToSeconds(settings.start_time);
|
||
|
else if (!isNaN(settings.start_time))
|
||
|
settings.start_time = parseInt(settings.start_time, 10);
|
||
|
else
|
||
|
settings.start_time = null;
|
||
|
}
|
||
|
|
||
|
// Check if 'duration_time' is in the format hours:minutes:seconds
|
||
|
if (settings.duration_time != null) {
|
||
|
if (/([0-9]+):([0-9]{2}):([0-9]{2})/.exec(settings.duration_time))
|
||
|
settings.duration_time = utils.durationToSeconds(settings.duration_time);
|
||
|
else if (!isNaN(settings.duration_time))
|
||
|
settings.duration_time = parseInt(settings.duration_time, 10);
|
||
|
else
|
||
|
settings.duration_time = null;
|
||
|
}
|
||
|
|
||
|
// Check if the value of the framerate is number type
|
||
|
if (settings.frame_rate != null && isNaN(settings.frame_rate))
|
||
|
settings.frame_rate = null;
|
||
|
|
||
|
// If the size is not settings then the size of the screenshots is equal to video size
|
||
|
if (settings.size == null)
|
||
|
settings.size = this.metadata.video.resolution.w + 'x' + this.metadata.video.resolution.h;
|
||
|
|
||
|
// Check if the value of the 'number frame to capture' is number type
|
||
|
if (settings.number != null && isNaN(settings.number))
|
||
|
settings.number = null;
|
||
|
|
||
|
var every_n_check = 0;
|
||
|
|
||
|
// Check if the value of the 'every_n_frames' is number type
|
||
|
if (settings.every_n_frames != null && isNaN(settings.every_n_frames)) {
|
||
|
settings.every_n_frames = null;
|
||
|
every_n_check++;
|
||
|
}
|
||
|
|
||
|
// Check if the value of the 'every_n_seconds' is number type
|
||
|
if (settings.every_n_seconds != null && isNaN(settings.every_n_seconds)) {
|
||
|
settings.every_n_seconds = null;
|
||
|
every_n_check++;
|
||
|
}
|
||
|
|
||
|
// Check if the value of the 'every_n_percentage' is number type
|
||
|
if (settings.every_n_percentage != null && (isNaN(settings.every_n_percentage) || settings.every_n_percentage > 100)) {
|
||
|
settings.every_n_percentage = null;
|
||
|
every_n_check++;
|
||
|
}
|
||
|
|
||
|
if (every_n_check >= 2) {
|
||
|
if (callback) {
|
||
|
callback(errors.renderError('extract_frame_invalid_everyN_options'));
|
||
|
} else {
|
||
|
throw errors.renderError('extract_frame_invalid_everyN_options');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If filename is null then his value is equal to original filename
|
||
|
if (settings.file_name == null) {
|
||
|
settings.file_name = path.basename(this.file_path, path.extname(this.file_path));
|
||
|
} else {
|
||
|
// Retrieve all possible replacements
|
||
|
var replacements = settings.file_name.match(/(\%[a-zA-Z]{1})/g);
|
||
|
// Check if exists replacements. The scan all replacements and build the final filename
|
||
|
if (replacements) {
|
||
|
for (var i in replacements) {
|
||
|
switch (replacements[i]) {
|
||
|
case '%t':
|
||
|
settings.file_name = settings.file_name.replace('%t', new Date().getTime());
|
||
|
break;
|
||
|
case '%s':
|
||
|
settings.file_name = settings.file_name.replace('%s', settings.size);
|
||
|
break;
|
||
|
case '%x':
|
||
|
settings.file_name = settings.file_name.replace('%x', settings.size.split(':')[0]);
|
||
|
break;
|
||
|
case '%y':
|
||
|
settings.file_name = settings.file_name.replace('%y', settings.size.split(':')[1]);
|
||
|
break;
|
||
|
default:
|
||
|
settings.file_name = settings.file_name.replace(replacements[i], '');
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// At the filename will added the number of the frame
|
||
|
settings.file_name = path.basename(settings.file_name, path.extname(settings.file_name)) + '_%d.jpg';
|
||
|
|
||
|
// Create the directory to save the extracted frames
|
||
|
utils.mkdir(destinationFolder, 0777);
|
||
|
|
||
|
resetCommands(this);
|
||
|
|
||
|
// Adding commands to the list
|
||
|
if (settings.startTime)
|
||
|
this.addCommand('-ss', settings.startTime);
|
||
|
if (settings.duration_time)
|
||
|
this.addCommand('-t', settings.duration_time);
|
||
|
if (settings.frame_rate)
|
||
|
this.addCommand('-r', settings.frame_rate);
|
||
|
|
||
|
// Setting the size and padding settings
|
||
|
this.setVideoSize(settings.size, settings.keep_pixel_aspect_ratio, settings.keep_aspect_ratio, settings.padding_color);
|
||
|
// Get the dimensions
|
||
|
var newDimension = _calculateNewDimension.call(this);
|
||
|
// Apply the size and padding commands
|
||
|
this.addCommand('-s', newDimension.width + 'x' + newDimension.height);
|
||
|
// CHeck if isset aspect ratio options
|
||
|
if (newDimension.aspect != null) {
|
||
|
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 : ''));
|
||
|
this.addCommand('-aspect', newDimension.aspect.string);
|
||
|
}
|
||
|
|
||
|
if (settings.number)
|
||
|
this.addCommand('-vframes', settings.number);
|
||
|
if (settings.every_n_frames) {
|
||
|
this.addCommand('-vsync', 0);
|
||
|
this.addFilterComplex('select=not(mod(n\\,' + settings.every_n_frames + '))');
|
||
|
}
|
||
|
if (settings.every_n_seconds) {
|
||
|
this.addCommand('-vsync', 0);
|
||
|
this.addFilterComplex('select=not(mod(t\\,' + settings.every_n_seconds + '))');
|
||
|
}
|
||
|
if (settings.every_n_percentage) {
|
||
|
this.addCommand('-vsync', 0);
|
||
|
this.addFilterComplex('select=not(mod(t\\,' + parseInt((this.metadata.duration.seconds / 100) * settings.every_n_percentage) + '))');
|
||
|
}
|
||
|
|
||
|
// Add destination file path to the command list
|
||
|
setOutput([destinationFolder,settings.file_name].join('/'));
|
||
|
|
||
|
// Executing the commands list
|
||
|
return execCommand.call(this, callback, destinationFolder);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Add a watermark to the video and save it
|
||
|
*/
|
||
|
this.fnAddWatermark = function (watermarkPath /* newFilepath , settings, callback */) {
|
||
|
|
||
|
var newFilepath = null
|
||
|
, newSettings = null
|
||
|
, callback = null;
|
||
|
|
||
|
// Scan all arguments
|
||
|
for (var i = 1; i < arguments.length; i++) {
|
||
|
// Check the type of the argument
|
||
|
switch (typeof arguments[i]) {
|
||
|
case 'string':
|
||
|
newFilepath = arguments[i];
|
||
|
break;
|
||
|
case 'object':
|
||
|
newSettings = arguments[i];
|
||
|
break;
|
||
|
case 'function':
|
||
|
callback = arguments[i];
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
resetCommands(this);
|
||
|
|
||
|
// Call the function to add the watermark options
|
||
|
this.setWatermark(watermarkPath, newSettings, true);
|
||
|
|
||
|
if (newFilepath == null)
|
||
|
newFilepath = path.dirname(this.file_path) + '/' +
|
||
|
path.basename(this.file_path, path.extname(this.file_path)) + '_watermark_' +
|
||
|
path.basename(watermarkPath, path.extname(watermarkPath)) +
|
||
|
path.extname(this.file_path);
|
||
|
|
||
|
// Add destination file path to the command list
|
||
|
setOutput(newFilepath);
|
||
|
|
||
|
// Executing the commands list
|
||
|
return execCommand.call(this, callback);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Constructor
|
||
|
*/
|
||
|
var __constructor = function (self) {
|
||
|
resetCommands(self);
|
||
|
}(this);
|
||
|
}
|