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('mwe-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' ){ //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('firefogg::doChunkUpload');
455 _this.action_done = false;
457 //extension should already be ogg but since its user editable,
459 //we are transcoding so we know it will be an ogg
460 //(should not be done for passthrough mode)
461 var sf = _this.formData['filename'];
463 if( sf.lastIndexOf('.') != -1){
464 ext = sf.substring( sf.lastIndexOf('.') ).toLowerCase();
466 if( !_this.encoder_settings['passthrough'] && $j.inArray(ext.substr(1), _this.ogg_extensions) == -1 ){
467 var extreg = new RegExp(ext + '$', 'i');
468 _this.formData['filename'] = sf.replace(extreg, '.ogg');
470 //add chunk response hook to build the resultURL when uploading chunks
472 //check for editToken:
474 js_log('missing token try ' + _this.formData['token']);
475 if( _this.formData['token']){
476 _this.etoken = _this.formData['token'];
477 _this.doChunkWithFormData();
480 'File:'+ _this.formData['filename'],
483 if( !eToken || eToken == '+\\' ){
484 _this.updateProgressWin( gM('fogg-badtoken'), gM('fogg-badtoken') );
487 _this.etoken = eToken;
488 _this.doChunkWithFormData();
493 js_log('we already have token: ' + this.etoken);
494 _this.doChunkWithFormData();
497 doChunkWithFormData:function(){
499 js_log("firefogg::doChunkWithFormData" + _this.etoken);
504 'filename': _this.formData['filename'],
505 'comment': _this.formData['comment'],
506 'enablechunks': 'true'
510 aReq['token'] = this.etoken;
512 if( _this.formData['watch'] )
513 aReq['watch'] = _this.formData['watch'];
515 if( _this.formData['ignorewarnings'] )
516 aReq['ignorewarnings'] = _this.formData['ignorewarnings'];
518 js_log('do fogg upload/encode call: '+ _this.api_url + ' :: ' + JSON.stringify( aReq ) );
519 js_log('foggEncode: '+ JSON.stringify( _this.encoder_settings ) );
520 _this.fogg.upload( JSON.stringify( _this.encoder_settings ), _this.api_url , JSON.stringify( aReq ) );
522 //update upload status:
523 _this.doUploadStatus();
525 //doEncode and monitor progress:
526 doEncode : function(){
528 _this.action_done = false;
529 _this.dispProgressOverlay();
530 js_log('doEncode: with: ' + JSON.stringify( _this.encoder_settings ) );
531 _this.fogg.encode( JSON.stringify( _this.encoder_settings ) );
534 //show transcode status:
535 $j('#up-status-state').html( gM('mwe-upload-transcoded-status') );
537 //setup a local function for timed callback:
538 var encodingStatus = function() {
539 var status = _this.fogg.status();
541 //update progress bar
542 _this.updateProgress( _this.fogg.progress() );
544 //loop to get new status if still encoding
545 if( _this.fogg.state == 'encoding' ) {
546 setTimeout(encodingStatus, 500);
547 }else if ( _this.fogg.state == 'encoding done' ) { //encoding done, state can also be 'encoding failed
549 }else if(_this.fogg.state == 'encoding fail'){
550 //@@todo error handling:
551 js_error('encoding failed');
556 encodeDone:function(){
558 js_log('::encodeDone::');
559 _this.action_done = true;
560 //send to the post url:
561 if( _this.form_rewrite && _this.upload_mode == 'post' ){
562 js_log('done with encoding do POST upload:' + _this.editForm.action);
563 // ignore warnings & set source type
564 //_this.formData[ 'wpIgnoreWarning' ]='true';
565 _this.formData[ 'wpSourceType' ] = 'upload';
566 _this.formData[ 'action' ] = 'submit';
567 //wpUploadFile is set by firefogg
568 delete _this.formData[ 'file' ];
570 _this.fogg.post( _this.editForm.action, 'wpUploadFile', JSON.stringify( _this.formData ) );
571 //update upload status:
572 _this.doUploadStatus();
574 js_log("done with encoding (no upload) ");
575 //set stuats to 100% for one second:
576 _this.updateProgress( 1 );
577 setTimeout(function(){
578 _this.updateProgressWin(gM('fogg-encoding-done'), gM('fogg-encoding-done'));
582 doUploadStatus:function() {
584 $j( '#up-status-state' ).html( gM('mwe-uploaded-status') );
586 _this.oldResponseText = '';
587 //setup a local function for timed callback:
588 var uploadStatus = function(){
589 //get the response text:
590 var response_text = _this.fogg.responseText;
593 var pstatus = JSON.parse( _this.fogg.uploadstatus() );
594 response_text = pstatus["responseText"];
596 js_log("could not parse uploadstatus / could not get responseText");
600 if( _this.oldResponseText != response_text){
601 js_log('new result text:' + response_text + ' state:' + _this.fogg.state);
602 _this.oldResponseText = response_text;
603 //try and parse the response text and check for errors
605 var apiResult = JSON.parse( response_text );
607 js_log("could not parse response_text::" + response_text + ' ...for now try with eval...');
609 var apiResult = eval( response_text );
611 var apiResult = null;
614 if(apiResult && _this.apiUpdateErrorCheck( apiResult ) === false){
615 //stop status update we have an error
616 _this.action_done = true;
621 //update progress bar
622 _this.updateProgress( _this.fogg.progress() );
624 //loop to get new status if still uploading (could also be encoding if we are in chunk upload mode)
625 if( _this.fogg.state == 'encoding' || _this.fogg.state == 'uploading') {
626 setTimeout(uploadStatus, 100);
627 }//check upload state
628 else if( _this.fogg.state == 'upload done' ||
629 _this.fogg.state == 'done' ||
630 _this.fogg.state == 'encoding done' ) {
631 //if in "post" upload mode read the html response (should be depricated):
632 if( _this.upload_mode == 'api' ){
633 if( apiResult && apiResult.resultUrl ){
635 buttons[gM('mwe-go-to-resource')] = function(){
636 window.location = apiResult.resultUrl;
638 var go_to_url_txt = gM('mwe-go-to-resource');
639 if( typeof _this.done_upload_cb == 'function' ){
640 //if done action return 'true'
641 if( _this.done_upload_cb( _this.formData ) ){
643 _this.updateProgressWin( gM('mwe-successfulupload'), gM( 'mwe-upload_done', apiResult.resultUrl),buttons);
645 //if done action returns 'false' //close progress window
646 this.action_done = true;
647 $j('#upProgressDialog').dialog('close');
650 //update status (without done_upload_cb)
651 _this.updateProgressWin( gM('mwe-successfulupload'), gM( 'mwe-upload_done', apiResult.resultUrl),buttons);
654 //done state with error? ..not really possible given how firefogg works
655 js_log(" Upload done in chunks mode, but no resultUrl!");
657 }else if( _this.upload_mode == 'post' && _this.api_url ) {
658 _this.procPageResponse( response_text );
662 js_log('Error:firefogg upload error: ' + _this.fogg.state );
667 cancel_action:function( dlElm ){
668 if(!this.fogg_enabled){
669 return this.pe_cancel_action();
671 js_log('firefogg:cancel')
672 if( confirm( gM('mwe-cancel-confim') )){
673 if(navigator.oscpu && navigator.oscpu.search('Win') >= 0){
674 alert( 'sorry we do not yet support cancel on windows' );
676 this.action_done = true;
678 $j(dlElm).dialog('close');
685 * procPageResponse should be faded out in favor of the upload api soon..
686 * its all very fragile to read the html output and guess at stuff
688 procPageResponse:function( result_page ){
690 js_log('f:procPageResponse');
691 var sstring = 'var wgTitle = "' + this.formData['filename'].replace('_',' ');
694 var result_txt = gM('mwe-upload_done', wgArticlePath.replace(/\$1/, 'File:' + _this.formData['filename'] ) );
696 result_txt = 'File has uploaded but api "done" URL was provided. Check the log for result page output';
699 //set the error text in case we dont' get far along in processing the response
700 _this.updateProgressWin( gM('mwe-upload_completed'), result_txt );
702 if( result_page && result_page.toLowerCase().indexOf( sstring.toLowerCase() ) != -1){
703 js_log( 'upload done got redirect found: ' + sstring + ' r:' + _this.done_upload_cb );
704 if( _this.done_upload_cb == 'redirect' ){
705 $j( '#dlbox-centered' ).html( '<h3>Upload Completed:</h3>' + result_txt + '<br>' + form_txt);
706 window.location = wgArticlePath.replace( /\$1/, 'File:' + _this.formData['wpDestFile'] );
708 //check if the add_done_action is a callback:
709 if( typeof _this.done_upload_cb == 'function' )
710 _this.done_upload_cb();
713 //js_log( 'upload page error: did not find: ' +sstring + ' in ' + "\n" + result_page );
717 //the mediaWiki upload system does not have an API so we can\'t read errors
719 var res = grabWikiFormError( result_page );
722 result_txt = res.error_txt;
725 form_txt = res.form_txt;
727 js_log( 'error text is: ' + result_txt );
728 $j( '#dlbox-centered' ).html( '<h3>' + gM('mwe-upload_completed') + '</h3>' + result_txt + '<br>' + form_txt);