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 "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('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
) );