1 /* adds firefogg support.
2 * autodetects: new upload api or old http POST.
6 "fogg-select_file" : "Select file",
7 "fogg-select_new_file" : "Select new file",
8 "fogg-select_url" : "Select URL",
9 "fogg-save_local_file" : "Save Ogg",
10 "fogg-check_for_fogg" : "Checking for Firefogg <blink>...<\/blink>",
11 "fogg-installed" : "Firefogg is installed",
12 "fogg-for_improved_uplods" : "For improved uploads : ",
13 "fogg-please_install" : "<a href=\"$1\">Install Firefogg<\/a>. More <a href=\"http : \/\/commons.wikimedia.org\/wiki\/Commons : Firefogg\">about Firefogg<\/a>",
14 "fogg-use_latest_fox" : "Please first install <a href=\"http : \/\/www.mozilla.com\/en-US\/firefox\/upgrade.html?from=firefogg\">Firefox 3.5<\/a> (or later). <i>Then revisit this page to install the <b>Firefogg<\/b> extension.<\/i>",
15 "fogg-passthrough_mode" : "Your selected file is already Ogg or not a video file",
16 "fogg-transcoding" : "Encoding video to Ogg",
17 "fogg-encoding-done" : "Encoding complete",
18 "fogg-badtoken" : "Token is not valid"
21 var firefogg_install_links
= {
22 'macosx': 'http://firefogg.org/macosx/Firefogg.xpi',
23 'win32': 'http://firefogg.org/win32/Firefogg.xpi',
24 'linux' : 'http://firefogg.org/linux/Firefogg.xpi'
27 var default_firefogg_options
= {
28 //what to do when finished uploading
29 'done_upload_cb':false,
30 //if firefoog is enabled
32 //the api url to upload to
34 //the passthrough flag (enables un-modified uploads)
36 //if we will be showing the encoder interface
37 'encoder_interface': false,
38 //if we want to limit the library functionality to "only firefoog" (no upload or progress bars)
43 'new_source_cb': false, //called on source name update passes along source name
45 //target control container or form (can't be left null)
48 //if not rewriting a form we are encoding local.
49 'form_rewrite': false,
52 'target_btn_select_file': false,
53 'target_btn_select_new_file': false,
55 //'target_btn_select_url': false,
57 'target_btn_save_local_file': false,
58 'target_input_file_name': false,
61 //target install descriptions
62 'target_check_for_fogg': false,
63 'target_installed': false,
64 'target_please_install': false,
65 'target_use_latest_fox': false,
67 'target_passthrough_mode':false,
69 //if firefogg should take over the form submit action
70 'firefogg_form_action':true
74 var mvFirefogg = function(iObj
){
75 return this.init( iObj
);
77 mvFirefogg
.prototype = { //extends mvBaseUploadInterface
79 min_firefogg_version
: '0.9.9.5',
80 fogg_enabled
: false, //if firefogg is enabled or not.
81 encoder_settings
:{ //@@todo allow server to set this
83 'videoBitrate' : '544',
84 'audioBitrate' : '96',
88 ogg_extensions
: ['ogg', 'ogv', 'oga'],
89 video_extensions
: ['avi', 'mov', 'mp4', 'mp2', 'mpeg', 'mpeg2', 'mpeg4', 'dv', 'wmv'],
94 init: function( iObj
){
98 //if we have no api_url set upload to "post"
100 iObj
.upload_mode
= 'post';
102 //inherit iObj properties:
103 for(var i
in default_firefogg_options
){
107 this[i
] = default_firefogg_options
[i
];
110 //check if we want to limit the usage:
112 var myBUI
= new mvBaseUploadInterface( iObj
);
114 //standard extends code:
117 this['pe_'+ i
] = myBUI
[i
];
125 js_log('firefogg: missing selector ');
128 doRewrite:function( callback
){
130 js_log('sel len: ' + this.selector
+ '::' + $j(this.selector
).length
+ ' tag:'+ $j(this.selector
).get(0).tagName
);
131 if( $j(this.selector
).length
>=0 ){
133 if( $j(this.selector
).get(0).tagName
.toLowerCase() == 'input' ){
134 _this
.form_rewrite
= true;
137 //check if we are rewriting an input or a form:
138 if( this.form_rewrite
){
141 this.doControlHTML();
142 this.doControlBindings();
149 doControlHTML: function( ){
152 $j
.each(default_firefogg_options
, function(target
, na
){
153 if(target
.substring(0, 6)=='target'){
154 //js_log('check for target html: ' + target);
155 //check for the target if missing add to the output:
156 if( _this
[target
] === false){
157 out
+= _this
.getTargetHtml(target
) + ' ';
158 //update the target selector
159 _this
[target
] = _this
.selector
+ ' .' + target
;
163 $j( this.selector
).append( out
).hide();
165 getTargetHtml:function(target
){
166 if( target
.substr(7,3)=='btn'){
167 return '<input style="" class="' + target
+ '" type="button" value="' + gM( 'fogg-' + target
.substring(11)) + '"/> ';
168 }else if(target
.substr(7,5)=='input'){
169 return '<input style="" class="' + target
+ '" type="text" value="' + gM( 'fogg-' + target
.substring(11)) + '"/> ';
171 return '<div style="" class="' + target
+ '" >'+ gM('fogg-'+ target
.substring(7)) + '</div> ';
174 doControlBindings: function(){
178 var hide_target_list
='';
180 $j
.each(default_firefogg_options
, function(target
, na
){
181 if(target
.substring(0, 6)=='target'){
182 hide_target_list
+=coma
+ _this
[target
];
186 $j( hide_target_list
).hide();
187 //now that the proper set of items has been hiiden show:
188 $j( this.selector
).show();
191 //hide all but check-for-fogg
193 if( _this
.firefoggCheck() ){
195 //if rewriting the form lets keep the text input around:
196 if( _this
.form_rewrite
)
197 $j(this.target_input_file_name
).show();
200 $j( this.target_btn_select_file
).unbind(
201 ).attr('disabled', false
202 ).css({'display':'inline'}
207 //also setup the text file display on Click to select file:
208 $j(this.target_input_file_name
).unbind().attr('readonly', 'readonly').click(function(){
213 //first check firefox version:
214 if(!( $j
.browser
.mozilla
&& $j
.browser
.version
>= '1.9.1' )) {
215 js_log( 'show use latest::' + _this
.target_use_latest_fox
);
216 if( _this
.target_use_latest_fox
){
217 if( _this
.form_rewrite
)
218 $j( _this
.target_use_latest_fox
).prepend( gM('fogg-for_improved_uplods') );
220 $j( _this
.target_use_latest_fox
).show();
225 //if rewriting form use upload msg text
226 var upMsg
= (_this
.form_rewrite
) ? gM('fogg-for_improved_uplods') : '';
227 $j( _this
.target_please_install
).html( upMsg
+ gM('fogg-please_install', _this
.getOSlink() )).css('padding', '10px').show();
229 //setup the target save local file bindins:
230 $j( _this
.target_btn_save_local_file
).unbind().click(function(){
231 _this
.saveLocalFogg();
235 * returns the firefogg link for your os:
237 getOSlink:function(){
240 if(navigator
.oscpu
.search('Linux') >= 0)
241 os_link
= firefogg_install_links
['linux'];
242 else if(navigator
.oscpu
.search('Mac') >= 0)
243 os_link
= firefogg_install_links
['macosx'];
244 else if(navigator
.oscpu
.search('Win') >= 0)
245 os_link
= firefogg_install_links
['win32'];
249 firefoggCheck:function(){
250 if(typeof(Firefogg
) != 'undefined' && Firefogg().version
>= this.min_firefogg_version
){
251 this.fogg
= new Firefogg();
252 this.fogg_enabled
= true;
258 //assume input target
259 setupForm: function(){
260 js_log('firefogg::setupForm::');
261 //to parent form setup if we want http updates
262 if( this.form_rewrite
){
263 //do parent form setup:
267 //check if we have firefogg (if not just add a link and stop proccessing)
268 if( !this.firefoggCheck() ){
269 //add some status indicators if not provided:
270 if(!this.target_please_install
){
271 $j(this.selector
).after ( this.getTargetHtml('target_please_install') );
272 this.target_please_install
= this.selector
+ ' ~ .target_please_install';
274 if(!this.target_use_latest_fox
){
275 $j(this.selector
).after ( this.getTargetHtml('target_use_latest_fox') );
276 this.target_use_latest_fox
= this.selector
+ ' ~ .target_use_latest_fox';
278 //update download link:
279 this.doControlBindings();
283 //change the file browser to type text: (can't directly change input from "file" to "text" so longer way:
284 var inTag
= '<input ';
285 $j
.each($j(this.selector
).get(0).attributes
, function(i
, attr
){
286 var val
= attr
.value
;
287 if( attr
.name
== 'type')
289 inTag
+= attr
.name
+ '="' + val
+ '" ';
291 if(!$j(this.selector
).attr('style'))
292 inTag
+= 'style="display:inline" ';
294 inTag
+= '/><span id="' + $j(this.selector
).attr('name') + '_fogg-control"></span>';
296 js_log('set input: ' + inTag
);
297 $j(this.selector
).replaceWith(inTag
);
299 this.target_input_file_name
= 'input[name=' + $j(this.selector
).attr('name') + ']';
300 //update the selector to the control target:
301 this.selector
= '#' + $j(this.selector
).attr('name') + "_fogg-control";
303 this.doControlHTML();
304 //check for the other inline status indicator targets:
306 //update the bindings:
307 this.doControlBindings();
309 getEditForm:function(){
310 if( this.target_edit_from
)
311 return this.pe_getEditForm();
312 //else try to get the parent "from" of the file selector:
313 return $j(this.selector
).parents('form:first').get(0);
315 selectFogg:function(){
317 if(_this
.fogg
.selectVideo() ){
318 this.selectFoggActions();
321 selectFoggActions:function(){
323 js_log('videoSelectReady');
324 //if not already hidden hide select file and show "select new":
325 $j(_this
.target_btn_select_file
).hide();
327 //show and setup binding for select new file:
328 $j(_this
.target_btn_select_new_file
).show().unbind().click(function(){
329 //create new fogg instance:
330 _this
.fogg
= new Firefogg();
334 //update if we are in passthrough mode or going to encode
335 if( _this
.fogg
.sourceInfo
&& _this
.fogg
.sourceFilename
){
336 //update the source status
338 _this
.sourceFileInfo
= JSON
.parse( _this
.fogg
.sourceInfo
) ;
340 js_error('error could not parse fogg sourceInfo');
343 //now setup encoder settings based source type:
344 _this
.autoEncoderSettings();
346 //if set to passthough update the interface:
347 if(_this
.encoder_settings
['passthrough']==true){
348 $j(_this
.target_passthrough_mode
).show();
350 $j(_this
.target_passthrough_mode
).hide();
351 //if set to encoder expose the encode button:
352 if( !_this
.form_rewrite
){
353 $j(_this
.target_btn_save_local_file
).show();
356 //~otherwise the encoding will be triggered by the form~
358 //do source name update callback:
359 js_log(" should update: " + _this
.target_input_file_name
+ ' to: ' + _this
.fogg
.sourceFilename
);
360 $j(_this
.target_input_file_name
).val(_this
.fogg
.sourceFilename
).show();
362 if(_this
.new_source_cb
){
363 if(_this
.encoder_settings
['passthrough']){
364 var fName
= _this
.fogg
.sourceFilename
366 var oggExt
= (_this
.isSourceAudio())?'oga':'ogg';
367 oggExt
= (_this
.isSourceVideo())?'ogv':oggExt
;
368 oggExt
= (_this
.isUnknown())?'ogg':oggExt
;
369 oggName
= _this
.fogg
.sourceFilename
.substr(0,
370 _this
.fogg
.sourceFilename
.lastIndexOf('.'));
371 var fName
= oggName
+'.'+ oggExt
373 _this
.new_source_cb( _this
.fogg
.sourceFilename
, fName
);
377 saveLocalFogg:function(){
378 //request target location:
380 if(!this.fogg
.saveVideoAs() )
383 //we have set a target now call the encode:
387 //simple auto encoder settings just enable passthough if file is not video or > 480 pixles tall
388 autoEncoderSettings:function(){
390 //grab the extension:
391 var sf
= _this
.fogg
.sourceFilename
;
393 if( sf
.lastIndexOf('.') != -1){
394 ext
= sf
.substring( sf
.lastIndexOf('.')+1 ).toLowerCase();
397 //set to passthrough to true by default (images, arbitrary files that we want to send with http chunks)
398 this.encoder_settings
['passthrough'] = true;
400 //see if we have video or audio:
401 if( _this
.isSourceAudio() || _this
.isSourceVideo() ){
402 _this
.encoder_settings
['passthrough'] = false;
405 //special case see if we already have ogg video:
406 if( _this
.isOggFormat() ){
407 _this
.encoder_settings
['passthrough'] = true;
410 js_log('base autoEncoderSettings::' + _this
.sourceFileInfo
.contentType
+ ' passthrough:' + _this
.encoder_settings
['passthrough']);
412 isUnknown:function(){
413 return (this.sourceFileInfo
.contentType
.indexOf("unknown") != -1);
415 isSourceAudio:function(){
416 return (this.sourceFileInfo
.contentType
.indexOf("audio/") != -1);
418 isSourceVideo:function(){
419 return (this.sourceFileInfo
.contentType
.indexOf("video/") != -1);
421 isOggFormat:function(){
422 return ( this.sourceFileInfo
.contentType
.indexOf("video/ogg") != -1 ||
423 this.sourceFileInfo
.contentType
.indexOf("application/ogg") != -1 );
425 getProgressTitle:function(){
426 js_log("fogg:getProgressTitle f:" + this.fogg_enabled
+ ' rw:' + this.form_rewrite
);
427 //return the parent if we don't have fogg turned on:
428 if(! this.fogg_enabled
|| !this.firefogg_form_action
)
429 return this.pe_getProgressTitle();
430 if( !this.form_rewrite
)
431 return gM('fogg-transcoding');
432 //else return our upload+transcode msg:
433 return gM('upload-transcode-in-progress');
435 doUploadSwitch:function(){
437 js_log("firefogg: doUploadSwitch:: " + this.fogg_enabled
+ ' up mode:' + _this
.upload_mode
);
438 //make sure firefogg is enabled otherwise do parent UploadSwich:
439 if( !this.fogg_enabled
|| !this.firefogg_form_action
)
440 return _this
.pe_doUploadSwitch();
442 //check what mode to use firefogg in:
443 if( _this
.upload_mode
== 'post' ){
445 }else if( _this
.upload_mode
== 'api' && _this
.chunks_supported
){ //if api mode and chunks supported do chunkUpload
446 _this
.doChunkUpload();
448 js_error( 'Error: unrecongized upload mode: ' + _this
.upload_mode
);
451 //doChunkUpload does both uploading and encoding at the same time and uploads one meg chunks as they are ready
452 doChunkUpload : function(){
453 js_log('doChunkUpload::');
455 _this
.action_done
= false;
456 //extension should already be ogg but since its user editable,
458 //we are transcoding so we know it will be an ogg
459 //(should not be done for passthrough mode)
460 var sf
= _this
.formData
['wpDestFile'];
462 if( sf
.lastIndexOf('.') != -1){
463 ext
= sf
.substring( sf
.lastIndexOf('.') ).toLowerCase();
465 if(!_this
.encoder_settings
['passthrough'] && $j
.inArray(ext
.substr(1), _this
.ogg_extensions
) == -1 ){
466 var extreg
= new RegExp(ext
+ '$', 'i');
467 _this
.formData
['wpDestFile'] = sf
.replace(extreg
, '.ogg');
469 //add chunk response hook to build the resultURL when uploading chunks
471 //check for editToken:
473 if( _this
.formData
['wpEditToken']){
474 _this
.etoken
= _this
.formData
['wpEditToken'];
475 _this
.doChunkWithFormData();
478 'File:'+ _this
.formData
['wpDestFile'],
481 if( !eToken
|| eToken
== '+\\' ){
482 _this
.updateProgressWin(gM('fogg-badtoken'), gM('fogg-badtoken'));
485 _this
.etoken
= eToken
;
486 _this
.doChunkWithFormData();
491 _this
.doChunkWithFormData();
494 doChunkWithFormData:function(){
496 js_log("doChunkWithFormData::" + _this
.etoken
);
501 'filename': _this
.formData
['wpDestFile'],
502 'comment': _this
.formData
['wpUploadDescription'],
503 'enablechunks': 'true'
507 aReq
['token'] = this.etoken
;
509 if( _this
.formData
['wpWatchthis'] )
510 aReq
['watch'] = _this
.formData
['wpWatchthis'];
512 if( _this
.formData
['wpIgnoreWarning'] )
513 aReq
['ignorewarnings'] = _this
.formData
['wpIgnoreWarning'];
515 js_log('do fogg upload/encode call: '+ _this
.api_url
+ ' :: ' + JSON
.stringify( aReq
) );
516 js_log('foggEncode: '+ JSON
.stringify( _this
.encoder_settings
) );
517 _this
.fogg
.upload( JSON
.stringify( _this
.encoder_settings
), _this
.api_url
, JSON
.stringify( aReq
) );
519 //update upload status:
520 _this
.doUploadStatus();
522 //doEncode and monitor progress:
523 doEncode : function(){
525 _this
.action_done
= false;
526 _this
.dispProgressOverlay();
527 js_log('doEncode: with: ' + JSON
.stringify( _this
.encoder_settings
) );
528 _this
.fogg
.encode( JSON
.stringify( _this
.encoder_settings
) );
531 //show transcode status:
532 $j('#up-status-state').html( gM('upload-transcoded-status') );
534 //setup a local function for timed callback:
535 var encodingStatus = function() {
536 var status
= _this
.fogg
.status();
538 //update progress bar
539 _this
.updateProgress( _this
.fogg
.progress() );
541 //loop to get new status if still encoding
542 if( _this
.fogg
.state
== 'encoding' ) {
543 setTimeout(encodingStatus
, 500);
544 }else if ( _this
.fogg
.state
== 'encoding done' ) { //encoding done, state can also be 'encoding failed
546 }else if(_this
.fogg
.state
== 'encoding fail'){
547 //@@todo error handling:
548 js_error('encoding failed');
553 encodeDone:function(){
555 js_log('::encodeDone::');
556 _this
.action_done
= true;
557 //send to the post url:
558 if( _this
.form_rewrite
&& _this
.upload_mode
== 'post' ){
559 js_log('done with encoding do POST upload:' + _this
.editForm
.action
);
560 // ignore warnings & set source type
561 //_this.formData[ 'wpIgnoreWarning' ]='true';
562 _this
.formData
[ 'wpSourceType' ] = 'upload';
563 _this
.formData
[ 'action' ] = 'submit';
564 //wpUploadFile is set by firefogg
565 delete _this
.formData
[ 'wpUploadFile' ];
567 _this
.fogg
.post( _this
.editForm
.action
, 'wpUploadFile', JSON
.stringify( _this
.formData
) );
568 //update upload status:
569 _this
.doUploadStatus();
571 js_log("done with encoding (no upload) ");
572 //set stuats to 100% for one second:
573 _this
.updateProgress( 1 );
574 setTimeout(function(){
575 _this
.updateProgressWin(gM('fogg-encoding-done'), gM('fogg-encoding-done'));
579 doUploadStatus:function() {
581 $j( '#up-status-state' ).html( gM('uploaded-status') );
583 _this
.oldResponseText
= '';
584 //setup a local function for timed callback:
585 var uploadStatus = function(){
586 //get the response text:
587 var response_text
= _this
.fogg
.responseText
;
590 var pstatus
= JSON
.parse( _this
.fogg
.uploadstatus() );
591 response_text
= pstatus
["responseText"];
593 js_log("could not parse uploadstatus / could not get responseText");
597 if( _this
.oldResponseText
!= response_text
){
598 js_log('new result text:' + response_text
+ ' state:' + _this
.fogg
.state
);
599 _this
.oldResponseText
= response_text
;
600 //try and parse the response text and check for errors
602 var apiResult
= JSON
.parse( response_text
);
604 js_log("could not parse response_text::" + response_text
+ ' ...for now try with eval...');
606 var apiResult
= eval( response_text
);
608 var apiResult
= null;
611 if(apiResult
&& _this
.apiUpdateErrorCheck( apiResult
) === false){
612 //stop status update we have an error
613 _this
.action_done
= true;
618 //update progress bar
619 _this
.updateProgress( _this
.fogg
.progress() );
621 //loop to get new status if still uploading (could also be encoding if we are in chunk upload mode)
622 if( _this
.fogg
.state
== 'encoding' || _this
.fogg
.state
== 'uploading') {
623 setTimeout(uploadStatus
, 100);
625 }//check upload state
626 else if( _this
.fogg
.state
== 'upload done' ||
627 _this
.fogg
.state
== 'done' ||
628 _this
.fogg
.state
== 'encoding done' ) {
629 //if in "post" upload mode read the html response (should be depricated):
630 if( _this
.upload_mode
== 'api' ){
631 if( apiResult
.resultUrl
){
633 buttons
[gM('go-to-resource')] = function(){
634 window
.location
= apiResult
.resultUrl
;
636 var go_to_url_txt
= gM('go-to-resource');
637 if( typeof _this
.done_upload_cb
== 'function' ){
638 //if done action return 'true'
639 if( _this
.done_upload_cb() ){
641 _this
.updateProgressWin( gM('successfulupload'), gM( 'mv_upload_done', apiResult
.resultUrl
),buttons
);
643 //if done action returns 'false' //close progress window
644 this.action_done
= true;
645 $j('#upProgressDialog').dialog('close');
648 //update status (without done_upload_cb)
649 _this
.updateProgressWin( gM('successfulupload'), gM( 'mv_upload_done', apiResult
.resultUrl
),buttons
);
652 //done state with error? ..not really possible given how firefogg works
653 js_log(" upload done, in chunks mode, but no resultUrl!");
655 }else if( _this
.upload_mode
== 'post' && _this
.api_url
) {
656 _this
.procPageResponse( response_text
);
660 js_log('Error:firefogg upload error: ' + _this
.fogg
.state
);
665 cancel_action:function( dlElm
){
666 if(!this.fogg_enabled
){
667 return this.pe_cancel_action();
669 js_log('firefogg:cancel')
670 if( confirm( gM('mv-cancel-confim') )){
671 if(navigator
.oscpu
&& navigator
.oscpu
.search('Win') >= 0){
672 alert( 'sorry we do not yet support cancel on windows' );
674 this.action_done
= true;
676 $j(dlElm
).dialog('close');
683 * procPageResponse should be faded out in favor of the upload api soon..
684 * its all very fragile to read the html output and guess at stuff
686 procPageResponse:function( result_page
){
688 js_log('f:procPageResponse');
689 var sstring
= 'var wgTitle = "' + this.formData
['wpDestFile'].replace('_',' ');
692 var result_txt
= gM('mv_upload_done', wgArticlePath
.replace(/\$1/, 'File:' + _this
.formData
['wpDestFile'] ) );
694 result_txt
= 'File has uploaded but api "done" url was provided. Check the log for result page output';
697 //set the error text in case we dont' get far along in processing the response
698 _this
.updateProgressWin( gM('mv_upload_completed'), result_txt
);
700 if( result_page
&& result_page
.toLowerCase().indexOf( sstring
.toLowerCase() ) != -1){
701 js_log( 'upload done got redirect found: ' + sstring
+ ' r:' + _this
.done_upload_cb
);
702 if( _this
.done_upload_cb
== 'redirect' ){
703 $j( '#dlbox-centered' ).html( '<h3>Upload Completed:</h3>' + result_txt
+ '<br>' + form_txt
);
704 window
.location
= wgArticlePath
.replace( /\$1/, 'File:' + _this
.formData
['wpDestFile'] );
706 //check if the add_done_action is a callback:
707 if( typeof _this
.done_upload_cb
== 'function' )
708 _this
.done_upload_cb();
711 //js_log( 'upload page error: did not find: ' +sstring + ' in ' + "\n" + result_page );
715 //the mediaWiki upload system does not have an API so we can\'t read errors
717 var res
= grabWikiFormError( result_page
);
720 result_txt
= res
.error_txt
;
723 form_txt
= res
.form_txt
;
725 js_log( 'error text is: ' + result_txt
);
726 $j( '#dlbox-centered' ).html( '<h3>' + gM('mv_upload_completed') + '</h3>' + result_txt
+ '<br>' + form_txt
);