// -*- coding: utf-8 -*- // vi:si:et:sw=2:sts=2:ts=2:ft=javascript Components.utils.import("resource://firefogg/subprocess.jsm"); Components.utils.import("resource://firefogg/utils.jsm"); let EXPORTED_SYMBOLS = [ "FirefoggEncoder", "ffenc" ]; const Cc = Components.classes; const Ci = Components.interfaces; const FIREFOGG_ID = "firefogg@firefogg.org"; let ffenc = { ready: false, _init: function() { if(!this.ready) { var _this = this; _this.ready=true; var exec_name = "ffmpeg2theora"; var xulRuntime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime); this._is_windows = false; if (xulRuntime.OS.indexOf("WIN") != -1) { exec_name = "ffmpeg2theora.exe"; this._is_windows = true; } var file = __LOCATION__.parent.parent.QueryInterface(Ci.nsILocalFile); file.appendRelativePath('chrome'); file.appendRelativePath('content'); file.appendRelativePath('mime.types'); this.mimetypesfile = file.path; var file = __LOCATION__.parent.parent.QueryInterface(Ci.nsILocalFile); file.appendRelativePath('bin'); file.appendRelativePath(exec_name); var ffmpeg2theora = file.path; if (file.exists()) { this._cmd_base = ffmpeg2theora.replace('ffmpeg2theora', '_cmd_'); } else { this._cmd_base = "/usr/local/bin/_cmd_"; } } }, bin: function(cmd) { cmd = this._cmd_base.replace('_cmd_', cmd); return cmd; }, subprocess: function(command, args, callback, progress, mergeStderr) { var _this = this, result; mergeStderr = mergeStderr || false; /* var dbg = 'subprocess:\n'+command+' \\\n'; for(var i=0;i= 1) { var mimetype = line[0]; var mime_ext = line[line.length-1]; for(var j=1;j < line.length;j++) { mime_ext = line[j].replace(/^\s+|\s+$/g,""); if (mime_ext) { mime_ext = mime_ext.split(' '); for(var k=0;k < mime_ext.length;k++) { if (ext == mime_ext[k]) { return mimetype; } } } } } } } return "text/plain"; }, ffmpeg_options: function(input, output, options, info, pass) { if (typeof(pass) == 'undefined') { pass = 0; } var cmd_options = []; // i/o cmd_options[cmd_options.length] = "-i"; cmd_options[cmd_options.length] = input; if (options.preset) { //FIXME: check and support those /* libvpx-360p libvpx-720p libvpx-720p50_60 libvpx-1080p libvpx-1080p50_60 /* if (options.preset == "360p") { cmd_options[cmd_options.length] = "-vpre"; cmd_options[cmd_options.length] = "libvpx-360p"; } else if (options.preset == "720p") { cmd_options[cmd_options.length] = "-vpre"; cmd_options[cmd_options.length] = "libvpx-720p"; } else if (options.preset == "1080p") { cmd_options[cmd_options.length] = "-vpre"; cmd_options[cmd_options.length] = "libvpx-1080p"; } */ } if (options.novideo) { cmd_options[cmd_options.length] = "-vn"; } else { //FIXME: those options should be available via options, or some of them //cmd_options[cmd_options.length] = "-vpre"; //cmd_options[cmd_options.length] = "libvpx-firefogg"; var opts = "-y -skip_threshold 0 -rc_buf_aggressivity 0 -bufsize 6000k -rc_init_occupancy 4000 -threads 4".split(" "); for (i in opts) { cmd_options[cmd_options.length] = opts[i]; } if (options.videoQuality >= 0) { //map 0-10 to 63-0, higher values worse quality var quality = 63 - parseInt(parseInt(options.videoQuality)/10 * 63); cmd_options[cmd_options.length] = "-qmin"; cmd_options[cmd_options.length] = String(quality); cmd_options[cmd_options.length] = "-qmax"; cmd_options[cmd_options.length] = String(quality); } if (options.videoBitrate) { cmd_options[cmd_options.length] = "-qmin"; cmd_options[cmd_options.length] = '1'; cmd_options[cmd_options.length] = "-qmax"; cmd_options[cmd_options.length] = '51'; var vb = parseInt(options.videoBitrate) * 1000; cmd_options[cmd_options.length] = "-vb"; cmd_options[cmd_options.length] = String(vb); } cmd_options[cmd_options.length] = "-vcodec"; cmd_options[cmd_options.length] = "libvpx"; } if (options.aspect) { var display_aspect_ratio = options.aspect; } else { //dump('aspect ' + info.video[0]); if(info.video[0].display_aspect_ratio) { var display_aspect_ratio = info.video[0].display_aspect_ratio; } else { var display_aspect_ratio = info.video[0].width+":"+info.video[0].height; } } var dar = display_aspect_ratio.split(':'); dar = parseInt(dar[0]) / parseInt(dar[1]); cmd_options[cmd_options.length] = "-aspect"; cmd_options[cmd_options.length] = display_aspect_ratio; if (options.noUpscaling) { if(info.video) { var sourceWidth = info.video[0].width; var sourceHeight = info.video[0].height; var maxSourceSize = Math.max(sourceWidth, sourceHeight); if (options.maxSize && parseInt(options.maxSize) > maxSourceSize) options.maxSize = maxSourceSize; if (options.width && parseInt(options.width) > sourceWidth) options.width = sourceWidth; if (options.height && parseInt(options.height) > sourceHeight) options.height = sourceHeight; if (options.framerate) { function getFramerate(fps) { fps = fps.split(':'); if(_f.length == 2) fps = parseInt(fps[0]) / parseInt(fps[1]); else fps = parseFloat(fps[0]); } if(getFramerate(options.framerate) > getFramerate(info.video[0].framerate)) delete options.framerate; } } if(info.audio && info.audio[0]) { var sourceSamplerate = info.audio[0].samplerate; var sourceChannels = info.audio[0].channels; if (options.samplerate && parseInt(options.samplerate) > sourceSamplerate) delete options.samplerate; if (options.channels && parseInt(options.channels) > sourceChannels) delete options.channels; } } if (options.maxSize && parseInt(options.maxSize) > 0) { var sourceWidth = info.video[0].width; var sourceHeight = info.video[0].height; if (sourceWidth > sourceHeight) { var width = parseInt(options.maxSize); var height = parseInt(width / dar); height = height + height%2; } else { var height = parseInt(options.maxSize); var width = parseInt(height * dar); width = width + width%2; } cmd_options[cmd_options.length] = "-s"; cmd_options[cmd_options.length] = String(width) + "x" + String(height); } else if (options.width && parseInt(options.width) > 0) { cmd_options[cmd_options.length] = "-s"; cmd_options[cmd_options.length] = String(options.width) + "x" + String(options.height); } if (options.framerate) { cmd_options[cmd_options.length] = "-r"; cmd_options[cmd_options.length] = String(options.framerate); var _enc_framerate = options.framerate; } else { var _enc_framerate = info.video[0].framerate; } var _f = _enc_framerate.split(':'); if(_f.length == 2) _enc_framerate = parseInt(_f[0]) / parseInt(_f[1]); else _enc_framerate = parseFloat(_enc_framerate); if (options.starttime) { cmd_options[cmd_options.length] = "-ss"; cmd_options[cmd_options.length] = String(options.starttime); } if (options.endtime) { cmd_options[cmd_options.length] = "-t"; cmd_options[cmd_options.length] = String(parseInt(options.endtime)-parseInt(options.starttime)); } if (options.cropTop) { cmd_options[cmd_options.length] = "-croptop"; cmd_options[cmd_options.length] = String(options.cropTop); } if (options.cropBottom) { cmd_options[cmd_options.length] = "-cropbottom"; cmd_options[cmd_options.length] = String(options.cropBottom); } if (options.cropLeft) { cmd_options[cmd_options.length] = "-cropleft"; cmd_options[cmd_options.length] = String(options.cropLeft); } if (options.cropRight) { cmd_options[cmd_options.length] = "-cropright"; cmd_options[cmd_options.length] = String(options.cropRight); } var keyframeInterval = 250; if (options.keyframeInterval) { keyframeInterval = options.keyframeInterval; } cmd_options[cmd_options.length] = "-g"; cmd_options[cmd_options.length] = String(keyframeInterval); cmd_options[cmd_options.length] = "-keyint_min"; cmd_options[cmd_options.length] = String(keyframeInterval); /* if (options.bufferDelay) { cmd_options[cmd_options.length] = "--buf-delay"; cmd_options[cmd_options.length] = String(options.bufferDelay); } if (options.softTarget) { cmd_options[cmd_options.length] = "--soft-target"; } */ if (options.deinterlace) { cmd_options[cmd_options.length] = "-deinterlace"; } /* if (options.brightness) { cmd_options[cmd_options.length] = "--brightness"; cmd_options[cmd_options.length] = String(options.brightness); } if (options.contrast) { cmd_options[cmd_options.length] = "--contrast"; cmd_options[cmd_options.length] = String(options.contrast); } if (options.gamma) { cmd_options[cmd_options.length] = "--gamma"; cmd_options[cmd_options.length] = String(options.gamma); } if (options.saturation) { cmd_options[cmd_options.length] = "--saturation"; cmd_options[cmd_options.length] = String(options.saturation); } if (options.postprocessing) { cmd_options[cmd_options.length] = "--pp"; cmd_options[cmd_options.length] = String(options.pp); } */ if (pass == 1 || options.noaudio) { cmd_options[cmd_options.length] = "-an"; } else { //audio if (options.audioQuality) { cmd_options[cmd_options.length] = "-aq"; cmd_options[cmd_options.length] = String(options.audioQuality); } if (options.audioBitrate) { var ab = parseInt(options.audioBitrate) * 1000; cmd_options[cmd_options.length] = "-ab"; cmd_options[cmd_options.length] = String(ab); } if (options.samplerate) { cmd_options[cmd_options.length] = "-ar"; cmd_options[cmd_options.length] = String(options.samplerate); } if (options.channels) { cmd_options[cmd_options.length] = "-ac"; cmd_options[cmd_options.length] = String(options.channels); } cmd_options[cmd_options.length] = "-acodec"; cmd_options[cmd_options.length] = "libvorbis"; } cmd_options[cmd_options.length] = "-f"; cmd_options[cmd_options.length] = "webm"; if (pass!=0) { cmd_options[cmd_options.length] = "-pass"; cmd_options[cmd_options.length] = String(pass); cmd_options[cmd_options.length] = "-passlogfile"; cmd_options[cmd_options.length] = output + '.log'; } if (pass==1) { if(this._is_windows) { cmd_options[cmd_options.length] = 'NUL'; } else { cmd_options[cmd_options.length] = '/dev/null'; } } else { cmd_options[cmd_options.length] = output; } return cmd_options; }, ffmpeg2theora_options: function(input, output, options, info) { var cmd_options = []; // i/o cmd_options[cmd_options.length] = "--frontend"; cmd_options[cmd_options.length] = input; cmd_options[cmd_options.length] = "-o"; cmd_options[cmd_options.length] = output; //disable subtitles, since embeded subtitles can cause problems cmd_options[cmd_options.length] = "--nosubtitles"; //presets if (options.preset) { cmd_options[cmd_options.length] = "--preset"; cmd_options[cmd_options.length] = String(options.preset); } //video if (options.width && parseInt(options.width) > 0) { cmd_options[cmd_options.length] = "--width"; cmd_options[cmd_options.length] = String(options.width); } if (options.height && parseInt(options.height) > 0) { cmd_options[cmd_options.length] = "--height"; cmd_options[cmd_options.length] = String(options.height); } if (options.maxSize && parseInt(options.maxSize) > 0) { cmd_options[cmd_options.length] = "--max_size"; cmd_options[cmd_options.length] = String(options.maxSize); } if (options.noUpscaling) { cmd_options[cmd_options.length] = "--no-upscaling"; } if (options.videoQuality >= 0) { cmd_options[cmd_options.length] = "-v"; cmd_options[cmd_options.length] = String(options.videoQuality); } if (options.videoBitrate) { cmd_options[cmd_options.length] = "-V"; cmd_options[cmd_options.length] = String(options.videoBitrate); } if (options.twopass) { cmd_options[cmd_options.length] = "--two-pass"; } if (options.framerate) { cmd_options[cmd_options.length] = "-F"; cmd_options[cmd_options.length] = String(options.framerate); } if (options.aspect) { cmd_options[cmd_options.length] = "--aspect"; cmd_options[cmd_options.length] = String(options.aspect); } if (options.starttime) { cmd_options[cmd_options.length] = "--starttime"; cmd_options[cmd_options.length] = String(options.starttime); } if (options.endtime) { cmd_options[cmd_options.length] = "--endtime"; cmd_options[cmd_options.length] = String(options.endtime); } if (options.cropTop) { cmd_options[cmd_options.length] = "--croptop"; cmd_options[cmd_options.length] = String(options.cropTop); } if (options.cropBottom) { cmd_options[cmd_options.length] = "--cropbottom"; cmd_options[cmd_options.length] = String(options.cropBottom); } if (options.cropLeft) { cmd_options[cmd_options.length] = "--cropleft"; cmd_options[cmd_options.length] = String(options.cropLeft); } if (options.cropRight) { cmd_options[cmd_options.length] = "--cropright"; cmd_options[cmd_options.length] = String(options.cropRight); } if (options.keyframeInterval) { cmd_options[cmd_options.length] = "--keyint"; cmd_options[cmd_options.length] = String(options.keyframeInterval); } if (options.denoise) { cmd_options[cmd_options.length] = "--pp"; cmd_options[cmd_options.length] = "de"; } if (options.bufferDelay) { cmd_options[cmd_options.length] = "--buf-delay"; cmd_options[cmd_options.length] = String(options.bufferDelay); } if (options.softTarget) { cmd_options[cmd_options.length] = "--soft-target"; } if (options.deinterlace) { cmd_options[cmd_options.length] = "--deinterlace"; } if (options.brightness) { cmd_options[cmd_options.length] = "--brightness"; cmd_options[cmd_options.length] = String(options.brightness); } if (options.contrast) { cmd_options[cmd_options.length] = "--contrast"; cmd_options[cmd_options.length] = String(options.contrast); } if (options.gamma) { cmd_options[cmd_options.length] = "--gamma"; cmd_options[cmd_options.length] = String(options.gamma); } if (options.saturation) { cmd_options[cmd_options.length] = "--saturation"; cmd_options[cmd_options.length] = String(options.saturation); } if (options.postprocessing) { cmd_options[cmd_options.length] = "--pp"; cmd_options[cmd_options.length] = String(options.pp); } if (options.novideo) { cmd_options[cmd_options.length] = "--novideo"; cmd_options[cmd_options.length] = "--no-skeleton"; } //audio if (options.audioQuality) { cmd_options[cmd_options.length] = "-a"; cmd_options[cmd_options.length] = String(options.audioQuality); } if (options.audioBitrate) { cmd_options[cmd_options.length] = "-A"; cmd_options[cmd_options.length] = String(options.audioBitrate); } if (options.samplerate) { cmd_options[cmd_options.length] = "-H"; cmd_options[cmd_options.length] = String(options.samplerate); } if (options.channels) { cmd_options[cmd_options.length] = "-c"; cmd_options[cmd_options.length] = String(options.channels); } if (options.noaudio) { cmd_options[cmd_options.length] = "--noaudio"; } //metadata if (options.artist) { cmd_options[cmd_options.length] = "--artist"; cmd_options[cmd_options.length] = String(options.artist); } if (options.title) { cmd_options[cmd_options.length] = "--title"; cmd_options[cmd_options.length] = String(options.title); } if (options.date) { cmd_options[cmd_options.length] = "--date"; cmd_options[cmd_options.length] = String(options.date); } if (options.location) { cmd_options[cmd_options.length] = "--location"; cmd_options[cmd_options.length] = String(options.location); } if (options.organization) { cmd_options[cmd_options.length] = "--organization"; cmd_options[cmd_options.length] = String(options.organization); } if (options.copyright) { cmd_options[cmd_options.length] = "--copyright"; cmd_options[cmd_options.length] = String(options.copyright); } if (options.license) { cmd_options[cmd_options.length] = "--license"; cmd_options[cmd_options.length] = String(options.license); } if (options.contact) { cmd_options[cmd_options.length] = "--contact"; cmd_options[cmd_options.length] = String(options.contact); } return cmd_options; }, } ffenc._init(); function FirefoggEncoder(input, output, options, done_cb, progress_cb) { var _this = this; this.input = input; this.output = output; this.options = options; this.done_cb = function (data) { _this.done = true; done_cb(data); } this.progress_cb = progress_cb; this.process = null; this.done = false; this.info = ffenc.info(this.input); this.status = {}; var webm = this.options.videoCodec == 'vp8' || this.output.substr(-4) == 'webm'; if (webm) { var command = ffenc.bin('ffmpeg'); if (this.options.twopass) { var pass = 1; var options = ffenc.ffmpeg_options(this.input, this.output, this.options, this.info, 1); var progress_cb = function(pass) { return function(data) { var info = _this.parse_ffmpeg(data, pass); _this.progress_cb(info); } } this.process = ffenc.subprocess(command, options, function(data) { if(data.status == 'ok') { var pass = 2; var options = ffenc.ffmpeg_options(_this.input, _this.output, _this.options, _this.info, pass); _this.done = false; _this.process = ffenc.subprocess(command, options, function(data) { var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); file.initWithPath(_this.output + '.log-0.log'); if(file.exists()) { try { file.remove(false); } catch(e) { utils.debug("failed to remove temporary file"); } } _this.done_cb(data); }, progress_cb(pass), true); } else { //dump('pass 1 failed\n'); data.error = 'pass 1 failed.'; _this.done_cb(data); } }, progress_cb(pass), true); } else { var pass = 0; var options = ffenc.ffmpeg_options(this.input, this.output, this.options, this.info); this.process = ffenc.subprocess(command, options, this.done_cb, function(data) { var info = _this.parse_ffmpeg(data, pass); _this.progress_cb(info); }, true); } } else { //Ogg Theora var command = ffenc.bin('ffmpeg2theora'); var options = ffenc.ffmpeg2theora_options(this.input, this.output, this.options, this.info); this.process = ffenc.subprocess(command, options, this.done_cb, function(data) { var info = _this.parse_ffmpeg2theora(data); _this.progress_cb(info); }); } } FirefoggEncoder.prototype = { cancel: function() { if(!this.done) { if (this.process) { this.process.kill(); if(!this.done) { var output = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); output.initWithPath(this.output); if(output.exists()) { try { output.remove(false); } catch(e) { //utils.debug("failed to remove temporary file: " + this.output); } } var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); file.initWithPath(this.output + '.log-0.log'); if(file.exists()) { try { file.remove(false); } catch(e) { } } } } this.process = null; } }, parse_ffmpeg2theora: function(data) { var _this = this; var _data = data.replace(/^\s+|\s+$/g,"").replace(/\r/g, '\n').split('\n'); // strip and split at newlines _data = _data[_data.length-1]; // look at last line try { var _info = JSON.parse(_data); } catch(e) { var _info = {}; } if (_info.position && _info.duration) _info.progress = parseFloat(_info.position) / parseFloat(_info.duration); ['duration', 'position', 'progress'].forEach(function(key) { if(_info[key]) _this.status[key] = _info[key]; }); return this.status; }, parse_ffmpeg: function(_data, pass) { var _this = this; var _info = {}; _data = _data.replace(/^\s+|\s+$/g,"").replace(/\r/g, '\n').split('\n'); // strip and split at newlines _data = _data[_data.length-1]; // look at last line //dump('ffmpeg:'+_data + '\n'); try { var data = _data.replace(/=/g, '= ').split(' '); if (_data.search('overhead') > -1) { data = _data.replace(/muxing overhead/g, ' muxing_overhead'); data = data.replace(/global headers/g, ' global_headers').replace(/:/g, '= ').split(' '); } var key = ''; for (i in data) { var k = data[i]; if (k.substr(-1) == '=') { key = k.substr(0, k.length-1); } else if(key.length>0) { if(_info[key]) _info[key] = _info[key] + k; else _info[key] = k; } } } catch(e) { dump('\n'); dump(e); dump('\n'); dump("failed to parse output " + _data); dump('\n'); } if (this.options.framerate) { var _enc_framerate = this.options.framerate; } else { var _enc_framerate = this.info.video[0].framerate; } var _f = _enc_framerate.split(':'); if(_f.length == 2) _enc_framerate = parseInt(_f[0]) / parseInt(_f[1]); else _enc_framerate = parseFloat(_enc_framerate); _info.duration = this.info.duration; if (pass == 1) { _info.position = parseInt(_info.frame) / _enc_framerate; } else { if(_info.time) _info.position = parseFloat(_info.time); } if (_info.position && _info.duration) { _info.progress = parseFloat(_info.position) / _info.duration; if(pass==1) _info.progress = _info.progress/2; else if(pass==2) _info.progress = 0.5 + _info.progress/2; if (_info.progress >= 1) { _info.progress = 0.99999; } } ['duration', 'position', 'progress'].forEach(function(key) { if(_info[key]) _this.status[key] = _info[key]; }); /* for(key in _info) { dump(key + '->'+_info[key] + '\n'); } */ return this.status; }, }