3 "mwe-select_transcript_set" : "Select layers",
4 "mwe-auto_scroll" : "auto scroll",
6 "mwe-improve_transcript" : "Improve",
7 "mwe-no_text_tracks_found" : "No text tracks were found",
8 "mwe-subtitles" : "$1 subtitles"
10 // text interface object (for inline display captions)
11 var mvTextInterface = function( parentEmbed
){
12 return this.init( parentEmbed
);
14 mvTextInterface
.prototype = {
15 text_lookahead_time
:0,
17 default_time_range
: "source", //by default just use the source don't get a time-range
20 add_to_end_on_this_pass
:false,
22 init:function( parentEmbed
){
23 //init a new availableTracks obj:
24 this.availableTracks
={};
25 //set the parent embed object:
27 //parse roe if not already done:
28 this.getTimedTextTracks();
30 //@@todo separate out data loader & data display
31 getTimedTextTracks:function(){
32 js_log("load timed text from roe: "+ this.pe
.roe
);
34 var apiUrl
= mwGetLocalApiUrl();
35 //if roe not yet loaded do load it:
36 if(this.pe
.roe
|| _this
.pe
.wikiTitleKey
){
37 if(!this.pe
.media_element
.addedROEData
){
38 js_log("load roe data!");
39 $j('#mv_txt_load_'+_this
.pe
.id
).show(); //show the loading icon
41 do_request( _this
.pe
.roe
, function(data
)
44 _this
.pe
.media_element
.addROE(data
);
45 _this
.getParseTimedText_rowReady();
47 }else if( _this
.pe
.wikiTitleKey
){
52 'apprefix' : 'TimedText:' + _this
.pe
.wikiTitleKey
54 }, function( subData
) {
59 'siprop' : 'languages'
61 }, function( langDataRaw
) {
63 var lagRaw
= langDataRaw
.query
.languages
;
65 langData
[ lagRaw
[j
].code
] = lagRaw
[j
]['*'];
67 for(var i
in subData
.query
.allpages
){
68 var subPage
= subData
.query
.allpages
[i
];
69 langKey
= subPage
.title
.split('.');
70 langKey
= langKey
[ langKey
.length
-2 ];
71 if( !langData
[ langKey
] ){
72 js_log('Error: langkey:'+ langKey
+ ' not found');
74 var textElm
= document
.createElement('text');
78 'type' : "text/x-srt",
79 'title' : gM('mwe-subtitles', langData
[ langKey
]),
80 'src' : wgServer
+ wgScript
+ '?title=' + subPage
.title
+ '&action=raw'
82 _this
.pe
.media_element
.tryAddSource( textElm
);
83 _this
.getParseTimedText_rowReady();
87 }); //function( subData ) {
90 js_log('row data ready (no roe request)');
91 _this
.getParseTimedText_rowReady();
94 if( this.pe
.media_element
.timedTextSources() ){
95 _this
.getParseTimedText_rowReady();
97 js_log('no roe data or timed text sources');
101 getParseTimedText_rowReady: function (){
103 //create timedTextObj
104 var default_found
=false;
105 js_log("mv_txt_load_:SHOW mv_txt_load_");
106 $j('#mv_txt_load_'+_this
.pe
.id
).show(); //show the loading icon
108 $j
.each( this.pe
.media_element
.sources
, function(inx
, source
){
110 if( typeof source
.id
== 'undefined' || source
.id
== null ){
111 source
.id
= 'tt_' + inx
;
113 var tObj
= new timedTextObj( source
);
114 //make sure its a valid timed text format (we have not loaded or parsed yet) : (
115 if( tObj
.lib
!= null ){
116 js_log('adding Track: ' + source
.id
+ ' to ' + _this
.pe
.id
);
117 _this
.availableTracks
[ source
.id
] = tObj
;
118 //js_log( 'is : ' + source.id + ' default: ' + source.default );
119 //display if requested:
120 if( source
['default'] == "true" ){
121 //we did set at least one track by default tag
123 js_log('do load timed text: ' + source
.id
);
124 _this
.loadAndDisplay( source
.id
);
126 //don't load the track and don't display
131 //no default clip found take the first_id
133 $j
.each( _this
.availableTracks
, function(inx
, sourceTrack
){
134 _this
.loadAndDisplay( sourceTrack
.id
);
136 //retun after loading first available
141 //if nothing found anywhere update the loading icon to say no tracks found
143 $j('#mv_txt_load_'+_this
.pe
.id
).html( gM('mwe-no_text_tracks_found') );
147 loadAndDisplay: function ( track_id
){
149 $j('#mv_txt_load_'+_this
.pe
.id
).show();//show the loading icon
150 _this
.availableTracks
[ track_id
].load(_this
.default_time_range
, function(){
151 $j('#mv_txt_load_'+_this
.pe
.id
).hide();
152 _this
.addTrack( track_id
);
155 addTrack: function( track_id
){
156 js_log('f:displayTrack:'+ track_id
);
158 //set the display flag to true:
159 _this
.availableTracks
[ track_id
].display
=true;
162 js_log("SHOULD ADD: "+ track_id
+ ' count:' + _this
.availableTracks
[ track_id
].textNodes
.length
);
164 //a flag to avoid checking all clips if we know we are adding to the end:
165 _this
.add_to_end_on_this_pass
= false;
167 //run clip adding on a timed interval to not lock the browser on large srt file merges (should use worker threads)
169 var track_id
= track_id
;
170 var addNextClip = function(){
171 var text_clip
= _this
.availableTracks
[ track_id
].textNodes
[i
];
172 _this
.add_merge_text_clip(text_clip
);
174 if(i
< _this
.availableTracks
[ track_id
].textNodes
.length
){
175 setTimeout(addNextClip
, 1);
180 add_merge_text_clip: function( text_clip
){
182 //make sure the clip does not already exist:
183 if($j('#tc_'+text_clip
.id
).length
==0){
184 var inserted
= false;
185 var text_clip_start_time
= npt2seconds( text_clip
.start
);
187 var insertHTML
= '<div id="tc_'+text_clip
.id
+'" ' +
188 'start_sec="' + text_clip_start_time
+ '" ' +
189 'start="'+text_clip
.start
+'" end="'+text_clip
.end
+'" class="mvtt tt_'+text_clip
.type_id
+'">' +
190 '<div class="mvttseek" style="top:0px;left:0px;right:0px;height:20px;font-size:small">'+
191 text_clip
.start
+ ' to ' +text_clip
.end
+
195 //js_log("ADDING CLIP: " + text_clip_start_time + ' html: ' + insertHTML);
196 if(!_this
.add_to_end_on_this_pass
){
197 $j('#mmbody_'+this.pe
.id
+' .mvtt').each(function(){
199 //js_log( npt2seconds($j(this).attr('start')) + ' > ' + text_clip_start_time);
200 if( $j(this).attr('start_sec') > text_clip_start_time
){
202 $j(this).before(insertHTML
);
205 _this
.add_to_end
= true;
209 //js_log('should just add to end: '+insertHTML);
211 $j('#mmbody_'+this.pe
.id
).append(insertHTML
);
214 //apply the mouse over transcript seek/click functions:
215 $j(".mvttseek").click( function() {
216 _this
.pe
.doSeek( $j(this).parent().attr("start_sec") / _this
.pe
.getDuration() );
218 $j(".mvttseek").hoverIntent({
219 interval
:200, //polling interval
220 timeout
:200, //delay before onMouseOut
222 js_log('mvttseek: over');
223 $j(this).parent().addClass('tt_highlight');
224 //do section highlight
225 _this
.pe
.highlightPlaySection( {
226 'start' : $j(this).parent().attr("start"),
227 'end' : $j(this).parent().attr("end")
231 js_log('mvttseek: out');
232 $j(this).parent().removeClass('tt_highlight');
233 //de highlight section
234 _this
.pe
.hideHighlight();
240 setup_layout:function(){
241 //check if we have already loaded the menu/body:
242 if($j('#tt_mmenu_'+this.pe
.id
).length
==0){
243 $j('#metaBox_'+this.pe
.id
).html(
247 this.doMenuBindings();
251 //setup layout if not already done:
253 //display the interface if not already displayed:
254 $j('#metaBox_'+this.pe
.id
).fadeIn("fast");
255 //start the autoscroll timer:
256 if( this.autoscroll
)
257 this.setAutoScroll();
261 $j('#metaBox_'+this.pe
.id
).fadeOut('fast');
263 $j('#metaButton_'+this.pe
.id
).fadeIn('fast');
266 return '<div id="mmbody_'+this.pe
.id
+'" ' +
267 'style="position:absolute;top:30px;left:0px;' +
268 'right:0px;bottom:0px;' +
269 'height:'+(this.pe
.height
-30)+
270 'px;overflow:auto;"><span style="display:none;" id="mv_txt_load_' + this.pe
.id
+ '">'+
271 gM('mwe-loading_txt')+'</span>' +
274 getTsSelect:function(){
276 js_log('getTsSelect');
277 var selHTML
= '<div id="mvtsel_' + this.pe
.id
+ '" style="position:absolute;background:#FFF;top:30px;left:0px;right:0px;bottom:0px;overflow:auto;">';
278 selHTML
+='<b>' + gM('mwe-select_transcript_set') + '</b><ul>';
280 for(var i
in _this
.availableTracks
){ //for in loop ok on object
281 var checked
= ( _this
.availableTracks
[i
].display
) ? 'checked' : '';
282 selHTML
+='<li><input name="'+i
+'" class="mvTsSelect" type="checkbox" ' + checked
+ '>'+
283 _this
.availableTracks
[i
].getTitle() + '</li>';
286 '<a href="#" onClick="document.getElementById(\'' + this.pe
.id
+ '\').textInterface.applyTsSelect();return false;">'+gM('mwe-close')+'</a>'+
288 $j('#metaBox_'+_this
.pe
.id
).append( selHTML
);
290 applyTsSelect:function(){
292 //update availableTracks
293 $j('#mvtsel_'+this.pe
.id
+' .mvTsSelect').each(function(){
295 var track_id
= this.name
;
296 //if not yet loaded now would be a good time
297 if(! _this
.availableTracks
[ track_id
].loaded
){
298 _this
.loadAndDisplay( track_id
);
300 _this
.availableTracks
[this.name
].display
=true;
301 //display the named class:
302 $j('#mmbody_'+_this
.pe
.id
+' .tt_'+this.name
).fadeIn("fast");
305 if(_this
.availableTracks
[this.name
].display
){
306 _this
.availableTracks
[this.name
].display
=false;
308 $j('#mmbody_'+_this
.pe
.id
+' .tt_'+this.name
).fadeOut("fast");
312 $j('#mvtsel_'+_this
.pe
.id
).fadeOut("fast").remove();
316 //grab the time from the video object
317 var cur_time
= this.pe
.currentTime
;
319 var search_for_range
= true;
320 //check if the current transcript is already where we want:
321 if($j('#mmbody_'+this.pe
.id
+' .tt_scroll_highlight').length
!= 0){
322 var curhl
= $j('#mmbody_'+this.pe
.id
+' .tt_scroll_highlight').get(0);
323 if(npt2seconds($j(curhl
).attr('start') ) < cur_time
&&
324 npt2seconds($j(curhl
).attr('end') ) > cur_time
){
325 /*js_log('in range of current hl: ' +
326 npt2seconds($j(curhl).attr('start')) + ' to ' + npt2seconds($j(curhl).attr('end')));
328 search_for_range
= false;
330 search_for_range
= true;
331 //remove the highlight from all:
332 $j('#mmbody_'+this.pe
.id
+' .tt_scroll_highlight').removeClass('tt_scroll_highlight');
335 /*js_log('search_for_range:'+search_for_range + ' for: '+ cur_time);*/
336 if( search_for_range
){
337 //search for current time: add tt_scroll_highlight to clip
339 // should do binnary search not iterative
340 // avoid jquery function calls do native loops
341 $j('#mmbody_'+this.pe
.id
+' .mvtt').each(function(){
342 if(npt2seconds($j(this).attr('start') ) < cur_time
&&
343 npt2seconds($j(this).attr('end') ) > cur_time
){
344 _this
.prevTimeScroll
=cur_time
;
345 $j('#mmbody_'+_this
.pe
.id
).animate({
346 scrollTop
: $j(this).get(0).offsetTop
348 $j(this).addClass('tt_scroll_highlight');
349 //js_log('should add class to: ' + $j(this).attr('id'));
357 setAutoScroll:function( timer
){
359 this.autoscroll
= ( typeof timer
=='undefined' )?this.autoscroll
:timer
;
361 //start the timer if its not already running
362 if(!this.scrollTimerId
){
363 this.scrollTimerId
= setInterval('$j(\'#'+_this
.pe
.id
+'\').get(0).textInterface.monitor()', 500);
365 //jump to the current position:
366 var cur_time
= parseInt (this.pe
.currentTime
);
367 js_log('cur time: '+ cur_time
);
371 $j('#mmbody_'+this.pe
.id
+' .mvtt').each(function(){
372 if(cur_time
> npt2seconds($j(this).attr('start')) ){
373 _this
.prevTimeScroll
=cur_time
;
374 if( $j(this).attr('id') )
375 scroll_to_id
= $j(this).attr('id');
378 if(scroll_to_id
!= '')
379 $j( '#mmbody_' + _this
.pe
.id
).animate( { scrollTop
: $j('#'+scroll_to_id
).position().top
} , 'slow' );
382 clearInterval(this.scrollTimerId
);
383 this.scrollTimerId
=0;
388 //add in loading icon:
389 var as_checked
= (this.autoscroll
)?'checked':'';
390 out
+= '<div id="tt_mmenu_'+this.pe
.id
+'" class="ui-widget-header" style="font-size:.6em;position:absolute;top:0;height:30px;left:0px;right:0px;">' +
391 $j
.btnHtml(gM('mwe-select_transcript_set'), 'tt-select', 'shuffle');
392 if(this.pe
.media_element
.linkback
){
393 out
+=' ' + $j
.btnHtml(gM('mwe-improve_transcript'), 'tt-improve', 'document', {href
:this.pe
.media_element
.linkback
, target
:'_new'});
395 out
+='<input onClick="document.getElementById(\''+this.pe
.id
+'\').textInterface.setAutoScroll(this.checked);return false;" ' +
396 'type="checkbox" '+as_checked
+'>'+gM('mwe-auto_scroll') + ' ' +
397 $j
.btnHtml(gM('mwe-close'), 'tt-close', 'circle-close');
401 doMenuBindings:function(){
403 var mt
= '#tt_mmenu_'+ _this
.pe
.id
;
404 $j(mt
+ ' .tt-close').unbind().btnBind().click(function(){
405 $j( '#' + _this
.pe
.id
).get(0).closeTextInterface();
408 $j(mt
+ ' .tt-select').unbind().btnBind().click(function(){
409 $j( '#' + _this
.pe
.id
).get(0).textInterface
.getTsSelect();
412 //use hard-coded link:
413 $j(mt
+ ' .tt-improve').btnBind();
417 /* text format objects
418 * @@todo allow loading from external lib set
420 var timedTextObj = function( source
){
421 //@@todo in the future we could support timed text in oggs if they can be accessed via javascript
422 //we should be able to do a HEAD request to see if we can read transcripts from the file.
423 switch( source
.mime_type
){
432 js_log( source
.mime_type
+ ' is not suported timed text fromat');
436 //extend with the per-mime type lib:
437 eval('var tObj = timedText' + this.lib
+ ';');
438 for( var i
in tObj
){
441 return this.init( source
);
444 //base timedText object
445 timedTextObj
.prototype = {
449 textNodes
:new Array(),
450 init: function( source
){
451 //copy source properties
452 this.source
= source
;
456 return this.source
.title
;
459 return this.source
.src
;
463 // Specific Timed Text formats:
466 load: function( range
, callback
){
468 js_log('textCMML: loading track: '+ this.src
);
470 //:: Load transcript range ::
472 var pcurl
= parseUri( _this
.getSRC() );
473 //check for urls without time keys:
474 if( typeof pcurl
.queryKey
['t'] == 'undefined'){
475 //in which case just get the full time req:
476 do_request( this.getSRC(), function(data
){
477 _this
.doParse( data
);
483 var req_time
= pcurl
.queryKey
['t'].split('/');
484 req_time
[0]=npt2seconds(req_time
[0]);
485 req_time
[1]=npt2seconds(req_time
[1]);
486 if(req_time
[1]-req_time
[0]> _this
.request_length
){
487 //longer than 5 min will only issue a (request 5 min)
488 req_time
[1] = req_time
[0]+_this
.request_length
;
490 //set up request url:
491 url
= pcurl
.protocol
+ '://' + pcurl
.authority
+ pcurl
.path
+'?';
492 $j
.each(pcurl
.queryKey
, function(key
, val
){
494 url
+=key
+'='+val
+'&';
496 url
+= 't=' + seconds2npt(req_time
[0]) + '/' + seconds2npt(req_time
[1]) + '&';
499 do_request( url
, function(data
){
500 js_log("load track clip count:" + data
.getElementsByTagName('clip').length
);
501 _this
.doParse( data
);
506 doParse: function(data
){
508 $j
.each(data
.getElementsByTagName('clip'), function(inx
, clip
){
509 //js_log(' on clip ' + clip.id);
511 start
: $j(clip
).attr('start').replace('npt:', ''),
512 end
: $j(clip
).attr('end').replace('npt:', ''),
514 id
: $j(clip
).attr('id')
516 $j
.each( clip
.getElementsByTagName('body'), function(binx
, bn
){
518 text_clip
.body
= bn
.textContent
;
520 text_clip
.body
= bn
.text
;
523 _this
.textNodes
.push( text_clip
);
528 load: function( range
, callback
){
530 js_log('textSRT: loading : '+ _this
.getSRC() );
531 do_request( _this
.getSRC() , function(data
){
532 _this
.doParse( data
);
537 doParse:function( data
){
538 //split up the transcript chunks:
540 var tc
= data
.split(/[\r]?\n[\r]?\n/);
541 //pushing can take time
542 for(var s
=0; s
< tc
.length
; s
++) {
543 var st
= tc
[s
].split('\n');
546 var i
= st
[1].split(' --> ')[0].replace(/^\s+|\s+$/g,"");
547 var o
= st
[1].split(' --> ')[1].replace(/^\s+|\s+$/g,"");
550 for(j
=3; j
<st
.length
;j
++)
557 "id": this.id
+ '_' + n
,
560 this.textNodes
.push( text_clip
);