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-save_local_file" : "Save Ogg",
9 "fogg-check_for_fogg" : "Checking for Firefogg <blink>...</blink>",
10 "fogg-installed" : "Firefogg is Installed",
11 "fogg-for_improved_uplods" : "For Improved uploads: ",
12 "fogg-please_install" : "<a href=\"$1\">Install Firefogg</a>. More <a href=\"http://commons.wikimedia.org/wiki/Commons:Firefogg\">about firefogg</a>",
13 "fogg-use_latest_fox" : "Please first install <a href=\"http://www.mozilla.com/en-US/firefox/upgrade.html?from=firefogg\">Firefox 3.5</a>. <i>then revisit this page to install the <b>firefogg</b> extention</i>",
14 "fogg-passthrough_mode" : "Your selected file is already ogg or not a video file",
15 "fogg-transcoding" : "Encoding Video to Ogg",
16 "fogg-encoding-done" : "Encoding Done"
19 var firefogg_install_links
= {
20 'macosx': 'http://firefogg.org/macosx/Firefogg.xpi',
21 'win32' : 'http://firefogg.org/win32/Firefogg.xpi',
22 'linux' : 'http://firefogg.org/linux/Firefogg.xpi'
25 var default_firefogg_options
= {
26 //what to do when finished uploading
27 'upload_done_action':'redirect',
28 //if firefoog is enabled
30 //the api url to upload to
32 //the passthrough flag (enables un-modified uploads)
34 //if we will be showing the encoder interface
35 'encoder_interface': false,
36 //if we want to limit the library functionality to "only firefoog" (no upload or progress bars)
41 'new_source_cb': false, //called on source name update passes along source name
43 //target control container or form (can't be left null)
46 //if not rewriting a form we are encoding local.
47 'form_rewrite' : false,
50 'target_btn_select_file' : false,
51 'target_btn_select_new_file': false,
52 'target_input_file_name' : false,
53 'target_btn_save_local_file': false,
56 //target install descriptions
57 'target_check_for_fogg' : false,
58 'target_installed' : false,
59 'target_please_install' : false,
60 'target_use_latest_fox': false,
62 'target_passthrough_mode':false,
64 //if firefogg should take over the form submit action
65 'firefogg_form_action':true
69 var mvFirefogg = function(iObj
){
70 return this.init( iObj
);
72 mvFirefogg
.prototype = { //extends mvBaseUploadInterface
74 min_firefogg_version
: '0.9.9',
75 fogg_enabled
: false, //if firefogg is enabled or not.
76 encoder_settings
:{ //@@todo allow server to set this
82 ogg_extensions
: ['ogg', 'ogv', 'oga'],
83 video_extensions
: ['avi', 'mov', 'mp4', 'mp2', 'mpeg', 'mpeg2', 'mpeg4', 'dv', 'wmv'],
87 init: function( iObj
){
91 //if we have no api_url set upload to "post"
93 iObj
.upload_mode
= 'post';
95 //inherit iObj properties:
96 for(var i
in default_firefogg_options
){
100 this[i
] = default_firefogg_options
[i
];
103 //check if we want to limit the usage:
105 var myBUI
= new mvBaseUploadInterface( iObj
);
107 //standard extends code:
110 this['pe_'+ i
] = myBUI
[i
];
118 js_log('firefogg: missing selector ');
121 doRewrite:function( callback
){
123 js_log('sel len: ' + this.selector
+ '::' + $j(this.selector
).length
+ ' tag:'+ $j(this.selector
).get(0).tagName
);
124 if( $j(this.selector
).length
>=0 ){
126 if( $j(this.selector
).get(0).tagName
.toLowerCase() == 'input' ){
127 _this
.form_rewrite
= true;
130 //check if we are rewriting an input or a form:
131 if( this.form_rewrite
){
134 this.doControlHTML();
135 this.doControlBindings();
142 doControlHTML: function( ){
145 $j
.each(default_firefogg_options
, function(target
, na
){
146 if(target
.substring(0, 6)=='target'){
147 //check for the target if missing add to the output:
148 if( _this
[target
] === false){
149 out
+= _this
.getTargetHtml(target
) + ' ';
150 //update the target selector
151 _this
[target
] = _this
.selector
+ ' .' + target
;
155 $j( this.selector
).append( out
).hide();
157 getTargetHtml:function(target
){
158 if( target
.substr(7,3)=='btn'){
159 return '<input style="" class="' + target
+ '" type="button" value="' + gM( 'fogg-' + target
.substring(11)) + '"/> ';
160 }else if(target
.substr(7,5)=='input'){
161 return '<input style="" class="' + target
+ '" type="text" value="' + gM( 'fogg-' + target
.substring(11)) + '"/> ';
163 return '<div style="" class="' + target
+ '" >'+ gM('fogg-'+ target
.substring(7)) + '</div> ';
166 doControlBindings: function(){
170 var hide_target_list
='';
172 $j
.each(default_firefogg_options
, function(target
, na
){
173 if(target
.substring(0, 6)=='target'){
174 hide_target_list
+=coma
+ _this
[target
];
178 $j( hide_target_list
).hide();
179 //now that the proper set of items has been hiiden show:
180 $j( this.selector
).show();
183 //hide all but check-for-fogg
185 if( _this
.firefoggCheck() ){
187 //if rewriting the form lets keep the text input around:
188 if( _this
.form_rewrite
)
189 $j(this.target_input_file_name
).show();
192 $j( this.target_btn_select_file
).unbind(
193 ).attr('disabled', false
194 ).css({'display':'inline'}
198 //also setup the text file display on Click to select file:
199 $j( this.target_input_file_name
).unbind().attr('readonly', 'readonly').click(function(){
204 //first check firefox version:
205 if(!( $j
.browser
.mozilla
&& $j
.browser
.version
>= '1.9.1' )) {
206 js_log( 'show use latest::' + _this
.target_use_latest_fox
);
207 if( _this
.target_use_latest_fox
){
208 if( _this
.form_rewrite
)
209 $j( _this
.target_use_latest_fox
).prepend( gM('fogg-for_improved_uplods') );
211 $j( _this
.target_use_latest_fox
).show();
215 //if they have the right version of mozilla provide install link:
218 if(navigator
.oscpu
.search('Linux') >= 0)
219 os_link
= firefogg_install_links
['linux'];
220 else if(navigator
.oscpu
.search('Mac') >= 0)
221 os_link
= firefogg_install_links
['macosx'];
222 else if(navigator
.oscpu
.search('Win') >= 0)
223 os_link
= firefogg_install_links
['win32'];
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',os_link
)).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();
234 firefoggCheck:function(){
235 if(typeof(Firefogg
) != 'undefined' && Firefogg().version
>= this.min_firefogg_version
){
236 this.fogg
= new Firefogg();
237 this.fogg_enabled
= true;
243 //assume input target
244 setupForm: function(){
245 js_log('firefogg::setupForm::');
246 //to parent form setup if we want http updates
247 if( this.form_rewrite
){
248 //do parent form setup:
252 //check if we have firefogg (if not just add a link and stop proccessing)
253 if( !this.firefoggCheck() ){
254 //add some status indicators if not provided:
255 if(!this.target_please_install
){
256 $j(this.selector
).after ( this.getTargetHtml('target_please_install') );
257 this.target_please_install
= this.selector
+ ' ~ .target_please_install';
259 if(!this.target_use_latest_fox
){
260 $j(this.selector
).after ( this.getTargetHtml('target_use_latest_fox') );
261 this.target_use_latest_fox
= this.selector
+ ' ~ .target_use_latest_fox';
263 //update download link:
264 this.doControlBindings();
268 //change the file browser to type text: (can't directly change input from "file" to "text" so longer way:
269 var inTag
= '<input ';
270 $j
.each($j(this.selector
).get(0).attributes
, function(i
, attr
){
271 var val
= attr
.value
;
272 if( attr
.name
== 'type')
274 inTag
+= attr
.name
+ '="' + val
+ '" ';
276 if(!$j(this.selector
).attr('style'))
277 inTag
+= 'style="display:inline" ';
279 inTag
+= '/><span id="' + $j(this.selector
).attr('name') + '_fogg-control"></span>';
281 js_log('set input: ' + inTag
);
282 $j( this.selector
).replaceWith( inTag
);
284 this.target_input_file_name
= 'input[name=' + $j(this.selector
).attr('name') + ']';
285 //update the selector to the control target:
286 this.selector
= '#' + $j(this.selector
).attr('name') + "_fogg-control";
288 this.doControlHTML();
289 //check for the other inline status indicator targets:
291 //update the bindings:
292 this.doControlBindings();
294 getEditForm:function(){
295 if( this.target_edit_from
){
296 return this.pe_getEditForm();
298 js_log('get form: action=' + $j(this.selector
).parents().find("form").attr('action'));
299 return $j(this.selector
).parents().find("form").get(0);
301 selectFogg:function(){
303 if( _this
.fogg
.selectVideo() ) {
304 js_log('videoSelectReady');
305 //if not already hidden hide select file and show "select new":
306 $j(_this
.target_btn_select_file
).hide();
307 //show and setup binding for select new file:
308 $j(_this
.target_btn_select_new_file
).show().unbind().click(function(){
309 //create new fogg instance:
310 _this
.fogg
= new Firefogg();
313 //update if we are in passthrough mode or going to encode
314 if( _this
.fogg
.sourceInfo
&& _this
.fogg
.sourceFilename
){
315 //update the source status
317 _this
.sourceFileInfo
= JSON
.parse( _this
.fogg
.sourceInfo
) ;
319 js_error('error could not parse fogg sourceInfo');
322 //now setup encoder settings based source type:
323 _this
.autoEncoderSettings();
325 //if set to passthough update the interface:
326 if(_this
.encoder_settings
['passthrough']==true){
327 $j(_this
.target_passthrough_mode
).show();
329 $j(_this
.target_passthrough_mode
).hide();
330 //if set to encoder expose the encode button:
331 if( !_this
.form_rewrite
){
332 $j(_this
.target_btn_save_local_file
).show();
335 //~otherwise the encoding will be triggered by the form~
337 //do source name update callback:
338 js_log(" should update: " + _this
.target_input_file_name
+ ' to: ' + _this
.fogg
.sourceFilename
);
339 $j(_this
.target_input_file_name
).val(_this
.fogg
.sourceFilename
).show();
341 if(_this
.new_source_cb
){
342 if(_this
.encoder_settings
['passthrough']){
343 var fName
= _this
.fogg
.sourceFilename
345 var oggExt
= (_this
.isSourceAudio())?'oga':'ogg';
346 oggExt
= (_this
.isSourceVideo())?'ogv':oggExt
;
347 oggExt
= (_this
.isUnknown())?'ogg':oggExt
;
348 oggName
= _this
.fogg
.sourceFilename
.substr(0,
349 _this
.fogg
.sourceFilename
.lastIndexOf('.'));
350 var fName
= oggName
+'.'+ oggExt
352 _this
.new_source_cb( _this
.fogg
.sourceFilename
, fName
);
356 //js_error("Firefogg error selecting file");
359 saveLocalFogg:function(){
360 //request target location:
362 if(!this.fogg
.saveVideoAs() )
365 //we have set a target now call the encode:
369 //simple auto encoder settings just enable passthough if file is not video or > 480 pixles tall
370 autoEncoderSettings:function(){
372 //grab the extension:
373 var sf
= _this
.fogg
.sourceFilename
;
375 if( sf
.lastIndexOf('.') != -1){
376 ext
= sf
.substring( sf
.lastIndexOf('.')+1 ).toLowerCase();
379 //set to passthrough to true by default (images, arbitrary files that we want to send with http chunks)
380 this.encoder_settings
['passthrough'] = true;
382 //see if we have video or audio:
383 if( _this
.isSourceAudio() || _this
.isSourceVideo() ){
384 _this
.encoder_settings
['passthrough'] = false;
387 //special case see if we already have ogg video:
388 if( _this
.isOggFormat() ){
389 _this
.encoder_settings
['passthrough'] = true;
392 js_log('base autoEncoderSettings::' + _this
.sourceFileInfo
.contentType
+ ' passthrough:' + _this
.encoder_settings
['passthrough']);
394 isUnknown:function(){
395 return (this.sourceFileInfo
.contentType
.indexOf("unknown") != -1);
397 isSourceAudio:function(){
398 return (this.sourceFileInfo
.contentType
.indexOf("audio/") != -1);
400 isSourceVideo:function(){
401 return (this.sourceFileInfo
.contentType
.indexOf("video/") != -1);
403 isOggFormat:function(){
404 return ( this.sourceFileInfo
.contentType
.indexOf("video/ogg") != -1 ||
405 this.sourceFileInfo
.contentType
.indexOf("application/ogg") != -1 );
407 getProgressTitle:function(){
408 js_log("fogg:getProgressTitle f:" + this.fogg_enabled
+ ' rw:' + this.form_rewrite
);
409 //return the parent if we don't have fogg turned on:
410 if(! this.fogg_enabled
)
411 return this.pe_getProgressTitle();
412 if( !this.form_rewrite
)
413 return gM('fogg-transcoding');
414 //else return our upload+transcode msg:
415 return gM('upload-transcode-in-progress');
417 doUploadSwitch:function(){
419 js_log( "firefogg: doUploadSwitch:: " + this.fogg_enabled
);
420 //make sure firefogg is enabled otherwise do parent UploadSwich:
421 if( !this.fogg_enabled
|| !this.firefogg_form_action
)
422 return _this
.pe_doUploadSwitch();
424 //check what mode to use firefogg in:
425 if( _this
.upload_mode
== 'post' ){
427 }else if( _this
.upload_mode
== 'api' && _this
.chunks_supported
){ //if api mode and chunks supported do chunkUpload
428 _this
.doChunkUpload();
430 js_error( 'Error: unrecongized upload mode: ' + _this
.upload_mode
);
433 //doChunkUpload does both uploading and encoding at the same time and uploads one meg chunks as they are ready
434 doChunkUpload : function(){
436 _this
.action_done
= false;
438 //extension should already be ogg but since its user editable,
440 //we are transcoding so we know it will be an ogg
441 //(should not be done for passthrough mode)
442 var sf
= _this
.formData
['wpDestFile'];
444 if( sf
.lastIndexOf('.') != -1){
445 ext
= sf
.substring( sf
.lastIndexOf('.') ).toLowerCase();
447 if(!_this
.encoder_settings
['passthrough'] && $j
.inArray(ext
.substr(1), _this
.ogg_extensions
) == -1 ){
448 var extreg
= new RegExp(ext
+ '$', 'i');
449 _this
.formData
['wpDestFile'] = sf
.replace(extreg
, '.ogg');
451 //add chunk response hook to build the resultURL when uploading chunks
457 'filename' : _this
.formData
['wpDestFile'],
458 'comment' : _this
.formData
['wpUploadDescription'],
462 //check for editToken:
464 this.etoken
= _this
.formData
['wpEditToken'];
467 aReq
['token'] = this.etoken
;
469 if( _this
.formData
['wpWatchthis'] )
470 aReq
['watch'] = _this
.formData
['wpWatchthis'];
472 if( _this
.formData
['wpIgnoreWarning'] )
473 aReq
['ignorewarnings'] = _this
.formData
['wpIgnoreWarning'];
475 js_log('do fogg upload/encode call: '+ _this
.api_url
+ ' :: ' + JSON
.stringify( aReq
) );
476 js_log('foggEncode: '+ JSON
.stringify( _this
.encoder_settings
) );
477 _this
.fogg
.upload( JSON
.stringify( _this
.encoder_settings
), _this
.api_url
, JSON
.stringify( aReq
) );
479 //update upload status:
480 _this
.doUploadStatus();
482 //doEncode and monitor progress:
483 doEncode : function(){
484 js_log('firefogg:doEncode');
486 _this
.action_done
= false;
487 _this
.dispProgressOverlay();
488 js_log('doEncode: with: ' + JSON
.stringify( _this
.encoder_settings
) );
489 _this
.fogg
.encode( JSON
.stringify( _this
.encoder_settings
) );
492 //show transcode status:
493 $j('#up-status-state').html( gM('upload-transcoded-status') );
495 //setup a local function for timed callback:
496 var encodingStatus = function() {
497 var status
= _this
.fogg
.status();
499 //update progress bar
500 _this
.updateProgress( _this
.fogg
.progress() );
502 //loop to get new status if still encoding
503 if( _this
.fogg
.state
== 'encoding' ) {
504 setTimeout(encodingStatus
, 500);
505 }else if ( _this
.fogg
.state
== 'encoding done' ) { //encoding done, state can also be 'encoding failed
507 }else if(_this
.fogg
.state
== 'encoding fail'){
508 //@@todo error handling:
509 js_error('encoding failed');
514 encodeDone:function(){
516 js_log('::encodeDone::');
517 _this
.action_done
= true;
518 //send to the post url:
519 if( _this
.form_rewrite
&& _this
.upload_mode
== 'post' ){
520 js_log('done with encoding do POST upload:' + _this
.editForm
.action
);
521 // ignore warnings & set source type
522 //_this.formData[ 'wpIgnoreWarning' ]='true';
523 _this
.formData
[ 'wpSourceType' ] = 'upload';
524 _this
.formData
[ 'action' ] = 'submit';
525 //wpUploadFile is set by firefogg
526 delete _this
.formData
[ 'wpUploadFile' ];
528 _this
.fogg
.post( _this
.editForm
.action
, 'wpUploadFile', JSON
.stringify( _this
.formData
) );
529 //update upload status:
530 _this
.doUploadStatus();
532 js_log("done with encoding (no upload) ");
533 //set stuats to 100% for one second:
534 _this
.updateProgress( 1 );
535 setTimeout(function(){
536 _this
.updateProgressWin(gM('fogg-encoding-done'), gM('fogg-encoding-done'));
540 doUploadStatus:function() {
542 $j('#up-status-state').html( gM('uploaded-status') );
544 _this
.oldResponseText
= '';
545 //setup a local function for timed callback:
546 var uploadStatus = function(){
547 //get the response text:
548 var response_text
= _this
.fogg
.responseText
;
551 var pstatus
= JSON
.parse( _this
.fogg
.uploadstatus() );
552 response_text
= pstatus
["responseText"];
554 js_log("could not parse uploadstatus / could not get responseText");
558 if( _this
.oldResponseText
!= response_text
){
559 js_log('new result text:' + response_text
+ ' state:' + _this
.fogg
.state
);
560 _this
.oldResponseText
= response_text
;
561 //try and parse the response text and check for errors
563 var apiResult
= JSON
.parse( response_text
);
565 js_log("could not parse response_text::" + response_text
+ ' ...for now try with eval...');
567 var apiResult
= eval( response_text
);
569 var apiResult
= null;
572 if(apiResult
&& _this
.apiUpdateErrorCheck( apiResult
) === false){
573 //stop status update we have an error
574 _this
.action_done
= true;
579 //update progress bar
580 _this
.updateProgress( _this
.fogg
.progress() );
582 //loop to get new status if still uploading (could also be encoding if we are in chunk upload mode)
583 if( _this
.fogg
.state
== 'encoding' || _this
.fogg
.state
== 'uploading') {
584 setTimeout(uploadStatus
, 100);
586 }//check upload state
587 else if( _this
.fogg
.state
== 'upload done' ||
588 _this
.fogg
.state
== 'done' ||
589 _this
.fogg
.state
== 'encoding done' ) {
590 js_log( 'firefogg:upload done: ');
591 //if in "post" upload mode read the html response (should be depricated):
592 if( _this
.upload_mode
== 'post' && _this
.api_url
) {
593 _this
.procPageResponse( response_text
);
594 }else if( _this
.upload_mode
== 'api'){
595 if( _this
.fogg
.resultUrl
){
597 buttons
[gM('go-to-resource')] = function(){
598 window
.location
= _this
.fogg
.resultUrl
;
600 var go_to_url_txt
= gM('go-to-resource');
601 //should have an json result:
602 _this
.updateProgressWin( gM('successfulupload'), gM( 'mv_upload_done', _this
.fogg
.resultUrl
),buttons
);
604 //done state with error? ..not really possible given how firefogg works
605 js_log(" upload done, in chunks mode, but no resultUrl::" + response_text
);
610 js_log('Error:firefogg upload error: ' + _this
.fogg
.state
);
615 cancel_action:function( dlElm
){
616 if(!this.fogg_enabled
){
617 return this.pe_cancel_action();
619 js_log('firefogg:cancel')
620 if( confirm( gM('mv-cancel-confim') )){
621 if(navigator
.oscpu
&& navigator
.oscpu
.search('Win') >= 0){
622 alert( 'sorry we do not yet support cancel on windows' );
624 this.action_done
= true;
626 $j(dlElm
).dialog('close');
633 * procPageResponse should be faded out in favor of the upload api soon..
634 * its all very fragile to read the html output and guess at stuff
636 procPageResponse:function( result_page
){
638 js_log('f:procPageResponse');
639 var sstring
= 'var wgTitle = "' + this.formData
['wpDestFile'].replace('_',' ');
642 var result_txt
= gM('mv_upload_done', wgArticlePath
.replace(/\$1/, 'File:' + this.formData
['wpDestFile'] ) );
644 result_txt
= 'File has uploaded but api "done" url was provided. Check the log for result page output';
647 //set the error text in case we dont' get far along in processing the response
648 _this
.updateProgressWin( gM('mv_upload_completed'), result_txt
);
650 if( result_page
&& result_page
.toLowerCase().indexOf( sstring
.toLowerCase() ) != -1){
651 js_log( 'upload done got redirect found: ' + sstring
+ ' r:' + _this
.upload_done_action
);
652 if( _this
.upload_done_action
== 'redirect' ){
653 $j( '#dlbox-centered' ).html( '<h3>Upload Completed:</h3>' + result_txt
+ '<br>' + form_txt
);
654 window
.location
= wgArticlePath
.replace( /\$1/, 'File:' + formData
['wpDestFile'] );
656 //check if the add_done_action is a callback:
657 if( typeof _this
.upload_done_action
== 'function' )
658 _this
.upload_done_action();
661 //js_log( 'upload page error: did not find: ' +sstring + ' in ' + "\n" + result_page );
665 //the mediaWiki upload system does not have an API so we can\'t read errors
667 var res
= grabWikiFormError( result_page
);
670 result_txt
= res
.error_txt
;
673 form_txt
= res
.form_txt
;
675 js_log( 'error text is: ' + result_txt
);
676 $j( '#dlbox-centered' ).html( '<h3>' + gM('mv_upload_completed') + '</h3>' + result_txt
+ '<br>' + form_txt
);