Comparing version 0.0.1 to 0.0.4
@@ -14,4 +14,6 @@ Scoped = require("betajs-scoped/dist/scoped.js"); | ||
ffmpeg_simple: require(__dirname + "/src/ffmpeg-simple.js").ffmpeg_simple | ||
ffmpeg_simple: require(__dirname + "/src/ffmpeg-simple.js").ffmpeg_simple, | ||
ffmpeg_volume_detect: require(__dirname + "/src/ffmpeg-volume-detect.js").ffmpeg_volume_detect | ||
}; |
{ | ||
"name": "js-ffmpeg", | ||
"description": "JS FFMpeg", | ||
"version": "0.0.1", | ||
"version": "0.0.4", | ||
"author": "Ziggeo", | ||
@@ -6,0 +6,0 @@ "repository": "https://github.com/jsonize/jsffmpeg", |
@@ -1,8 +0,6 @@ | ||
# js-ffmpeg 0.0.1 | ||
# js-ffmpeg 0.0.4 | ||
This is a simple wrapper for FFMPEG and FFPROBE. | ||
This is very much work in progress. | ||
## Getting Started | ||
@@ -39,3 +37,3 @@ | ||
// raw call of ffprobe (source(s), arguments, target, progress callback) | ||
// raw call of ffmpeg (source(s), arguments, target, progress callback) | ||
ffmpeg.ffmpeg('video.mp4', [...], 'output.mp4', function (progress) { | ||
@@ -48,2 +46,19 @@ console.log(progress); | ||
}); | ||
// improved and simplified call of ffmpeg (source(s), arguments, target, progress callback) | ||
ffmpeg.ffmpeg('video.mp4', { | ||
width: 640, | ||
height: 360, | ||
auto_rotate: true, | ||
ratio_strategy: "fixed", | ||
shrink_strategy: "crop", | ||
mixed_strategy: "crop-pad", | ||
stretch_strategy: "pad" | ||
}, 'output.mp4', function (progress) { | ||
console.log(progress); | ||
}).success(function (json) { | ||
console.log(json); | ||
}).error(function (error) { | ||
console.log(error); | ||
}); | ||
``` |
@@ -23,3 +23,4 @@ Scoped.require([ | ||
passes: 2, | ||
modulus: 2 | ||
modulus: 2, | ||
params: "-pix_fmt yuv420p" | ||
}, | ||
@@ -26,0 +27,0 @@ "ogg": { |
@@ -9,2 +9,3 @@ Scoped.require([ | ||
var ffprobe_simple = require(__dirname + "/ffprobe-simple.js"); | ||
var ffmpeg_volume_detect = require(__dirname + "/ffmpeg-volume-detect.js"); | ||
var helpers = require(__dirname + "/ffmpeg-helpers.js"); | ||
@@ -18,23 +19,56 @@ | ||
files = [files]; | ||
options = Objs.extend({ | ||
output_type: "video", // video, audio, image | ||
synchronize: true, | ||
framerate: 25, // null | ||
framerate_gop: 250, | ||
image_percentage: null, | ||
image_position: null, | ||
time_limit: null, | ||
time_start: 0, | ||
time_end: null, | ||
video_map: null, //0,1,2,... | ||
audio_map: null, //0,1,2 | ||
video_profile: "baseline", | ||
faststart: true, | ||
video_format: "mp4", | ||
audio_bit_rate: null, | ||
video_bit_rate: null, | ||
normalize_audio: false, | ||
width: null, | ||
height: null, | ||
auto_rotate: true, | ||
ratio_strategy: "fixed", // "shrink", "stretch" | ||
shrink_strategy: "shrink-pad", // "crop", "shrink-crop" | ||
stretch_strategy: "pad", // "stretch-pad", "stretch-crop" | ||
mixed_strategy: "shrink-pad", // "stretch-crop", "crop-pad" | ||
watermark: null, | ||
watermark_size: 0.25, | ||
watermark_x: 0.95, | ||
watermark_y: 0.95 | ||
}, options); | ||
return Promise.and(files.map(function (file) { | ||
var promises = files.map(function (file) { | ||
return ffprobe_simple.ffprobe_simple(file); | ||
})).mapSuccess(function (infos) { | ||
options = Objs.extend({ | ||
output_type: "video", // audio, image | ||
synchronize: true, | ||
framerate: 25, // null | ||
framerate_gop: 250, | ||
image_percentage: null, | ||
image_position: null, | ||
time_limit: null, | ||
time_start: 0, | ||
time_end: null, | ||
video_map: null, //0,1,2,... | ||
audio_map: null, //0,1,2 | ||
video_profile: "baseline", | ||
faststart: true, | ||
video_format: "mp4" | ||
}, options); | ||
}); | ||
if (options.normalize_audio) | ||
promises.push(ffmpeg_volume_detect.ffmpeg_volume_detect(files[options.audio_map || 0])); | ||
if (options.watermark) | ||
promises.push(ffprobe_simple.ffprobe_simple(options.watermark)); | ||
return Promise.and(promises).mapSuccess(function (infos) { | ||
var watermarkInfo = null; | ||
if (options.watermark) | ||
watermarkInfo = infos.pop(); | ||
var audioNormalizationInfo = null; | ||
if (options.normalize_audio) | ||
audioNormalizationInfo = infos.pop(); | ||
var passes = 1; | ||
@@ -69,3 +103,13 @@ | ||
/* | ||
* | ||
* Audio Normalization? | ||
* | ||
*/ | ||
if (audioNormalizationInfo) { | ||
args.push("-af"); | ||
args.push("volume=" + (-audioNormalizationInfo.max_volume) + "dB"); | ||
} | ||
/* | ||
@@ -83,22 +127,167 @@ * | ||
/* | ||
* | ||
* Which sizing should be used? | ||
* | ||
*/ | ||
var videoInfo = infos[0].video; | ||
var audioInfo = infos[1] ? infos[1].audio || infos[0].audio : infos[0].audio; | ||
var sourceWidth = 0; | ||
var sourceHeight = 0; | ||
var targetWidth = 0; | ||
var targetHeight = 0; | ||
//try { | ||
if (options.output_type !== 'audio') { | ||
// TODO: Rotation, Resize, Pad, Crop | ||
var source = infos[0]; | ||
sourceWidth = source.video.rotated_width; | ||
sourceHeight = source.video.rotated_height; | ||
var sourceRatio = sourceWidth / sourceHeight; | ||
targetWidth = sourceWidth; | ||
targetHeight = sourceHeight; | ||
var targetRatio = sourceRatio; | ||
var ratioSourceTarget = 0; | ||
// '-vf' 'transpose=1' '-metadata:s:v:0' 'rotate=0' | ||
// '-vf' 'transpose=1' '-metadata:s:v:0' 'rotate=0' '-s' '270x480' | ||
} | ||
/* | ||
* | ||
* Which sizing should be used? | ||
* | ||
*/ | ||
// Step 1: Fix Rotation | ||
if (options.auto_rotate && source.video.rotation) { | ||
if (source.video.rotation % 180 === 90) { | ||
args.push("-vf"); | ||
args.push("transpose=" + (source.video.rotation === 90 ? 1 : 2)); | ||
} | ||
if (source.video.otation >= 180) { | ||
args.push("-vf"); | ||
args.push("hflip,vflip"); | ||
} | ||
args.push("-metadata:s:v:0"); | ||
args.push("rotate=0"); | ||
} | ||
if (options.width && options.height) { | ||
// Step 2: Fix Size & Ratio | ||
targetWidth = options.width; | ||
targetHeight = options.height; | ||
targetRatio = targetWidth / targetHeight; | ||
ratioSourceTarget = Math.sign(sourceWidth * targetHeight - targetWidth * sourceHeight); | ||
if (options.ratio_strategy !== "fixed" && ratioSourceTarget !== 0) { | ||
if ((options.ratio_strategy === "stretch" && ratioSourceTarget > 0) || (options.ratio_strategy === "shrink" && ratioSourceTarget < 0)) | ||
targetWidth = targetHeight * sourceRatio; | ||
if ((options.ratio_strategy === "stretch" && ratioSourceTarget < 0) || (options.ratio_strategy === "shrink" && ratioSourceTarget > 0)) | ||
targetHeight = targetWidth / sourceRatio; | ||
targetRatio = sourceRatio; | ||
ratioSourceTarget = 0; | ||
} | ||
var vf = []; | ||
// Step 3: Modulus | ||
var modulus = options.output_type === 'video' ? helpers.videoFormats[options.video_format].modulus || 1 : 1; | ||
targetWidth = targetWidth % modulus === 0 ? targetWidth : (Math.round(targetWidth / modulus) * modulus); | ||
targetHeight = targetHeight % modulus === 0 ? targetHeight : (Math.round(targetHeight / modulus) * modulus); | ||
var cropped = false; | ||
var addCrop = function (x, y, multi) { | ||
x = Math.round(x); | ||
y = Math.round(y); | ||
if (x === 0 && y === 0) | ||
return; | ||
cropped = true; | ||
var cropWidth = targetWidth - 2 * x; | ||
var cropHeight = targetHeight - 2 * y; | ||
args.push("-vf"); | ||
args.push("scale=" + [multi || ratioSourceTarget >= 0 ? cropWidth : targetWidth, !multi && ratioSourceTarget >= 0 ? targetHeight : cropHeight].join(":") + "," + | ||
"crop=" + [!multi && ratioSourceTarget <= 0 ? cropWidth : targetWidth, multi || ratioSourceTarget <= 0 ? targetHeight : cropHeight, -x, -y].join(":")); | ||
}; | ||
var padded = false; | ||
var addPad = function (x, y, multi) { | ||
x = Math.round(x); | ||
y = Math.round(y); | ||
if (x === 0 && y === 0) | ||
return; | ||
padded = true; | ||
var padWidth = targetWidth - 2 * x; | ||
var padHeight = targetHeight - 2 * y; | ||
args.push("-vf"); | ||
args.push("scale=" + [multi || ratioSourceTarget <= 0 ? padWidth : targetWidth, !multi && ratioSourceTarget <= 0 ? targetHeight : padHeight].join(":") + "," + | ||
"pad=" + [!multi && ratioSourceTarget >= 0 ? padWidth : targetWidth, multi || ratioSourceTarget >= 0 ? targetHeight : padHeight, x, y].join(":")); | ||
}; | ||
// Step 4: Crop & Pad | ||
if (targetWidth >= sourceWidth && targetHeight >= sourceHeight) { | ||
if (options.stretch_strategy === "pad") | ||
addPad((targetWidth - sourceWidth) / 2, | ||
(targetHeight - sourceHeight) / 2, | ||
true); | ||
else if (options.stretch_strategy === "stretch-pad") | ||
addPad(ratioSourceTarget <= 0 ? (targetWidth - targetHeight * sourceRatio) / 2 : 0, | ||
ratioSourceTarget >= 0 ? (targetHeight - targetWidth / sourceRatio) / 2 : 0); | ||
else // stretch-crop | ||
addCrop(ratioSourceTarget >= 0 ? (targetWidth - targetHeight * sourceRatio) / 2 : 0, | ||
ratioSourceTarget <= 0 ? (targetHeight - targetWidth / sourceRatio) / 2 : 0); | ||
} else if (targetWidth <= sourceWidth && targetHeight <= sourceHeight) { | ||
if (options.shrink_strategy === "crop") | ||
addCrop((targetWidth - sourceWidth) / 2, | ||
(targetHeight - sourceHeight) / 2, | ||
true); | ||
else if (options.shrink_strategy === "shrink-crop") | ||
addCrop(ratioSourceTarget >= 0 ? (targetWidth - targetHeight * sourceRatio) / 2 : 0, | ||
ratioSourceTarget <= 0 ? (targetHeight - targetWidth / sourceRatio) / 2 : 0); | ||
else // shrink-pad | ||
addPad(ratioSourceTarget <= 0 ? (targetWidth - targetHeight * sourceRatio) / 2 : 0, | ||
ratioSourceTarget >= 0 ? (targetHeight - targetWidth / sourceRatio) / 2 : 0); | ||
} else { | ||
if (options.mixed_strategy === "shrink-pad") | ||
addPad(ratioSourceTarget <= 0 ? (targetWidth - targetHeight * sourceRatio) / 2 : 0, | ||
ratioSourceTarget >= 0 ? (targetHeight - targetWidth / sourceRatio) / 2 : 0); | ||
else if (options.mixed_strategy === "stretch-crop") | ||
addCrop(ratioSourceTarget >= 0 ? (targetWidth - targetHeight * sourceRatio) / 2 : 0, | ||
ratioSourceTarget <= 0 ? (targetHeight - targetWidth / sourceRatio) / 2 : 0); | ||
else { | ||
// crop-pad | ||
cropped = true; | ||
padded = true; | ||
var direction = ratioSourceTarget >= 0; | ||
var dirX = Math.abs(Math.round((sourceWidth - targetWidth) / 2)); | ||
var dirY = Math.abs(Math.round((sourceHeight - targetHeight) / 2)); | ||
args.push("-vf"); | ||
args.push("crop=" + [direction ? targetWidth : sourceWidth, direction ? sourceHeight : targetHeight, direction ? dirX : 0, direction ? 0 : dirY].join(":") + "," + | ||
"pad=" + [targetWidth, targetHeight, direction ? 0 : dirX, direction ? dirY : 0].join(":")); | ||
} | ||
} | ||
if (!padded && !cropped) { | ||
args.push("-s"); | ||
args.push(targetWidth + "x" + targetHeight); | ||
} | ||
} | ||
/* | ||
* | ||
* Watermark (depends on sizing) | ||
* | ||
*/ | ||
if (options.output_type !== 'audio') { | ||
// TODO: Watermark | ||
/* | ||
* | ||
* Watermark (depends on sizing) | ||
* | ||
*/ | ||
if (watermarkInfo) { | ||
var scaleWidth = watermarkInfo.video.width; | ||
var scaleHeight = watermarkInfo.video.height; | ||
var maxWidth = targetWidth * options.watermark_size; | ||
var maxHeight = targetHeight * options.watermark_size; | ||
if (scaleWidth > maxWidth || scaleHeight > maxHeight) { | ||
var watermarkRatio = maxWidth * scaleHeight >= maxHeight * scaleWidth; | ||
scaleWidth = watermarkRatio ? scaleWidth * maxHeight / scaleHeight : maxWidth; | ||
scaleHeight = !watermarkRatio ? scaleHeight * maxWidth / scaleWidth : maxHeight; | ||
} | ||
var posX = options.watermark_x * (targetWidth - scaleWidth); | ||
var posY = options.watermark_y * (targetHeight - scaleHeight); | ||
args.push("-vf"); | ||
args.push("movie=" + watermarkInfo.filename + "," + | ||
"scale=" + [Math.round(scaleWidth), Math.round(scaleHeight)].join(":") + "[wm];[in][wm]" + | ||
"overlay=" + [Math.round(posX), Math.round(posY)].join(":") + "[out]"); | ||
} | ||
} | ||
@@ -115,5 +304,5 @@ | ||
if (options.output_type === 'video') { | ||
if (options.video_profile && format === "mp4") | ||
if (options.video_profile && options.video_format === "mp4") | ||
args.push(helpers.paramsVideoProfile(options.video_profile)); | ||
if (options.faststart && format === "mp4") | ||
if (options.faststart && options.video_format === "mp4") | ||
args.push(helpers.paramsFastStart); | ||
@@ -125,3 +314,3 @@ var format = helpers.videoFormats[options.video_format]; | ||
args.push(helpers.paramsFramerate(options.framerate, format.bframes, options.framerate_gop)); | ||
args.push(helpers.paramsVideoCodecUniversalConfig()); | ||
args.push(helpers.paramsVideoCodecUniversalConfig); | ||
if (format && format.passes > 1) | ||
@@ -137,9 +326,21 @@ passes = format.passes; | ||
*/ | ||
// '-b:v' '211k' | ||
// '-b:a' '64k' | ||
if (options.output_type === "video") { | ||
args.push("-b:v"); | ||
var video_bit_rate = options.video_bit_rate || Math.min(videoInfo.bit_rate * targetWidth * targetHeight / sourceWidth / sourceHeight, videoInfo.bit_rate); | ||
args.push(Math.round(video_bit_rate / 1000) + "k"); | ||
if (audioInfo) { | ||
args.push("-b:a"); | ||
var audio_bit_rate = options.audio_bit_rate || audioInfo.bit_rate; | ||
args.push(Math.round(audio_bit_rate / 1000) + "k"); | ||
} | ||
} | ||
//} catch(e) {console.log(e);} | ||
//console.log(files, args, passes, output); | ||
return ffmpeg_multi_pass.ffmpeg_multi_pass(files, args, passes, output, function (progress) { | ||
if (eventCallback) | ||
eventCallback.call(eventContext || this, helper.parseProgress(progress, duration)); | ||
}, this).mapSuccess(function () { | ||
return ffprobe_simple.ffprobe_simple(output); | ||
}, this); | ||
@@ -146,0 +347,0 @@ }); |
@@ -22,3 +22,4 @@ Scoped.require([ | ||
commands.push(output); | ||
var file = require("child_process").spawn("ffmpeg", commands); | ||
// console.log(commands.join(" ")); | ||
var file = require("child_process").spawn("ffmpeg", commands.join(" ").split(" ")); | ||
var lines = ""; | ||
@@ -25,0 +26,0 @@ file.stderr.on("data", function (data) { |
@@ -25,7 +25,10 @@ Scoped.require([ | ||
if (stream.codec_type === 'video') { | ||
var rotation = stream.tags && stream.tags.rotate ? parseInt(stream.tags.rotate, 10) : 0; | ||
result.video = { | ||
index: stream.index, | ||
rotation: stream.tags && stream.tags.rotate ? parseInt(stream.tags.rotate, 10) : 0, | ||
rotation: rotation, | ||
width: stream.width, | ||
height: stream.height, | ||
rotated_width: rotation % 180 === 0 ? stream.width : stream.height, | ||
rotated_height: rotation % 180 === 0 ? stream.height : stream.width, | ||
codec_name: stream.codec_tag_string, | ||
@@ -32,0 +35,0 @@ codec_long_name: stream.codec_long_name, |
@@ -8,3 +8,3 @@ var ffmpeg = require(__dirname + "/../../index.js"); | ||
stop(); | ||
ffmpeg.ffprobe_simple(NOT_EXISTING_VIDEO).callback(function (error, value) { | ||
ffmpeg.ffprobe_simple(NOT_EXISTING_VIDEO).callback(function(error, value) { | ||
QUnit.equal(error, 'File does not exist'); | ||
@@ -17,3 +17,3 @@ start(); | ||
stop(); | ||
ffmpeg.ffprobe_simple(ROTATED_MOV_VIDEO).callback(function (error, value) { | ||
ffmpeg.ffprobe_simple(ROTATED_MOV_VIDEO).callback(function(error, value) { | ||
QUnit.deepEqual(value, { | ||
@@ -27,4 +27,3 @@ filename : ROTATED_MOV_VIDEO, | ||
format_name : 'QuickTime / MOV', | ||
format_extensions : [ 'mov', 'mp4', | ||
'm4a', '3gp', '3g2', 'mj2' ], | ||
format_extensions : [ 'mov', 'mp4', 'm4a', '3gp', '3g2', 'mj2' ], | ||
format_default_extension : 'mov', | ||
@@ -45,2 +44,4 @@ audio : { | ||
height : 320, | ||
rotated_width : 320, | ||
rotated_height : 568, | ||
codec_name : 'avc1', | ||
@@ -54,2 +55,2 @@ codec_long_name : 'H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10', | ||
}); | ||
}); | ||
}); |
var ffmpeg = require(__dirname + "/../../index.js"); | ||
var ROTATED_MOV_VIDEO = "tests/assets/iphone_rotated.mov"; | ||
var IMAGE_FILE = "tests/assets/logo.png"; | ||
@@ -11,2 +12,11 @@ test("ffprobe rotated mov", function() { | ||
}); | ||
}); | ||
test("ffprobe image", function() { | ||
stop(); | ||
ffmpeg.ffprobe(IMAGE_FILE).callback(function(error, value) { | ||
QUnit.deepEqual(value.format.nb_streams, 1); | ||
start(); | ||
}); | ||
}); |
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
304595
23
1139
63
20
3