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": "Meta Data 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",
62 //core firefogg default encoder configuration
63 //see encoder options here: http://www.firefogg.org/dev/index.html
64 default_encoder_config
: {
65 //base quality settings:
68 't' : 'Video Quality',
69 'range' : {'min':0,'max':10},
72 'help' : "Used to set the <i>Visual Quality</i> of the encoded video. (not used if you set bitrate in advanced controls below)"
75 't' : "Two Pass Encoding",
78 'help' : "Two Pass Encoding enables more consitant quality by making two passes over the video file"
84 'help' : "Only encode from time in seconds"
90 'help' : "only encode to time in seconds"
94 't' : 'Audio Quality',
95 'range' : {'min':-1,'max':10},
98 'help' : "Used to set the <i>Acoustic Quality</i> of the encoded audio. (not used if you set bitrate in advanced controls below)"
103 'selectVal' : ['theora'],
106 '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> "
111 'selectVal' : ['vorbis'],
114 '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> "
118 'range' : {'min':0,'max':1080},
122 'help' : "Resize to given width."
125 't' : 'Video Height',
126 'range' : {'min':0,'max':1080},
130 'help' : "Resize to given height"
132 //advanced Video control configs:
134 't' : 'Video Bitrate',
135 'range' : {'min':1, 'max':16778},
137 'group' : "advVideo",
138 'help' : "Video Bitrate sets the encoding bitrate for video in (kb/s)"
143 'selectVal' : ['12', '16', {'24000:1001':'23.97'}, '24', '25', {'30000:1001':'29.97'}, '30'],
145 'group' : "advVideo",
146 'help' : "The video Framerate. More about <a target=\"_new\" href=\"http://en.wikipedia.org/wiki/Frame_rate\">Framerate</a>"
149 't' : 'Aspect Ratio',
152 'selectVal' : ['4:3', '16:9'],
153 'group' : "advVideo",
154 '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>"
158 't' : 'Key Frame Interval',
159 'range' : {'min':0,'max':65536},
160 'numberType': 'force keyframe every $1 frames',
162 'group' : 'advVideo',
163 '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>"
167 't' : "Denoise Filter",
168 'group' : 'advVideo',
169 'help' : "Denoise input video. More about <a target=\"_new\" href=\"http://en.wikipedia.org/wiki/Video_denoising\">denoise</a>"
174 'group' : 'advVideo',
175 'help' : "disable video in the output"
178 //advanced Audio control Config:
180 't' : "Audio Bitrate",
181 'range' : {'min':32,'max':500},
182 'numberType': '$1 kbs',
187 't' : "Audio Sample Rate",
189 'selectVal' : [{'22050':'22 kHz'}, {'44100':'44 khz'}, {'48000':'48 khz'}],
190 'formatSelect' : function(val
){
191 return (Math
.round(val
/100)*10) + ' Hz';
193 'help' : "set output samplerate (in Hz)."
198 'group' : 'advAudio',
199 'help' : "disable audio in the output"
207 'help' : "A title for your clip"
213 'help' : "The artist that created this clip"
219 'help' : "The date the footage was created or released"
225 'help' : "The location of the footage"
228 't' : "Organization",
231 'help' : "Name of organization (studio)"
237 'help' : "The Copyright of the clip"
243 'help' : "The license of the clip (preferably a creative commons url)"
249 'help' : "Contact link"
252 init:function( iObj
){
253 //setup a "supported" iObj:
255 if( typeof default_mvAdvFirefogg_config
[i
] != 'undefined' ){
259 //inherit the base mvFirefogg class:
260 var myFogg
= new mvFirefogg( iObj
);
261 for(var i
in myFogg
){
262 if( typeof this[i
] != 'undefined'){
263 this[ 'basefogg_' + i
] = myFogg
[i
];
265 this[ i
] = myFogg
[i
];
269 setupForm:function(){
270 //call base firefogg form setup
271 basefogg_setupForm();
273 //gennerate the control html
274 this.doControlHTML();
276 //setup control bindings:
277 this.doControlBindings();
280 doControlHTML: function(){
281 js_log("adv doControlHTML");
283 //load presets from cookie:
284 this.loadEncSettings();
286 //add base control buttons:
287 this.basefogg_doControlHTML();
289 //build the config group outpouts
291 $j
.each(this.config_groups
, function(inx
, group_key
){
293 '<h3><a href="#" class="gd_'+group_key
+'" >' + gM('fogg-cg-'+group_key
) + '</a></h3>'+
295 //output that group control options:
296 gdout
+='<table width="' + ($j(_this
.selector
).width()-60) + '" ><tr><td width="35%"></td><td width="65%"></td></tr>';
297 //output the special prset output
298 if(group_key
=='preset'){
299 gdout
+= _this
.proccessPresetControl();
301 for(var cK
in _this
.default_encoder_config
){
302 var cConf
= _this
.default_encoder_config
[cK
];
303 if(cConf
.group
== group_key
){
304 gdout
+= _this
.proccessCkControlHTML( cK
);
312 //add the control container:
313 if(!this.target_control_container
){
314 this.target_control_container
= this.selector
+ ' .control_container';
315 //set the target contorl container to height
316 $j(this.selector
).append( '<p><div class="control_container"></div>' );
318 //hide the container and add the output
319 $j(this.target_control_container
).hide();
320 $j(this.target_control_container
).html( gdout
);
323 //custom advanced target rewrites:
324 getTargetHtml:function(target
){
325 if( target
=='target_btn_select_file' ||
326 target
=='target_btn_select_new_file'||
327 target
=='target_btn_save_local_file'){
328 var icon
= (target
=='target_btn_save_local_file')?'ui-icon-video':'ui-icon-folder-open';
329 return '<a class="ui-state-default ui-corner-all ui-icon_link '+
330 target
+'" href="#"><span class="ui-icon ' + icon
+ '"/>' +
331 gM( 'fogg-' + target
.substring(11) ) +
333 }else if( target
=='target_btn_select_url'){
334 //return the btnHtml:
335 return $j
.btnHtml( gM( 'fogg-' + target
.substring(11) ), target
, 'link');
337 }else if( target
=='target_use_latest_fox' ||
338 target
=='target_please_install' ||
339 target
== 'target_passthrough_mode'){
340 return '<div style="margin-top:1em;padding: 0pt 0.7em;" class="ui-state-error ui-corner-all ' +
342 '<p><span style="float: left; margin-right: 0.3em;" class="ui-icon ui-icon-alert"/>'+
343 gM( 'fogg-' + target
.substring(7)) +'</p>'+
345 }else if( target
== 'target_input_file_name'){
346 return '<br><br><input style="" class="text ui-widget-content ui-corner-all ' + target
+ '" '+
347 'type="text" value="' + gM( 'fogg-' + target
.substring(11)) + '" size="60" /> ';
349 js_log('call : basefogg_getTargetHtml');
350 return this.basefogg_getTargetHtml(target
);
353 proccessPresetControl:function(){
356 js_log('proccessPresetControl::');
357 if(typeof this.local_settings
.pSet
!= 'undefined' ){
358 out
+= '<select class="_preset_select">';
359 $j
.each(this.local_settings
.pSet
, function(pKey
, pSet
){
360 var pDesc
= (pSet
.descKey
) ? gM(pSet
.descKey
) : pSet
.desc
;
361 var sel
= (_this
.local_settings
.d
== pKey
)?' selected':'';
362 out
+='<option value="'+pKey
+'" '+sel
+'>'+ pDesc
+'</option>';
368 proccessCkControlHTML:function( cK
){
369 var cConf
= this.default_encoder_config
[cK
];
371 out
+='<tr><td valign="top">'+
372 '<label for="_' + cK
+ '">' +
374 '<span title="' + gM('help-sticky') + '" class="help_'+ cK
+ ' ui-icon ui-icon-info" style="float:left"></span>'+
375 '</label></td><td valign="top">';
376 //if we don't value for this:
377 var dv
= ( this.default_encoder_config
[cK
].d
) ? this.default_encoder_config
[cK
].d
: '';
378 //switch on the config type
379 switch( cConf
.type
){
384 var size
= ( cConf
.type
=='string' ||cConf
.type
== 'date' )?'14':'4';
385 out
+= '<input size="' + size
+ '" type="text" class="_' + cK
+ ' text ui-widget-content ui-corner-all" value="' + dv
+ '" >' ;
388 var checked_attr
= (dv
===true)?' checked="true"':'';
389 out
+='<input type="checkbox" class="_'+cK
+ ' ui-widget-content ui-corner-all" ' + checked_attr
+ '>';
392 var strMax
= this.default_encoder_config
[ cK
].range
.max
+ '';
393 maxdigits
= strMax
.length
+1;
394 out
+= '<input type="text" maxlength="'+maxdigits
+'" size="' +maxdigits
+ '" '+
395 'class="_'+cK
+ ' text ui-widget-content ui-corner-all" style="display:inline;border:0; color:#f6931f; font-weight:bold;" ' +
396 'value="' + dv
+ '" >' +
397 '<div class="slider_' + cK
+ '"></div>';
400 out
+= '<select class="_' + cK
+ '">'+
401 '<option value=""> </option>';
402 for(var i
in cConf
.selectVal
){
403 var val
= cConf
.selectVal
[i
];
404 if(typeof val
== 'string'){
405 var sel
= ( cConf
.selectVal
[i
] == val
)?' selected':'';
406 out
+= '<option value="'+val
+'"'+sel
+'>'+val
+'</option>';
407 }else if(typeof val
== 'object'){
411 var sel
= ( cConf
.selectVal
[i
] == key
)?' selected':'';
413 out
+= '<option value="'+key
+'"'+sel
+'>'+hr_val
+'</option>';
419 //output the help row:
421 out
+='<div class="helpRow_' + cK
+ '">'+
422 '<span class="helpClose_' + cK
+ ' ui-icon ui-icon-circle-close" '+
423 'title="Close Help"'+
424 'style="float:left"/>'+
428 out
+='</td></tr><tr><td colspan="2" height="10"></td></tr>';
431 selectByUrl:function(){
432 var urlValue
= prompt("Please enter the source media url you would like to transcode from.","http://");
435 this.sourceMode
= 'url';
436 this.sourceUrl
= urlValue
;
437 this.selectFoggActions();
438 this.autoEncoderSettings();
439 //update the input target
440 $j(this.target_input_file_name
).unbind().val( urlValue
).removeAttr('readonly');
443 doControlBindings:function(){
445 _this
.basefogg_doControlBindings();
446 //show the select by url if present:
447 /*$j( this.target_btn_select_url ).unbind(
448 ).attr('disabled', false
449 ).css({'display':'inline'}
456 //hide the base advanced controls untill a file is selected:
457 $j(this.target_control_container
).hide();
460 //do some display tweeks:
461 js_log('tw:' + $j(this.selector
).width() +
462 'ssf:' + $j(this.target_btn_select_new_file
).width() +
463 'sf:' + $j(this.target_btn_save_local_file
).width() );
466 $j(this.target_input_file_name
).width( 250 );
468 //special preset action:
469 $j(this.selector
+ ' ._preset_select').change(function(){
470 _this
.updatePresetSelection( $j(this).val() );
473 //bind control actions
474 for(var cK
in this.default_encoder_config
){
475 var cConf
= this.default_encoder_config
[cK
];
476 //set up the help for all types:
478 //initial state is hidden:
479 $j( this.selector
+ ' .helpRow_' + cK
).hide();
480 $j(this.selector
+ ' .help_' + cK
).click(function(){
481 //get the ckId (assume its the last class)
482 var cK
= _this
.getClassId(this, 'help_');
485 $j(_this
.selector
+ ' .helpRow_' + cK
).hide('slow');
486 helpState
[cK
] = false;
488 $j(_this
.selector
+ ' .helpRow_' + cK
).show('slow');
489 helpState
[cK
] = true;
494 //get the ckId (assume its the last class)
495 var cK
= _this
.getClassId(this, 'help_');
496 $j( _this
.selector
+ ' .helpRow_' + cK
).show('slow');
498 var cK
= _this
.getClassId(this, 'help_');
500 $j( _this
.selector
+ ' .helpRow_' + cK
).hide('slow')
503 $j( _this
.selector
+ ' .helpClose_' + cK
).click(function(){
504 js_log("close help: " +cK
);
505 //get the ckId (assume its the last class)
506 var cK
= _this
.getClassId(this, 'helpClose_');
507 $j(_this
.selector
+ ' .helpRow_' + cK
).hide('slow');
508 helpState
[cK
] = false;
510 }).css('cursor', 'pointer');
512 $j(this.selector
+ ' .help_' + cK
).hide();
515 //setup bindings for change values: (validate input)
517 switch( cConf
.type
){
519 $j(_this
.selector
+ ' ._'+cK
).click(function(){
520 _this
.updateLocalValue( _this
.getClassId(this), $j(this).is(":checked") );
521 _this
.updatePresetSelection('custom');
528 //@@check if we have a validate function on the string
529 $j(_this
.selector
+ ' ._'+cK
).change(function(){
530 $j(this).val( _this
.updateLocalValue(
531 _this
.getClassId(this),
533 _this
.updatePresetSelection('custom');
537 $j(_this
.selector
+ ' ._'+cK
).datepicker({
540 dateFormat
: 'd MM, yy',
541 onSelect: function(dateText
) {
542 _this
.updateInterfaceValue(_this
.getClassId(this), dateText
);
547 $j(this.selector
+ ' .slider_' + cK
).slider({
550 step
: (cConf
.step
)?cConf
.step
:1,
551 value
: $j( this.selector
+' ._' + cK
).val(),
552 min
: this.default_encoder_config
[ cK
].range
.min
,
553 max
: this.default_encoder_config
[ cK
].range
.max
,
554 slide: function(event
, ui
) {
555 $j( _this
.selector
+ ' ._' + _this
.getClassId(this, 'slider_') ).val( ui
.value
);
557 //maintain source video aspect ratio:
558 if(_this
.getClassId(this, 'slider_') == 'width'){
559 var hv
= parseInt((_this
.sourceFileInfo
.video
[0]['height']/_this
.sourceFileInfo
.video
[0]['width'])* ui
.value
);
560 //update the height value: the new hight value is > that orginal the slider:
561 if(hv
> _this
.updateInterfaceValue('height', hv
))
564 if(_this
.getClassId(this, 'slider_') == 'height'){
565 var wv
= parseInt((_this
.sourceFileInfo
.video
[0]['width']/_this
.sourceFileInfo
.video
[0]['height'])* ui
.value
);
566 //update the height value: the new hight value is > that orginal the slider:
567 if(wv
> _this
.updateInterfaceValue('width', wv
))
571 change: function(event
, ui
){
572 //update the local settings
573 _this
.updateLocalValue( _this
.getClassId(this, 'slider_'), ui
.value
);
574 _this
.updatePresetSelection('custom');
577 $j( this.selector
+' ._' + cK
).change(function(){
578 var scid
= _this
.getClassId(this);
579 var valdVal
= _this
.updateLocalValue(scid
.substr(1),$j(this).val() );
580 _this
.updatePresetSelection('custom');
581 //(validate user form input)
582 $j(this).val(valdVal
);
584 js_log("update: " + _this
.selector
+ ' .slider' + scid
);
585 $j(_this
.selector
+ ' .slider'+ scid
).slider('option', 'value', valdVal
);
590 $j(this.target_control_container
).accordion({
597 //do config value updates if any
598 this.updateValuesInHtml();
600 updatePresetSelection:function( pKey
){
601 //update the local key:
602 this.local_settings
.d
= pKey
;
603 //js_log('update preset desc: '+ pKey);
605 if(this.local_settings
.pSet
[ pKey
].desc
){
606 pset_desc
= this.local_settings
.pSet
[ pKey
].desc
;
608 pset_desc
= gM('fogg-preset-'+ pKey
);
610 //update the preset title:
611 $j( this.selector
+ ' .gd_preset' ).html(
612 gM('fogg-cg-preset', pset_desc
)
614 //update the selector
615 $j(this.selector
+ ' ._preset_select').val(pKey
);
618 * updates the interface
620 updateInterfaceValue:function(confKey
, val
){
625 //js_log('updateInterfaceValue:: ' + confKey + ' v:' + val + ' cv:'+ _this.selector + '._'+ confKey+' len:' + $j(_this.selector + ' ._'+confKey).length );
627 if(typeof this.default_encoder_config
[confKey
] == 'undefined'){
628 js_error('error: missing default key: '+ confKey
);
632 //update the local value (if not already up-to-date
633 if( this.local_settings
.pSet
['custom']['conf'][confKey
] != val
){
634 val
= this.updateLocalValue(confKey
, val
);
636 //update the text filed:
637 $j(_this
.selector
+ ' ._'+confKey
).val( val
);
638 //update the interaface widget:
639 switch(this.default_encoder_config
[confKey
].type
){
641 $j(_this
.selector
+ ' .slider_' + confKey
).slider('option',
642 'value', $j(_this
.selector
+ ' ._'+ confKey
).val() );
647 updateLocalValue:function(confKey
, value
){
648 //update the local value (return the value we acutally set)
649 if(typeof this.default_encoder_config
[confKey
] == 'undefined'){
650 js_log("Error:could not update conf key:" + confKey
)
653 dec
= this.default_encoder_config
[confKey
];
655 value
= parseInt(value
);
656 var min
= ( dec
.range
.local_min
) ? dec
.range
.local_min
:dec
.range
.min
;
659 var max
= ( dec
.range
.local_max
) ? dec
.range
.local_max
: dec
.range
.max
664 value
= parseInt(value
);
668 if((value % dec.step)!=0){
669 value = value - (value % dec.step);
673 js_log('update:local_settings:custom:conf:'+ confKey
+ ' = ' + value
);
674 this.local_settings
.pSet
['custom']['conf'][confKey
] = value
;
678 getLocalValue:function(confKey
){
679 return this.local_settings
.pSet
['custom']['conf'][confKey
];
681 getClassId:function(elm
, rmstr
){
682 var elmclass
= $j(elm
).attr("class").split(' ').slice(0,1).toString();
684 return elmclass
.replace( rmstr
, '' );
686 //strip leading underscore:
687 return (elmclass
[0]=='_')?elmclass
.substr(1):elmclass
;
691 * sets up the autoEncoder settings
693 autoEncoderSettings:function(){
695 //do the base encoder settings setup:
696 this.basefogg_autoEncoderSettings();
697 //make sure we are "encoding" if not display not a video file eror:
698 if( this.encoder_settings
['passthrough'] ){
699 js_log("in passthrough mode (hide control)");
701 //dispaly not encodable video
702 $j(this.target_control_container
).hide('slow');
703 $j(this.target_passthrough_mode
).show('slow');
707 $j(this.target_control_container
).show('slow');
708 $j(this.target_passthrough_mode
).hide('slow');
710 //do setup settings based on local_settings /default_encoder_config with sourceFileInfo
711 //see: http://firefogg.org/dev/sourceInfo_example.html
712 var setValues = function(k
, val
, maxVal
) {
714 //update the value if unset:
715 _this
.updateLocalValue(k
, val
);
718 //update the local range:
719 if(_this
.default_encoder_config
[k
].range
){
720 _this
.default_encoder_config
[k
].range
.local_max
= maxVal
;
724 //container level settings
725 for(var i
in this.sourceFileInfo
){
726 var val
= this.sourceFileInfo
[i
];
730 //do nothing with these:
733 maxVal
= (val
*2 > this.default_encoder_config
[k
])?this.default_encoder_config
[k
]:val
*2;
736 setValues(k
, val
, maxVal
);
738 //video stream settings
739 for(var i
in this.sourceFileInfo
.video
[0]){
740 var val
= this.sourceFileInfo
.video
[0][i
];
750 setValues(k
, val
, maxVal
);
752 //audio stream settings, assumes for now thare is only one stream
753 for(var i
in this.sourceFileInfo
.audio
[0]){
754 var val
= this.sourceFileInfo
.audio
[0][i
];
760 maxVal
= (val
*2 > this.default_encoder_config
[k
])?this.default_encoder_config
[k
]:val
*2;
763 setValues(k
, val
, maxVal
);
766 //set all values to new default ranges & update slider:
767 $j
.each(this.default_encoder_config
, function(inx
, val
){
768 if($j(_this
.selector
+ ' ._'+inx
).length
!=0){
769 if(typeof val
.range
!= 'undefined'){
771 var new_max
= (val
.range
.local_max
)?val
.range
.local_max
: val
.range
.max
772 $j( _this
.selector
+ ' .slider_'+inx
).slider('option', 'max', new_max
);
774 //update slider/input value:
775 _this
.updateInterfaceValue(inx
, _this
.local_settings
.pSet
['custom']['conf'][inx
]);
780 this.updateValuesInHtml();
783 //update the encoder settings (from local settings)
784 pKey
= this.local_settings
.d
;
785 this.encoder_settings
= this.local_settings
.pSet
[ pKey
].conf
;
786 this.basefogg_doEncode();
788 updateValuesInHtml:function(){
789 js_log('updateValuesInHtml::');
791 var pKey
= this.local_settings
.d
;
792 this.updatePresetSelection( pKey
);
794 //set the actual HTML & widgets based on any local settings values:
795 $j
.each(_this
.local_settings
.pSet
['custom']['conf'], function(inx
, val
){
796 if($j(_this
.selector
+ ' ._'+inx
).length
!=0){
797 $j(_this
.selector
+ ' ._'+inx
).val( val
);
801 //restors settings from a cookie if you have them)
802 loadEncSettings:function( force
){
803 if($j
.cookie('fogg_encoder_config')){
804 js_log("load:fogg_encoder_config from cookie ");
805 this.local_settings
= JSON
.parse( $j
.cookie('fogg_settings') );
807 //set to default if not loaded yet:
808 if( this.local_settings
&& this.local_settings
.pSet
&& this.local_settings
.pSet
['custom']['conf']){
809 js_log('local settings already populated');
811 this.local_settings
= this.default_local_settings
;
815 //clear preset settings:
816 clearSettings:function(force
){
819 //save settings to the cookie
820 saveEncSettings:function(){
821 $j
.cookie('fogg_settings', JSON
.stringify( this.local_settings
) );