2 * Adds advanced firefogg support (let you control and structure advanced controls over many aspects of video encoding)
5 //@@todo put all msg text into loadGM json
8 "fogg-help-sticky" : "Help (click to stick)",
9 "fogg-cg-preset" : "Preset: <strong>$1<\/strong>",
10 "fogg-cg-quality" : "Basic quality and resolution control",
11 "fogg-cg-meta" : "Metadata for the clip",
12 "fogg-cg-range" : "Encoding range",
13 "fogg-cg-advVideo" : "Advanced video encoding controls",
14 "fogg-cg-advAudio" : "Advanced audio encoding controls",
15 "fogg-preset-custom" : "Custom settings"
18 var mvAdvFirefogg = function( iObj ){
19 return this.init( iObj );
21 var default_mvAdvFirefogg_config = {
22 //which config groups to include
23 'config_groups' : ['preset', 'range', 'quality', 'meta', 'advVideo', 'advAudio'],
25 //if you want to load any custom presets must follow the mvAdvFirefogg.presetConf json outline below
26 'custom_presets' : {},
28 //any firefog config properties that may need to be excluded from options
29 'exclude_settings' : [],
31 //the control container (where we put all the controls)
32 'target_control_container':false
35 mvAdvFirefogg.prototype = {
36 //the global groupings and titles for for configuration options :
37 config_groups : [ 'preset', 'range', 'quality', 'meta', 'advVideo', 'advAudio'],
39 //local instance encoder config:
40 default_local_settings:{
43 'selectVal': ['webvideo'],
47 'descKey': 'fogg-preset-custom',
51 'desc': "Web Video Theora, Vorbis 400kbs & 400px max width",
60 'desc': "Low Bandwith Theora, Vorbis 164kbs & 200px max size",
72 'desc': "High Quality Theora, Vorbis 1080px max width",
84 //core firefogg default encoder configuration
85 //see encoder options here: http://www.firefogg.org/dev/index.html
86 default_encoder_config : {
87 //base quality settings:
90 't' : 'Video Quality',
91 'range' : {'min':0,'max':10},
94 'help' : "Used to set the <i>Visual Quality</i> of the encoded video. (not used if you set bitrate in advanced controls below)"
97 't' : "Two Pass Encoding",
100 'help' : "Two Pass Encoding enables more consitant quality by making two passes over the video file"
103 't' : "Start Second",
106 'help' : "Only encode from time in seconds"
112 'help' : "only encode to time in seconds"
116 't' : 'Audio Quality',
117 'range' : {'min':-1,'max':10},
120 'help' : "Used to set the <i>Acoustic Quality</i> of the encoded audio. (not used if you set bitrate in advanced controls below)"
125 'selectVal' : ['theora'],
128 'help' : "Used to select the clip video codec. Presently only Theora is supported. More about the <a href=\"http://www.theora.org/\">theora codec</a> "
133 'selectVal' : ['vorbis'],
136 'help' : "Used to set the clip audio codec. Presently only Vorbis is supported. More about the <a href=\"http://www.vorbis.com//\">vorbis codec</a> "
140 'range' : {'min':0,'max':1080},
144 'help' : "Resize to given width."
147 't' : 'Video Height',
148 'range' : {'min':0,'max':1080},
152 'help' : "Resize to given height"
154 //advanced Video control configs:
156 't' : 'Video Bitrate',
157 'range' : {'min':1, 'max':16778},
159 'group' : "advVideo",
160 'help' : "Video Bitrate sets the encoding bitrate for video in (kb/s)"
165 'selectVal' : ['12', '16', {'24000:1001':'23.97'}, '24', '25', {'30000:1001':'29.97'}, '30'],
167 'group' : "advVideo",
168 'help' : "The video Framerate. More about <a target=\"_new\" href=\"http://en.wikipedia.org/wiki/Frame_rate\">Framerate</a>"
171 't' : 'Aspect Ratio',
174 'selectVal' : ['4:3', '16:9'],
175 'group' : "advVideo",
176 'help' : "The video aspect ratio can be fraction 4:3 or 16:9. More about <a target=\"_new\" href=\"http://en.wikipedia.org/wiki/Aspect_ratio_%28image%29\">aspect ratios</a>"
180 't' : 'Key Frame Interval',
181 'range' : {'min':0,'max':65536},
182 'numberType': 'force keyframe every $1 frames',
184 'group' : 'advVideo',
185 'help' : "The keyframe interval in frames. Note: Most codecs force keyframes if the difference between frames is greater than keyframe encode size. More about <a href=\"http://en.wikipedia.org/wiki/I-frame\">keyframes</a>"
189 't' : "Denoise Filter",
190 'group' : 'advVideo',
191 'help' : "Denoise input video. More about <a target=\"_new\" href=\"http://en.wikipedia.org/wiki/Video_denoising\">denoise</a>"
196 'group' : 'advVideo',
197 'help' : "disable video in the output"
200 //advanced Audio control Config:
202 't' : "Audio Bitrate",
203 'range' : {'min':32,'max':500},
204 'numberType': '$1 kbs',
209 't' : "Audio Sample Rate",
211 'selectVal' : [{'22050':'22 kHz'}, {'44100':'44 khz'}, {'48000':'48 khz'}],
212 'formatSelect' : function(val){
213 return (Math.round(val/100)*10) + ' Hz';
215 'help' : "set output samplerate (in Hz)."
220 'group' : 'advAudio',
221 'help' : "disable audio in the output"
229 'help' : "A title for your clip"
235 'help' : "The artist that created this clip"
241 'help' : "The date the footage was created or released"
247 'help' : "The location of the footage"
250 't' : "Organization",
253 'help' : "Name of organization (studio)"
259 'help' : "The Copyright of the clip"
265 'help' : "The license of the clip (preferably a creative commons url)"
271 'help' : "Contact link"
274 init:function( iObj ){
275 //setup a "supported" iObj:
277 if( typeof default_mvAdvFirefogg_config [i] != 'undefined' ){
281 //inherit the base mvFirefogg class:
282 var myFogg = new mvFirefogg( iObj );
283 for(var i in myFogg){
284 if( typeof this[i] != 'undefined'){
285 this[ 'basefogg_' + i ] = myFogg[i];
287 this[ i ] = myFogg[i];
291 setupForm:function(){
292 //call base firefogg form setup
293 basefogg_setupForm();
295 //gennerate the control html
296 this.doControlHTML();
298 //setup control bindings:
299 this.doControlBindings();
302 doControlHTML: function(){
303 js_log("adv doControlHTML");
305 //load presets from cookie:
306 this.loadEncSettings();
308 //add base control buttons:
309 this.basefogg_doControlHTML();
311 //build the config group outpouts
313 $j.each(this.config_groups, function(inx, group_key){
315 '<h3><a href="#" class="gd_'+group_key+'" >' + gM('fogg-cg-'+group_key) + '</a></h3>'+
317 //output that group control options:
318 gdout+='<table width="' + ($j(_this.selector).width()-60) + '" ><tr><td width="35%"></td><td width="65%"></td></tr>';
319 //output the special prset output
320 if(group_key=='preset'){
321 gdout += _this.proccessPresetControl();
323 for(var cK in _this.default_encoder_config){
324 var cConf = _this.default_encoder_config[cK];
325 if(cConf.group == group_key){
326 gdout+= _this.proccessCkControlHTML( cK );
334 //add the control container:
335 if(!this.target_control_container){
336 this.target_control_container = this.selector + ' .control_container';
337 //set the target contorl container to height
338 $j(this.selector).append( '<p><div class="control_container"></div>' );
340 //hide the container and add the output
341 $j(this.target_control_container).hide();
342 $j(this.target_control_container).html( gdout );
345 //custom advanced target rewrites:
346 getTargetHtml:function(target){
347 if( target=='target_btn_select_file' ||
348 target=='target_btn_select_new_file'||
349 target=='target_btn_save_local_file'){
350 var icon = (target=='target_btn_save_local_file')?'ui-icon-video':'ui-icon-folder-open';
351 return '<a class="ui-state-default ui-corner-all ui-icon_link '+
352 target +'" href="#"><span class="ui-icon ' + icon + '"/>' +
353 gM( 'fogg-' + target.substring(11) ) +
355 }else if( target=='target_btn_select_url'){
356 //return the btnHtml:
357 return $j.btnHtml( gM( 'fogg-' + target.substring(11) ), target, 'link');
359 }else if( target=='target_use_latest_fox' ||
360 target=='target_please_install' ||
361 target == 'target_passthrough_mode'){
362 return '<div style="margin-top:1em;padding: 0pt 0.7em;" class="ui-state-error ui-corner-all ' +
364 '<p><span style="float: left; margin-right: 0.3em;" class="ui-icon ui-icon-alert"/>'+
365 gM( 'fogg-' + target.substring(7)) +'</p>'+
367 }else if( target == 'target_input_file_name'){
368 return '<br><br><input style="" class="text ui-widget-content ui-corner-all ' + target + '" '+
369 'type="text" value="' + gM( 'fogg-' + target.substring(11)) + '" size="60" /> ';
371 js_log('call : basefogg_getTargetHtml');
372 return this.basefogg_getTargetHtml(target);
375 proccessPresetControl:function(){
378 js_log('proccessPresetControl::');
379 if(typeof this.local_settings.pSet!= 'undefined' ){
380 out+= '<select class="_preset_select">';
381 $j.each(this.local_settings.pSet, function(pKey, pSet){
382 var pDesc = (pSet.descKey) ? gM(pSet.descKey) : pSet.desc;
383 var sel = (_this.local_settings.d == pKey)?' selected':'';
384 out+='<option value="'+pKey+'" '+sel+'>'+ pDesc+'</option>';
390 proccessCkControlHTML:function( cK ){
391 var cConf = this.default_encoder_config[cK];
393 out+='<tr><td valign="top">'+
394 '<label for="_' + cK + '">' +
396 '<span title="' + gM('fogg-help-sticky') + '" class="help_'+ cK + ' ui-icon ui-icon-info" style="float:left"></span>'+
397 '</label></td><td valign="top">';
398 //if we don't value for this:
399 var dv = ( this.default_encoder_config[cK].d ) ? this.default_encoder_config[cK].d : '';
400 //switch on the config type
401 switch( cConf.type ){
406 var size = ( cConf.type =='string' ||cConf.type == 'date' )?'14':'4';
407 out+= '<input size="' + size + '" type="text" class="_' + cK + ' text ui-widget-content ui-corner-all" value="' + dv + '" >' ;
410 var checked_attr = (dv===true)?' checked="true"':'';
411 out+='<input type="checkbox" class="_'+cK+ ' ui-widget-content ui-corner-all" ' + checked_attr + '>';
414 var strMax = this.default_encoder_config[ cK ].range.max + '';
415 maxdigits = strMax.length +1;
416 out+= '<input type="text" maxlength="'+maxdigits+'" size="' +maxdigits + '" '+
417 'class="_'+cK+ ' text ui-widget-content ui-corner-all" style="display:inline;border:0; color:#f6931f; font-weight:bold;" ' +
418 'value="' + dv + '" >' +
419 '<div class="slider_' + cK + '"></div>';
422 out+= '<select class="_' + cK + '">'+
423 '<option value=""> </option>';
424 for(var i in cConf.selectVal){
425 var val = cConf.selectVal[i];
426 if(typeof val == 'string'){
427 var sel = ( cConf.selectVal[i] == val)?' selected':'';
428 out+= '<option value="'+val+'"'+sel+'>'+val+'</option>';
429 }else if(typeof val == 'object'){
433 var sel = ( cConf.selectVal[i] == key )?' selected':'';
435 out+= '<option value="'+key+'"'+sel+'>'+hr_val+'</option>';
441 //output the help row:
443 out+='<div class="helpRow_' + cK + '">'+
444 '<span class="helpClose_' + cK + ' ui-icon ui-icon-circle-close" '+
445 'title="Close Help"'+
446 'style="float:left"/>'+
450 out+='</td></tr><tr><td colspan="2" height="10"></td></tr>';
453 selectByUrl:function(){
454 var urlValue = prompt("Please enter the source media url you would like to transcode from.","http://");
457 this.sourceMode = 'url';
458 this.sourceUrl = urlValue;
459 this.selectFoggActions();
460 this.autoEncoderSettings();
461 //update the input target
462 $j(this.target_input_file_name).unbind().val( urlValue ).removeAttr('readonly');
465 doControlBindings:function(){
467 _this.basefogg_doControlBindings();
468 //show the select by url if present:
469 /*$j( this.target_btn_select_url ).unbind(
470 ).attr('disabled', false
471 ).css({'display':'inline'}
478 //hide the base advanced controls untill a file is selected:
479 $j(this.target_control_container).hide();
482 //do some display tweeks:
483 js_log('tw:' + $j(this.selector).width() +
484 'ssf:' + $j(this.target_btn_select_new_file).width() +
485 'sf:' + $j(this.target_btn_save_local_file).width() );
488 $j(this.target_input_file_name).width( 250 );
490 //special preset action:
491 $j(this.selector + ' ._preset_select').change(function(){
492 _this.updatePresetSelection( $j(this).val() );
495 //bind control actions
496 for(var cK in this.default_encoder_config){
497 var cConf = this.default_encoder_config[cK];
498 //set up the help for all types:
500 //initial state is hidden:
501 $j( this.selector + ' .helpRow_' + cK).hide();
502 $j(this.selector + ' .help_' + cK).click(function(){
503 //get the ckId (assume its the last class)
504 var cK = _this.getClassId(this, 'help_');
507 $j(_this.selector + ' .helpRow_' + cK).hide('slow');
508 helpState[cK] = false;
510 $j(_this.selector + ' .helpRow_' + cK).show('slow');
511 helpState[cK] = true;
516 //get the ckId (assume its the last class)
517 var cK = _this.getClassId(this, 'help_');
518 $j( _this.selector + ' .helpRow_' + cK).show('slow');
520 var cK = _this.getClassId(this, 'help_');
522 $j( _this.selector + ' .helpRow_' + cK).hide('slow')
525 $j( _this.selector + ' .helpClose_' + cK).click(function(){
526 js_log("close help: " +cK);
527 //get the ckId (assume its the last class)
528 var cK = _this.getClassId(this, 'helpClose_');
529 $j(_this.selector + ' .helpRow_' + cK).hide('slow');
530 helpState[cK] = false;
532 }).css('cursor', 'pointer');
534 $j(this.selector + ' .help_' + cK).hide();
537 //setup bindings for change values: (validate input)
539 switch( cConf.type ){
541 $j(_this.selector + ' ._'+cK).click(function(){
542 _this.updateLocalValue( _this.getClassId(this), $j(this).is(":checked") );
543 _this.updatePresetSelection('custom');
550 //@@check if we have a validate function on the string
551 $j(_this.selector + ' ._'+cK).change(function(){
552 $j(this).val( _this.updateLocalValue(
553 _this.getClassId(this),
555 _this.updatePresetSelection('custom');
559 $j(_this.selector + ' ._'+cK).datepicker({
562 dateFormat: 'd MM, yy',
563 onSelect: function(dateText) {
564 _this.updateInterfaceValue(_this.getClassId(this), dateText);
569 $j(this.selector + ' .slider_' + cK ).slider({
572 step: (cConf.step)?cConf.step:1,
573 value: $j( this.selector +' ._' + cK ).val(),
574 min: this.default_encoder_config[ cK ].range.min,
575 max: this.default_encoder_config[ cK ].range.max,
576 slide: function(event, ui) {
577 $j( _this.selector + ' ._' + _this.getClassId(this, 'slider_') ).val( ui.value );
579 //maintain source video aspect ratio:
580 if(_this.getClassId(this, 'slider_') == 'width'){
581 var hv = parseInt((_this.sourceFileInfo.video[0]['height']/_this.sourceFileInfo.video[0]['width'])* ui.value );
582 //update the height value: the new hight value is > that orginal the slider:
583 if(hv > _this.updateInterfaceValue('height', hv))
586 if(_this.getClassId(this, 'slider_') == 'height'){
587 var wv = parseInt((_this.sourceFileInfo.video[0]['width']/_this.sourceFileInfo.video[0]['height'])* ui.value );
588 //update the height value: the new hight value is > that orginal the slider:
589 if(wv > _this.updateInterfaceValue('width', wv))
593 change: function(event, ui){
594 //update the local settings
595 _this.updateLocalValue( _this.getClassId(this, 'slider_'), ui.value);
596 _this.updatePresetSelection('custom');
599 $j( this.selector +' ._' + cK).change(function(){
600 var scid = _this.getClassId(this);
601 var valdVal = _this.updateLocalValue(scid.substr(1),$j(this).val() );
602 _this.updatePresetSelection('custom');
603 //(validate user form input)
604 $j(this).val(valdVal);
606 js_log("update: " + _this.selector + ' .slider' + scid);
607 $j(_this.selector + ' .slider'+ scid).slider('option', 'value', valdVal );
612 $j(this.target_control_container).accordion({
619 //do config value updates if any
620 this.updateValuesInHtml();
622 updatePresetSelection:function( pKey ){
623 //update the local key:
624 this.local_settings.d = pKey;
625 //js_log('update preset desc: '+ pKey);
627 if(this.local_settings.pSet[ pKey ].desc){
628 pset_desc = this.local_settings.pSet[ pKey ].desc;
630 pset_desc = gM('fogg-preset-'+ pKey);
632 //update the preset title:
633 $j( this.selector + ' .gd_preset' ).html(
634 gM('fogg-cg-preset', pset_desc)
636 //update the selector
637 $j(this.selector + ' ._preset_select').val(pKey);
640 * updates the interface
642 updateInterfaceValue:function(confKey, val){
647 //js_log('updateInterfaceValue:: ' + confKey + ' v:' + val + ' cv:'+ _this.selector + '._'+ confKey+' len:' + $j(_this.selector + ' ._'+confKey).length );
649 if(typeof this.default_encoder_config[confKey] == 'undefined'){
650 js_error('error: missing default key: '+ confKey);
654 //update the local value (if not already up-to-date
655 if( this.local_settings.pSet['custom']['conf'][confKey] != val ){
656 val = this.updateLocalValue(confKey, val);
658 //update the text filed:
659 $j(_this.selector + ' ._'+confKey).val( val );
660 //update the interaface widget:
661 switch(this.default_encoder_config[confKey].type){
663 $j(_this.selector + ' .slider_' + confKey).slider('option',
664 'value', $j(_this.selector + ' ._'+ confKey).val() );
669 updateLocalValue:function(confKey, value){
670 //update the local value (return the value we acutally set)
671 if(typeof this.default_encoder_config[confKey] == 'undefined'){
672 js_log("Error:could not update conf key:" + confKey)
675 dec = this.default_encoder_config[confKey];
677 value = parseInt(value);
678 var min = ( dec.range.local_min) ? dec.range.local_min :dec.range.min;
681 var max = ( dec.range.local_max) ? dec.range.local_max : dec.range.max
686 value = parseInt(value);
690 if((value % dec.step)!=0){
691 value = value - (value % dec.step);
695 js_log('update:local_settings:custom:conf:'+ confKey + ' = ' + value);
696 this.local_settings.pSet['custom']['conf'][confKey] = value;
700 getLocalValue:function(confKey){
701 return this.local_settings.pSet['custom']['conf'][confKey];
703 getClassId:function(elm, rmstr){
704 var elmclass = $j(elm).attr("class").split(' ').slice(0,1).toString();
706 return elmclass.replace( rmstr, '' );
708 //strip leading underscore:
709 return (elmclass[0]=='_')?elmclass.substr(1):elmclass;
713 * sets up the autoEncoder settings
715 autoEncoderSettings:function(){
717 //do the base encoder settings setup:
718 this.basefogg_autoEncoderSettings();
719 //make sure we are "encoding" if not display not a video file eror:
720 if( this.encoder_settings['passthrough'] ){
721 js_log("in passthrough mode (hide control)");
723 //dispaly not encodable video
724 $j(this.target_control_container).hide('slow');
725 $j(this.target_passthrough_mode).show('slow');
729 $j(this.target_control_container).show('slow');
730 $j(this.target_passthrough_mode).hide('slow');
732 //do setup settings based on local_settings /default_encoder_config with sourceFileInfo
733 //see: http://firefogg.org/dev/sourceInfo_example.html
734 var setValues = function(k, val, maxVal) {
736 //update the value if unset:
737 _this.updateLocalValue(k, val);
740 //update the local range:
741 if(_this.default_encoder_config[k].range){
742 _this.default_encoder_config[k].range.local_max = maxVal;
746 //container level settings
747 for(var i in this.sourceFileInfo){
748 var val = this.sourceFileInfo[i];
752 //do nothing with these:
755 maxVal = (val*2 > this.default_encoder_config[k])?this.default_encoder_config[k]:val*2;
758 setValues(k, val, maxVal);
760 //video stream settings
761 for(var i in this.sourceFileInfo.video[0]){
762 var val = this.sourceFileInfo.video[0][i];
772 setValues(k, val, maxVal);
774 //audio stream settings, assumes for now thare is only one stream
775 for(var i in this.sourceFileInfo.audio[0]){
776 var val = this.sourceFileInfo.audio[0][i];
782 maxVal = (val*2 > this.default_encoder_config[k])?this.default_encoder_config[k]:val*2;
785 setValues(k, val, maxVal);
788 //set all values to new default ranges & update slider:
789 $j.each(this.default_encoder_config, function(inx, val){
790 if($j(_this.selector + ' ._'+inx).length!=0){
791 if(typeof val.range != 'undefined'){
793 var new_max = (val.range.local_max)?val.range.local_max: val.range.max
794 $j( _this.selector + ' .slider_'+inx).slider('option', 'max', new_max);
796 //update slider/input value:
797 _this.updateInterfaceValue(inx, _this.local_settings.pSet['custom']['conf'][inx]);
802 this.updateValuesInHtml();
805 //update the encoder settings (from local settings)
806 pKey = this.local_settings.d;
807 this.encoder_settings = this.local_settings.pSet[ pKey ].conf;
808 this.basefogg_doEncode();
810 updateValuesInHtml:function(){
811 js_log('updateValuesInHtml::');
813 var pKey = this.local_settings.d;
814 this.updatePresetSelection( pKey );
816 //set the actual HTML & widgets based on any local settings values:
817 $j.each(_this.local_settings.pSet['custom']['conf'], function(inx, val){
818 if($j(_this.selector + ' ._'+inx).length !=0){
819 $j(_this.selector + ' ._'+inx).val( val );
823 //restors settings from a cookie if you have them)
824 loadEncSettings:function( force ){
825 if($j.cookie('fogg_encoder_config')){
826 js_log("load:fogg_encoder_config from cookie ");
827 this.local_settings = JSON.parse( $j.cookie('fogg_settings') );
829 //set to default if not loaded yet:
830 if( this.local_settings && this.local_settings.pSet && this.local_settings.pSet['custom']['conf']){
831 js_log('local settings already populated');
833 this.local_settings = this.default_local_settings;
837 //clear preset settings:
838 clearSettings:function(force){
841 //save settings to the cookie
842 saveEncSettings:function(){
843 $j.cookie('fogg_settings', JSON.stringify( this.local_settings ) );