Commit RELEASE-NOTES line for the wgCategories js variable I added some time ago.
[lhc/web/wiklou.git] / js2 / mwEmbed / libAddMedia / mvFirefogg.js
1 /* Firefogg support.
2 * autodetects: new upload api or old http POST.
3 */
4
5 loadGM({
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_firefogg" : "Checking for Firefogg...",
11 "fogg-installed" : "Firefogg is installed",
12 "fogg-for_improved_uploads" : "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_firefox" : "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",
19 "fogg-preview" : "Preview video",
20 "fogg-hidepreview" : "Hide preview"
21 });
22
23 var firefogg_install_links = {
24 'macosx': 'http://firefogg.org/macosx/Firefogg.xpi',
25 'win32': 'http://firefogg.org/win32/Firefogg.xpi',
26 'linux': 'http://firefogg.org/linux/Firefogg.xpi'
27 };
28
29 var default_firefogg_options = {
30 // Callback for upload completion
31 'done_upload_cb': false,
32
33 // The API URL to upload to
34 'api_url': null,
35
36 // True when a file is uploaded without re-encoding
37 'passthrough': false,
38
39 // True if we will be showing the encoder interface
40 'encoder_interface': false,
41
42 // True if we want to limit the library functionality to "only firefogg"
43 // (no upload or progress bars)
44 'only_firefogg': false,
45
46 // Callback which is called when the source name changes
47 'new_source_cb': false,
48
49 // CSS selector identifying the target control container or form (can't be left null)
50 'selector': '',
51
52 // May be "upload" to if we are rewriting an upload form, or "local" if we are encoding a local file
53 'form_type': 'local',
54
55 // CSS selector for the select file button
56 'target_btn_select_file': false,
57
58 // CSS selector for the select new file button
59 'target_btn_select_new_file': false,
60
61 // CSS selector for the save local file button
62 'target_btn_save_local_file': false,
63
64 // CSS selector for the input file name button
65 'target_input_file_name': false,
66
67 // CSS selector for the "checking for firefogg..." message div
68 'target_check_for_firefogg': false,
69
70 // CSS selector for the "firefogg is installed" message div
71 'target_installed': false,
72
73 // CSS selector for the "please install firefogg" message div
74 'target_please_install': false,
75
76 // CSS selector for the "please use Firefox 3.5" message div
77 'target_use_latest_firefox': false,
78
79 // CSS selector for the message div warning that passthrough mode is enabled
80 'target_passthrough_mode': false,
81
82 // True if firefogg should take over the form submit action
83 'firefogg_form_action': true,
84
85 // True if we should show a preview of the encoding progress
86 'show_preview': true,
87
88 //If we should enable chunk uploads ( mediaWiki api supports chunk uploads)
89 'enable_chunks' : false
90 };
91
92
93 var mvFirefogg = function( options ) {
94 return this.init( options );
95 };
96
97 mvFirefogg.prototype = { // extends mvBaseUploadInterface
98 min_firefogg_version: '0.9.9.5',
99 default_encoder_settings: { // @@todo allow the server to set these
100 'maxSize' : '400',
101 'videoBitrate' : '544',
102 'audioBitrate' : '96',
103 'noUpscaling' : true
104 },
105 have_firefogg: null, // lazy initialised, use getFirefogg()
106 current_encoder_settings: null, // lazy initialised, use getEncoderSettings()
107 sourceFileInfo: null, // lazy initialised, use getSourceFileInfo()
108 ogg_extensions: [ 'ogg', 'ogv', 'oga' ],
109 video_extensions: [ 'avi', 'mov', 'mp4', 'mp2', 'mpeg', 'mpeg2', 'mpeg4', 'dv', 'wmv' ],
110
111 passthrough: false,
112 sourceMode: 'file',
113
114 /**
115 * Object initialisation
116 */
117 init: function( options ) {
118 if ( !options )
119 options = {};
120
121 // If we have no api_url, set upload mode to "post"
122 if ( !options.api_url )
123 options.upload_mode = 'post';
124
125 // Set options
126 for ( var i in default_firefogg_options ) {
127 if ( options[i] ) {
128 this[i] = options[i];
129 } else {
130 this[i] = default_firefogg_options[i];
131 }
132 }
133
134 // Inherit from mvBaseUploadInterface (unless we're in only_firefogg mode)
135 if ( !this.only_firefogg ) {
136 var myBUI = new mvBaseUploadInterface( options );
137
138 // Prefix conflicting members with pe_
139 for ( var i in myBUI ) {
140 if ( this[i] ) {
141 this['pe_'+ i] = myBUI[i];
142 } else {
143 this[i] = myBUI[i];
144 }
145 }
146 }
147
148 if ( !this.selector ) {
149 js_log('firefogg: missing selector ');
150 }
151 },
152
153 /**
154 * Rewrite the upload form, or create our own upload controls for local transcoding.
155 * Called from $j.firefogg(), in mv_embed.js.
156 */
157 doRewrite: function( callback ) {
158 var _this = this;
159 js_log( 'sel len: ' + this.selector + '::' + $j( this.selector ).length +
160 ' tag:' + $j( this.selector ).get( 0 ).tagName );
161 if ( $j( this.selector ).length >= 0 ) {
162 if ( $j( this.selector ).get( 0 ).tagName.toLowerCase() == 'input' ) {
163 _this.form_type = 'upload';
164 }
165 }
166 if ( this.form_type == 'upload' ) {
167 // Initialise existing upload form
168 this.setupForm();
169 } else {
170 // Create our own form controls
171 this.createControls();
172 this.bindControls();
173 }
174
175 if ( callback )
176 callback();
177 },
178
179 /**
180 * Create controls for local transcoding and add them to the page
181 */
182 createControls: function() {
183 var _this = this;
184 var out = '';
185 $j.each( default_firefogg_options, function( target, na ) {
186 if ( /^target/.test( target ) ) {
187 // Create the control if it doesn't already exist
188 if( _this[target] === false ) {
189 out += _this.getControlHtml(target) + ' ';
190 // Update the target selector
191 _this[target] = _this.selector + ' .' + target;
192 }
193 }
194 });
195 $j( this.selector ).append( out ).hide();
196 },
197
198 /**
199 * Get the HTML for the control with a particular name
200 */
201 getControlHtml: function( target ) {
202 if ( /^target_btn_/.test( target ) ) {
203 // Button
204 var msg = gM( target.replace( /^target_btn_/, 'fogg-' ) );
205 return '<input style="" ' +
206 'class="' + target + '" ' +
207 'type="button" ' +
208 'value="' + msg + '"/> ';
209 } else if ( /^target_input_/.test( target ) ) {
210 // Text input
211 var msg = gM( target.replace( /^target_input_/, 'fogg-' ) );
212 return '<input style="" ' +
213 'class="' + target + '" ' +
214 'type="text" ' +
215 'value="' + msg + '"/> ';
216 } else if ( /^target_/.test( target ) ) {
217 // Message
218 var msg = gM( target.replace( '/^target_/', 'fogg-' ) );
219 return '<div style="" class="' + target + '" >' + msg + '</div> ';
220 } else {
221 js_error( 'Invalid target: ' + target );
222 return '';
223 }
224 },
225
226 /**
227 * Set up events for the controls which were created with createControls()
228 */
229 bindControls: function() {
230 var _this = this;
231
232 // Hide all controls
233 var hide_target_list = '';
234 var comma = '';
235 $j.each( default_firefogg_options, function( target, na ) {
236 if ( /^target/.test( target ) ) {
237 hide_target_list += comma + _this[target];
238 comma = ',';
239 }
240 });
241 $j( hide_target_list ).hide();
242
243 // Now show the form
244 $j( _this.selector ).show();
245 if ( _this.getFirefogg() ) {
246 // Firefogg enabled
247 // If we're in upload mode, show the input filename
248 if ( _this.form_type == 'upload' )
249 $j( _this.target_input_file_name ).show();
250
251 // Show the select file button
252 $j( this.target_btn_select_file )
253 .unbind()
254 .attr( 'disabled', false )
255 .css( { 'display': 'inline' } )
256 .click( function() {
257 _this.selectSourceFile();
258 } );
259
260 // Set up the click handler for the filename box
261 $j( this.target_input_file_name )
262 .unbind()
263 .attr( 'readonly', 'readonly' )
264 .click( function() {
265 _this.selectSourceFile();
266 });
267 } else {
268 // Firefogg disabled
269 // FIXME: move this elsewhere. None of this is related to binding.
270
271 // Show the "use latest Firefox" message if necessary
272 if ( !( $j.browser.mozilla && $j.browser.version >= '1.9.1' ) ) {
273 js_log( 'show use latest::' + _this.target_use_latest_firefox );
274 if ( _this.target_use_latest_firefox ) {
275 if ( _this.form_type == 'upload' )
276 $j( _this.target_use_latest_firefox )
277 .prepend( gM( 'fogg-for_improved_uploads' ) );
278
279 $j( _this.target_use_latest_firefox ).show();
280 }
281 return;
282 }
283
284 // Otherwise show the "install Firefogg" message
285 var upMsg = ( _this.form_type == 'upload' ) ? gM( 'fogg-for_improved_uploads' ) : '';
286 var firefoggUrl = _this.getFirefoggInstallUrl();
287 if( firefoggUrl ){
288 $j( _this.target_please_install )
289 .html( upMsg + gM( 'fogg-please_install', firefoggUrl ) )
290 .css( 'padding', '10px' )
291 .show();
292 }
293 }
294
295 // Set up the click handler for the "save local file" button
296 if( _this.target_btn_save_local_file ){
297 $j( _this.target_btn_save_local_file )
298 .unbind()
299 .click( function() {
300 _this.doLocalEncodeAndSave();
301 } );
302 }
303 },
304
305 /*
306 * Get the URL for installing firefogg on the client OS
307 */
308 getFirefoggInstallUrl: function() {
309 var os_link = false;
310 if ( navigator.oscpu ) {
311 if ( navigator.oscpu.search( 'Linux' ) >= 0 )
312 os_link = firefogg_install_links['linux'];
313 else if ( navigator.oscpu.search( 'Mac' ) >= 0 )
314 os_link = firefogg_install_links['macosx'];
315 else if (navigator.oscpu.search( 'Win' ) >= 0 )
316 os_link = firefogg_install_links['win32'];
317 }
318 return os_link;
319 },
320
321 /**
322 * Get the Firefogg instance (or false if firefogg is unavailable)
323 */
324 getFirefogg: function() {
325 if ( this.have_firefogg == null ) {
326 if ( typeof( Firefogg ) != 'undefined'
327 && Firefogg().version >= this.min_firefogg_version )
328 {
329 this.have_firefogg = true;
330 this.fogg = new Firefogg();
331 } else {
332 this.have_firefogg = false;
333 this.fogg = false;
334 }
335 }
336 return this.fogg;
337 },
338
339 /**
340 * Set up the upload form
341 */
342 setupForm: function() {
343 js_log( 'firefogg::setupForm::' );
344
345 // Set up the parent if we are in upload mode
346 if ( this.form_type == 'upload' ) {
347 this.pe_setupForm();
348 }
349
350 // If Firefogg is not available, just show a "please install" message
351 if ( !this.getFirefogg() ) {
352 if ( !this.target_please_install ) {
353 $j( this.selector ).after( this.getControlHtml( 'target_please_install' ) );
354 this.target_please_install = this.selector + ' ~ .target_please_install';
355 }
356 if ( !this.target_use_latest_firefox ) {
357 $j( this.selector ).after( this.getControlHtml( 'target_use_latest_firefox' ) );
358 this.target_use_latest_firefox = this.selector + ' ~ .target_use_latest_firefox';
359 }
360 // Show download link
361 this.bindControls();
362 return;
363 }
364
365 // Change the file browser to type text. We can't simply change the attribute so
366 // we have to delete and recreate.
367 var inputTag = '<input ';
368 $j.each( $j( this.selector ).get( 0 ).attributes, function( i, attr ) {
369 var val = attr.value;
370 if ( attr.name == 'type' )
371 val = 'text';
372 inputTag += attr.name + '="' + val + '" ';
373 } );
374 if ( !$j( this.selector ).attr( 'style' ) )
375 inputTag += 'style="display:inline" ';
376
377 var id = $j( this.selector ).attr( 'name' ) + '_firefogg-control';
378 inputTag += '/><span id="' + id + '"></span>';
379
380 js_log( 'set input: ' + inputTag );
381 $j( this.selector ).replaceWith( inputTag );
382
383 this.target_input_file_name = 'input[name=' + $j( this.selector ).attr( 'name' ) + ']';
384
385 // Point the selector at the span we just created
386 this.selector = '#' + id;
387
388 // Create controls for local transcoding
389 this.createControls();
390 this.bindControls();
391 },
392
393 /**
394 * Display an upload progress overlay. Overrides the function in mvBaseUploadInterface.
395 */
396 displayProgressOverlay: function() {
397 this.pe_displayProgressOverlay();
398 // If we are uploading video (not in passthrough mode), show preview button
399 if( this.getFirefogg()
400 && !this.isCopyUpload()
401 && !this.getEncoderSettings()['passthrough'] )
402 {
403 this.createPreviewControls();
404 }
405 },
406
407 /**
408 * Create controls for showing a transcode/crop/resize preview
409 */
410 createPreviewControls: function() {
411 var _this = this;
412
413 // Set the initial button html:
414 var buttonHtml = '';
415 if( _this.show_preview == true ){
416 buttonHtml = $j.btnHtml( gM( 'fogg-hidepreview' ), 'fogg_preview', 'triangle-1-s' );
417 } else {
418 buttonHtml = $j.btnHtml( gM( 'fogg-preview' ), 'fogg_preview', 'triangle-1-e' );
419 }
420
421 // Add the preview button and canvas
422 $j( '#upProgressDialog' ).append(
423 '<div style="clear:both;height:3em"/>' +
424 buttonHtml +
425 '<div style="padding:10px;">' +
426 '<canvas style="margin:auto;" id="fogg_preview_canvas" />' +
427 '</div>'
428 );
429
430 // Set the initial state
431 if ( _this.show_preview == true ) {
432 $j( '#fogg_preview_canvas' ).show();
433 }
434
435 // Bind the preview button
436 $j( '#upProgressDialog .fogg_preview' ).btnBind().click( function() {
437 return _this.onPreviewClick( this );
438 });
439 },
440
441 /**
442 * onclick handler for the hide/show preview button
443 */
444 onPreviewClick: function( sourceNode ) {
445 var button = $j( sourceNode );
446 var icon = button.children( '.ui-icon' );
447 js_log( "click .fogg_preview" + icon.attr( 'class' ) );
448
449 if ( icon.hasClass( 'ui-icon-triangle-1-e' ) ) {
450 // Show preview
451 // Toggle button class and set button text to "hide".
452 this.show_preview = true;
453 icon.removeClass( 'ui-icon-triangle-1-e' ).addClass( 'ui-icon-triangle-1-s' );
454 button.children( '.btnText' ).text( gM( 'fogg-hidepreview' ) );
455 $j( '#fogg_preview_canvas' ).show( 'fast' );
456 } else {
457 // Hide preview
458 // Toggle button class and set button text to "show".
459 this.show_preview = false;
460 icon.removeClass( 'ui-icon-triangle-1-s' ).addClass( 'ui-icon-triangle-1-e' );
461 button.children( '.btnText' ).text( gM( 'fogg-preview' ) );
462 $j( '#fogg_preview_canvas' ).hide( 'slow' );
463 }
464 // Don't follow the # link
465 return false;
466 },
467
468 /**
469 * Render the preview frame (asynchronously)
470 */
471 renderPreview: function() {
472 var _this = this;
473 // Set up the hidden video to pull frames from
474 if( $j( '#fogg_preview_vid' ).length == 0 )
475 $j( 'body' ).append( '<video id="fogg_preview_vid" style="display:none"></video>' );
476 var v = $j( '#fogg_preview_vid' ).get( 0 );
477
478 function seekToEnd() {
479 var v = $j( '#fogg_preview_vid' ).get( 0 );
480 // TODO: document arbitrary 0.4s constant
481 v.currentTime = v.duration - 0.4;
482 }
483 function renderFrame() {
484 var v = $j( '#fogg_preview_vid' ).get( 0 );
485 var canvas = $j( '#fogg_preview_canvas' ).get( 0 );
486 if ( canvas ) {
487 canvas.width = 160;
488 canvas.height = canvas.width * v.videoHeight / v.videoWidth;
489 var ctx = canvas.getContext( "2d" );
490 ctx.drawImage( v, 0, 0, canvas.width, canvas.height );
491 }
492 }
493 function preview() {
494 // Initialize the video if it is not set up already
495 var v = $j( '#fogg_preview_vid' ).get( 0 );
496 if ( v.src != _this.fogg.previewUrl ) {
497 js_log( 'init preview with url:' + _this.fogg.previewUrl );
498 v.src = _this.fogg.previewUrl;
499
500 // Once it's loaded, seek to the end
501 v.removeEventListener( "loadedmetadata", seekToEnd, true );
502 v.addEventListener( "loadedmetadata", seekToEnd, true );
503
504 // When the seek is done, render a frame
505 v.removeEventListener( "seeked", renderFrame, true );
506 v.addEventListener( "seeked", renderFrame, true );
507
508 // Refresh the video duration and metadata
509 var previewTimer = setInterval( function() {
510 if ( _this.fogg.status() != "encoding" ) {
511 clearInterval( previewTimer );
512 _this.show_preview == false;
513 }
514 if ( _this.show_preview == true ) {
515 v.load();
516 }
517 }, 1000 );
518 }
519 }
520 preview();
521 },
522
523 /**
524 * Get the DOMNode of the form element we are rewriting.
525 * Returns false if it can't be found.
526 * Overrides mvBaseUploadInterface.getForm().
527 */
528 getForm: function() {
529 if ( this.form_selector ) {
530 return this.pe_getForm();
531 } else {
532 // No configured form selector
533 // Use the first form descendant of the current container
534 return $j( this.selector ).parents( 'form:first' ).get( 0 );
535 }
536 },
537
538 /**
539 * Show a dialog box allowing the user to select the source file of the
540 * encode/upload operation. The filename is stored by Firefogg until the
541 * next encode/upload call.
542 *
543 * After a successful select, the UI is updated accordingly.
544 */
545 selectSourceFile: function() {
546 var _this = this;
547 if( !_this.fogg.selectVideo() ) {
548 // User clicked "cancel"
549 return;
550 }
551 _this.clearSourceInfoCache();
552 _this.updateSourceFileUI();
553 },
554
555 /**
556 * Update the UI due to the source file changing
557 */
558 updateSourceFileUI: function() {
559 js_log( 'videoSelectReady' );
560 var _this = this;
561 if ( !_this.fogg.sourceInfo || !_this.fogg.sourceFilename ) {
562 // Something wrong with the source file?
563 js_log( 'selectSourceFile: sourceInfo/sourceFilename missing' );
564 return;
565 }
566
567 // Hide the "select file" button and show "select new"
568 $j( _this.target_btn_select_file ).hide();
569 $j( _this.target_btn_select_new_file)
570 .show()
571 .unbind()
572 .click( function() {
573 _this.fogg = new Firefogg();
574 _this.selectSourceFile();
575 } );
576
577 var settings = this.getEncoderSettings();
578
579 // If we're in passthrough mode, update the interface (if not a form)
580 if ( settings['passthrough'] == true && _this.form_type == 'local' ) {
581 $j( _this.target_passthrough_mode ).show();
582 } else {
583 $j( _this.target_passthrough_mode ).hide();
584 // Show the "save file" button if this is a local form
585 if ( _this.form_type == 'local' ) {
586 $j( _this.target_btn_save_local_file ).show();
587 } // else the upload will be done on form submit
588 }
589
590 // Update the input file name box and show it
591 js_log( " should update: " + _this.target_input_file_name +
592 ' to: ' + _this.fogg.sourceFilename );
593 $j( _this.target_input_file_name )
594 .val( _this.fogg.sourceFilename )
595 .show();
596
597
598 // Notify callback new_source_cb
599 if ( _this.new_source_cb ) {
600 if ( settings['passthrough'] ) {
601 var fName = _this.fogg.sourceFilename;
602 } else {
603 var oggExt = _this.isSourceAudio() ? 'oga' : 'ogg';
604 oggExt = _this.isSourceVideo() ? 'ogv' : oggExt;
605 oggExt = _this.isUnknown() ? 'ogg' : oggExt;
606 oggName = _this.fogg.sourceFilename.substr( 0,
607 _this.fogg.sourceFilename.lastIndexOf( '.' ) );
608 var fName = oggName + '.' + oggExt;
609 }
610 _this.new_source_cb( _this.fogg.sourceFilename, fName );
611 }
612 },
613
614 /**
615 * Get the source file info for the current file selected into this.fogg
616 */
617 getSourceFileInfo: function() {
618 if ( this.sourceFileInfo == null ) {
619 if ( !this.fogg.sourceInfo ) {
620 js_error( 'No firefogg source info is available' );
621 return false;
622 }
623 try {
624 this.sourceFileInfo = JSON.parse( this.fogg.sourceInfo );
625 } catch ( e ) {
626 js_error( 'error could not parse fogg sourceInfo' );
627 return false;
628 }
629 }
630 return this.sourceFileInfo;
631 },
632
633 /**
634 * Clear the cache of the source file info, presumably due to a new file
635 * being selected into this.fogg
636 */
637 clearSourceInfoCache: function() {
638 this.sourceFileInfo = null;
639 this.current_encoder_settings = null;
640 },
641
642 /**
643 * Save the result of the transcode as a local file
644 */
645 doLocalEncodeAndSave: function() {
646 var _this = this;
647 if ( !this.fogg ) {
648 js_error( 'doLocalEncodeAndSave: no Firefogg object!' );
649 return false;
650 }
651
652 // Set up the target location
653 // Firefogg shows the "save as" dialog box, and sets the path chosen as
654 // the destination for a later encode() call.
655 if ( !this.fogg.saveVideoAs() ) {
656 // User clicked "cancel"
657 return false;
658 }
659
660 // We have a source file, now do the encode
661 this.doEncode(
662 function /* onProgress */ ( progress ) {
663 _this.updateProgress( progress );
664 },
665 function /* onDone */ () {
666 js_log( "done with encoding (no upload) " );
667 // Set status to 100% for one second
668 // FIXME: this is either a hack or a waste of time, not sure which
669 _this.updateProgress( 1 );
670 setTimeout( function() {
671 _this.onLocalEncodeDone();
672 });
673 }
674 );
675 },
676
677 /**
678 * This is called when a local encode operation has completed. It updates the UI.
679 */
680 onLocalEncodeDone: function() {
681 var _this = this;
682 _this.updateProgressWin( gM( 'fogg-encoding-done' ),
683 gM( 'fogg-encoding-done' ) + '<br>' +
684 //show the video at full resolution upto 720px wide
685 '<video controls="true" style="margin:auto" id="fogg_final_vid" src="' +
686 _this.fogg.previewUrl + '"></video>'
687 );
688 //load the video and set a callback:
689 var v = $j( '#fogg_final_vid' ).get( 0 );
690 function resizeVid() {
691 var v = $j( '#fogg_final_vid' ).get(0);
692 if ( v.videoWidth > 720 ) {
693 var vW = 720;
694 var vH = 720 * v.videoHeight / v.videoWidth;
695 } else {
696 var vW = v.videoWidth;
697 var vH = v.videoHeight;
698 }
699 //reize the video:
700 $j( v ).css({
701 'width': vW,
702 'height': vH
703 });
704 //if large video resize the dialog box:
705 if( vW + 5 > 400 ) {
706 //also resize the dialog box
707 $j( '#upProgressDialog' ).dialog( 'option', 'width', vW + 20 );
708 $j( '#upProgressDialog' ).dialog( 'option', 'height', vH + 120 );
709
710 //also position the dialog container
711 $j( '#upProgressDialog') .dialog( 'option', 'position', 'center' );
712 }
713 }
714 v.removeEventListener( "loadedmetadata", resizeVid, true );
715 v.addEventListener( "loadedmetadata", resizeVid, true );
716 v.load();
717 },
718
719 /**
720 * Get the appropriate encoder settings for the current Firefogg object,
721 * into which a video has already been selected.
722 */
723 getEncoderSettings: function() {
724 if ( this.current_encoder_settings == null ) {
725 // Clone the default settings
726 var defaults = function () { };
727 defaults.prototype = this.default_encoder_settings;
728 var settings = new defaults();
729
730 // Grab the extension
731 var sf = this.fogg.sourceFilename;
732 if ( !sf ) {
733 js_error( 'getEncoderSettings(): No Firefogg source filename is available!' );
734 return false;
735 }
736 var ext = '';
737 if ( sf.lastIndexOf('.') != -1 )
738 ext = sf.substring( sf.lastIndexOf( '.' ) + 1 ).toLowerCase();
739
740 // Determine passthrough mode
741 if ( this.isOggFormat() ) {
742 // Already Ogg, no need to encode
743 settings['passthrough'] = true;
744 } else if ( this.isSourceAudio() || this.isSourceVideo() ) {
745 // OK to encode
746 settings['passthrough'] = false;
747 } else {
748 // Not audio or video, can't encode
749 settings['passthrough'] = true;
750 }
751
752 js_log( 'base setupAutoEncoder::' + this.getSourceFileInfo().contentType +
753 ' passthrough:' + settings['passthrough'] );
754 this.current_encoder_settings = settings;
755 }
756 return this.current_encoder_settings;
757 },
758
759 isUnknown: function() {
760 return ( this.getSourceFileInfo().contentType.indexOf("unknown") != -1 );
761 },
762
763 isSourceAudio: function() {
764 return ( this.getSourceFileInfo().contentType.indexOf("audio/") != -1 );
765 },
766
767 isSourceVideo: function() {
768 return ( this.getSourceFileInfo().contentType.indexOf("video/") != -1 );
769 },
770
771 isOggFormat: function() {
772 var contentType = this.getSourceFileInfo().contentType;
773 return ( contentType.indexOf("video/ogg") != -1
774 || contentType.indexOf("application/ogg") != -1 );
775 },
776
777 /**
778 * Get the default title of the progress window
779 */
780 getProgressTitle: function() {
781 js_log( "fogg:getProgressTitle f:" + ( this.getFirefogg() ? 'on' : 'off' ) +
782 ' mode:' + this.form_type );
783 // Return the parent's title if we don't have Firefogg turned on
784 if ( !this.getFirefogg() || !this.firefogg_form_action ) {
785 return this.pe_getProgressTitle();
786 } else if ( this.form_type == 'local' ) {
787 return gM( 'fogg-transcoding' );
788 } else {
789 return gM( 'mwe-upload-transcode-in-progress' );
790 }
791 },
792
793 /**
794 * Do an upload, with the mode given by this.upload_mode
795 */
796 doUpload: function() {
797 var _this = this;
798 js_log( "firefogg: doUpload:: " +
799 ( this.getFirefogg() ? 'on' : 'off' ) +
800 ' up mode:' + _this.upload_mode );
801
802 // If Firefogg is disabled or doing an copyByUrl upload, just invoke the parent method
803 if( !this.getFirefogg() || this.isCopyUpload() ) {
804 _this.pe_doUpload();
805 return;
806 }
807 // We can do a chunk upload
808 if( _this.upload_mode == 'post' && _this.enable_chunks ){
809 _this.doChunkUpload();
810 } else if ( _this.upload_mode == 'post' ) {
811 // Encode and then do a post upload
812 _this.doEncode(
813 function /* onProgress */ ( progress ) {
814 _this.updateProgress( progress );
815 },
816 function /* onDone */ () {
817 js_log( 'done with encoding do POST upload:' + _this.form.action );
818 // ignore warnings & set source type
819 //_this.formData[ 'wpIgnoreWarning' ]='true';
820 _this.formData['wpSourceType'] = 'upload';
821 _this.formData['action'] = 'submit';
822
823 // wpUploadFile is set by firefogg
824 delete _this.formData['file'];
825
826 _this.fogg.post( _this.editForm.action, 'wpUploadFile',
827 JSON.stringify( _this.formData ) );
828 _this.doUploadStatus();
829 }
830 );
831 } else {
832 js_error( 'Error: unrecongized upload mode: ' + _this.upload_mode );
833 }
834 },
835
836 /**
837 * Do both uploading and encoding at the same time. Uploads 1MB chunks as
838 * they become ready.
839 */
840 doChunkUpload : function() {
841 js_log( 'firefogg::doChunkUpload' );
842 var _this = this;
843 _this.action_done = false;
844
845 if ( !_this.getEncoderSettings()['passthrough'] ) {
846 // We are transcoding to Ogg. Fix the destination extension, it
847 // must be ogg/ogv/oga.
848 var fileName = _this.formData['filename'];
849 var ext = '';
850 var dotPos = fileName.lastIndexOf( '.' );
851 if ( dotPos != -1 ) {
852 ext = fileName.substring( dotPos ).toLowerCase();
853 }
854 if ( $j.inArray( ext.substr( 1 ), _this.ogg_extensions ) == -1 ) {
855 var extreg = new RegExp( ext + '$', 'i' );
856 _this.formData['filename'] = fileName.replace( extreg, '.ogg' );
857 }
858 }
859
860 // Get the edit token from formData if it's not set already
861 if ( !_this.editToken && _this.formData['token'] ) {
862 _this.editToken = _this.formData['token'];
863 }
864
865 if( _this.editToken ) {
866 js_log( 'we already have an edit token: ' + _this.editToken );
867 _this.doChunkUploadWithFormData();
868 return;
869 }
870
871 // No edit token. Fetch it asynchronously and then do the upload.
872 get_mw_token(
873 'File:'+ _this.formData['filename'],
874 _this.api_url,
875 function( editToken ) {
876 if( !editToken || editToken == '+\\' ) {
877 _this.updateProgressWin( gM( 'fogg-badtoken' ), gM( 'fogg-badtoken' ) );
878 return false;
879 }
880 _this.editToken = editToken;
881 _this.doChunkUploadWithFormData();
882 }
883 );
884 },
885
886 /**
887 * Internal function called from doChunkUpload() when the edit token is available
888 */
889 doChunkUploadWithFormData: function() {
890 var _this = this;
891 js_log( "firefogg::doChunkUploadWithFormData" + _this.editToken );
892 // Build the API URL
893 var aReq = {
894 'action': 'upload',
895 'format': 'json',
896 'filename': _this.formData['filename'],
897 'comment': _this.formData['comment'],
898 'enablechunks': 'true'
899 };
900
901 if ( _this.editToken )
902 aReq['token'] = this.editToken;
903
904 if ( _this.formData['watch'] )
905 aReq['watch'] = _this.formData['watch'];
906
907 if ( _this.formData['ignorewarnings'] )
908 aReq['ignorewarnings'] = _this.formData['ignorewarnings'];
909
910 var encoderSettings = this.getEncoderSettings();
911 js_log( 'do fogg upload/encode call: ' + _this.api_url + ' :: ' + JSON.stringify( aReq ) );
912 js_log( 'foggEncode: ' + JSON.stringify( encoderSettings ) );
913 _this.fogg.upload( JSON.stringify( encoderSettings ), _this.api_url,
914 JSON.stringify( aReq ) );
915
916 // Start polling the upload status
917 _this.doUploadStatus();
918 },
919
920 /**
921 * Encode the video and monitor progress.
922 *
923 * Calls progressCallback() regularly with a number between 0 and 1 indicating progress.
924 * Calls doneCallback() when the encode is finished.
925 *
926 * Asynchronous, returns immediately.
927 */
928 doEncode : function( progressCallback, doneCallback ) {
929 var _this = this;
930 _this.action_done = false;
931 _this.displayProgressOverlay();
932 var encoderSettings = this.getEncoderSettings();
933 js_log( 'doEncode: with: ' + JSON.stringify( encoderSettings ) );
934 _this.fogg.encode( JSON.stringify( encoderSettings ) );
935
936 //show transcode status:
937 $j( '#up-status-state' ).html( gM( 'mwe-upload-transcoded-status' ) );
938
939 //setup a local function for timed callback:
940 var encodingStatus = function() {
941 var status = _this.fogg.status();
942
943 if ( _this.show_preview == true && _this.fogg.state == 'encoding' ) {
944 _this.renderPreview();
945 }
946
947 // Update progress
948 progressCallback( _this.fogg.progress() );
949
950 //loop to get new status if still encoding
951 if ( _this.fogg.state == 'encoding' ) {
952 setTimeout( encodingStatus, 500 );
953 } else if ( _this.fogg.state == 'encoding done' ) {
954 _this.action_done = true;
955 progressCallback( 1 );
956 doneCallback();
957 } else if ( _this.fogg.state == 'encoding fail' ) {
958 //@@todo error handling:
959 js_error( 'encoding failed' );
960 }
961 }
962 encodingStatus();
963 },
964
965 /**
966 * Poll the upload progress and update the UI regularly until the upload is complete.
967 *
968 * Asynchronous, returns immediately.
969 */
970 doUploadStatus: function() {
971 var _this = this;
972 $j( '#up-status-state' ).html( gM( 'mwe-uploaded-status' ) );
973
974 _this.oldResponseText = '';
975
976 // Create a local function for timed callback
977 var uploadStatus = function() {
978 var response_text = _this.fogg.responseText;
979 if ( !response_text ) {
980 try {
981 var pstatus = JSON.parse( _this.fogg.uploadstatus() );
982 response_text = pstatus["responseText"];
983 } catch( e ) {
984 js_log( "could not parse uploadstatus / could not get responseText" );
985 }
986 }
987
988 if ( _this.oldResponseText != response_text ) {
989 js_log( 'new result text:' + response_text + ' state:' + _this.fogg.state );
990 _this.oldResponseText = response_text;
991 // Parse the response text and check for errors
992 try {
993 var apiResult = JSON.parse( response_text );
994 } catch( e ) {
995 js_log( "could not parse response_text::" + response_text +
996 ' ...for now try with eval...' );
997 try {
998 var apiResult = eval( response_text );
999 } catch( e ) {
1000 var apiResult = null;
1001 }
1002 }
1003 if ( apiResult && !_this.isApiSuccess( apiResult ) ) {
1004 // Show the error and stop the upload
1005 _this.showApiError( apiResult );
1006 _this.action_done = true;
1007 _this.fogg.cancel();
1008 return false;
1009 }
1010 }
1011 if ( _this.show_preview == true ) {
1012 if ( _this.fogg.state == 'encoding' ) {
1013 _this.renderPreview();
1014 }
1015 }
1016
1017 // Update the progress bar
1018 _this.updateProgress( _this.fogg.progress() );
1019
1020 // If we're still uploading or encoding, continue to poll the status
1021 if ( _this.fogg.state == 'encoding' || _this.fogg.state == 'uploading' ) {
1022 setTimeout( uploadStatus, 100 );
1023 return;
1024 }
1025
1026 // Upload done?
1027 if ( -1 == $j.inArray( _this.fogg.state, [ 'upload done', 'done', 'encoding done' ] ) ) {
1028 js_log( 'Error:firefogg upload error: ' + _this.fogg.state );
1029 return;
1030 }
1031 if ( apiResult && apiResult.resultUrl ) {
1032 var buttons = {};
1033 buttons[ gM( 'mwe-go-to-resource' ) ] = function() {
1034 window.location = apiResult.resultUrl;
1035 }
1036 var go_to_url_txt = gM( 'mwe-go-to-resource' );
1037 var showMessage = true;
1038 if ( typeof _this.done_upload_cb == 'function' ) {
1039 // Call the callback
1040 // It will return false if it doesn't want us to show our own "done" message
1041 showMessage = _this.done_upload_cb( _this.formData );
1042 }
1043 if ( showMessage ) {
1044 _this.updateProgressWin( gM( 'mwe-successfulupload' ),
1045 gM( 'mwe-upload_done', apiResult.resultUrl ), buttons );
1046 } else {
1047 this.action_done = true;
1048 $j( '#upProgressDialog' ).empty().dialog( 'close' );
1049 }
1050 } else {
1051 // Done state with error? Not really possible given how firefogg works...
1052 js_log( " Upload done in chunks mode, but no resultUrl!" );
1053 }
1054
1055 }
1056 uploadStatus();
1057 },
1058
1059 /**
1060 * This is the cancel button handler, referenced by getCancelButton() in the parent.
1061 */
1062 onCancel: function( dlElm ) {
1063 if ( !this.have_firefogg ) {
1064 return this.pe_cancel_action();
1065 }
1066 js_log( 'firefogg:cancel' )
1067 if ( confirm( gM( 'mwe-cancel-confim' ) ) ) {
1068 // FIXME: sillyness ( upstream firefogg cancel fix needed )
1069 if ( navigator.oscpu && navigator.oscpu.search( 'Win' ) >= 0 ) {
1070 alert( 'sorry we do not yet support cancel on windows' );
1071 } else {
1072 this.action_done = true;
1073 this.fogg.cancel();
1074 $j( dlElm ).empty().dialog( 'close' );
1075 }
1076 }
1077 // Don't follow the # link:
1078 return false;
1079 }
1080 };