+
-
-
-
+
+
+
+
+
+
+
+
+
-
+ WebVTT renderer debug mode | Size cues by text bounding box | High Resolution Mode
Media Controls
-
- Transcript
-
+
+
-
+
+
+
-
-
diff --git a/js/captionator-example-api.js b/js/captionator-example-api.js
new file mode 100644
index 0000000..d83ff89
--- /dev/null
+++ b/js/captionator-example-api.js
@@ -0,0 +1,96 @@
+// This file shows how the TextTrack API can be used (with captionator) to generate an interface for video captions.
+
+var generateTranscript = function() {
+
+};
+
+var generateMediaControls = function(videoElement) {
+ if (!(videoElement instanceof HTMLVideoElement) || !videoElement.tracks) return null;
+
+ var tableFragment = document.createDocumentFragment();
+ // Set up table structure
+ var table = document.createElement("table");
+ var thead = document.createElement("thead");
+ var tbody = document.createElement("tbody");
+
+ thead.innerHTML = "" +
+ " Kind " +
+ " Label " +
+ " Language (srclang/language) " +
+ " Ready State (readyState) " +
+ " Visibility Mode (mode) " +
+ " Enabled " +
+ " ";
+
+ // Our functions that do most of the work
+
+ var createRowHeader = function(headerText) {
+ var tmpRowHeader = document.createElement("tr");
+ tmpRowHeader.innerHTML = "" + headerText + " ";
+ tbody.appendChild(tmpRowHeader);
+ };
+
+ var createRowsForTrackList = function(trackList) {
+ if (!trackList) return null;
+
+ if (trackList.length) {
+ for (trackIndex in trackList) {
+ if (trackList.hasOwnProperty(trackIndex)) {
+ (function() {
+ var readyStateTD, modeTD, toggleTD, trackToggle;
+ var currentTrack = trackList[trackIndex];
+ var trackRow = document.createElement("tr");
+
+ trackRow.innerHTML ="" + currentTrack.kind + " " +
+ "" + currentTrack.label + " " +
+ "" + currentTrack.language + " " +
+ "" + ["Not Loaded","Loading","Loaded","Error"][currentTrack.readyState] + " " +
+ "" + ["Not Showing","Hidden","Showing"][currentTrack.mode] + " " +
+ " ";
+
+ readyStateTD = trackRow.childNodes[trackRow.childNodes.length-3];
+ modeTD = trackRow.childNodes[trackRow.childNodes.length-2];
+ toggleTD = trackRow.childNodes[trackRow.childNodes.length-1];
+ trackToggle = document.createElement("input");
+ trackToggle.type = "checkbox";
+ trackToggle.title = "Toggle track visibility";
+
+ if (currentTrack.mode === 2) {
+ trackToggle.checked = true;
+ }
+
+ toggleTD.appendChild(trackToggle);
+
+ trackToggle.addEventListener("change",function(eventData) {
+ currentTrack.mode = [0,2][Number(this.checked)];
+ modeTD.innerHTML = ["Not Showing","Hidden","Showing"][currentTrack.mode];
+ },false);
+
+ currentTrack.onload = function(eventData) {
+ readyStateTD.innerHTML = ["Not Loaded","Loading","Loaded","Error"][currentTrack.readyState];
+ modeTD.innerHTML = ["Not Showing","Hidden","Showing"][currentTrack.mode];
+ };
+ currentTrack.onerror = currentTrack.onload;
+
+ tbody.appendChild(trackRow);
+ })();
+ }
+ }
+ }
+ };
+
+ // Now make them go!
+
+ createRowHeader("Text Tracks");
+ createRowsForTrackList(videoElement.tracks);
+ createRowHeader("Media Tracks");
+ createRowsForTrackList(videoElement.mediaTracks);
+
+ table.appendChild(thead);
+ table.appendChild(tbody);
+ tableFragment.appendChild(table);
+ table.border = 1;
+ table.cellPadding = 4;
+
+ return tableFragment;
+};
\ No newline at end of file
diff --git a/js/captionator-min.js b/js/captionator-min.js
index 0933e6b..7fb1cca 100644
--- a/js/captionator-min.js
+++ b/js/captionator-min.js
@@ -1,30 +1,64 @@
-/*
- Captionator 0.2
+/*
+ Captionator 0.5.1 [CaptionCrunch]
Christopher Giffard, 2011
Share and enjoy
https://github.com/cgiffard/Captionator
*/
-var captionator={createDOMException:function(d,h,b){try{document.querySelectorAll("div/[]")}catch(i){var a=function(c,f,e){this.code=c;this.message=f;this.name=e};a.prototype=i;return new a(d,h,b)}},captionify:function(d,h,b){var i=[],a=0;b=b instanceof Object?b:{};if(HTMLVideoElement){if(typeof document.createElement("video").addTrack==="function")return false}else return false;captionator.TextTrack=function(c,f,e,j){this.onload=function(){};this.onerror=function(){};this.oncuechange=function(){};
-this.internalMode=0;this.cues=new captionator.TextTrackCueList(this);this.activeCues=new captionator.ActiveTextTrackCueList(this.cues);this.kind=c||"caption";this.label=f||"";this.language=e||"";this.src=j||"";this.readyState=0;this.getMode=function(){return this.internalMode};this.setMode=function(g){if([0,1,2].indexOf(g)!==-1){if(g!==this.internalMode){this.internalMode=g;this.readyState===0&&this.src.length>0&&g>0&&this.loadTrack(this.src);this.readyState===2&&captionator.rebuildCaptions(this.videoNode);
-if(g===0||g===1){g="captionator-"+this.videoNode.id+"-"+this.kind+"-"+this.language;(g=document.getElementById(g))&&g.parentNode.removeChild(g)}}}else throw Error("Illegal mode value for track.");};if(Object.prototype.__defineGetter__){this.__defineGetter__("mode",this.getMode);this.__defineSetter__("mode",this.setMode)}else Object.defineProperty&&Object.defineProperty(this,"mode",{get:this.getMode,set:this.setMode});this.loadTrack=function(g,m){var n,l=new XMLHttpRequest;if(this.readyState===2)m instanceof
-Function&&m(n);else{this.src=g;this.readyState=1;var k=this;l.open("GET",g,true);l.onreadystatechange=function(){if(l.readyState===4)if(l.status===200){n=captionator.parseCaptions(l.responseText);k.readyState=2;captionator.rebuildCaptions(k.videoNode);k.cues.loadCues(n);k.onload();m instanceof Function&&m.call(k,n)}else k.onerror()};l.send(null)}};this.generateTranscript=function(g){console.log("generating transcript",g);if(typeof g==="string")g=document.querySelectorAll(g)[0];if(typeof g==="object")if(this.readyState===
-2)this.cues.forEach(function(m){g.innerHTML+=""+m.getCueAsSource()+"
"});else if(this.readyState===1)this.onload=function(){this.generateTranscript(g)};else this.loadTrack(this.src,function(){this.generateTranscript(g)});else return false};this.addCue=function(){};this.removeCue=function(){}};captionator.TextTrackCueList=function(c){this.track=c instanceof captionator.TextTrack?c:null;this.getCueById=function(f){return this.filter(function(e){return e.id===f})[0]};this.loadCues=
-function(f){for(var e=0;e0});g instanceof
-Array&&g.forEach(function(k){var o={D:"verticalText",L:"linePosition",T:"textPosition",A:"textAlignment",S:"textSize"};k=k.split(":");if(o[k[0]])l[o[k[0]]]=k[1]})}this.getCueAsSource=function(){return this.text};this.getCueAsHTML=function(){var k=document.createDocumentFragment(),o=document.createElement("div");o.innerHTML=this.text;Array.prototype.forEach.call(o.childNodes,function(p){k.appendChild(p.cloneNode(true))});return k};this.isActive=function(){var k=0;if(this.track instanceof captionator.TextTrack)if(this.track.mode===
-2&&this.track.readyState===2)try{k=this.track.videoNode.currentTime;if(this.startTime<=k&&this.endTime>=k)return true}catch(o){return false}return false};if(Object.prototype.__defineGetter__)this.__defineGetter__("active",this.isActive);else Object.defineProperty&&Object.defineProperty(this,"active",{get:this.isActive});this.onenter=function(){};this.onexit=function(){}};if(b.exportObjects){window.TextTrack=captionator.TextTrack;window.TextTrackCueList=captionator.TextTrackCueList;window.ActiveTextTrackCueList=
-captionator.ActiveTextTrackCueList;window.TextTrackCue=captionator.TextTrackCue}[].slice.call(document.getElementsByTagName("video"),0).forEach(function(c){c.addTrack=function(f,e,j,g){e=typeof e==="string"?e:"";j=typeof j==="string"?j:"";if(!["subtitles","captions","descriptions","captions","metadata"].filter(function(m){return f===m?true:false}).length)throw captionator.createDOMException(12,"DOMException 12: SYNTAX_ERR: You must use a valid kind when creating a TimedTextTrack.","SYNTAX_ERR");if(e=
-new captionator.TextTrack(f,e,j,g)){if(!(c.tracks instanceof Array))c.tracks=[];c.tracks.push(e);return e}else return false}});if(!d||d===false||d===undefined||d===null)i=[].slice.call(document.getElementsByTagName("video"),0);else if(d instanceof Array)for(a=0;a'+f.getCueAsSource()+""});if(String(b.innerHTML)!==i)b.innerHTML=
-i;b.style.display=i.length?"block":"none"}})},styleContainer:function(d,h,b){var i=function(c,f){for(var e in f)if({}.hasOwnProperty.call(f,e))c.style[e]=f[e]},a=function(c){var f=window.getComputedStyle(c,null),e=c,j=c.offsetTop,g=c.offsetLeft,m=c,n=0,l=0;m=parseInt(f.getPropertyValue("width"),10);for(n=parseInt(f.getPropertyValue("height"),10);e=e.offsetParent;){j+=e.offsetTop;g+=e.offsetLeft}if(c.hasAttribute("controls"))if(navigator.userAgent.toLowerCase().indexOf("chrome")!==-1)l=32;else if(navigator.userAgent.toLowerCase().indexOf("firefox")!==
--1)l=28;else if(navigator.userAgent.toLowerCase().indexOf("ie9")!==-1)l=31;else if(navigator.userAgent.toLowerCase().indexOf("safari")!==-1)l=25;return{left:g,top:j,width:m,height:n,controlHeight:l}};if(d instanceof HTMLElement&&b instanceof HTMLVideoElement){b=a(b);a=0;switch(h){case "caption":case "captions":case "subtitle":a=Math.ceil(b.height*0.15<30?30:b.height*0.15);i(d,{display:"block",position:"absolute",width:b.width-40+"px",height:a+"px",backgroundColor:"rgba(0,0,0,0.5)",left:b.left+"px",
-top:b.top+b.height-(a+b.controlHeight)+"px",fontSize:(a<=50?a*0.7/96*72:a*0.3/96*72)+"pt",lineHeight:(a<=50?a/96*72:a/2/96*72)+"pt",color:"white",textShadow:"black 0px 0px 5px",fontFamily:"Helvetica, Arial, sans-serif",fontWeight:"bold",textAlign:"center",paddingLeft:"20px",paddingRight:"20px"});break;case "karaoke":case "lyrics":a=b.height*0.1<20?20:b.height*0.1;i(d,{display:"block",position:"absolute",width:b.width-40+"px",minHeight:a+"px",backgroundColor:"rgba(0,0,0,0.5)",left:b.left+"px",top:b.top+
-"px",fontSize:(a<=50?a*0.5/96*72:a*0.2/96*72)+"pt",lineHeight:(a<=50?a/96*72:a/2/96*72)+"pt",color:"gold",fontStyle:"oblique",textShadow:"black 0px 0px 5px",fontFamily:"Helvetica, Arial, sans-serif",fontWeight:"lighter",textAlign:"center",paddingLeft:"20px",paddingRight:"20px"})}if(d.className.indexOf("captionator-kind")===-1)d.className+=(d.className.length?" ":"")+"captionator-kind-"+h}},parseCaptions:function(d){if(d)return d.replace(/\r\n/g,"\n").replace(/\r/g,"\n").split(/\n\n/g).filter(function(h){return h.match(/WEBVTT FILE/ig)?
-false:true}).map(function(h){h=h.split(/\n/g);var b,i,a,c,f,e;if(h[0].match(/^\s*\d+\s*$/ig))e=String(h.shift(0).split(/\s+/).join(""));for(c=0;c=4)f=a.splice(3).join(" ");h=h.slice(0,c).concat(h.slice(c+1));break}h=h.join("\n");return new captionator.TextTrackCue(e,b,i,h,f,false,null)});else throw Error("Required parameter captionData not supplied.");}};
\ No newline at end of file
+(function(){var A=10,B=16,D=4.5,E=1.5,I=[0,0,0,0.5],J=false,c={createDOMException:function(b,c,a){try{document.querySelectorAll("div/[]")}catch(d){var e=function(a,b,c){this.code=a;this.message=b;this.name=c};e.prototype=d;return new e(b,c,a)}},compareArray:function(b,c){if(!(b instanceof Array)||!(c instanceof Array))return false;if(b.length!==c.length)return false;for(var a in b)if(b.hasOwnProperty(a)&&b[a]!==c[a])return false;return true},generateID:function(b){for(var c="",b=b?b:10;c.length0&&a>c.TextTrack.OFF&&this.loadTrack(this.src,null),this.videoNode._captionator_dirtyBit=true,c.rebuildCaptions(this.videoNode),a===c.TextTrack.OFF))this.cues.length=
+0,this.readyState=c.TextTrack.NONE}else throw Error("Illegal mode value for track: "+a);};this.getDefault=function(){return this.internalDefault};Object.prototype.__defineGetter__?(this.__defineGetter__("mode",this.getMode),this.__defineSetter__("mode",this.setMode),this.__defineGetter__("default",this.getDefault)):Object.defineProperty&&(Object.defineProperty(this,"mode",{get:this.getMode,set:this.setMode}),Object.defineProperty(this,"default",{get:this.getDefault}));this.loadTrack=function(a,b){var h,
+f=new XMLHttpRequest;if(this.readyState===c.TextTrack.LOADED)b instanceof Function&&b(h);else{this.src=a;this.readyState=c.TextTrack.LOADING;var d=this;f.open("GET",a,true);f.onreadystatechange=function(){if(f.readyState===4)if(f.status===200){var a=d.videoNode._captionatorOptions||{};if(d.kind==="metadata")a.processCueHTML=false,a.sanitiseCueHTML=false;h=c.parseCaptions(f.responseText,a);d.readyState=c.TextTrack.LOADED;d.cues.loadCues(h);d.activeCues.refreshCues.apply(d.activeCues);d.videoNode._captionator_dirtyBit=
+true;c.rebuildCaptions(d.videoNode);d.onload.call(this);b instanceof Function&&b.call(d,h)}else d.readyState=c.TextTrack.ERROR,d.onerror()};try{f.send(null)}catch(k){d.readyState=c.TextTrack.ERROR,d.onerror(k)}}};this.addCue=function(a){if(a&&a instanceof c.TextTrackCue)this.cues.addCue(a);else throw Error("The argument is null or not an instance of TextTrackCue.");};this.removeCue=function(){}};c.TextTrack.NONE=0;c.TextTrack.LOADING=1;c.TextTrack.LOADED=2;c.TextTrack.ERROR=3;c.TextTrack.OFF=0;c.TextTrack.HIDDEN=
+1;c.TextTrack.SHOWING=2;c.TextTrackCueList=function(a){this.track=a instanceof c.TextTrack?a:null;this.getCueById=function(a){return this.filter(function(b){return b.id===a})[0]};this.loadCues=function(a){for(var b=0;b
+0});e instanceof Array&&e.forEach(function(a){var b={D:"direction",L:"linePosition",T:"textPosition",A:"alignment",S:"size"},a=a.split(":");b[a[0]]&&(q[b[a[0]]]=a[1]);b[a[0]]in u&&(u[b[a[0]]]=a[1])})}if(this.linePosition.match(/\%/))this.snapToLines=false;this.getCueAsSource=function(){return String(this.text)};this.getCueAsHTML=function(){var a=document.createDocumentFragment(),b=document.createElement("div");b.innerHTML=String(this.text);Array.prototype.forEach.call(b.childNodes,function(b){a.appendChild(b.cloneNode(true))});
+return a};this.isActive=function(){var a=0;if(this.track instanceof c.TextTrack&&(this.track.mode===c.TextTrack.SHOWING||this.track.mode===c.TextTrack.HIDDEN)&&this.track.readyState===c.TextTrack.LOADED)try{if(a=this.track.videoNode.currentTime,this.startTime<=a&&this.endTime>=a){if(!this.wasActive)this.wasActive=true,this.onenter();return true}}catch(b){return false}if(this.wasActive)this.wasActive=false,this.onexit();return false};Object.prototype.__defineGetter__?this.__defineGetter__("active",
+this.isActive):Object.defineProperty&&Object.defineProperty(this,"active",{get:this.isActive});this.toString=function(){return"TextTrackCue:"+this.id+"\n"+String(this.text)};this.onenter=function(){};this.onexit=function(){}};c.MediaTrack=function(a,b,f,d,e,o){var l=function(a){return a.filter(function(a){try{var b=document.createElement(a.getAttribute("type").split("/").shift());return!(!b.canPlayType||!b.canPlayType(a.getAttribute("type")).replace(/no/,""))}catch(c){return false}}).shift().getAttribute("src")};
+this.onload=function(){};this.onerror=function(){};this.id=a||"";this.internalMode=this.internalMode=c.TextTrack.OFF;this.mediaElement=null;this.kind=b||"audiodescription";this.label=f||"";this.language=d||"";this.readyState=c.TextTrack.NONE;this.type=o||"x/unknown";this.mediaType=null;this.src="";if(typeof e==="string")this.src=e;else if(e instanceof NodeList)this.src=l(e);if(this.type.match(/^video\//))this.mediaType="video";else if(this.type.match(/^audio\//))this.mediaType="audio";this.getMode=
+function(){return this.internalMode};this.setMode=function(a){if([c.TextTrack.OFF,c.TextTrack.HIDDEN,c.TextTrack.SHOWING].indexOf(a)!==-1){if(a!==this.internalMode)this.internalMode=a,a===c.TextTrack.HIDDEN&&!this.mediaElement&&this.buildMediaElement(),a===c.TextTrack.SHOWING&&this.showMediaElement(),(a===c.TextTrack.OFF||a===c.TextTrack.HIDDEN)&&this.hideMediaElement()}else throw Error("Illegal mode value for track.");};Object.prototype.__defineGetter__?(this.__defineGetter__("mode",this.getMode),
+this.__defineSetter__("mode",this.setMode)):Object.defineProperty&&Object.defineProperty(this,"mode",{get:this.getMode,set:this.setMode});this.hideMediaElement=function(){if(this.mediaElement&&(this.mediaElement.paused||this.mediaElement.pause(),this.mediaElement instanceof HTMLVideoElement))this.mediaElement.style.display="none"};this.showMediaElement=function(){if(this.mediaElement){if(this.mediaElement.parentNode||document.body.appendChild(this.mediaElement),this.mediaElement instanceof HTMLVideoElement)this.mediaElement.style.display=
+"block"}else this.buildMediaElement(),document.body.appendChild(this.mediaElement)};this.buildMediaElement=function(){try{if(this.type.match(/^video\//))this.mediaElement=document.createElement("video"),this.mediaElement.className="captionator-mediaElement-"+this.kind,c.styleNode(this.mediaElement,this.kind,this.videoNode);else if(this.type.match(/^audio\//))this.mediaElement=new Audio;this.mediaElement.type=this.type;this.mediaElement.src=this.src;this.mediaElement.load();this.mediaElement.trackObject=
+this;this.readyState=c.TextTrack.LOADING;var a=this.mediaElement;this.mediaElement.addEventListener("progress",function(){a.trackObject.readyState=c.TextTrack.LOADING},false);this.mediaElement.addEventListener("canplaythrough",function(){a.trackObject.readyState=c.TextTrack.LOADED;a.trackObject.onload.call(a.trackObject)},false);this.mediaElement.addEventListener("error",function(b){a.trackObject.readyState=c.TextTrack.ERROR;a.trackObject.mode=c.TextTrack.OFF;a.trackObject.mediaElement=null;a.trackObject.onerror.call(a.trackObject,
+b)},false)}catch(b){this.readyState=c.TextTrack.ERROR,this.mode=c.TextTrack.OFF,this.mediaElement=null,this.onerror&&this.onerror.apply(this,b)}}};c.CaptionatorCueStructure=function(a,b){var c=this;this.isTimeDependent=false;this.cueSource=a;this.options=b;this.processedCue=null;this.toString=function(f){if(b.processCueHTML!==false){var d=function(a,b){if(c.processedCue===null){var e="",h,g;for(h in a)if(h.match(/^\d+$/)&&a.hasOwnProperty(h))if(g=a[h],g instanceof Object&&g.children&&g.children.length)if(g.token===
+"v")e+=''+d(g.children,b+1)+" ";else if(g.token==="c")e+=""+d(g.children,b+1)+" ";else if(g.timeIn>0){if(f===null||f===void 0||f>0&&f>=g.timeIn)e+=""+d(g.children,b+1)+
+" "}else e+=g.rawToken+d(g.children,b+1)+""+g.token+">";else if(g instanceof String||typeof g==="string"||typeof g==="number")e+=g;if(!c.isTimeDependent&&b===0)c.processedCue=e;return e}else return c.processedCue};return d(this,0)}else return a}};c.CaptionatorCueStructure.prototype=[];if(a.exportObjects)window.TextTrack=c.TextTrack,window.TextTrackCueList=c.TextTrackCueList,window.ActiveTextTrackCueList=c.ActiveTextTrackCueList,window.TextTrackCue=c.TextTrackCue,window.MediaTrack=c.MediaTrack;
+J=true}[].slice.call(document.getElementsByTagName("video"),0).forEach(function(a){a.addTextTrack=function(b,f,d,e,o,l,q){var u="subtitles,captions,descriptions,captions,metadata,chapters,karaoke,lyrics,tickertext,audiodescription,commentary,alternate,signlanguage".split(","),x=u.slice(0,7),b=typeof b==="string"?b:"",d=typeof d==="string"?d:"",e=typeof e==="string"?e:"",q=typeof q==="boolean"?q:false;if(!u.filter(function(a){return f===a?true:false}).length)throw c.createDOMException(12,"DOMException 12: SYNTAX_ERR: You must use a valid kind when creating a TimedTextTrack.",
+"SYNTAX_ERR");if(x.filter(function(a){return f===a?true:false}).length)if(b=new c.TextTrack(b,f,d,e,o,null)){if(!(a.tracks instanceof Array))a.tracks=[];a.tracks.push(b);return b}else return false;else if(b=new c.MediaTrack(b,f,d,e,o,l,q)){if(!(a.mediaTracks instanceof Array))a.mediaTracks=[];a.mediaTracks.push(b);return b}else return false}});if(!b||b===false||b===void 0||b===null)d=[].slice.call(document.getElementsByTagName("video"),0);else if(b instanceof Array)for(e=0;e0?e.querySelectorAll("source"):e.getAttribute("src"),h=b.addTextTrack(e.getAttribute("id")||c.generateID(),e.getAttribute("kind"),e.getAttribute("label"),e.getAttribute("srclang").split("-")[0],h,e.getAttribute("type"),e.hasAttribute("default"));e.track=h;h.trackNode=e;h.videoNode=
+b;d.push(h);var k=false;if((h.kind==="subtitles"||h.kind==="captions")&&f===h.language&&a.enableCaptionsByDefault)d.filter(function(a){return(a.kind==="captions"||a.kind==="subtitles")&&f===a.language&&a.mode===c.TextTrack.SHOWING?true:false}).length||(k=true);h.kind==="chapters"&&f===h.language&&(d.filter(function(a){return a.kind==="chapters"&&a.mode===c.TextTrack.SHOWING?true:false}).length||(k=true));h.kind==="descriptions"&&a.enableDescriptionsByDefault===true&&f===h.language&&(d.filter(function(a){return a.kind===
+"descriptions"&&a.mode===c.TextTrack.SHOWING?true:false}).length||(k=true));k===true&&d.forEach(function(a){if(a.trackNode.hasAttribute("default")&&a.mode===c.TextTrack.SHOWING)a.mode=c.TextTrack.HIDDEN});if(e.hasAttribute("default")&&!d.filter(function(a){return a.trackNode.hasAttribute("default")&&a.trackNode!==e?true:false}).length)k=true,h.internalDefault=true;if(k===true)h.mode=c.TextTrack.SHOWING});b.addEventListener("timeupdate",function(b){b=b.target;try{b.tracks.forEach(function(a){a.activeCues.refreshCues.apply(a.activeCues)})}catch(d){}a.renderer instanceof
+Function?a.renderer.call(c,b):c.rebuildCaptions(b);c.synchroniseMediaElements(b)},false);window.addEventListener("resize",function(){b._captionator_dirtyBit=true;c.rebuildCaptions(b)},false);b.addEventListener("play",function(){c.synchroniseMediaElements(b)},false);b.addEventListener("pause",function(){c.synchroniseMediaElements(b)},false);a.enableHighResolution===true&&window.setInterval(function(){try{b.tracks.forEach(function(a){a.activeCues.refreshCues.apply(a.activeCues)})}catch(d){}a.renderer instanceof
+Function?a.renderer.call(c,b):c.rebuildCaptions(b)},20)}return b},rebuildCaptions:function(b){var f=b.currentTime,a=[],d=false,e=[],g=[];(b.tracks||[]).forEach(function(b){b.mode===c.TextTrack.SHOWING&&b.readyState===c.TextTrack.LOADED&&(g=[].slice.call(b.activeCues,0),g=g.sort(function(a,b){return a.startTime>b.startTime?-1:1}),a=a.concat(g))});e=a.map(function(a){return a.track.id+"."+a.id+":"+a.text.toString(f).length});if((d=!c.compareArray(e,b._captionator_previousActiveCues))||b._captionator_dirtyBit)b._captionator_dirtyBit=
+false,b._captionator_availableCueArea=null,b._captionator_previousActiveCues=e,c.styleCueCanvas(b),b._containerObject.innerHTML="",a.forEach(function(a){var d=document.createElement("div");d.id=String(a.id).length?a.id:c.generateID();d.className="captionator-cue";d.innerHTML=a.text.toString(f);b._containerObject.appendChild(d);c.styleCue(d,a,b)})},synchroniseMediaElements:function(b){var f=function(a,b){try{b.seeking&&a.pause();if(a.currentTimeb.currentTime+0.5)a.currentTime=
+b.currentTime;a.paused&&!b.paused?a.play():!a.paused&&b.paused&&a.pause()}catch(c){}};(b.mediaTracks||[]).forEach(function(a){a.mode===c.TextTrack.SHOWING&&a.readyState>=c.TextTrack.LOADING&&f(a.mediaElement,b)});b.id&&[].slice.call(document.body.querySelectorAll("*[syncMaster="+b.id+"]"),0).forEach(function(a){(a.tagName.toLowerCase()==="video"||a.tagName.toLowerCase()==="audio")&&f(a,b)})},getNodeMetrics:function(b){for(var c=window.getComputedStyle(b,null),a=b,d=b.offsetTop,e=b.offsetLeft,g=b,
+h=0,k=0,g=parseInt(c.getPropertyValue("width"),10),h=parseInt(c.getPropertyValue("height"),10);a=a.offsetParent;)d+=a.offsetTop,e+=a.offsetLeft;if(b.hasAttribute("controls"))b=navigator.userAgent.toLowerCase(),b.indexOf("chrome")!==-1?k=32:b.indexOf("opera")!==-1?k=25:b.indexOf("firefox")!==-1?k=28:b.indexOf("ie 9")!==-1||b.indexOf("ipad")!==-1?k=44:b.indexOf("safari")!==-1&&(k=25);else if(b._captionatorOptions)b=b._captionatorOptions,b.controlHeight&&(k=parseInt(b.controlHeight,10));return{left:e,
+top:d,width:g,height:h,controlHeight:k}},applyStyles:function(b,c){for(var a in c)({}).hasOwnProperty.call(c,a)&&(b.style[a]=c[a])},checkDirection:function(b){var c=RegExp("^[^\u0591-\u07ff\ufb1d-\ufdfd\ufe70-\ufefc]*[A-Za-z\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02b8\u0300-\u0590\u0800-\u1fff\u2c00-\ufb1c\ufdfe-\ufe6f\ufefd-\uffff]");return RegExp("^[^A-Za-z\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02b8\u0300-\u0590\u0800-\u1fff\u2c00-\ufb1c\ufdfe-\ufe6f\ufefd-\uffff]*[\u0591-\u07ff\ufb1d-\ufdfd\ufe70-\ufefc]").test(b)?
+"rtl":c.test(b)?"ltr":""},styleCue:function(b,f,a){var d=0,e=0,g=0,h=0,k,z,m=0,o=0,l,q,u,x,p,t,C=0,F=k=d=0,G=0,y=0,v=0,j,s,r=0,G=a._captionatorOptions||{},i,e=50,d=m=0,e=true,K=function(a){var b=function(a){return!!a.length},d,e,f,g,h=0,i=function(a){h++;c.applyStyles(a,{display:"block",lineHeight:"auto",height:l+"px",width:t+"px",textAlign:"center"})};for(d in a.childNodes)if(a.childNodes.hasOwnProperty(d))e=a.childNodes[d],e.nodeType===3?(g=document.createDocumentFragment(),f=e.nodeValue,g.appendChild(document.createElement("span")),
+g.childNodes[0].innerHTML=""+f.split(/(.)/).filter(b).join(" ")+" ",[].slice.call(g.querySelectorAll("span.captionator-cue-character"),0).forEach(i),e.parentNode.replaceChild(g,e)):a.childNodes[d].nodeType===1&&(h+=K(a.childNodes[d]));return h};i=c.getNodeMetrics(a);if(!a._captionator_availableCueArea)a._captionator_availableCueArea={bottom:i.height-i.controlHeight,right:i.width,top:0,left:0,height:i.height-
+i.controlHeight,width:i.width};f.direction==="horizontal"&&(c.applyStyles(b,{width:"auto",position:"static",display:"inline-block",padding:"1em"}),m=parseInt(b.offsetWidth,10),d=Math.floor(m/a._captionator_availableCueArea.width*100),d=d<=100?d:100);m=i.height*(D/100)/96*72;m=m>=A?m:A;l=Math.floor(m/72*96);q=Math.floor(m*E);q=q>B?q:B;t=p=Math.ceil(q/72*96);p*Math.floor(i.height/p)d?k-=e:k=d),g=f.snapToLines===true?a._captionator_availableCueArea.width*(k/100):i.width*(k/100),f.textPosition==="auto"?d=(a._captionator_availableCueArea.right-g)/2+a._captionator_availableCueArea.left:(e=parseFloat(String(f.textPosition).replace(/[^\d\.]/ig,
+"")),d=(a._captionator_availableCueArea.right-g)*(e/100)+a._captionator_availableCueArea.left),f.snapToLines===true?e=(u-1)*p+a._captionator_availableCueArea.top:(k=i.controlHeight+p+o*2,e=(i.height-k)*(f.linePosition/100))):(e=a._captionator_availableCueArea.top,d=a._captionator_availableCueArea.right-t,g=t,h=a._captionator_availableCueArea.height*(k/100),d=K(b),k=[].slice.call(b.querySelectorAll("span.captionator-cue-character"),0),C=Math.floor((h-o*2)/l),g=Math.ceil(d/C)*t,F=Math.ceil(d/C),y=(d-
+C*(F-1))*l,f.snapToLines===true?d=f.direction==="vertical-lr"?a._captionator_availableCueArea.left:a._captionator_availableCueArea.right-g:(d=g+m*2,d=f.direction==="vertical-lr"?(i.width-d)*(f.linePosition/100):i.width-d-(i.width-d)*(f.linePosition/100)),f.textPosition==="auto"?e=(a._captionator_availableCueArea.bottom-h)/2+a._captionator_availableCueArea.top:(f.textPosition=parseFloat(String(f.textPosition).replace(/[^\d\.]/ig,"")),e=(a._captionator_availableCueArea.bottom-h)*(f.textPosition/100)+
+a._captionator_availableCueArea.top),s=j=r=v=0,k.forEach(function(a){j=f.direction==="vertical-lr"?t*v:g-t*(v+1);f.alignment==="start"||f.alignment!=="start"&&v=C-1?(r=0,v++):r++}));f.direction==="horizontal"&&(z=c.checkDirection(String(f.text))==="rtl"?{start:"right",middle:"center",end:"left"}[f.alignment]:{start:"left",middle:"center",end:"right"}[f.alignment]);
+c.applyStyles(b,{position:"absolute",overflow:"hidden",width:g+"px",height:h+"px",top:e+"px",left:d+"px",padding:o+"px "+m+"px",textAlign:z,backgroundColor:"rgba("+I.join(",")+")",direction:c.checkDirection(String(f.text)),lineHeight:q+"pt",boxSizing:"border-box"});if(f.direction==="vertical"||f.direction==="vertical-lr")d-a._captionator_availableCueArea.left-a._captionator_availableCueArea.left>=a._captionator_availableCueArea.right-(d+g)?a._captionator_availableCueArea.right=d:a._captionator_availableCueArea.left=
+d+g,a._captionator_availableCueArea.width=a._captionator_availableCueArea.right-a._captionator_availableCueArea.left;else{if(b.scrollHeight>b.offsetHeight*1.2){if(f.snapToLines){for(z=0;b.scrollHeight>b.offsetHeight*1.2;)h+=p,b.style.height=h+"px",z++;e-=z*p}else h=b.scrollHeight+o,k=i.controlHeight+h+o*2,e=(i.height-k)*(f.linePosition/100),b.style.height=h+"px";b.style.top=e+"px"}if(e-a._captionator_availableCueArea.top-a._captionator_availableCueArea.top>=a._captionator_availableCueArea.bottom-
+(e+h)&&a._captionator_availableCueArea.bottom>e)a._captionator_availableCueArea.bottom=e;else if(a._captionator_availableCueArea.top=0;b--)n.moveTo(i.width-b*t-0.5,-0.5),n.lineTo(i.width-b*t-0.5,i.height);n.closePath();n.stroke();n.beginPath();n.strokeStyle="rgba(255,255,0,0.5)";for(b=0;b<=x;b++)n.moveTo(b*t+0.5,-0.5),n.lineTo(b*t+0.5,i.height);n.stroke();a.linesDrawn=true})()}},styleCueCanvas:function(b){var f,
+a,d=b._captionatorOptions instanceof Object?b._captionatorOptions:{};if(!(b instanceof HTMLVideoElement))throw Error("Cannot style a cue canvas for a non-video node!");if(b._containerObject)a=b._containerObject,f=a.id;if(a)a.parentNode||document.body.appendChild(a);else{a=document.createElement("div");a.className="captionator-cue-canvas";f=c.generateID();a.id=f;if(d.appendCueCanvasTo){var e=null;if(d.appendCueCanvasTo instanceof HTMLElement)e=d.appendCueCanvasTo;else if(typeof d.appendCueCanvasTo===
+"string")try{var g=document.querySelectorAll(d.appendCueCanvasTo);if(g.length>0)e=g[0];else throw null;}catch(h){e=document.body,d.appendCueCanvasTo=false}else e=document.body,d.appendCueCanvasTo=false;e.appendChild(a)}else document.body.appendChild(a);b._containerObject=a;a.setAttribute("aria-live","polite");a.setAttribute("aria-atomic","true")}String(b.getAttribute("aria-describedby")).indexOf(f)===-1&&(e=b.hasAttribute("aria-describedby")?b.getAttribute("aria-describedby")+" ":"",b.setAttribute("aria-describedby",
+e+f));e=c.getNodeMetrics(b);b=e.height*(D/100)/96*72;b=b>=A?b:A;f=Math.floor(b*E);f=f>B?f:B;c.applyStyles(a,{position:"absolute",overflow:"hidden",zIndex:100,height:e.height-e.controlHeight+"px",width:e.width+"px",top:(d.appendCueCanvasTo?0:e.top)+"px",left:(d.appendCueCanvasTo?0:e.left)+"px",color:"white",fontFamily:"Verdana, Helvetica, Arial, sans-serif",fontSize:b+"pt",lineHeight:f+"pt",boxSizing:"border-box"});if(window.navigator.userAgent.toLowerCase().indexOf("chrome/10")>-1)a.style.backgroundColor=
+"rgba(0,0,0,0.01"+Math.random().toString().replace(".","")+")"},parseCaptions:function(b,f){var f=f instanceof Object?f:{},a="",d=[],e="",g=[],h=/^(\d{2})?:?(\d{2}):(\d{2})\.(\d+)\,(\d{2})?:?(\d{2}):(\d{2})\.(\d+)\s*(.*)/,k=/^(\d+)?:?(\d{2}):(\d{2})\.(\d+)\,(\d+)?:?(\d{2}):(\d{2})\.(\d+)\s*(.*)/,z=/^(\d{2})?:?(\d{2}):(\d{2})[\.\,](\d+)\s+\-\-\>\s+(\d{2})?:?(\d{2}):(\d{2})[\.\,](\d+)\s*(.*)/,m=/(\d{2})?:?(\d{2}):(\d{2})[\.\,](\d+)/,o=/^([\d\.]+)\s+\+([\d\.]+)\s*(.*)/,l=/^\[(\d{2})?:?(\d{2})\:(\d{2})\.(\d{2})\]\s*(.*?)$/i,
+q=/^(DEFAULTS|DEFAULT)\s+\-\-\>\s+(.*)/g,u=/^(STYLE|STYLES)\s+\-\-\>\s*\n([\s\S]*)/g,x=/^(COMMENT|COMMENTS)\s+\-\-\>\s+(.*)/g;if(b){var p=function(a){var b=new c.CaptionatorCueStructure(a,f),d=[],e,g,h,j=[];h=0;var k=function(a){return!!a.replace(/[^a-z0-9]+/ig,"").length},d=a.split(/(<\/?[^>]+>)/ig).filter(function(a){return!!a.replace(/\s*/ig,"")});h=b;for(e in d)if(d.hasOwnProperty(e))if(g=d[e],g.substr(0,1)==="<")if(g.substr(1,1)==="/"){if(a=g.substr(2).split(/[\s>]+/g)[0],j.length>0){g=0;for(h=
+j.length-1;h>=0;h--){var o=j[h][j[h].length-1];g=h;if(o.token===a)break}h=j[g];j=j.slice(0,g)}}else{if(g.substr(1).match(m)||g.match(/^]+>/i)||g.match(/^/)||g.match(/^<(b|i|u|ruby|rt)>/)||f.sanitiseCueHTML!==false){a={token:g.replace(/[<\/>]+/ig,"").split(/[\s\.]+/)[0],rawToken:g,children:[]};if(a.token==="v")a.voice=g.match(/^]+)>/i)[1];else if(a.token==="c")a.classes=g.replace(/[<\/>\s]+/ig,"").split(/[\.]+/ig).slice(1).filter(k);else if(g=a.rawToken.match(m))b.isTimeDependent=
+true,g=g.slice(1),a.timeIn=parseInt((g[0]||0)*3600,10)+parseInt((g[1]||0)*60,10)+parseInt(g[2]||0,10)+parseFloat("0."+(g[3]||0));h.push(a);j.push(h);h=a.children}}else f.sanitiseCueHTML!==false&&(g=g.replace(//g,">").replace(/\&/g,"&"),f.ignoreWhitespace||(g=g.replace(/\n+/g," "))),h.push(g);return b},d=b.replace(/\r\n/g,"\n").replace(/\r/g,"\n");l.exec(b)?(d=d.split(/\n+/g),a="LRC"):d=d.split(/\n\n+/g);return d=d.filter(function(b){return b.match(/^WEBVTT(\s*FILE)?/ig)?
+(a="WebVTT",false):b.replace(/\s*/ig,"").length?true:false}).map(function(b,d){var l,m,y,v,j,s="",r;if(r=q.exec(b))return g=r.slice(2).join(""),g=g.split(/\s+/g).filter(function(a){return a&&!!a.length}),null;else if(r=u.exec(b))return e+=r[r.length-1],null;else if(r=x.exec(b))return null;for(l=a==="LRC"?[b.substr(0,b.indexOf("]")),b.substr(b.indexOf("]")+1)]:b.split(/\n/g);!l[0].replace(/\s+/ig,"").length&&l.length>0;)l.shift();for(r=l[0].match(/^\s*[a-z0-9]+\s*$/ig)?String(l.shift().replace(/\s*/ig,
+"")):d;0 0 && value > OFF) {
- this.loadTrack(this.src);
- }
-
- if (this.readyState === LOADED) {
- captionator.rebuildCaptions(this.videoNode);
- }
-
- if (value === OFF || value === HIDDEN) {
- containerID = "captionator-" + this.videoNode.id + "-" + this.kind + "-" + this.language;
- container = document.getElementById(containerID);
- if (container) {
- container.parentNode.removeChild(container);
- }
- }
- }
- } else {
- throw new Error("Illegal mode value for track.");
+ Rough and ready array comparison function we can use to easily determine
+ whether cues have changed or not.
+
+ First parameter: The first aray to compare
+
+ Second parameter: The second array to compare
+
+ RETURNS:
+
+ True if the arrays are the same length and all elements in each array are the strictly equal (index for index.)
+ False in all other circumstances.
+ Returns false if either parameter is not an instance of Array().
+
+ */
+ "compareArray": function compareArray(array1,array2) {
+ // If either of these arguments aren't arrays, we consider them unequal
+ if (!(array1 instanceof Array) || !(array2 instanceof Array)) { return false; }
+ // If the lengths are different, we consider then unequal
+ if (array1.length !== array2.length) { return false; }
+ // Loop through, break at first value inequality
+ for (var index in array1) {
+ if (array1.hasOwnProperty(index)) {
+ if (array1[index] !== array2[index]) { return false; }
}
- };
-
- if (Object.prototype.__defineGetter__) {
- this.__defineGetter__("mode", this.getMode);
- this.__defineSetter__("mode", this.setMode);
- } else if (Object.defineProperty) {
- Object.defineProperty(this,"mode",
- {get: this.getMode, set: this.setMode}
- );
}
+ // If we haven't broken, they're the same!
+ return true;
+ },
+ /*
+ captionator.generateID([number ID length])
+
+ Generates a randomised string prefixed with the word captionator. This function is used internally to keep track of
+ objects and nodes in the DOM.
+
+ First parameter: A number of random characters/numbers to generate. This defaults to 10.
+
+ RETURNS:
+
+ The generated ID string.
+
+ */
+ "generateID": function(stringLength) {
+ var idComposite = "";
+ stringLength = stringLength ? stringLength : 10;
+ while (idComposite.length < stringLength) {
+ idComposite += String.fromCharCode(65 + Math.floor(Math.random()*26));
+ }
+
+ return "captionator" + idComposite;
+ },
+ /*
+ captionator.secondsToHmsm(totalSeconds)
+
+ Function which allows to convert time format expressed a total number of seconds into HH:MM:SS.mmm time format used in VTT.
- this.loadTrack = function(source, callback) {
- var captionData, ajaxObject = new XMLHttpRequest();
- if (this.readyState === LOADED) {
- if (callback instanceof Function) {
- callback(captionData);
- }
- } else {
- this.src = source;
- this.readyState = LOADING;
-
- var currentTrackElement = this;
- ajaxObject.open('GET', source, true);
- ajaxObject.onreadystatechange = function (eventData) {
- if (ajaxObject.readyState === 4) {
- if(ajaxObject.status === 200) {
- captionData = captionator.parseCaptions(ajaxObject.responseText);
- currentTrackElement.readyState = LOADED;
- captionator.rebuildCaptions(currentTrackElement.videoNode);
- currentTrackElement.cues.loadCues(captionData);
- currentTrackElement.onload();
-
- if (callback instanceof Function) {
- callback.call(currentTrackElement,captionData);
- }
- } else {
- // Throw error handler, if defined
- currentTrackElement.onerror();
- }
- }
- };
- ajaxObject.send(null);
- }
- };
+ First parameter: Total number of seconds expressed as a float number
+
+ RETURNS:
+
+ Time format expressed as HH:MM:SS.mmm where
+ HH - hours
+ MM - minutes
+ SS - seconds
+ mmm - milliseconds
+ */
+ "secondsToHmsm": function(totalSeconds) {
+ var hours = parseInt( totalSeconds / 3600 ) % 24;
+ var minutes = parseInt( totalSeconds / 60 ) % 60;
+ var seconds = (totalSeconds % 60).toFixed(3);
- this.generateTranscript = function(transcriptDestination) {
- console.log("generating transcript",transcriptDestination);
- var captionID, captionData;
-
- if (typeof(transcriptDestination) === "string") {
- transcriptDestination = document.querySelectorAll(transcriptDestination)[0];
- }
-
- if (typeof(transcriptDestination) === "object") {
- if (this.readyState === LOADED) {
- this.cues.forEach(function(cue) {
- transcriptDestination.innerHTML += "" + cue.getCueAsSource() + "
";
- });
- } else {
- if (this.readyState === LOADING) {
- this.onload = function() {
- this.generateTranscript(transcriptDestination);
- }
- } else {
- this.loadTrack(this.src,function() {
- this.generateTranscript(transcriptDestination);
- });
- }
- }
- } else {
- return false;
- }
- };
+ // Return result of type HH:MM:SS.mmm
+ return "".concat((hours < 10 ? "0" + hours : hours), ":", (minutes < 10 ? "0" + minutes : minutes), ":", (seconds < 10 ? "0" + seconds : seconds));
+ },
+ /*
+ captionator.unisubJSONtoWebVTT(jsonData)
+
+ Function which comverts Universal Subtitles JSON format into acceptable captionator WebVTT format
- // mutableTextTrack.addCue(cue)
- // Adds the given cue to mutableTextTrack's text track list of cues.
- // Raises an exception if the argument is null, associated with another text track, or already in the list of cues.
+ First parameter: JSON caption data which we get from http://www.universalsubtitles.org/api/1.0/subtitles/
+
+ RETURNS:
+
+ A properly formatted WebVTT format used in captions
+ */
+ "unisubJSONtoWebVTT": function(jsonData) {
+ var vttData = "WEBVTT";
- this.addCue = function() {
+ for(var i=0; i < jsonData.length; i++) {
+ var caption = jsonData[i];
+ var startTime = captionator.secondsToHmsm(caption.start_time);
+ var endTime = captionator.secondsToHmsm(caption.end_time);
- };
-
- // mutableTextTrack.removeCue(cue)
- // Removes the given cue from mutableTextTrack's text track list of cues.
- // Raises an exception if the argument is null, associated with another text track, or not in the list of cues.
+ // Generate a vtt chunk of type
+ //
+ //
+ // N
+ // HH:MM:SS.mmm --> HH:MM:SS.mmm
+ // Some caption text here
+ vttData = vttData.concat("\n\n", caption.sub_order, "\n", startTime, " --> ", endTime, "\n", caption.text);
+ }
- this.removeCue = function() {
-
- };
- };
-
- // Define read-only properties
+ return vttData;
+ },
+ /*
+ captionator.getJSONP(URL, success)
- captionator.TextTrackCueList = function TextTrackCueList(track) {
- this.track = track instanceof captionator.TextTrack ? track : null;
+ Function to get jsonp cross domain by injecting a website with a data passed as an argument into the callback
- this.getCueById = function(cueID) {
- return this.filter(function(currentCue) {
- return currentCue.id === cueID;
- })[0];
- };
-
- this.loadCues = function(cueData) {
- for (var cueIndex = 0; cueIndex < cueData.length; cueIndex ++) {
- cueData[cueIndex].track = this.track;
- Array.prototype.push.call(this,cueData[cueIndex]);
- }
- };
-
- this.toString = function() {
- return "[TextTrackCueList]";
- };
- };
- captionator.TextTrackCueList.prototype = [];
-
- captionator.ActiveTextTrackCueList = function ActiveTextTrackCueList(textTrackCueList) {
- // Among active cues:
-
- // The text track cues of a media element's text tracks are ordered relative to each
- // other in the text track cue order, which is determined as follows: first group the
- // cues by their text track, with the groups being sorted in the same order as their
- // text tracks appear in the media element's list of text tracks; then, within each
- // group, cues must be sorted by their start time, earliest first; then, any cues with
- // the same start time must be sorted by their end time, earliest first; and finally,
- // any cues with identical end times must be sorted in the order they were created (so
- // e.g. for cues from a WebVTT file, that would be the order in which the cues were
- // listed in the file).
- this.refreshCues = function() {
- var cueList = this;
- this.length = 0;
- textTrackCueList.forEach(function(cue) {
- if (cue.active) {
- cueList.push(cue);
- }
- });
- };
-
- this.toString = function() {
- return "[ActiveTextTrackCueList]";
- };
-
- this.refreshCues();
- };
- captionator.ActiveTextTrackCueList.prototype = new captionator.TextTrackCueList();
-
- captionator.TextTrackCue = function TextTrackCue(id, startTime, endTime, text, settings, pauseOnExit, track) {
- // Set up internal data store
- this.id = id;
- this.track = track instanceof captionator.TextTrack ? track : null;
- this.startTime = parseFloat(startTime,10);
- this.endTime = parseFloat(endTime,10);
- this.text = typeof(text) === "string" ? text : "";
- this.settings = typeof(settings) === "string" ? settings : "";
- this.intSettings = {};
- this.pauseOnExit = !!pauseOnExit;
-
- // Parse settings & set up cue defaults
-
- // A writing direction, either horizontal (a line extends horizontally and is positioned vertically,
- // with consecutive lines displayed below each other), vertical growing left (a line extends vertically
- // and is positioned horizontally, with consecutive lines displayed to the left of each other), or
- // vertical growing right (a line extends vertically and is positioned horizontally, with consecutive
- // lines displayed to the right of each other).
- this.direction = "horizontal";
-
- // A boolean indicating whether the line's position is a line position (positioned to a multiple of the
- // line dimensions of the first line of the cue), or whether it is a percentage of the dimension of the video.
- this.snapToLines = false;
-
- // Either a number giving the position of the lines of the cue, to be interpreted as defined by the
- // writing direction and snap-to-lines flag of the cue, or the special value auto, which means the
- // position is to depend on the other active tracks.
- this.linePosition = "auto";
-
- // A number giving the position of the text of the cue within each line, to be interpreted as a percentage
- // of the video, as defined by the writing direction.
- this.textPosition = 0;
-
- // A number giving the size of the box within which the text of each line of the cue is to be aligned, to
- // be interpreted as a percentage of the video, as defined by the writing direction.
- this.size = 0;
-
- // An alignment for the text of each line of the cue, either start alignment (the text is aligned towards its
- // start side), middle alignment (the text is aligned centered between its start and end sides), end alignment
- // (the text is aligned towards its end side). Which sides are the start and end sides depends on the
- // Unicode bidirectional algorithm and the writing direction. [BIDI]
- this.alignment = "";
-
- // Parse VTT Settings...
- if (this.settings.length) {
- var intSettings = this.intSettings;
- settings = settings.split(/\s+/).filter(function(settingItem) { return settingItem.length > 0;});
- if (settings instanceof Array) {
- settings.forEach(function(cueItem) {
- var settingMap = {"D":"verticalText","L":"linePosition","T":"textPosition","A":"textAlignment","S":"textSize"};
- cueItem = cueItem.split(":");
- if (settingMap[cueItem[0]]) {
- intSettings[settingMap[cueItem[0]]] = cueItem[1];
- }
- });
- }
- }
+ First parameter: URL to the domain which returns a JSONP object with data
+ URL must end with a "callback=?" string.
+ Example: http://www.universalsubtitles.org/api/1.0/subtitles/?video_url=http://www.youtube.com/watch?v=_VxQEPw1x9E&callback=?
- // Functions defined by spec (getters, kindof)
- this.getCueAsSource = function getCueAsSource() {
- return this.text;
+ Second parameter: A callback which will accept returning data as an argument
+
+ RETURNS:
+
+ Nothing
+ */
+ "getJSONP": function(URL, callback) {
+ // Generate a random callback function name
+ var callbackName = "json"+(Math.random()*100).toString().replace(/\./g,"");
+ // Attach the function to the global namespace
+ window[callbackName]= function(data){
+ callback&&callback(data);
};
- this.getCueAsHTML = function getCueAsHTML() {
- var DOMFragment = document.createDocumentFragment();
- var DOMNode = document.createElement("div");
- DOMNode.innerHTML = this.text;
-
- Array.prototype.forEach.call(DOMNode.childNodes,function(child) {
- DOMFragment.appendChild(child.cloneNode(true));
- });
-
- return DOMFragment;
- };
+ // Inject body tag with a script tag for JSONP call
+ var scriptTag = document.createElement("script");
+ scriptTag.type = "text/javascript";
- this.isActive = function() {
- var currentTime = 0;
- if (this.track instanceof captionator.TextTrack) {
- if (this.track.mode === 2 && this.track.readyState === 2) {
- try {
- currentTime = this.track.videoNode.currentTime;
- if (this.startTime <= currentTime && this.endTime >= currentTime) {
- return true;
- }
- } catch(Error) {
- return false;
- }
- }
- }
-
- return false;
- };
+ scriptTag.src = URL.replace("callback=?","callback=" + callbackName);
+ document.body.appendChild(scriptTag);
+
+ },
+ /*
+ captionator.loadTrackWebVTTCaptions(data, currentTrackElement, callback)
+
+ Function which loads Captions to the trackElement using passed WebVTT text. This function is here so that we do not have the same code in jsonp and XMLHttpRequest cases
- if (Object.prototype.__defineGetter__) {
- this.__defineGetter__("active", this.isActive);
- } else if (Object.defineProperty) {
- Object.defineProperty(this,"active",
- {get: this.isActive}
- );
- }
+ First parameter: WebVTT text
- // Events defined by spec
+ Second parameter: A trackElement which will have parsed captions
- this.onenter = function() {};
- this.onexit = function() {};
- };
-
- // if requested by options, export the object types
- if (options.exportObjects) {
- window.TextTrack = captionator.TextTrack;
- window.TextTrackCueList = captionator.TextTrackCueList;
- window.ActiveTextTrackCueList = captionator.ActiveTextTrackCueList;
- window.TextTrackCue = captionator.TextTrackCue;
- }
-
- [].slice.call(document.getElementsByTagName("video"),0).forEach(function(videoElement) {
- videoElement.addTrack = function(kind,label,language,cueDataArray) {
- var allowedKinds = ["subtitles","captions","descriptions","captions","metadata"];
- var newTrack;
- label = typeof(label) === "string" ? label : "";
- language = typeof(language) === "string" ? language : "";
-
- // If the kind isn't known, throw DOM syntax error exception
- if (!allowedKinds.filter(function (currentKind){
- return kind === currentKind ? true : false;
- }).length) {
- throw captionator.createDOMException(12,"DOMException 12: SYNTAX_ERR: You must use a valid kind when creating a TimedTextTrack.","SYNTAX_ERR");
- }
-
- newTrack = new captionator.TextTrack(kind,label,language,cueDataArray);
- if (newTrack) {
- if (!(videoElement.tracks instanceof Array)) {
- videoElement.tracks = [];
- }
-
- videoElement.tracks.push(newTrack);
- return newTrack;
- } else {
- return false;
- }
- };
- });
-
+ Third parameter: A callback
- if (!element || element === false || element === undefined || element === null) {
- videoElements = [].slice.call(document.getElementsByTagName("video"),0); // select and convert to array
- } else {
- if (element instanceof Array) {
- for (elementIndex = 0; elementIndex < element.length; elementIndex ++) {
- if (typeof(element[elementIndex]) === "string") {
- videoElements = videoElements.concat([].slice.call(document.querySelectorAll(element[elementIndex]),0)); // select and convert to array
- } else if (element[elementIndex].constructor === HTMLVideoElement) {
- videoElements.push(element[elementIndex]);
- }
- }
- } else if (typeof(element) === "string") {
- videoElements = [].slice.call(document.querySelectorAll(element),0); // select and convert to array
- } else if (element.constructor === HTMLVideoElement) {
- videoElements.push(element);
+ RETURNS:
+
+ Nothing
+ */
+ "loadTrackWebVTTCaptions": function(data, currentTrackElement, callback) {
+ var captionData, TrackProcessingOptions = currentTrackElement.videoNode._captionatorOptions || {};
+ if (currentTrackElement.kind === "metadata") {
+ // People can load whatever data they please into metadata tracks.
+ // Don't process it.
+ TrackProcessingOptions.processCueHTML = false;
+ TrackProcessingOptions.sanitiseCueHTML = false;
}
- }
+
+ captionData = captionator.parseCaptions(data, TrackProcessingOptions);
+ currentTrackElement.readyState = captionator.TextTrack.LOADED;
+ currentTrackElement.cues.loadCues(captionData);
+ currentTrackElement.activeCues.refreshCues.apply(currentTrackElement.activeCues);
+ currentTrackElement.videoNode._captionator_dirtyBit = true;
+ captionator.rebuildCaptions(currentTrackElement.videoNode);
+ currentTrackElement.onload.call(this);
- if (videoElements.length) {
- for (elementIndex = 0; elementIndex < videoElements.length; elementIndex ++) {
- captionator.processVideoElement(videoElements[elementIndex],defaultLanguage,options);
+ if (callback instanceof Function) {
+ callback.call(currentTrackElement, captionData);
}
- return true;
- } else {
- return false;
- }
- },
- /*
- captionator.processVideoElement(videoElement ,
- [defaultLanguage - string in BCP47],
- [options - JS Object])
+ },
+ /*
+ captionator.captionify([selector string array | DOMElement array | selector string | singular dom element ],
+ [defaultLanguage - string in BCP47],
+ [options - JS Object])
- Processes track items within an HTMLVideoElement. The second and third parameter are both optional.
+ Adds closed captions to video elements. The first, second and third parameter are both optional.
- First parameter: Mandatory HTMLVideoElement object.
+ First parameter: Use an array of either DOMElements or selector strings (compatible with querySelectorAll.)
+ All of these elements will be captioned if tracks are available. If this parameter is omitted, all video elements
+ present in the DOM will be captioned if tracks are available.
- Second parameter: BCP-47 string for default language. If this parameter is omitted, the User Agent's language
- will be used to choose a track.
+ Second parameter: BCP-47 string for default language. If this parameter is omitted, the User Agent's language
+ will be used to choose a track.
- Third parameter: as yet unused - will implement animation settings and some other global options with this
- parameter later.
+ Third parameter: as yet unused - will implement animation settings and some other global options with this
+ parameter later.
- RETURNS:
- Reference to the HTMLVideoElement.
+ RETURNS:
+ False on immediate failure due to input being malformed, otherwise true (even if the process fails later.)
+ Because of the asynchronous download requirements, this function can't really return anything meaningful.
- */
- "processVideoElement": function(videoElement,defaultLanguage,options) {
- "use strict";
- var trackList = [];
- var globalLanguage = defaultLanguage || navigator.language.split("-")[0];
- options = options instanceof Object? options : {};
- if (!videoElement.captioned) {
- videoElement.captionatorOptions = options;
- videoElement.className += (videoElement.className.length ? " " : "") + "captioned";
- videoElement.captioned = true;
+ */
+ "captionify": function(element,defaultLanguage,options) {
+ var videoElements = [], elementIndex = 0;
+ options = options instanceof Object? options : {};
+
+ // Override defaults if options are present...
+ if (options.minimumFontSize && typeof(options.minimumFontSize) === "number") {
+ minimumFontSize = options.minimumFontSize;
+ }
+
+ if (options.minimumLineHeight && typeof(options.minimumLineHeight) === "number") {
+ minimumLineHeight = options.minimumLineHeight;
+ }
- // Check whether video element has an ID. If not, create one
- if (videoElement.id.length === 0) {
- var idComposite = "";
- while (idComposite.length < 10) {
- idComposite += String.fromCharCode(65 + Math.floor(Math.random()*26));
+ if (options.fontSizeVerticalPercentage && typeof(options.fontSizeVerticalPercentage) === "number") {
+ fontSizeVerticalPercentage = options.fontSizeVerticalPercentage;
+ }
+
+ if (options.lineHeightRatio && typeof(options.lineHeightRatio) !== "number") {
+ lineHeightRatio = options.lineHeightRatio;
+ }
+
+ if (options.cueBackgroundColour && options.cueBackgroundColour instanceof Array) {
+ cueBackgroundColour = options.cueBackgroundColour;
+ }
+
+ /* Feature detection block */
+ if (!HTMLVideoElement) {
+ // Browser doesn't support HTML5 video - die here.
+ return false;
+ } else {
+ // Browser supports native track API
+ if (typeof(document.createElement("video").addTextTrack) === "function") {
+ return false;
}
-
- videoElement.id = "captionator" + idComposite;
}
- var enabledDefaultTrack = false;
- [].slice.call(videoElement.querySelectorAll("track"),0).forEach(function(trackElement) {
- var trackObject = videoElement.addTrack(
- trackElement.getAttribute("kind"),
- trackElement.getAttribute("label"),
- trackElement.getAttribute("srclang").split("-")[0],
- trackElement.getAttribute("src"));
+ if (!objectsCreated) {
+ // Set up objects & types
+ // As defined by http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html
+ /**
+ * @constructor
+ */
+ captionator.TextTrack = function TextTrack(id,kind,label,language,trackSource,isDefault,type) {
- trackElement.track = trackObject;
- trackObject.trackNode = trackElement;
- trackObject.videoNode = videoElement;
- trackList.push(trackObject);
+ this.onload = function () {};
+ this.onerror = function() {};
+ this.oncuechange = function() {};
- // Now determine whether the track is visible by default.
- // The comments in this section come straight from the spec...
- var trackEnabled = false;
+ this.id = id || "";
+ this.internalMode = captionator.TextTrack.OFF;
+ this.cues = new captionator.TextTrackCueList(this);
+ this.activeCues = new captionator.ActiveTextTrackCueList(this.cues,this);
+ this.kind = kind || "subtitles";
+ this.label = label || "";
+ this.language = language || "";
+ this.src = trackSource || "";
+ this.readyState = captionator.TextTrack.NONE;
+ this.internalDefault = isDefault || false;
+ this.type = type || "";
- // If the text track kind is subtitles or captions and the user has indicated an interest in having a track
- // with this text track kind, text track language, and text track label enabled, and there is no other text track
- // in the media element's list of text tracks with a text track kind of either subtitles or captions whose text track mode is showing
- // ---> Let the text track mode be showing.
+ // Create getters and setters for mode
+ this.getMode = function() {
+ return this.internalMode;
+ };
- if ((trackObject.kind === "subtitles" || trackObject.kind === "captions") &&
- (defaultLanguage === trackObject.language && options.enableCaptionsByDefault)) {
- if (!trackList.filter(function(trackObject) {
- if ((trackObject.kind === "captions" || trackObject.kind === "subtitles") && defaultLanguage === trackObject.language && trackObject.mode === 2) {
- return true;
- } else {
- return false;
+ this.setMode = function(value) {
+ var allowedModes = [captionator.TextTrack.OFF,captionator.TextTrack.HIDDEN,captionator.TextTrack.SHOWING], containerID, container;
+ if (allowedModes.indexOf(value) !== -1) {
+ if (value !== this.internalMode) {
+ this.internalMode = value;
+
+ if (this.readyState === captionator.TextTrack.NONE && this.src.length > 0 && value > captionator.TextTrack.OFF) {
+ this.loadTrack(this.src,null);
+ }
+
+ // Refresh all captions on video
+ this.videoNode._captionator_dirtyBit = true;
+ captionator.rebuildCaptions(this.videoNode);
+
+ if (value === captionator.TextTrack.OFF) {
+ // make sure the resource is reloaded next time (Is this correct behaviour?)
+ this.cues.length = 0; // Destroy existing cue data (bugfix)
+ this.readyState = captionator.TextTrack.NONE;
+ }
}
- }).length) {
- trackEnabled = true;
- }
- }
+ } else {
+ throw new Error("Illegal mode value for track: " + value);
+ }
+ };
- // If the text track kind is chapters and the text track language is one that the user agent has reason to believe is
- // appropriate for the user, and there is no other text track in the media element's list of text tracks with a text track
- // kind of chapters whose text track mode is showing
- // ---> Let the text track mode be showing.
+ // Create getter for default
+ this.getDefault = function() {
+ return this.internalDefault;
+ };
- if (trackObject.kind === "chapters" && (defaultLanguage === trackObject.language)) {
- if (!trackList.filter(function(trackObject) {
- if (trackObject.kind === "chapters" && trackObject.mode === 2) {
- return true;
- } else {
- return false;
- }
- }).length) {
- trackEnabled = true;
+ if (Object.prototype.__defineGetter__) {
+ this.__defineGetter__("mode", this.getMode);
+ this.__defineSetter__("mode", this.setMode);
+ this.__defineGetter__("default", this.getDefault);
+ } else if (Object.defineProperty) {
+ Object.defineProperty(this,"mode",
+ {get: this.getMode, set: this.setMode}
+ );
+ Object.defineProperty(this,"default",
+ {get: this.getDefault}
+ );
}
- }
- // If the text track kind is descriptions and the user has indicated an interest in having text descriptions
- // with this text track language and text track label enabled, and there is no other text track in the media element's
- // list of text tracks with a text track kind of descriptions whose text track mode is showing
-
- if (trackObject.kind === "descriptions" && (options.enableDescriptionsByDefault === true) && (defaultLanguage === trackObject.language)) {
- if (!trackList.filter(function(trackObject) {
- if (trackObject.kind === "descriptions" && trackObject.mode === 2) {
- return true;
+ this.loadTrack = function(source, callback) {
+ var captionData, ajaxObject = new XMLHttpRequest();
+ if (this.readyState === captionator.TextTrack.LOADED) {
+ if (callback instanceof Function) {
+ callback(captionData);
+ }
+ } else {
+ this.src = source;
+ this.readyState = captionator.TextTrack.LOADING;
+
+ var currentTrackElement = this;
+
+ // Check if the currentTrack is jsonp one then we need to get caption JSON from the provided URL
+ if (this.type.match(/^jsonp\//)) {
+ captionator.getJSONP("".concat(source, "&callback=?"), function(data){
+ if(data && data.length > 0) {
+ // Covert JSON to accepted VTT format
+ data = captionator.unisubJSONtoWebVTT(data);
+ captionator.loadTrackWebVTTCaptions(data, currentTrackElement, callback);
+ } else {
+ currentTrackElement.readyState = captionator.TextTrack.ERROR;
+ }
+ });
} else {
- return false;
+ ajaxObject.open('GET', source, true);
+ ajaxObject.onreadystatechange = function (eventData) {
+ if (ajaxObject.readyState === 4) {
+ if(ajaxObject.status === 200) {
+ var data = ajaxObject.responseText;
+ captionator.loadTrackWebVTTCaptions(data, currentTrackElement, callback);
+ } else {
+ // Throw error handler, if defined
+ currentTrackElement.readyState = captionator.TextTrack.ERROR;
+ currentTrackElement.onerror();
+ }
+ }
+ };
+ try {
+ ajaxObject.send(null);
+ } catch(Error) {
+ // Throw error handler, if defined
+ currentTrackElement.readyState = captionator.TextTrack.ERROR;
+ currentTrackElement.onerror(Error);
+ }
}
- }).length) {
- trackEnabled = true;
- }
- }
+ }
+ };
- // ↪ If there is a text track in the media element's list of text tracks whose text track mode is showing by default,
- // the user agent must furthermore change that text track's text track mode to hidden.
+ // mutableTextTrack.addCue(cue)
+ // Adds the given cue to mutableTextTrack's text track list of cues.
+ // Raises an exception if the argument is null, associated with another text track, or already in the list of cues.
- if (trackEnabled === true) {
- trackList.forEach(function(trackObject) {
- if(trackObject.trackNode.hasAttribute("default") && trackObject.mode === 2) {
- trackObject.mode = 1;
+ this.addCue = function(cue) {
+ if (cue && cue instanceof captionator.TextTrackCue) {
+ this.cues.addCue(cue);
+ } else {
+ throw new Error("The argument is null or not an instance of TextTrackCue.");
}
- });
- }
+ };
+
+ // mutableTextTrack.removeCue(cue)
+ // Removes the given cue from mutableTextTrack's text track list of cues.
+ // Raises an exception if the argument is null, associated with another text track, or not in the list of cues.
+
+ this.removeCue = function() {
+
+ };
+ };
+ // Define constants for TextTrack.readyState
+ captionator.TextTrack.NONE = 0;
+ captionator.TextTrack.LOADING = 1;
+ captionator.TextTrack.LOADED = 2;
+ captionator.TextTrack.ERROR = 3;
+ // Define constants for TextTrack.mode
+ captionator.TextTrack.OFF = 0;
+ captionator.TextTrack.HIDDEN = 1;
+ captionator.TextTrack.SHOWING = 2;
+
+ // Define read-only properties
+ /**
+ * @constructor
+ */
+ captionator.TextTrackCueList = function TextTrackCueList(track) {
+ this.track = track instanceof captionator.TextTrack ? track : null;
- // If the track element has a default attribute specified, and there is no other text track in the media element's
- // list of text tracks whose text track mode is showing or showing by default
- // Let the text track mode be showing by default.
+ this.getCueById = function(cueID) {
+ return this.filter(function(currentCue) {
+ return currentCue.id === cueID;
+ })[0];
+ };
- if (trackElement.hasAttribute("default")) {
- if (!trackList.filter(function(trackObject) {
- if (trackObject.trackNode.hasAttribute("default") && trackObject.trackNode !== trackElement) {
- return true;
+ this.loadCues = function(cueData) {
+ for (var cueIndex = 0; cueIndex < cueData.length; cueIndex ++) {
+ cueData[cueIndex].track = this.track;
+ Array.prototype.push.call(this,cueData[cueIndex]);
+ }
+ };
+
+ this.addCue = function(cue) {
+ if (cue && cue instanceof captionator.TextTrackCue) {
+ if (cue.track === this.track || !cue.track) {
+ // TODO: Check whether cue is already in list of cues.
+ // TODO: Sort cue list based on TextTrackCue.startTime.
+ Array.prototype.push.call(this,cue);
} else {
- return false;
+ throw new Error("This cue is associated with a different track!");
}
- }).length) {
- trackEnabled = true;
- }
- }
-
- // Otherwise
- // Let the text track mode be disabled.
+ } else {
+ throw new Error("The argument is null or not an instance of TextTrackCue.");
+ }
+ };
- if (trackEnabled === true) {
- trackObject.mode = 2; // SHOWING
- }
- });
+ this.toString = function() {
+ return "[TextTrackCueList]";
+ };
+ };
+ captionator.TextTrackCueList.prototype = [];
- videoElement.addEventListener("timeupdate", function(eventData){
- var videoElement = eventData.target;
- // update active cues
- try {
- videoElement.tracks.forEach(function(track) {
- track.activeCues.refreshCues();
- });
- } catch(error) {}
+ /**
+ * @constructor
+ */
+ captionator.ActiveTextTrackCueList = function ActiveTextTrackCueList(textTrackCueList,textTrack) {
+ // Among active cues:
- // External renderer?
- if (options.renderer instanceof Function) {
- options.renderer.call(captionator,videoElement);
- } else {
- captionator.rebuildCaptions(videoElement);
- }
- }, false);
- }
-
- return videoElement;
- },
-
-
- "rebuildCaptions": function(videoElement) {
- "use strict";
- var trackList = videoElement.tracks;
- var options = videoElement.captionatorOptions instanceof Object ? videoElement.captionatorOptions : {};
- var currentTime = videoElement.currentTime;
- var containerID = "captionator-unset"; // Hopefully you don't actually see this in your id attribute!
- var containerObject = null;
- var compositeCueHTML = "";
-
- // Work out what cues are showing...
- trackList.forEach(function(track,trackIndex) {
- if (track.mode === 2 && track.readyState === 2) {
- containerID = "captionator-" + videoElement.id + "-" + track.kind + "-" + track.language;
- if (track.containerObject) {
- containerObject = track.containerObject;
- } else {
- containerObject = document.getElementById(containerID);
- }
+ // The text track cues of a media element's text tracks are ordered relative to each
+ // other in the text track cue order, which is determined as follows: first group the
+ // cues by their text track, with the groups being sorted in the same order as their
+ // text tracks appear in the media element's list of text tracks; then, within each
+ // group, cues must be sorted by their start time, earliest first; then, any cues with
+ // the same start time must be sorted by their end time, earliest first; and finally,
+ // any cues with identical end times must be sorted in the order they were created (so
+ // e.g. for cues from a WebVTT file, that would be the order in which the cues were
+ // listed in the file).
- if (!containerObject) {
- containerObject = document.createElement("div");
- containerObject.id = containerID;
- document.body.appendChild(containerObject);
- track.containerObject = containerObject;
- containerObject.setAttribute("aria-live","polite");
- containerObject.setAttribute("aria-atomic","true");
- captionator.styleContainer(containerObject,track.kind,track.videoNode);
- } else if (!containerObject.parentNode) {
- document.body.appendChild(containerObject);
- }
+ this.refreshCues = function() {
+ if (textTrackCueList.length) {
+ var cueList = this;
+ var cueListChanged = false;
+ var oldCueList = [].slice.call(this,0);
+ this.length = 0;
+
+ textTrackCueList.forEach(function(cue) {
+ if (cue.active) {
+ cueList.push(cue);
- if (String(videoElement.getAttribute("aria-describedby")).indexOf(containerID) === -1) {
- var existingValue = videoElement.hasAttribute("aria-describedby") ? videoElement.getAttribute("aria-describedby") + " " : "";
- videoElement.setAttribute("aria-describedby",existingValue + containerID);
- }
-
- compositeCueHTML = "";
- track.activeCues.forEach(function(cue) {
- compositeCueHTML += "" + cue.getCueAsSource() + "
";
- });
+ if (cueList[cueList.length-1] !== oldCueList[cueList.length-1]) {
+ cueListChanged = true;
+ }
+ }
+ });
+
+ if (cueListChanged) {
+ try {
+ textTrack.oncuechange();
+ } catch(error){}
+ }
+ }
+ };
- if (String(containerObject.innerHTML) !== compositeCueHTML) {
- containerObject.innerHTML = compositeCueHTML;
- }
+ this.toString = function() {
+ return "[ActiveTextTrackCueList]";
+ };
- if (compositeCueHTML.length) {
- containerObject.style.display = "block";
- } else {
- containerObject.style.display = "none";
- }
- }
- });
- },
- /*
- captionator.styleContainer(DOMNode, kind / role, videoElement, [boolean applyClassesOnly])
-
- Styles autogenerated caption containers according to the kind or 'role' (in the W3 spec) of the track.
- This function is not intended to allow easy application of arbitrary styles, but rather centralise all styling within
- the script (enabling easy removal of styles for replacement with CSS classes if desired.)
-
- First parameter: DOMNode to style. This parameter is mandatory.
-
- Second parameter: Role of the DOMNode. This parameter is mandatory.
-
- Third parameter: HTMLVideoElement to which the caption is attached. This is used to position the caption container appropriately.
-
- Fourth parameter: Optional boolean specifying whether to apply styles or just classes (classes are applied in both circumstances.)
- A false value will style the element - true values will only apply classes.
-
- RETURNS:
-
- Nothing.
-
- */
- "styleContainer": function(DOMNode, kind, videoElement, applyClassesOnly) {
- "use strict";
- var applyStyles = function(StyleNode, styleObject) {
- for (var styleName in styleObject) {
- if ({}.hasOwnProperty.call(styleObject, styleName)) {
- StyleNode.style[styleName] = styleObject[styleName];
- }
- }
- };
-
- var getVideoMetrics = function(DOMNode) {
- var videoComputedStyle = window.getComputedStyle(DOMNode,null);
- var offsetObject = DOMNode;
- var offsetTop = DOMNode.offsetTop, offsetLeft = DOMNode.offsetLeft;
- var width = DOMNode, height = 0;
- var controlHeight = 0;
-
- width = parseInt(videoComputedStyle.getPropertyValue("width"),10);
- height = parseInt(videoComputedStyle.getPropertyValue("height"),10);
-
- while (offsetObject = offsetObject.offsetParent) {
- offsetTop += offsetObject.offsetTop;
- offsetLeft += offsetObject.offsetLeft;
- }
-
- if (DOMNode.hasAttribute("controls")) {
- // Get heights of default control strip in various browsers
- // There could be a way to measure this live but I haven't thought/heard of it yet...
- if (navigator.userAgent.toLowerCase().indexOf("chrome") !== -1) {
- controlHeight = 32;
- } else if (navigator.userAgent.toLowerCase().indexOf("firefox") !== -1) {
- controlHeight = 28;
- } else if (navigator.userAgent.toLowerCase().indexOf("ie9") !== -1) {
- controlHeight = 31;
- } else if (navigator.userAgent.toLowerCase().indexOf("safari") !== -1) {
- controlHeight = 25;
- }
- }
+ this.refreshCues();
+ };
+ captionator.ActiveTextTrackCueList.prototype = new captionator.TextTrackCueList(null);
- return {
- left: offsetLeft,
- top: offsetTop,
- width: width,
- height: height,
- controlHeight: controlHeight
- };
- };
-
- if (DOMNode instanceof HTMLElement && videoElement instanceof HTMLVideoElement) {
- var videoMetrics = getVideoMetrics(videoElement);
- var captionHeight = 0;
- switch (kind) {
- case "caption":
- case "captions":
- case "subtitle":
- // Simple display, darkened rectangle, white or light text, down the bottom of the video container.
- // This is basically the default style.
- captionHeight = Math.ceil(videoMetrics.height * 0.15 < 30 ? 30 : videoMetrics.height * 0.15);
- applyStyles(DOMNode,{
- "display": "block",
- "position": "absolute",
- "width": (videoMetrics.width - 40) + "px",
- "height": captionHeight + "px",
- "backgroundColor": "rgba(0,0,0,0.5)",
- "left": videoMetrics.left + "px",
- "top": (videoMetrics.top + videoMetrics.height) - (captionHeight + videoMetrics.controlHeight) + "px",
- "fontSize": (captionHeight <= 50 ? ((captionHeight * 0.7) / 96) * 72 : ((captionHeight * 0.3) / 96) * 72) + "pt",
- "lineHeight": (captionHeight <= 50 ? (captionHeight / 96) * 72 : ((captionHeight / 2) / 96) * 72) + "pt",
- "color": "white",
- "textShadow": "black 0px 0px 5px",
- "fontFamily": "Helvetica, Arial, sans-serif",
- "fontWeight": "bold",
- "textAlign": "center",
- "paddingLeft": "20px",
- "paddingRight": "20px"
- });
-
- break;
- case "textaudiodesc":
- // No idea what this is supposed to look like...
- break;
- case "karaoke":
- case "lyrics":
- // Decided to put both of these together (they're basically the same thing, save for the bouncing ball!)
-
- captionHeight = (videoMetrics.height * 0.1 < 20 ? 20 : videoMetrics.height * 0.1);
- applyStyles(DOMNode,{
- "display": "block",
- "position": "absolute",
- "width": (videoMetrics.width - 40) + "px",
- "minHeight": captionHeight + "px",
- "backgroundColor": "rgba(0,0,0,0.5)",
- "left": videoMetrics.left + "px",
- "top": videoMetrics.top + "px",
- "fontSize": (captionHeight <= 50 ? ((captionHeight * 0.5) / 96) * 72 : ((captionHeight * 0.2) / 96) * 72) + "pt",
- "lineHeight": (captionHeight <= 50 ? (captionHeight / 96) * 72 : ((captionHeight / 2) / 96) * 72) + "pt",
- "color": "gold",
- "fontStyle": "oblique",
- "textShadow": "black 0px 0px 5px",
- "fontFamily": "Helvetica, Arial, sans-serif",
- "fontWeight": "lighter",
- "textAlign": "center",
- "paddingLeft": "20px",
- "paddingRight": "20px"
- });
+ /**
+ * @constructor
+ */
+ captionator.TextTrackCue = function TextTrackCue(id, startTime, endTime, text, settings, pauseOnExit, track) {
+ // Set up internal data store
+ this.id = id;
+ this.track = track instanceof captionator.TextTrack ? track : null;
+ this.startTime = parseFloat(startTime);
+ this.endTime = parseFloat(endTime);
+ this.text = typeof(text) === "string" || text instanceof captionator.CaptionatorCueStructure ? text : "";
+ this.settings = typeof(settings) === "string" ? settings : "";
+ this.intSettings = {};
+ this.pauseOnExit = !!pauseOnExit;
+ this.wasActive = false;
- break;
- case "chapters":
+ // Parse settings & set up cue defaults
- break;
- case "tickertext":
- // Stock ticker style, smaller than regular subtitles to fit more in.
+ // A writing direction, either horizontal (a line extends horizontally and is positioned vertically,
+ // with consecutive lines displayed below each other), vertical growing left (a line extends vertically
+ // and is positioned horizontally, with consecutive lines displayed to the left of each other), or
+ // vertical growing right (a line extends vertically and is positioned horizontally, with consecutive
+ // lines displayed to the right of each other).
+ // Values:
+ // horizontal, vertical, vertical-lr
+ this.direction = "horizontal";
- break;
- case "toolbar":
- // Non-standard extension for multi-track media selection toolbars
-
- break;
- default:
- // Whoah, we didn't prepare for this one. Just class it with the requested name and move on.
- }
-
- if (DOMNode.className.indexOf("captionator-kind") === -1) {
- DOMNode.className += (DOMNode.className.length ? " " : "") + "captionator-kind-" + kind;
+ // A boolean indicating whether the line's position is a line position (positioned to a multiple of the
+ // line dimensions of the first line of the cue), or whether it is a percentage of the dimension of the video.
+ this.snapToLines = true;
+
+ // Either a number giving the position of the lines of the cue, to be interpreted as defined by the
+ // writing direction and snap-to-lines flag of the cue, or the special value auto, which means the
+ // position is to depend on the other active tracks.
+ this.linePosition = "auto";
+
+ // A number giving the position of the text of the cue within each line, to be interpreted as a percentage
+ // of the video, as defined by the writing direction.
+ this.textPosition = 50;
+
+ // A number giving the size of the box within which the text of each line of the cue is to be aligned, to
+ // be interpreted as a percentage of the video, as defined by the writing direction.
+ this.size = 0;
+
+ // An alignment for the text of each line of the cue, either start alignment (the text is aligned towards its
+ // start side), middle alignment (the text is aligned centered between its start and end sides), end alignment
+ // (the text is aligned towards its end side). Which sides are the start and end sides depends on the
+ // Unicode bidirectional algorithm and the writing direction. [BIDI]
+ // Values:
+ // start, middle, end
+ this.alignment = "middle";
+
+ // Parse VTT Settings...
+ if (this.settings.length) {
+ var intSettings = this.intSettings;
+ var currentCue = this;
+ settings = settings.split(/\s+/).filter(function(settingItem) { return settingItem.length > 0;});
+ if (settings instanceof Array) {
+ settings.forEach(function(cueItem) {
+ var settingMap = {"D":"direction","L":"linePosition","T":"textPosition","A":"alignment","S":"size"};
+ cueItem = cueItem.split(":");
+ if (settingMap[cueItem[0]]) {
+ intSettings[settingMap[cueItem[0]]] = cueItem[1];
+ }
+
+ if (settingMap[cueItem[0]] in currentCue) {
+ currentCue[settingMap[cueItem[0]]] = cueItem[1];
+ }
+ });
+ }
+ }
+
+ if (this.linePosition.match(/\%/)) {
+ this.snapToLines = false;
+ }
+
+ // Functions defined by spec (getters, kindof)
+ this.getCueAsSource = function getCueAsSource() {
+ // Choosing the below line instead will mean that the raw, unprocessed source will be returned instead.
+ // Not really sure which is the correct behaviour.
+ // return this.text instanceof captionator.CaptionatorCueStructure? this.text.cueSource : this.text;
+ return String(this.text);
+ };
+
+ this.getCueAsHTML = function getCueAsHTML() {
+ var DOMFragment = document.createDocumentFragment();
+ var DOMNode = document.createElement("div");
+ DOMNode.innerHTML = String(this.text);
+
+ Array.prototype.forEach.call(DOMNode.childNodes,function(child) {
+ DOMFragment.appendChild(child.cloneNode(true));
+ });
+
+ return DOMFragment;
+ };
+
+ this.isActive = function() {
+ var currentTime = 0;
+ if (this.track instanceof captionator.TextTrack) {
+ if ((this.track.mode === captionator.TextTrack.SHOWING || this.track.mode === captionator.TextTrack.HIDDEN) && this.track.readyState === captionator.TextTrack.LOADED) {
+ try {
+ currentTime = this.track.videoNode.currentTime;
+
+ if (this.startTime <= currentTime && this.endTime >= currentTime) {
+ // Fire enter event if we were not active and now are
+ if (!this.wasActive) {
+ this.wasActive = true;
+ this.onenter();
+ }
+
+ return true;
+ }
+ } catch(Error) {
+ return false;
+ }
+ }
+ }
+
+ // Fire exit event if we were active and now are not
+ if (this.wasActive) {
+ this.wasActive = false;
+ this.onexit();
+ }
+
+ return false;
+ };
+
+ if (Object.prototype.__defineGetter__) {
+ this.__defineGetter__("active", this.isActive);
+ } else if (Object.defineProperty) {
+ Object.defineProperty(this,"active",
+ {get: this.isActive}
+ );
+ }
+
+ this.toString = function toString() {
+ return "TextTrackCue:" + this.id + "\n" + String(this.text);
+ };
+
+ // Events defined by spec
+ this.onenter = function() {};
+ this.onexit = function() {};
+ };
+
+ // Captionator media extensions
+ /**
+ * @constructor
+ */
+ captionator.MediaTrack = function MediaTrack(id,kind,label,language,src,type,isDefault) {
+ // This function is under construction!
+ // Eventually, the idea is that captionator will support timed video and audio tracks in addition to text subtitles
+
+ var getSupportedMediaSource = function(sources) {
+ // Thanks Mr Pilgrim! :)
+ var supportedSource = sources
+ .filter(function(source,index) {
+ try {
+ var mediaElement = document.createElement(source.getAttribute("type").split("/").shift());
+ return !!(mediaElement.canPlayType && mediaElement.canPlayType(source.getAttribute("type")).replace(/no/, ''));
+ } catch(Error) {
+ // (The type fragment before the / probably didn't match to 'video' or 'audio'. So... we don't support it.)
+ return false;
+ }
+ })
+ .shift()
+ .getAttribute("src");
+
+ return supportedSource;
+ };
+
+ this.onload = function () {};
+ this.onerror = function() {};
+
+ this.id = id || "";
+ this.internalMode = captionator.TextTrack.OFF;
+ this.internalMode = captionator.TextTrack.OFF;
+ this.mediaElement = null;
+ this.kind = kind || "audiodescription";
+ this.label = label || "";
+ this.language = language || "";
+ this.readyState = captionator.TextTrack.NONE;
+ this.type = type || "x/unknown"; // MIME type
+ this.mediaType = null;
+ this.src = "";
+
+ if (typeof(src) === "string") {
+ this.src = src;
+ } else if (src instanceof NodeList) {
+ this.src = getSupportedMediaSource(src);
+ }
+
+ if (this.type.match(/^video\//)) {
+ this.mediaType = "video";
+ } else if (this.type.match(/^audio\//)) {
+ this.mediaType = "audio";
+ }
+
+ // Create getters and setters for mode
+ this.getMode = function() {
+ return this.internalMode;
+ };
+
+ this.setMode = function(value) {
+ var allowedModes = [captionator.TextTrack.OFF,captionator.TextTrack.HIDDEN,captionator.TextTrack.SHOWING], containerID, container;
+ if (allowedModes.indexOf(value) !== -1) {
+ if (value !== this.internalMode) {
+ this.internalMode = value;
+ if (value === captionator.TextTrack.HIDDEN && !this.mediaElement) {
+ this.buildMediaElement();
+ }
+
+ if (value === captionator.TextTrack.SHOWING) {
+ this.showMediaElement();
+ }
+
+ if (value === captionator.TextTrack.OFF || value === captionator.TextTrack.HIDDEN) {
+ this.hideMediaElement();
+ }
+ }
+ } else {
+ throw new Error("Illegal mode value for track.");
+ }
+ };
+
+ if (Object.prototype.__defineGetter__) {
+ this.__defineGetter__("mode", this.getMode);
+ this.__defineSetter__("mode", this.setMode);
+ } else if (Object.defineProperty) {
+ Object.defineProperty(this,"mode",
+ {get: this.getMode, set: this.setMode}
+ );
+ }
+
+ this.hideMediaElement = function() {
+ if (this.mediaElement) {
+ if (!this.mediaElement.paused) {
+ this.mediaElement.pause();
+ }
+
+ if (this.mediaElement instanceof HTMLVideoElement) {
+ this.mediaElement.style.display = "none";
+ }
+ }
+ };
+
+ this.showMediaElement = function() {
+ if (!this.mediaElement) {
+ this.buildMediaElement();
+ document.body.appendChild(this.mediaElement);
+ } else {
+ if (!this.mediaElement.parentNode) {
+ document.body.appendChild(this.mediaElement);
+ }
+
+ if (this.mediaElement instanceof HTMLVideoElement) {
+ this.mediaElement.style.display = "block";
+ }
+ }
+ };
+
+ this.buildMediaElement = function() {
+ try {
+ if (this.type.match(/^video\//)) {
+ this.mediaElement = document.createElement("video");
+ this.mediaElement.className = "captionator-mediaElement-" + this.kind;
+ captionator.styleNode(this.mediaElement,this.kind,this.videoNode);
+
+ } else if (this.type.match(/^audio\//)) {
+ this.mediaElement = new Audio();
+ }
+
+ this.mediaElement.type = this.type;
+ this.mediaElement.src = this.src;
+ this.mediaElement.load();
+ this.mediaElement.trackObject = this;
+ this.readyState = captionator.TextTrack.LOADING;
+ var mediaElement = this.mediaElement;
+
+ this.mediaElement.addEventListener("progress",function(eventData) {
+ mediaElement.trackObject.readyState = captionator.TextTrack.LOADING;
+ },false);
+
+ this.mediaElement.addEventListener("canplaythrough",function(eventData) {
+ mediaElement.trackObject.readyState = captionator.TextTrack.LOADED;
+ mediaElement.trackObject.onload.call(mediaElement.trackObject);
+ },false);
+
+ this.mediaElement.addEventListener("error",function(eventData) {
+ mediaElement.trackObject.readyState = captionator.TextTrack.ERROR;
+ mediaElement.trackObject.mode = captionator.TextTrack.OFF;
+ mediaElement.trackObject.mediaElement = null;
+ mediaElement.trackObject.onerror.call(mediaElement.trackObject,eventData);
+ },false);
+
+ } catch(Error) {
+ this.readyState = captionator.TextTrack.ERROR;
+ this.mode = captionator.TextTrack.OFF;
+ this.mediaElement = null;
+
+ if (this.onerror) {
+ this.onerror.apply(this,Error);
+ }
+ }
+ };
+ };
+
+ // Captionator internal cue structure object
+ /**
+ * @constructor
+ */
+ captionator.CaptionatorCueStructure = function CaptionatorCueStructure(cueSource,options) {
+ var cueStructureObject = this;
+ this.isTimeDependent = false;
+ this.cueSource = cueSource;
+ this.options = options;
+ this.processedCue = null;
+ this.toString = function toString(currentTimestamp) {
+ if (options.processCueHTML !== false) {
+ var processLayer = function(layerObject,depth) {
+ if (cueStructureObject.processedCue === null) {
+ var compositeHTML = "", itemIndex, cueChunk;
+ for (itemIndex in layerObject) {
+ if (itemIndex.match(/^\d+$/) && layerObject.hasOwnProperty(itemIndex)) {
+ // We're not a prototype function or local property, and we're in range
+ cueChunk = layerObject[itemIndex];
+ // Don't generate text from the token if it has no contents
+ if (cueChunk instanceof Object && cueChunk.children && cueChunk.children.length) {
+ if (cueChunk.token === "v") {
+ compositeHTML +="" +
+ processLayer(cueChunk.children,depth+1) +
+ " ";
+ } else if(cueChunk.token === "c") {
+ compositeHTML +="" +
+ processLayer(cueChunk.children,depth+1) +
+ " ";
+ } else if(cueChunk.timeIn > 0) {
+ // If a timestamp is unspecified, or the timestamp suggests this token is valid to display, return it
+ if ((currentTimestamp === null || currentTimestamp === undefined) ||
+ (currentTimestamp > 0 && currentTimestamp >= cueChunk.timeIn)) {
+
+ compositeHTML +="" +
+ processLayer(cueChunk.children,depth+1) +
+ " ";
+ }
+ } else {
+ compositeHTML +=cueChunk.rawToken +
+ processLayer(cueChunk.children,depth+1) +
+ "" + cueChunk.token + ">";
+ }
+ } else if (cueChunk instanceof String || typeof(cueChunk) === "string" || typeof(cueChunk) === "number") {
+ compositeHTML += cueChunk;
+ } else {
+ // Didn't match - file a bug!
+ }
+ }
+ }
+
+ if (!cueStructureObject.isTimeDependent && depth === 0) {
+ cueStructureObject.processedCue = compositeHTML;
+ }
+
+ return compositeHTML;
+ } else {
+ return cueStructureObject.processedCue;
+ }
+ };
+ return processLayer(this,0);
+ } else {
+ return cueSource;
+ }
+ };
+ };
+ captionator.CaptionatorCueStructure.prototype = [];
+
+ // if requested by options, export the object types
+ if (options.exportObjects) {
+ window.TextTrack = captionator.TextTrack;
+ window.TextTrackCueList = captionator.TextTrackCueList;
+ window.ActiveTextTrackCueList = captionator.ActiveTextTrackCueList;
+ window.TextTrackCue = captionator.TextTrackCue;
+ window.MediaTrack = captionator.MediaTrack;
+ }
+
+ // Next time captionator.captionify() is called, the objects are already available to us.
+ objectsCreated = true;
+ }
+
+ [].slice.call(document.getElementsByTagName("video"),0).forEach(function(videoElement) {
+ videoElement.addTextTrack = function(id,kind,label,language,src,type,isDefault) {
+ var allowedKinds = ["subtitles","captions","descriptions","captions","metadata","chapters", // WHATWG SPEC
+ "karaoke","lyrics","tickertext", // CAPTIONATOR TEXT EXTENSIONS
+ "audiodescription","commentary", // CAPTIONATOR AUDIO EXTENSIONS
+ "alternate","signlanguage"]; // CAPTIONATOR VIDEO EXTENSIONS
+
+ var textKinds = allowedKinds.slice(0,7);
+ var newTrack;
+ id = typeof(id) === "string" ? id : "";
+ label = typeof(label) === "string" ? label : "";
+ language = typeof(language) === "string" ? language : "";
+ isDefault = typeof(isDefault) === "boolean" ? isDefault : false; // Is this track set as the default?
+ type = typeof(type) === "string" ? type : "";
+
+ // If the kind isn't known, throw DOM syntax error exception
+ if (!allowedKinds.filter(function (currentKind){
+ return kind === currentKind ? true : false;
+ }).length) {
+ throw captionator.createDOMException(12,"DOMException 12: SYNTAX_ERR: You must use a valid kind when creating a TimedTextTrack.","SYNTAX_ERR");
+ }
+
+ if (textKinds.filter(function (currentKind){
+ return kind === currentKind ? true : false;
+ }).length) {
+ newTrack = new captionator.TextTrack(id,kind,label,language,src,null,type);
+ if (newTrack) {
+ if (!(videoElement.tracks instanceof Array)) {
+ videoElement.tracks = [];
+ }
+
+ videoElement.tracks.push(newTrack);
+ return newTrack;
+ } else {
+ return false;
+ }
+ } else {
+ newTrack = new captionator.MediaTrack(id,kind,label,language,src,type,isDefault);
+ if (newTrack) {
+ if (!(videoElement.mediaTracks instanceof Array)) {
+ videoElement.mediaTracks = [];
+ }
+
+ videoElement.mediaTracks.push(newTrack);
+ return newTrack;
+ } else {
+ return false;
+ }
+ }
+ };
+ });
+
+
+ if (!element || element === false || element === undefined || element === null) {
+ videoElements = [].slice.call(document.getElementsByTagName("video"),0); // select and convert to array
+ } else {
+ if (element instanceof Array) {
+ for (elementIndex = 0; elementIndex < element.length; elementIndex ++) {
+ if (typeof(element[elementIndex]) === "string") {
+ videoElements = videoElements.concat([].slice.call(document.querySelectorAll(element[elementIndex]),0)); // select and convert to array
+ } else if (element[elementIndex].constructor === HTMLVideoElement) {
+ videoElements.push(element[elementIndex]);
+ }
+ }
+ } else if (typeof(element) === "string") {
+ videoElements = [].slice.call(document.querySelectorAll(element),0); // select and convert to array
+ } else if (element.constructor === HTMLVideoElement) {
+ videoElements.push(element);
+ }
+ }
+
+ if (videoElements.length) {
+ for (elementIndex = 0; elementIndex < videoElements.length; elementIndex ++) {
+ captionator.processVideoElement(videoElements[elementIndex],defaultLanguage,options);
+ }
+ return true;
+ } else {
+ return false;
+ }
+ },
+ /*
+ captionator.processVideoElement(videoElement ,
+ [defaultLanguage - string in BCP47],
+ [options - JS Object])
+
+ Processes track items within an HTMLVideoElement. The second and third parameter are both optional.
+
+ First parameter: Mandatory HTMLVideoElement object.
+
+ Second parameter: BCP-47 string for default language. If this parameter is omitted, the User Agent's language
+ will be used to choose a track.
+
+ Third parameter: as yet unused - will implement animation settings and some other global options with this
+ parameter later.
+
+ RETURNS:
+
+ Reference to the HTMLVideoElement.
+
+
+ */
+ "processVideoElement": function(videoElement,defaultLanguage,options) {
+ var trackList = [];
+ var language = navigator.language || navigator.userLanguage;
+ var globalLanguage = defaultLanguage || language.split("-")[0];
+ options = options instanceof Object? options : {};
+
+ if (!videoElement.captioned) {
+ videoElement._captionatorOptions = options;
+ videoElement.className += (videoElement.className.length ? " " : "") + "captioned";
+ videoElement.captioned = true;
+
+ // Check whether video element has an ID. If not, create one
+ if (videoElement.id.length === 0) {
+ videoElement.id = captionator.generateID();
+ }
+
+ var enabledDefaultTrack = false;
+ [].slice.call(videoElement.querySelectorAll("track"),0).forEach(function(trackElement) {
+ var sources = null;
+ if (trackElement.querySelectorAll("source").length > 0) {
+ sources = trackElement.querySelectorAll("source");
+ } else {
+ sources = trackElement.getAttribute("src");
+ }
+
+ var trackObject = videoElement.addTextTrack(
+ (trackElement.getAttribute("id")||captionator.generateID()),
+ trackElement.getAttribute("kind"),
+ trackElement.getAttribute("label"),
+ trackElement.getAttribute("srclang").split("-")[0],
+ sources,
+ trackElement.getAttribute("type"),
+ trackElement.hasAttribute("default")); // (Christopher) I think we can get away with this given it's a boolean attribute anyway
+
+ trackElement.track = trackObject;
+ trackObject.trackNode = trackElement;
+ trackObject.videoNode = videoElement;
+ trackList.push(trackObject);
+
+ // Now determine whether the track is visible by default.
+ // The comments in this section come straight from the spec...
+ var trackEnabled = false;
+
+ // If the text track kind is subtitles or captions and the user has indicated an interest in having a track
+ // with this text track kind, text track language, and text track label enabled, and there is no other text track
+ // in the media element's list of text tracks with a text track kind of either subtitles or captions whose text track mode is showing
+ // ---> Let the text track mode be showing.
+
+ if ((trackObject.kind === "subtitles" || trackObject.kind === "captions") &&
+ (defaultLanguage === trackObject.language && options.enableCaptionsByDefault)) {
+ if (!trackList.filter(function(trackObject) {
+ if ((trackObject.kind === "captions" || trackObject.kind === "subtitles") && defaultLanguage === trackObject.language && trackObject.mode === captionator.TextTrack.SHOWING) {
+ return true;
+ } else {
+ return false;
+ }
+ }).length) {
+ trackEnabled = true;
+ }
+ }
+
+ // If the text track kind is chapters and the text track language is one that the user agent has reason to believe is
+ // appropriate for the user, and there is no other text track in the media element's list of text tracks with a text track
+ // kind of chapters whose text track mode is showing
+ // ---> Let the text track mode be showing.
+
+ if (trackObject.kind === "chapters" && (defaultLanguage === trackObject.language)) {
+ if (!trackList.filter(function(trackObject) {
+ if (trackObject.kind === "chapters" && trackObject.mode === captionator.TextTrack.SHOWING) {
+ return true;
+ } else {
+ return false;
+ }
+ }).length) {
+ trackEnabled = true;
+ }
+ }
+
+ // If the text track kind is descriptions and the user has indicated an interest in having text descriptions
+ // with this text track language and text track label enabled, and there is no other text track in the media element's
+ // list of text tracks with a text track kind of descriptions whose text track mode is showing
+
+ if (trackObject.kind === "descriptions" && (options.enableDescriptionsByDefault === true) && (defaultLanguage === trackObject.language)) {
+ if (!trackList.filter(function(trackObject) {
+ if (trackObject.kind === "descriptions" && trackObject.mode === captionator.TextTrack.SHOWING) {
+ return true;
+ } else {
+ return false;
+ }
+ }).length) {
+ trackEnabled = true;
+ }
+ }
+
+ // If there is a text track in the media element's list of text tracks whose text track mode is showing by default,
+ // the user agent must furthermore change that text track's text track mode to hidden.
+
+ if (trackEnabled === true) {
+ trackList.forEach(function(trackObject) {
+ if(trackObject.trackNode.hasAttribute("default") && trackObject.mode === captionator.TextTrack.SHOWING) {
+ trackObject.mode = captionator.TextTrack.HIDDEN;
+ }
+ });
+ }
+
+ // If the track element has a default attribute specified, and there is no other text track in the media element's
+ // list of text tracks whose text track mode is showing or showing by default
+ // Let the text track mode be showing by default.
+
+ if (trackElement.hasAttribute("default")) {
+ if (!trackList.filter(function(trackObject) {
+ if (trackObject.trackNode.hasAttribute("default") && trackObject.trackNode !== trackElement) {
+ return true;
+ } else {
+ return false;
+ }
+ }).length) {
+ trackEnabled = true;
+ trackObject.internalDefault = true;
+ }
+ }
+
+ // Otherwise
+ // Let the text track mode be disabled.
+
+ if (trackEnabled === true) {
+ trackObject.mode = captionator.TextTrack.SHOWING;
+ }
+ });
+
+ videoElement.addEventListener("timeupdate", function(eventData){
+ var videoElement = eventData.target;
+ // update active cues
+ try {
+ videoElement.tracks.forEach(function(track) {
+ track.activeCues.refreshCues.apply(track.activeCues);
+ });
+ } catch(error) {}
+
+ // External renderer?
+ if (options.renderer instanceof Function) {
+ options.renderer.call(captionator,videoElement);
+ } else {
+ captionator.rebuildCaptions(videoElement);
+ }
+
+ captionator.synchroniseMediaElements(videoElement);
+ }, false);
+
+ window.addEventListener("resize", function(eventData) {
+ videoElement._captionator_dirtyBit = true; // mark video as dirty, force captionator to rerender captions
+ captionator.rebuildCaptions(videoElement);
+ },false);
+
+ videoElement.addEventListener("play", function(eventData){
+ captionator.synchroniseMediaElements(videoElement);
+ },false);
+
+ videoElement.addEventListener("pause", function(eventData){
+ captionator.synchroniseMediaElements(videoElement);
+ },false);
+
+ // Hires mode
+ if (options.enableHighResolution === true) {
+ window.setInterval(function captionatorHighResProcessor() {
+ try {
+ videoElement.tracks.forEach(function(track) {
+ track.activeCues.refreshCues.apply(track.activeCues);
+ });
+ } catch(error) {}
+
+ // External renderer?
+ if (options.renderer instanceof Function) {
+ options.renderer.call(captionator,videoElement);
+ } else {
+ captionator.rebuildCaptions(videoElement);
+ }
+ },20);
+ }
+ }
+
+ return videoElement;
+ },
+ /*
+ captionator.rebuildCaptions(HTMLVideoElement videoElement)
+
+ Loops through all the TextTracks for a given element and manages their display (including generation of container elements.)
+
+ First parameter: HTMLVideoElement object with associated TextTracks
+
+ RETURNS:
+
+ Nothing.
+
+ */
+ "rebuildCaptions": function(videoElement) {
+ var trackList = videoElement.tracks || [];
+ var options = videoElement._captionatorOptions instanceof Object ? videoElement._captionatorOptions : {};
+ var currentTime = videoElement.currentTime;
+ var compositeActiveCues = [];
+ var cuesChanged = false;
+ var activeCueIDs = [];
+ var cueSortArray = [];
+
+ // Work out what cues are showing...
+ trackList.forEach(function(track,trackIndex) {
+ if (track.mode === captionator.TextTrack.SHOWING && track.readyState === captionator.TextTrack.LOADED) {
+ cueSortArray = [].slice.call(track.activeCues,0);
+
+ // Do a reverse sort
+ // Since the available cue render area is a square which decreases in size
+ // (away from each side of the video) with each successive cue added,
+ // and we want cues which are older to be displayed above cues which are newer,
+ // we sort active cues within each track so that older ones are rendered first.
+
+ cueSortArray = cueSortArray.sort(function(cueA, cueB) {
+ if (cueA.startTime > cueB.startTime) {
+ return -1;
+ } else {
+ return 1;
+ }
+ });
+
+ compositeActiveCues = compositeActiveCues.concat(cueSortArray);
+ }
+ });
+
+ // Determine whether cues have changed - we generate an ID based on track ID, cue ID, and text length
+ activeCueIDs = compositeActiveCues.map(function(cue) {return cue.track.id + "." + cue.id + ":" + cue.text.toString(currentTime).length;});
+ cuesChanged = !captionator.compareArray(activeCueIDs,videoElement._captionator_previousActiveCues);
+
+ // If they've changed, we re-render our cue canvas.
+ if (cuesChanged || videoElement._captionator_dirtyBit) {
+ // If dirty bit was set, it certainly isn't now.
+ videoElement._captionator_dirtyBit = false;
+
+ // Destroy internal tracking variable (which is used for caption rendering)
+ videoElement._captionator_availableCueArea = null;
+
+ // Internal tracking variable to determine whether our composite active cue list for the video has changed
+ videoElement._captionator_previousActiveCues = activeCueIDs;
+
+ // Get the canvas ready if it isn't already
+ captionator.styleCueCanvas(videoElement);
+ videoElement._containerObject.innerHTML = "";
+
+ // Now we render the cues
+ compositeActiveCues.forEach(function(cue) {
+ var cueNode = document.createElement("div");
+ cueNode.id = String(cue.id).length ? cue.id : captionator.generateID();
+ cueNode.className = "captionator-cue";
+ cueNode.innerHTML = cue.text.toString(currentTime);
+ videoElement._containerObject.appendChild(cueNode);
+ captionator.styleCue(cueNode,cue,videoElement);
+ });
+ }
+ },
+ /*
+ captionator.synchroniseMediaElements(HTMLVideoElement videoElement)
+
+ Loops through all the MediaTracks for a given element and manages their display/audibility, synchronising them to the playback of the
+ master video element.
+
+ This function also synchronises regular HTML5 media elements with a property of syncMaster with a value equal to the ID of the current video
+ element.
+
+ First parameter: HTMLVideoElement object with associated MediaTracks
+
+ RETURNS:
+
+ Nothing.
+
+ */
+ "synchroniseMediaElements": function(videoElement) {
+ var trackList = videoElement.mediaTracks || [];
+ var options = videoElement._captionatorOptions instanceof Object ? videoElement._captionatorOptions : {};
+ var currentTime = videoElement.currentTime;
+ var synchronisationThreshold = 0.5; // How many seconds of drift will be tolerated before resynchronisation?
+
+ var synchroniseElement = function(slave,master) {
+ try {
+ if (master.seeking) {
+ slave.pause();
+ }
+
+ if (slave.currentTime < master.currentTime - synchronisationThreshold || slave.currentTime > master.currentTime + synchronisationThreshold) {
+ slave.currentTime = master.currentTime;
+ }
+
+ if (slave.paused && !master.paused) {
+ slave.play();
+ } else if (!slave.paused && master.paused) {
+ slave.pause();
+ }
+ } catch(Error) {
+ // Probably tried to seek to an unavailable chunk of video
+ }
+ };
+
+ // Work out what cues are showing...
+ trackList.forEach(function(track,trackIndex) {
+ if (track.mode === captionator.TextTrack.SHOWING && track.readyState >= captionator.TextTrack.LOADING) {
+ synchroniseElement(track.mediaElement,videoElement);
+ }
+ });
+
+ if (videoElement.id) {
+ [].slice.call(document.body.querySelectorAll("*[syncMaster=" + videoElement.id + "]"),0).forEach(function(mediaElement,index) {
+ if (mediaElement.tagName.toLowerCase() === "video" || mediaElement.tagName.toLowerCase() === "audio") {
+ synchroniseElement(mediaElement,videoElement);
+ }
+ });
+ }
+ },
+ /*
+ captionator.getNodeMetrics(DOMNode)
+
+ Calculates and returns a number of sizing and position metrics from a DOMNode of any variety (though this function is intended
+ to be used with HTMLVideoElements.) Returns the height of the default controls on a video based on user agent detection
+ (As far as I know, there's no way to dynamically calculate the height of browser UI controls on a video.)
+
+ First parameter: DOMNode from which to calculate sizing metrics. This parameter is mandatory.
+
+ RETURNS:
+
+ An object with the following properties:
+ left: The calculated left offset of the node
+ top: The calculated top offset of the node
+ height: The calculated height of the node
+ width: The calculated with of the node
+ controlHeight: If the node is a video and has the `controls` attribute present, the height of the UI controls for the video. Otherwise, zero.
+
+ */
+ "getNodeMetrics": function(DOMNode) {
+ var nodeComputedStyle = window.getComputedStyle(DOMNode,null);
+ var offsetObject = DOMNode;
+ var offsetTop = DOMNode.offsetTop, offsetLeft = DOMNode.offsetLeft;
+ var width = DOMNode, height = 0;
+ var controlHeight = 0;
+
+ width = parseInt(nodeComputedStyle.getPropertyValue("width"),10);
+ height = parseInt(nodeComputedStyle.getPropertyValue("height"),10);
+
+ // Slightly verbose expression in order to pass JSHint
+ while (!!(offsetObject = offsetObject.offsetParent)) {
+ offsetTop += offsetObject.offsetTop;
+ offsetLeft += offsetObject.offsetLeft;
+ }
+
+ if (DOMNode.hasAttribute("controls")) {
+ // Get heights of default control strip in various browsers
+ // There could be a way to measure this live but I haven't thought/heard of it yet...
+ var UA = navigator.userAgent.toLowerCase();
+ if (UA.indexOf("chrome") !== -1) {
+ controlHeight = 32;
+ } else if (UA.indexOf("opera") !== -1) {
+ controlHeight = 25;
+ } else if (UA.indexOf("firefox") !== -1) {
+ controlHeight = 28;
+ } else if (UA.indexOf("ie 9") !== -1 || UA.indexOf("ipad") !== -1) {
+ controlHeight = 44;
+ } else if (UA.indexOf("safari") !== -1) {
+ controlHeight = 25;
+ }
+ } else if (DOMNode._captionatorOptions) {
+ var tmpCaptionatorOptions = DOMNode._captionatorOptions;
+ if (tmpCaptionatorOptions.controlHeight) {
+ controlHeight = parseInt(tmpCaptionatorOptions.controlHeight,10);
+ }
}
- }
- },
- /*
- captionator.parseCaptions(string captionData)
-
- Accepts and parses SRT caption/subtitle data. Will extend for WebVTT shortly. Perhaps non-JSON WebVTT will work already?
- This function has been intended from the start to (hopefully) loosely parse both. I'll patch it as required.
-
- First parameter: Entire text data (UTF-8) of the retrieved SRT/WebVTT file. This parameter is mandatory. (really - what did
- you expect it was going to do without it!)
-
- RETURNS:
-
- An array of TextTrackCue Objects in initial state.
-
- */
- "parseCaptions": function(captionData) {
- "use strict";
- // Be liberal in what you accept from others...
- if (captionData) {
- var subtitles = captionData
- .replace(/\r\n/g,"\n")
- .replace(/\r/g,"\n")
- .split(/\n\n/g)
- .filter(function(lineGroup) {
- if (lineGroup.match(/WEBVTT FILE/ig)) {
- // This is useless - we just don't care as we'll be treating SRT and WebVTT the same anyway.
- return false;
+
+ return {
+ left: offsetLeft,
+ top: offsetTop,
+ width: width,
+ height: height,
+ controlHeight: controlHeight
+ };
+ },
+ /*
+ captionator.applyStyles(DOMNode, Style Object)
+
+ A fast way to apply multiple CSS styles to a DOMNode.
+
+ First parameter: DOMNode to style. This parameter is mandatory.
+
+ Second parameter: A key/value object where the keys are camel-cased variants of CSS property names to apply,
+ and the object values are CSS property values as per the spec. This parameter is mandatory.
+
+ RETURNS:
+
+ Nothing.
+
+ */
+ "applyStyles": function(StyleNode, styleObject) {
+ for (var styleName in styleObject) {
+ if ({}.hasOwnProperty.call(styleObject, styleName)) {
+ StyleNode.style[styleName] = styleObject[styleName];
+ }
+ }
+ },
+ /*
+ captionator.checkDirection(text)
+
+ Determines whether the text string passed into the function is an RTL (right to left) or LTR (left to right) string.
+
+ First parameter: Text string to check. This parameter is mandatory.
+
+ RETURNS:
+
+ The text string 'rtl' if the text is a right to left string, 'ltr' if the text is a left to right string, or an empty string
+ if the direction could not be determined.
+
+ */
+ "checkDirection": function(text) {
+ // Inspired by http://www.frequency-decoder.com/2008/12/12/automatically-detect-rtl-text
+ // Thanks guys!
+ var ltrChars = 'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF'+'\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF',
+ rtlChars = '\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC',
+ ltrDirCheckRe = new RegExp('^[^'+rtlChars+']*['+ltrChars+']'),
+ rtlDirCheckRe = new RegExp('^[^'+ltrChars+']*['+rtlChars+']');
+
+ return !!rtlDirCheckRe.test(text) ? 'rtl' : (!!ltrDirCheckRe.test(text) ? 'ltr' : '');
+ },
+ /*
+ captionator.styleCue(DOMNode, cueObject, videoNode)
+
+ Styles and positions cue nodes according to the WebVTT specification.
+
+ First parameter: The DOMNode representing the cue to style. This parameter is mandatory.
+
+ Second parameter: The TextTrackCue itself.
+
+ Third Parameter: The HTMLVideoElement with which the cue is associated. This parameter is mandatory.
+
+ RETURNS:
+
+ Nothing.
+
+ */
+ "styleCue": function(DOMNode, cueObject, videoElement) {
+ // Variables for maintaining render calculations
+ var cueX = 0, cueY = 0, cueWidth = 0, cueHeight = 0, cueSize, cueAlignment, cuePaddingLR = 0, cuePaddingTB = 0;
+ var baseFontSize, basePixelFontSize, baseLineHeight, tmpHeightExclusions;
+ var videoHeightInLines, videoWidthInLines, pixelLineHeight, verticalPixelLineHeight, charactersPerLine = 0, characterCount = 0;
+ var characters = 0, lineCount = 0, finalLineCharacterCount = 0, finalLineCharacterHeight = 0, currentLine = 0;
+ var characterX, characterY, characterPosition = 0;
+ var options = videoElement._captionatorOptions || {};
+ var videoMetrics;
+ var maxCueSize = 100, internalTextPosition = 50, textBoundingBoxWidth = 0, textBoundingBoxPercentage = 0, autoSize = true;
+
+ // Function to facilitate vertical text alignments in browsers which do not support writing-mode
+ // (sadly, all the good ones!)
+ var spanify = function(DOMNode) {
+ var stringHasLength = function(textString) { return !!textString.length; };
+ var spanCode = "";
+ var nodeIndex, currentNode, currentNodeValue, replacementFragment, characterCount = 0;
+ var styleSpan = function(span) {
+ characterCount ++;
+ captionator.applyStyles(span,{
+ "display": "block",
+ "lineHeight": "auto",
+ "height": basePixelFontSize + "px",
+ "width": verticalPixelLineHeight + "px",
+ "textAlign": "center"
+ });
+ };
+
+ for (nodeIndex in DOMNode.childNodes) {
+ if (DOMNode.childNodes.hasOwnProperty(nodeIndex)) {
+ currentNode = DOMNode.childNodes[nodeIndex];
+ if (currentNode.nodeType === 3) {
+ replacementFragment = document.createDocumentFragment();
+ currentNodeValue = currentNode.nodeValue;
+
+ replacementFragment.appendChild(document.createElement("span"));
+
+ replacementFragment.childNodes[0].innerHTML =
+ spanCode +
+ currentNodeValue
+ .split(/(.)/)
+ .filter(stringHasLength)
+ .join(" " + spanCode) +
+ "";
+
+ [].slice.call(replacementFragment.querySelectorAll("span.captionator-cue-character"),0).forEach(styleSpan);
+
+ currentNode.parentNode.replaceChild(replacementFragment,currentNode);
+ } else if (DOMNode.childNodes[nodeIndex].nodeType === 1) {
+ characterCount += spanify(DOMNode.childNodes[nodeIndex]);
+ }
+ }
+ }
+
+ return characterCount;
+ };
+
+ // Set up the cue canvas
+ videoMetrics = captionator.getNodeMetrics(videoElement);
+
+ // Define storage for the available cue area, diminished as further cues are added
+ // Cues occupy the largest possible area they can, either by width or height
+ // (depending on whether the `direction` of the cue is vertical or horizontal)
+ // Cues which have an explicit position set do not detract from this area.
+ // It is the subtitle author's responsibility to ensure they don't overlap if
+ // they decide to override default positioning!
+
+ if (!videoElement._captionator_availableCueArea) {
+ videoElement._captionator_availableCueArea = {
+ "bottom": (videoMetrics.height-videoMetrics.controlHeight),
+ "right": videoMetrics.width,
+ "top": 0,
+ "left": 0,
+ "height": (videoMetrics.height-videoMetrics.controlHeight),
+ "width": videoMetrics.width
+ };
+ }
+
+ if (cueObject.direction === "horizontal") {
+ // Calculate text bounding box
+ // (isn't useful for vertical cues, because we're doing all glyph positioning ourselves.)
+ captionator.applyStyles(DOMNode,{
+ "width": "auto",
+ "position": "static",
+ "display": "inline-block",
+ "padding": "1em"
+ });
+
+ textBoundingBoxWidth = parseInt(DOMNode.offsetWidth,10);
+ textBoundingBoxPercentage = Math.floor((textBoundingBoxWidth / videoElement._captionator_availableCueArea.width) * 100);
+ textBoundingBoxPercentage = textBoundingBoxPercentage <= 100 ? textBoundingBoxPercentage : 100;
+ }
+
+ // Calculate font metrics
+ baseFontSize = ((videoMetrics.height * (fontSizeVerticalPercentage/100))/96)*72;
+ baseFontSize = baseFontSize >= minimumFontSize ? baseFontSize : minimumFontSize;
+ basePixelFontSize = Math.floor((baseFontSize/72)*96);
+ baseLineHeight = Math.floor(baseFontSize * lineHeightRatio);
+ baseLineHeight = baseLineHeight > minimumLineHeight ? baseLineHeight : minimumLineHeight;
+ pixelLineHeight = Math.ceil((baseLineHeight/72)*96);
+ verticalPixelLineHeight = pixelLineHeight;
+
+ if (pixelLineHeight * Math.floor(videoMetrics.height / pixelLineHeight) < videoMetrics.height) {
+ pixelLineHeight = Math.floor(videoMetrics.height / Math.floor(videoMetrics.height / pixelLineHeight));
+ baseLineHeight = Math.ceil((pixelLineHeight/96)*72);
+ }
+
+ if (pixelLineHeight * Math.floor(videoMetrics.width / pixelLineHeight) < videoMetrics.width) {
+ verticalPixelLineHeight = Math.ceil(videoMetrics.width / Math.floor(videoMetrics.width / pixelLineHeight));
+ }
+
+ // Calculate render area height & width in lines
+ videoHeightInLines = Math.floor(videoElement._captionator_availableCueArea.height / pixelLineHeight);
+ videoWidthInLines = Math.floor(videoElement._captionator_availableCueArea.width / verticalPixelLineHeight);
+
+ // Calculate cue size and padding
+ if (parseFloat(String(cueObject.size).replace(/[^\d\.]/ig,"")) === 0) {
+ // We assume (given a size of 0) that no explicit size was set.
+ // Depending on settings, we either use the WebVTT default size of 100% (the Captionator.js default behaviour),
+ // or the proportion of the video the text bounding box takes up (widthwise) as a percentage (proposed behaviour, LeanBack's default)
+ if (options.sizeCuesByTextBoundingBox === true) {
+ cueSize = textBoundingBoxPercentage;
+ } else {
+ cueSize = 100;
+ autoSize = false;
+ }
+ } else {
+ autoSize = false;
+ cueSize = parseFloat(String(cueObject.size).replace(/[^\d\.]/ig,""));
+ cueSize = cueSize <= 100 ? cueSize : 100;
+ }
+
+ cuePaddingLR = cueObject.direction === "horizontal" ? Math.floor(videoMetrics.width * 0.01) : 0;
+ cuePaddingTB = cueObject.direction === "horizontal" ? 0 : Math.floor(videoMetrics.height * 0.01);
+
+ if (cueObject.linePosition === "auto") {
+ cueObject.linePosition = cueObject.direction === "horizontal" ? videoHeightInLines : videoWidthInLines;
+ } else if (String(cueObject.linePosition).match(/\%/)) {
+ cueObject.snapToLines = false;
+ cueObject.linePosition = parseFloat(String(cueObject.linePosition).replace(/\%/ig,""));
+ }
+
+ if (cueObject.direction === "horizontal") {
+ cueHeight = pixelLineHeight;
+
+ if (cueObject.textPosition !== "auto" && autoSize) {
+ internalTextPosition = parseFloat(String(cueObject.textPosition).replace(/[^\d\.]/ig,""));
+
+ // Don't squish the text
+ if (cueSize - internalTextPosition > textBoundingBoxPercentage) {
+ cueSize -= internalTextPosition;
+ } else {
+ cueSize = textBoundingBoxPercentage;
+ }
+ }
+
+ if (cueObject.snapToLines === true) {
+ cueWidth = videoElement._captionator_availableCueArea.width * (cueSize/100);
+ } else {
+ cueWidth = videoMetrics.width * (cueSize/100);
+ }
+
+ if (cueObject.textPosition === "auto") {
+ cueX = ((videoElement._captionator_availableCueArea.right - cueWidth) / 2) + videoElement._captionator_availableCueArea.left;
+ } else {
+ internalTextPosition = parseFloat(String(cueObject.textPosition).replace(/[^\d\.]/ig,""));
+ cueX = ((videoElement._captionator_availableCueArea.right - cueWidth) * (internalTextPosition/100)) + videoElement._captionator_availableCueArea.left;
+ }
+
+ if (cueObject.snapToLines === true) {
+ cueY = ((videoHeightInLines-1) * pixelLineHeight) + videoElement._captionator_availableCueArea.top;
+ } else {
+ tmpHeightExclusions = videoMetrics.controlHeight + pixelLineHeight + (cuePaddingTB*2);
+ cueY = (videoMetrics.height - tmpHeightExclusions) * (cueObject.linePosition/100);
+ }
+
+ } else {
+ // Basic positioning
+ cueY = videoElement._captionator_availableCueArea.top;
+ cueX = videoElement._captionator_availableCueArea.right - verticalPixelLineHeight;
+ cueWidth = verticalPixelLineHeight;
+ cueHeight = videoElement._captionator_availableCueArea.height * (cueSize/100);
+
+ // Split into characters, and continue calculating width & positioning with new info
+ characterCount = spanify(DOMNode);
+ characters = [].slice.call(DOMNode.querySelectorAll("span.captionator-cue-character"),0);
+ charactersPerLine = Math.floor((cueHeight-cuePaddingTB*2)/basePixelFontSize);
+ cueWidth = Math.ceil(characterCount/charactersPerLine) * verticalPixelLineHeight;
+ lineCount = Math.ceil(characterCount/charactersPerLine);
+ finalLineCharacterCount = characterCount - (charactersPerLine * (lineCount - 1));
+ finalLineCharacterHeight = finalLineCharacterCount * basePixelFontSize;
+
+ // Work out CueX taking into account linePosition...
+ if (cueObject.snapToLines === true) {
+ cueX = cueObject.direction === "vertical-lr" ? videoElement._captionator_availableCueArea.left : videoElement._captionator_availableCueArea.right - cueWidth;
+ } else {
+ var temporaryWidthExclusions = cueWidth + (cuePaddingLR * 2);
+ if (cueObject.direction === "vertical-lr") {
+ cueX = (videoMetrics.width - temporaryWidthExclusions) * (cueObject.linePosition/100);
+ } else {
+ cueX = (videoMetrics.width-temporaryWidthExclusions) - ((videoMetrics.width - temporaryWidthExclusions) * (cueObject.linePosition/100));
+ }
+ }
+
+ // Work out CueY taking into account textPosition...
+ if (cueObject.textPosition === "auto") {
+ cueY = ((videoElement._captionator_availableCueArea.bottom - cueHeight) / 2) + videoElement._captionator_availableCueArea.top;
+ } else {
+ cueObject.textPosition = parseFloat(String(cueObject.textPosition).replace(/[^\d\.]/ig,""));
+ cueY = ((videoElement._captionator_availableCueArea.bottom - cueHeight) * (cueObject.textPosition/100)) +
+ videoElement._captionator_availableCueArea.top;
+ }
+
+
+ // Iterate through the characters and position them accordingly...
+ currentLine = 0;
+ characterPosition = 0;
+ characterX = 0;
+ characterY = 0;
+
+ characters.forEach(function(characterSpan,characterCount) {
+ if (cueObject.direction === "vertical-lr") {
+ characterX = verticalPixelLineHeight * currentLine;
+ } else {
+ characterX = cueWidth - (verticalPixelLineHeight * (currentLine+1));
+ }
+
+ if (cueObject.alignment === "start" || (cueObject.alignment !== "start" && currentLine < lineCount-1)) {
+ characterY = (characterPosition * basePixelFontSize) + cuePaddingTB;
+ } else if (cueObject.alignment === "end") {
+ characterY = ((characterPosition * basePixelFontSize)-basePixelFontSize) + ((cueHeight+(cuePaddingTB*2))-finalLineCharacterHeight);
+ } else if (cueObject.alignment === "middle") {
+ characterY = (((cueHeight - (cuePaddingTB*2))-finalLineCharacterHeight)/2) + (characterPosition * basePixelFontSize);
+ }
+
+ captionator.applyStyles(characterSpan,{
+ "position": "absolute",
+ "top": characterY + "px",
+ "left": characterX + "px"
+ });
+
+ if (characterPosition >= charactersPerLine-1) {
+ characterPosition = 0;
+ currentLine ++;
+ } else {
+ characterPosition ++;
+ }
+ });
+ }
+
+ if (cueObject.direction === "horizontal") {
+ if (captionator.checkDirection(String(cueObject.text)) === "rtl") {
+ cueAlignment = {"start":"right","middle":"center","end":"left"}[cueObject.alignment];
+ } else {
+ cueAlignment = {"start":"left","middle":"center","end":"right"}[cueObject.alignment];
+ }
+ }
+
+ captionator.applyStyles(DOMNode,{
+ "position": "absolute",
+ "overflow": "hidden",
+ "width": cueWidth + "px",
+ "height": cueHeight + "px",
+ "top": cueY + "px",
+ "left": cueX + "px",
+ "padding": cuePaddingTB + "px " + cuePaddingLR + "px",
+ "textAlign": cueAlignment,
+ "backgroundColor": "rgba(" + cueBackgroundColour.join(",") + ")",
+ "direction": captionator.checkDirection(String(cueObject.text)),
+ "lineHeight": baseLineHeight + "pt",
+ "boxSizing": "border-box"
+ });
+
+ if (cueObject.direction === "vertical" || cueObject.direction === "vertical-lr") {
+ // Work out how to shrink the available render area
+ // If subtracting from the right works out to a larger area, subtract from the right.
+ // Otherwise, subtract from the left.
+ if (((cueX - videoElement._captionator_availableCueArea.left) - videoElement._captionator_availableCueArea.left) >=
+ (videoElement._captionator_availableCueArea.right - (cueX + cueWidth))) {
+
+ videoElement._captionator_availableCueArea.right = cueX;
+ } else {
+ videoElement._captionator_availableCueArea.left = cueX + cueWidth;
+ }
+
+ videoElement._captionator_availableCueArea.width =
+ videoElement._captionator_availableCueArea.right -
+ videoElement._captionator_availableCueArea.left;
+
+ } else {
+ // Now shift cue up if required to ensure it's all visible
+ if (DOMNode.scrollHeight > DOMNode.offsetHeight * 1.2) {
+ if (cueObject.snapToLines) {
+ var upwardAjustmentInLines = 0;
+ while (DOMNode.scrollHeight > DOMNode.offsetHeight * 1.2) {
+ cueHeight += pixelLineHeight;
+ DOMNode.style.height = cueHeight + "px";
+ upwardAjustmentInLines ++;
+ }
+
+ cueY = cueY - (upwardAjustmentInLines*pixelLineHeight);
+ DOMNode.style.top = cueY + "px";
+ } else {
+ // Not working by lines, so instead of shifting up, simply throw out old cueY calculation
+ // and completely recalculate its value
+ var upwardAjustment = (DOMNode.scrollHeight - cueHeight);
+ cueHeight = (DOMNode.scrollHeight + cuePaddingTB);
+ tmpHeightExclusions = videoMetrics.controlHeight + cueHeight + (cuePaddingTB*2);
+ cueY = (videoMetrics.height - tmpHeightExclusions) * (cueObject.linePosition/100);
+
+ DOMNode.style.height = cueHeight + "px";
+ DOMNode.style.top = cueY + "px";
+ }
+ }
+
+ // Work out how to shrink the available render area
+ // If subtracting from the bottom works out to a larger area, subtract from the bottom.
+ // Otherwise, subtract from the top.
+ if (((cueY - videoElement._captionator_availableCueArea.top) - videoElement._captionator_availableCueArea.top) >=
+ (videoElement._captionator_availableCueArea.bottom - (cueY + cueHeight)) &&
+ videoElement._captionator_availableCueArea.bottom > cueY) {
+
+ videoElement._captionator_availableCueArea.bottom = cueY;
+ } else {
+ if (videoElement._captionator_availableCueArea.top < cueY + cueHeight) {
+ videoElement._captionator_availableCueArea.top = cueY + cueHeight;
+ }
+ }
+
+ videoElement._captionator_availableCueArea.height =
+ videoElement._captionator_availableCueArea.bottom -
+ videoElement._captionator_availableCueArea.top;
+ }
+
+ // DEBUG FUNCTIONS
+ // This function can be used for debugging WebVTT captions. It will not be
+ // included in production versions of Captionator.
+ // -----------------------------------------------------------------------
+ if (options.debugMode) {
+ var debugCanvas, debugContext;
+ var generateDebugCanvas = function() {
+ if (!debugCanvas) {
+ if (videoElement._captionatorDebugCanvas) {
+ debugCanvas = videoElement._captionatorDebugCanvas;
+ debugContext = videoElement._captionatorDebugContext;
+ } else {
+ debugCanvas = document.createElement("canvas");
+ debugCanvas.setAttribute("width",videoMetrics.width);
+ debugCanvas.setAttribute("height",videoMetrics.height - videoMetrics.controlHeight);
+ document.body.appendChild(debugCanvas);
+ captionator.applyStyles(debugCanvas,{
+ "position": "absolute",
+ "top": videoMetrics.top + "px",
+ "left": videoMetrics.left + "px",
+ "width": videoMetrics.width + "px",
+ "height": (videoMetrics.height - videoMetrics.controlHeight) + "px",
+ "zIndex": 3000
+ });
+
+ debugContext = debugCanvas.getContext("2d");
+ videoElement._captionatorDebugCanvas = debugCanvas;
+ videoElement._captionatorDebugContext = debugContext;
+ }
+ }
+ };
+
+ var clearDebugCanvas = function() {
+ generateDebugCanvas();
+ debugCanvas.setAttribute("width",videoMetrics.width);
+ };
+
+ var drawLines = function() {
+ var lineIndex;
+
+ // Set up canvas for drawing debug information
+ generateDebugCanvas();
+
+ debugContext.strokeStyle = "rgba(255,0,0,0.5)";
+ debugContext.lineWidth = 1;
+
+ // Draw horizontal line dividers
+ debugContext.beginPath();
+ for (lineIndex = 0; lineIndex < videoHeightInLines; lineIndex ++) {
+ debugContext.moveTo(0.5,(lineIndex*pixelLineHeight)+0.5);
+ debugContext.lineTo(videoMetrics.width,(lineIndex*pixelLineHeight)+0.5);
+ }
+
+ debugContext.closePath();
+ debugContext.stroke();
+ debugContext.beginPath();
+ debugContext.strokeStyle = "rgba(0,255,0,0.5)";
+
+ // Draw vertical line dividers
+ // Right to left, vertical
+ for (lineIndex = videoWidthInLines; lineIndex >= 0; lineIndex --) {
+ debugContext.moveTo((videoMetrics.width-(lineIndex*verticalPixelLineHeight))-0.5,-0.5);
+ debugContext.lineTo((videoMetrics.width-(lineIndex*verticalPixelLineHeight))-0.5,videoMetrics.height);
+ }
+
+ debugContext.closePath();
+ debugContext.stroke();
+ debugContext.beginPath();
+ debugContext.strokeStyle = "rgba(255,255,0,0.5)";
+
+ // Draw vertical line dividers
+ // Left to right, vertical
+ for (lineIndex = 0; lineIndex <= videoWidthInLines; lineIndex ++) {
+ debugContext.moveTo((lineIndex*verticalPixelLineHeight)+0.5,-0.5);
+ debugContext.lineTo((lineIndex*verticalPixelLineHeight)+0.5,videoMetrics.height);
+ }
+
+ debugContext.stroke();
+
+ videoElement.linesDrawn = true;
+ };
+
+ var drawAvailableArea = function() {
+ generateDebugCanvas();
+
+ debugContext.fillStyle = "rgba(100,100,255,0.5)";
+
+ debugContext.fillRect(
+ videoElement._captionator_availableCueArea.left,
+ videoElement._captionator_availableCueArea.top,
+ videoElement._captionator_availableCueArea.right,
+ videoElement._captionator_availableCueArea.bottom);
+ debugContext.stroke();
+
+ };
+
+ clearDebugCanvas();
+ drawAvailableArea();
+ drawLines();
+ }
+ // END DEBUG FUNCTIONS
+ },
+ /*
+ captionator.styleCueCanvas(VideoNode)
+
+ Styles and positions a canvas (not a object - just a div) for displaying cues on a video.
+ If the HTMLVideoElement in question does not have a canvas, one is created for it.
+
+ First parameter: The HTMLVideoElement for which the cue canvas will be styled/created. This parameter is mandatory.
+
+ RETURNS:
+
+ Nothing.
+
+ */
+ "styleCueCanvas": function(videoElement) {
+ var baseFontSize, baseLineHeight;
+ var containerObject;
+ var containerID;
+ var options = videoElement._captionatorOptions instanceof Object ? videoElement._captionatorOptions : {};
+
+ if (!(videoElement instanceof HTMLVideoElement)) {
+ throw new Error("Cannot style a cue canvas for a non-video node!");
+ }
+
+ if (videoElement._containerObject) {
+ containerObject = videoElement._containerObject;
+ containerID = containerObject.id;
+ }
+
+ if (!containerObject) {
+ // visually display captions
+ containerObject = document.createElement("div");
+ containerObject.className = "captionator-cue-canvas";
+ containerID = captionator.generateID();
+ containerObject.id = containerID;
+
+ // We can choose to append the canvas to an element other than the body.
+ // If this option is specified, we no longer use the offsetTop/offsetLeft of the video
+ // to define the position, we just inherit it.
+ //
+ // options.appendCueCanvasTo can be an HTMLElement, or a DOM query.
+ // If the query fails, the canvas will be appended to the body as normal.
+ // If the query is successful, the canvas will be appended to the first matched element.
+
+ if (options.appendCueCanvasTo) {
+ var canvasParentNode = null;
+
+ if (options.appendCueCanvasTo instanceof HTMLElement) {
+ canvasParentNode = options.appendCueCanvasTo;
+ } else if (typeof(options.appendCueCanvasTo) === "string") {
+ try {
+ var canvasSearchResult = document.querySelectorAll(options.appendCueCanvasTo);
+ if (canvasSearchResult.length > 0) {
+ canvasParentNode = canvasSearchResult[0];
+ } else { throw null; /* Bounce to catch */ }
+ } catch(error) {
+ canvasParentNode = document.body;
+ options.appendCueCanvasTo = false;
+ }
+ } else {
+ canvasParentNode = document.body;
+ options.appendCueCanvasTo = false;
+ }
+
+ canvasParentNode.appendChild(containerObject);
+ } else {
+ document.body.appendChild(containerObject);
+ }
+
+ videoElement._containerObject = containerObject;
+ // TODO(silvia): we should only do aria-live on descriptions and that doesn't need visual display
+ containerObject.setAttribute("aria-live","polite");
+ containerObject.setAttribute("aria-atomic","true");
+ } else if (!containerObject.parentNode) {
+ document.body.appendChild(containerObject);
+ }
+
+ // TODO(silvia): we should not really muck with the aria-describedby attribute of the video
+ if (String(videoElement.getAttribute("aria-describedby")).indexOf(containerID) === -1) {
+ var existingValue = videoElement.hasAttribute("aria-describedby") ? videoElement.getAttribute("aria-describedby") + " " : "";
+ videoElement.setAttribute("aria-describedby",existingValue + containerID);
+ }
+
+ // Set up the cue canvas
+ var videoMetrics = captionator.getNodeMetrics(videoElement);
+
+ // Set up font metrics
+ baseFontSize = ((videoMetrics.height * (fontSizeVerticalPercentage/100))/96)*72;
+ baseFontSize = baseFontSize >= minimumFontSize ? baseFontSize : minimumFontSize;
+ baseLineHeight = Math.floor(baseFontSize * lineHeightRatio);
+ baseLineHeight = baseLineHeight > minimumLineHeight ? baseLineHeight : minimumLineHeight;
+
+ // Style node!
+ captionator.applyStyles(containerObject,{
+ "position": "absolute",
+ "overflow": "hidden",
+ "zIndex": 100,
+ "height": (videoMetrics.height - videoMetrics.controlHeight) + "px",
+ "width": videoMetrics.width + "px",
+ "top": (options.appendCueCanvasTo ? 0 : videoMetrics.top) + "px",
+ "left": (options.appendCueCanvasTo ? 0 : videoMetrics.left) + "px",
+ "color": "white",
+ "fontFamily": "Verdana, Helvetica, Arial, sans-serif",
+ "fontSize": baseFontSize + "pt",
+ "lineHeight": baseLineHeight + "pt",
+ "boxSizing": "border-box"
+ });
+
+ // Defeat a horrid Chrome 10 video bug
+ // http://stackoverflow.com/questions/5289854/chrome-10-custom-video-interface-problem/5400438#5400438
+ if (window.navigator.userAgent.toLowerCase().indexOf("chrome/10") > -1) {
+ containerObject.style.backgroundColor = "rgba(0,0,0,0.01" + Math.random().toString().replace(".","") + ")";
+ }
+ },
+ /*
+ captionator.parseCaptions(string captionData, object options)
+
+ Accepts and parses SRT caption/subtitle data. Will extend for WebVTT shortly. Perhaps non-JSON WebVTT will work already?
+ This function has been intended from the start to (hopefully) loosely parse both. I'll patch it as required.
+
+ First parameter: Entire text data (UTF-8) of the retrieved SRT/WebVTT file. This parameter is mandatory. (really - what did
+ you expect it was going to do without it!)
+
+ Second parameter: Captionator internal options object. See the documentation for allowed values.
+
+ RETURNS:
+
+ An array of TextTrackCue Objects in initial state.
+
+ */
+ "parseCaptions": function(captionData, options) {
+ // Be liberal in what you accept from others...
+ options = options instanceof Object ? options : {};
+ var fileType = "", subtitles = [];
+ var cueStyles = "";
+ var cueDefaults = [];
+
+ // Set up timestamp parsers - SRT does WebVTT timestamps as well.
+ var SUBTimestampParser = /^(\d{2})?:?(\d{2}):(\d{2})\.(\d+)\,(\d{2})?:?(\d{2}):(\d{2})\.(\d+)\s*(.*)/;
+ var SBVTimestampParser = /^(\d+)?:?(\d{2}):(\d{2})\.(\d+)\,(\d+)?:?(\d{2}):(\d{2})\.(\d+)\s*(.*)/;
+ var SRTTimestampParser = /^(\d{2})?:?(\d{2}):(\d{2})[\.\,](\d+)\s+\-\-\>\s+(\d{2})?:?(\d{2}):(\d{2})[\.\,](\d+)\s*(.*)/;
+ var SRTChunkTimestampParser = /(\d{2})?:?(\d{2}):(\d{2})[\.\,](\d+)/;
+ var GoogleTimestampParser = /^([\d\.]+)\s+\+([\d\.]+)\s*(.*)/;
+ var LRCTimestampParser = /^\[(\d{2})?:?(\d{2})\:(\d{2})\.(\d{2})\]\s*(.*?)$/i;
+ var WebVTTDEFAULTSCueParser = /^(DEFAULTS|DEFAULT)\s+\-\-\>\s+(.*)/g;
+ var WebVTTSTYLECueParser = /^(STYLE|STYLES)\s+\-\-\>\s*\n([\s\S]*)/g;
+ var WebVTTCOMMENTCueParser = /^(COMMENT|COMMENTS)\s+\-\-\>\s+(.*)/g;
+
+ if (captionData) {
+ // This function parses and validates cue HTML/VTT tokens, and converts them into something understandable to the renderer.
+ var processCaptionHTML = function processCaptionHTML(inputHTML) {
+ var cueStructure = new captionator.CaptionatorCueStructure(inputHTML,options),
+ cueSplit = [],
+ splitIndex,
+ currentToken,
+ currentContext,
+ stack = [],
+ stackIndex = 0,
+ chunkTimestamp,
+ timeData;
+
+ var hasRealTextContent = function(textInput) {
+ return !!textInput.replace(/[^a-z0-9]+/ig,"").length;
+ };
+
+ // Process out special cue spans
+ cueSplit = inputHTML
+ .split(/(<\/?[^>]+>)/ig)
+ .filter(function(cuePortionText) {
+ return !!cuePortionText.replace(/\s*/ig,"");
+ });
+
+ currentContext = cueStructure;
+ for (splitIndex in cueSplit) {
+ if (cueSplit.hasOwnProperty(splitIndex)) {
+ currentToken = cueSplit[splitIndex];
+
+ if (currentToken.substr(0,1) === "<") {
+ if (currentToken.substr(1,1) === "/") {
+ // Closing tag
+ var TagName = currentToken.substr(2).split(/[\s>]+/g)[0];
+ if (stack.length > 0) {
+ // Scan backwards through the stack to determine whether we've got an open tag somewhere to close.
+ var stackScanDepth = 0;
+ for (stackIndex = stack.length-1; stackIndex >= 0; stackIndex --) {
+ var parentContext = stack[stackIndex][stack[stackIndex].length-1];
+ stackScanDepth = stackIndex;
+ if (parentContext.token === TagName) { break; }
+ }
+
+ currentContext = stack[stackScanDepth];
+ stack = stack.slice(0,stackScanDepth);
+ } else {
+ // Tag mismatch!
+ }
} else {
- return true;
- }
- })
- .map(function(subtitleElement) {
- var subtitleParts = subtitleElement.split(/\n/g);
- var timeIn, timeOut, html, timeData, subtitlePartIndex, cueSettings, id;
+ // Opening Tag
+ // Check whether the tag is valid according to the WebVTT specification
+ // If not, don't allow it (unless the sanitiseCueHTML option is explicitly set to false)
- if (subtitleParts[0].match(/^\s*\d+\s*$/ig)) {
- // The identifier becomes the cue ID (when *we* load the cues from file. Programatically created cues can have an ID of whatever.)
- id = String(subtitleParts.shift(0).split(/\s+/).join(""));
- }
-
- for (subtitlePartIndex = 0; subtitlePartIndex < subtitleParts.length; subtitlePartIndex ++) {
- if (subtitleParts[subtitlePartIndex].match(/^\d{2}:\d{2}:\d{2}[\.\,]\d+/)) {
- timeData = subtitleParts[subtitlePartIndex].split(/\s+/ig);
- timeIn = parseFloat(((timeData[0].split(/[:\,\.]/ig)[0] * 60 * 60) +
- (timeData[0].split(/[:\,\.]/ig)[1] * 60) +
- parseInt(timeData[0].split(/[:\,\.]/ig)[2],10)) + "." +
- parseInt(timeData[0].split(/[:\,\.]/ig)[3],10));
+ if (( currentToken.substr(1).match(SRTChunkTimestampParser) ||
+ currentToken.match(/^]+>/i) ||
+ currentToken.match(/^/) ||
+ currentToken.match(/^<(b|i|u|ruby|rt)>/)) ||
+ options.sanitiseCueHTML !== false) {
- timeOut = parseFloat(((timeData[2].split(/[:\,\.]/ig)[0] * 60 * 60) +
- (timeData[2].split(/[:\,\.]/ig)[1] * 60) +
- parseInt(timeData[2].split(/[:\,\.]/ig)[2],10)) + "." +
- parseInt(timeData[2].split(/[:\,\.]/ig)[3],10));
+ var tmpObject = {
+ "token": currentToken.replace(/[<\/>]+/ig,"").split(/[\s\.]+/)[0],
+ "rawToken": currentToken,
+ "children": []
+ };
- if (timeData.length >= 4) {
- cueSettings = timeData.splice(3).join(" ");
+ if (tmpObject.token === "v") {
+ tmpObject.voice = currentToken.match(/^]+)>/i)[1];
+ } else if (tmpObject.token === "c") {
+ tmpObject.classes = currentToken
+ .replace(/[<\/>\s]+/ig,"")
+ .split(/[\.]+/ig)
+ .slice(1)
+ .filter(hasRealTextContent);
+ } else if (!!(chunkTimestamp = tmpObject.rawToken.match(SRTChunkTimestampParser))) {
+ cueStructure.isTimeDependent = true;
+ timeData = chunkTimestamp.slice(1);
+ tmpObject.timeIn = parseInt((timeData[0]||0) * 60 * 60,10) + // Hours
+ parseInt((timeData[1]||0) * 60,10) + // Minutes
+ parseInt((timeData[2]||0),10) + // Seconds
+ parseFloat("0." + (timeData[3]||0)); // MS
}
-
- subtitleParts = subtitleParts.slice(0,subtitlePartIndex).concat(subtitleParts.slice(subtitlePartIndex+1));
- break;
+
+ currentContext.push(tmpObject);
+ stack.push(currentContext);
+ currentContext = tmpObject.children;
}
}
-
- // The remaining lines are the subtitle payload itself (after removing an ID if present, and the time);
- html = subtitleParts.join("\n");
- return new captionator.TextTrackCue(id, timeIn, timeOut, html, cueSettings, false, null);
- });
+ } else {
+ // Text string
+ if (options.sanitiseCueHTML !== false) {
+ currentToken = currentToken
+ .replace(//g,">")
+ .replace(/\&/g,"&");
+
+ if (!options.ignoreWhitespace) {
+ currentToken = currentToken.replace(/\n+/g," ");
+ }
+ }
+
+ currentContext.push(currentToken);
+ }
+ }
+ }
+
+ return cueStructure;
+ };
+
+ // This function takes chunks of text representing cues, and converts them into cue objects.
+ var parseCaptionChunk = function parseCaptionChunk(subtitleElement,objectCount) {
+ var subtitleParts, timeIn, timeOut, html, timeData, subtitlePartIndex, cueSettings = "", id, specialCueData;
+ var timestampMatch, tmpCue;
+
+ // WebVTT Special Cue Logic
+ if ((specialCueData = WebVTTDEFAULTSCueParser.exec(subtitleElement))) {
+ cueDefaults = specialCueData.slice(2).join("");
+ cueDefaults = cueDefaults.split(/\s+/g).filter(function(def) { return def && !!def.length; });
+ return null;
+ } else if ((specialCueData = WebVTTSTYLECueParser.exec(subtitleElement))) {
+ cueStyles += specialCueData[specialCueData.length-1];
+ return null;
+ } else if ((specialCueData = WebVTTCOMMENTCueParser.exec(subtitleElement))) {
+ return null; // At this stage, we don't want to do anything with these.
+ }
+
+ if (fileType === "LRC") {
+ subtitleParts = [
+ subtitleElement.substr(0,subtitleElement.indexOf("]")),
+ subtitleElement.substr(subtitleElement.indexOf("]")+1)
+ ];
+ } else {
+ subtitleParts = subtitleElement.split(/\n/g);
+ }
+
+ // Trim off any blank lines (logically, should only be max. one, but loop to be sure)
+ while (!subtitleParts[0].replace(/\s+/ig,"").length && subtitleParts.length > 0) {
+ subtitleParts.shift();
+ }
+
+ if (subtitleParts[0].match(/^\s*[a-z0-9]+\s*$/ig)) {
+ // The identifier becomes the cue ID (when *we* load the cues from file. Programatically created cues can have an ID of whatever.)
+ id = String(subtitleParts.shift().replace(/\s*/ig,""));
+ } else {
+ // We're not parsing a format with an ID prior to each caption like SRT or WebVTT
+ id = objectCount;
+ }
+
+ for (subtitlePartIndex = 0; subtitlePartIndex < subtitleParts.length; subtitlePartIndex ++) {
+ var timestamp = subtitleParts[subtitlePartIndex];
+
+ if ((timestampMatch = SRTTimestampParser.exec(timestamp)) ||
+ (timestampMatch = SUBTimestampParser.exec(timestamp)) ||
+ (timestampMatch = SBVTimestampParser.exec(timestamp))) {
+
+ // WebVTT / SRT / SUB (VOBSub) / YouTube SBV style timestamp
+
+ timeData = timestampMatch.slice(1);
+
+ timeIn = parseInt((timeData[0]||0) * 60 * 60,10) + // Hours
+ parseInt((timeData[1]||0) * 60,10) + // Minutes
+ parseInt((timeData[2]||0),10) + // Seconds
+ parseFloat("0." + (timeData[3]||0)); // MS
+
+ timeOut = parseInt((timeData[4]||0) * 60 * 60,10) + // Hours
+ parseInt((timeData[5]||0) * 60,10) + // Minutes
+ parseInt((timeData[6]||0),10) + // Seconds
+ parseFloat("0." + (timeData[7]||0)); // MS
+
+ if (timeData[8]) {
+ cueSettings = timeData[8];
+ }
+
+ } else if (!!(timestampMatch = GoogleTimestampParser.exec(timestamp))) {
+
+ // Google's proposed WebVTT timestamp style
+ timeData = timestampMatch.slice(1);
+
+ timeIn = parseFloat(timeData[0]);
+ timeOut = timeIn + parseFloat(timeData[1]);
+
+ if (timeData[2]) {
+ cueSettings = timeData[2];
+ }
+ }
+
+ // We've got the timestamp - return all the other unmatched lines as the raw subtitle data
+ subtitleParts = subtitleParts.slice(0,subtitlePartIndex).concat(subtitleParts.slice(subtitlePartIndex+1));
+ break;
+ }
+
+ if (!timeIn && !timeOut) {
+ // We didn't extract any time information. Assume the cue is invalid!
+ return null;
+ }
+
+ // Consolidate cue settings, convert defaults to object
+ var compositeCueSettings =
+ cueDefaults
+ .reduce(function(previous,current,index,array){
+ previous[current.split(":")[0]] = current.split(":")[1];
+ return previous;
+ },{});
+
+ // Loop through cue settings, replace defaults with cue specific settings if they exist
+ compositeCueSettings =
+ cueSettings
+ .split(/\s+/g)
+ .filter(function(set) { return set && !!set.length; })
+ // Convert array to a key/val object
+ .reduce(function(previous,current,index,array){
+ previous[current.split(":")[0]] = current.split(":")[1];
+ return previous;
+ },compositeCueSettings);
+
+ // Turn back into string like the TextTrackCue constructor expects
+ cueSettings = "";
+ for (var key in compositeCueSettings) {
+ if (compositeCueSettings.hasOwnProperty(key)) {
+ cueSettings += !!cueSettings.length ? " " : "";
+ cueSettings += key + ":" + compositeCueSettings[key];
+ }
+ }
+
+ // The remaining lines are the subtitle payload itself (after removing an ID if present, and the time);
+ html = options.processCueHTML === false ? subtitleParts.join("\n") : processCaptionHTML(subtitleParts.join("\n"));
+ tmpCue = new captionator.TextTrackCue(id, timeIn, timeOut, html, cueSettings, false, null);
+ tmpCue.styleData = cueStyles;
+ return tmpCue;
+ };
+
+ // Begin parsing --------------------
+ subtitles = captionData
+ .replace(/\r\n/g,"\n")
+ .replace(/\r/g,"\n");
+
+ if (LRCTimestampParser.exec(captionData)) {
+ // LRC file... split by single line
+ subtitles = subtitles.split(/\n+/g);
+ fileType = "LRC";
+ } else {
+ subtitles = subtitles.split(/\n\n+/g);
+ }
- return subtitles;
- } else {
- throw new Error("Required parameter captionData not supplied.");
+ subtitles = subtitles.filter(function(lineGroup) {
+ if (lineGroup.match(/^WEBVTT(\s*FILE)?/ig)) {
+ fileType = "WebVTT";
+ return false;
+ } else {
+ if (lineGroup.replace(/\s*/ig,"").length) {
+ return true;
+ }
+ return false;
+ }
+ })
+ .map(parseCaptionChunk)
+ .filter(function(cue) {
+ // In the parseCaptionChunk function, we return null for special and malformed cues,
+ // and cues we want to ignore, rather than expose to JS. Filter these out now.
+ if (cue !== null) {
+ return true;
+ }
+
+ return false;
+ });
+
+ return subtitles;
+ } else {
+ throw new Error("Required parameter captionData not supplied.");
+ }
}
- }
-};
\ No newline at end of file
+ };
+
+ window.captionator = captionator;
+})();
diff --git a/js/node-createaudiofromsrt.js b/js/node-createaudiofromsrt.js
new file mode 100644
index 0000000..2c3171d
--- /dev/null
+++ b/js/node-createaudiofromsrt.js
@@ -0,0 +1,200 @@
+// Run this file with node.js. (You'll need Mac OS X for speech synthesis, and sox for audio processing.)
+// It is designed to generate a synchronised text track base on your subtitles / text cues in SRT format.
+// I'm committing this to the repository for completeness. Be warned though, it's barely tested and pretty hacky.
+// You could probably switch `say` out for `espeech` on linux.
+
+// Get sox with homebrew.
+// Do: brew install sox
+
+// usage: node node-createaudiofromsrt.js yoursrtfile.srt
+// It'll put files in the current working directory (sorry!)
+
+var sys = require('sys'),
+ exec = require('child_process').exec,
+ exit = process.exit,
+ fs = require("fs");
+
+
+// Load captions
+var fileToLoad = process.argv[2];
+var srtData, captions;
+var fileName = __filename.split(/[\/\\]/g).pop();
+var totalCues = 0;
+var cuesSaid = 0;
+var outFile = "";
+
+if (!fileToLoad) {
+ console.error("You must provide an SRT file to load.");
+ console.log("Usage: $>node " + fileName + " mysubs.srt");
+ exit(1);
+} else {
+ outFile = fileToLoad.split(".srt").shift().split(/[\/\\]/g).pop() + ".wav";
+}
+
+try {
+ console.log("Outputting spoken text to ",outFile + ".");
+ console.log("Speaking cues...");
+ if (srtData = fs.readFileSync(fileToLoad)) {
+ captions = parseCaptions(srtData);
+ totalCues = captions.length;
+ captions.forEach(function(caption,index) {
+ var safeText = caption.text.replace(/'/ig,"").replace(/\!/ig,".");
+ var sayCommand = "say -o 'audio-cue-" + caption.id + " " + caption.timeIn + "-" + caption.timeOut + ".aiff' ' " + safeText + "'";
+ //console.log(sayCommand);
+ exec(sayCommand,function(error, stdout, stderr) {
+ if (error) {
+ console.error("Failed to say cue! (The ID was: " + caption.id + ")");
+ cuesSaid ++;
+ } else {
+ console.log("Said cue successfully. " + Math.floor((cuesSaid / totalCues)*100) + "%");
+ captions[index].audioFile = "audio-cue-" + caption.id + " " + caption.timeIn + "-" + caption.timeOut + ".aiff";
+
+ // We've said the cue - find the length
+ var cueReadingLength = 0;
+ exec("soxi -D '" + captions[index].audioFile + "'",function(error, stdout, stderr) {
+ if (!error) {
+ cueReadingLength = parseFloat(stdout);
+ }
+
+ captions[index].cueReadingLength = cueReadingLength;
+
+ cuesSaid ++;
+ if (cuesSaid == totalCues) {
+ console.log("Finished. Stitching file...");
+ cuesSaid ++;
+ stitchAudio(captions);
+ }
+ });
+ }
+
+ if (cuesSaid == totalCues) {
+ console.log("Finished. Stitching file...");
+ cuesSaid ++;
+ stitchAudio(captions);
+ }
+ });
+ })
+ }
+} catch (Error) {
+ console.error("The file you provided could not be located, or failed to load.");
+ exit(2);
+}
+
+function stitchAudio(captions) {
+ var soxCommand = "sox -m ";
+ var captionIndex, cueDuration;
+
+ for (captionIndex = 0; captionIndex < captions.length; captionIndex ++) {
+ if (captions[captionIndex]) {
+ cueDuration = (captions[captionIndex].timeOut - captions[captionIndex].timeIn) - 0.25; // Allow quarter of a second's breathing room
+
+ // Defaults
+ var speedAjustment = 1;
+ var pitchAjustment = 0;
+ var cueReadingLength = captions[captionIndex].cueReadingLength || 0;
+
+ if (cueReadingLength > cueDuration) {
+ speedAjustment = Math.floor((cueReadingLength / cueDuration)*100)/100;
+ pitchAjustment = Math.round(Math.log(1000/(1000*speedAjustment))/Math.log(Math.pow(2,1/1200)));
+ }
+
+ if (pitchAjustment < 0) {
+ pitchAjustment = " pitch " + pitchAjustment;
+ } else {
+ pitchAjustment = "";
+ }
+
+ if (speedAjustment > 1) {
+ speedAjustment = " speed " + speedAjustment;
+ } else {
+ speedAjustment = "";
+ }
+
+ soxCommand += '"|sox \'' + captions[captionIndex].audioFile +
+ '\' -p ' + speedAjustment + pitchAjustment + ' pad ' + captions[captionIndex].timeIn + '" ';
+ }
+ }
+
+ soxCommand += " " + outFile + " gain 30";
+ // console.log(soxCommand);
+ exec(soxCommand,function(error, stdout, stderr) {
+ if (error) {
+ console.error("Whoops - an error occurred: ",stderr);
+ } else {
+ console.log("Cleaning up...");
+ var cuesCleaned = 0;
+ captions.forEach(function(caption,index) {
+ fs.unlink(captions[index].audioFile,function(error) {
+ cuesCleaned ++; // Just ignore failures for now
+
+ if (cuesCleaned == captions.length) {
+ console.log("Done! Enjoy the reading.");
+ exit(0);
+ }
+ });
+ });
+ }
+ });
+}
+
+
+function parseCaptions(captionData) {
+ "use strict";
+ var text;
+ var subtitles = captionData.toString()
+ .replace(/\r\n/g,"\n")
+ .replace(/\r/g,"\n")
+ .split(/\n\n/g)
+ .filter(function(lineGroup) {
+ if (lineGroup.match(/WEBVTT FILE/ig)) {
+ // This is useless - we just don't care as we'll be treating SRT and WebVTT the same anyway.
+ return false;
+ } else {
+ return true;
+ }
+ })
+ .map(function(subtitleElement) {
+ var subtitleParts = subtitleElement.split(/\n/g);
+ var timeIn, timeOut, html, timeData, subtitlePartIndex, cueSettings, id;
+
+ if (subtitleParts[0].match(/^\s*\d+\s*$/ig)) {
+ // The identifier becomes the cue ID (when *we* load the cues from file. Programatically created cues can have an ID of whatever.)
+ id = String(subtitleParts.shift(0).split(/\s+/).join(""));
+ }
+
+ for (subtitlePartIndex = 0; subtitlePartIndex < subtitleParts.length; subtitlePartIndex ++) {
+ if (subtitleParts[subtitlePartIndex].match(/^\d{2}:\d{2}:\d{2}[\.\,]\d+/)) {
+ timeData = subtitleParts[subtitlePartIndex].split(/\s+/ig);
+ timeIn = parseFloat(((timeData[0].split(/[:\,\.]/ig)[0] * 60 * 60) +
+ (timeData[0].split(/[:\,\.]/ig)[1] * 60) +
+ parseInt(timeData[0].split(/[:\,\.]/ig)[2],10)) + "." +
+ parseInt(timeData[0].split(/[:\,\.]/ig)[3],10));
+
+ timeOut = parseFloat(((timeData[2].split(/[:\,\.]/ig)[0] * 60 * 60) +
+ (timeData[2].split(/[:\,\.]/ig)[1] * 60) +
+ parseInt(timeData[2].split(/[:\,\.]/ig)[2],10)) + "." +
+ parseInt(timeData[2].split(/[:\,\.]/ig)[3],10));
+
+ if (timeData.length >= 4) {
+ cueSettings = timeData.splice(3).join(" ");
+ }
+
+ subtitleParts = subtitleParts.slice(0,subtitlePartIndex).concat(subtitleParts.slice(subtitlePartIndex+1));
+ break;
+ }
+ }
+
+ // The remaining lines are the subtitle payload itself (after removing an ID if present, and the time);
+ html = subtitleParts.join("\n");
+ text = html.replace(/<[^>]+>/ig,"");
+ return {
+ "id": id,
+ "timeIn": timeIn,
+ "timeOut": timeOut,
+ "text": text,
+ "settings": cueSettings
+ };
+ });
+
+ return subtitles;
+}
\ No newline at end of file
diff --git a/video/acid.vtt b/video/acid.vtt
new file mode 100644
index 0000000..1163338
--- /dev/null
+++ b/video/acid.vtt
@@ -0,0 +1,5390 @@
+WEBVTT
+
+1
+00:00:00.000 --> 00:00:10.000
+This WebVTT acid test will verify rendering and parsing compatibility with the WebVTT standard.
+
+2
+00:00:10.000 --> 00:00:15.000
+Test 1: Unicode Support
+
+3
+00:00:15.000 --> 00:00:20.000
+ÀÁÂÃĀĂȦÄẢÅǍȀȂĄẠḀẦẤẪẨẰẮẴẲǠǞǺẬẶÆǼǢ
+
+4
+00:00:20.000 --> 00:00:25.000
+East Asian Characters: だらり帯の結び方
+
+5
+00:00:25.000 --> 00:00:35.000
+Test 2: WebVTT Timestamp Support
+
+6
+00:00:35.000 --> 00:00:45.000
+<00:00:35.000>This <00:00:36.000>text <00:00:37.000>should <00:00:38.000>appear <00:00:39.000>word <00:00:40.000>by <00:00:41.000>word
+<00:00:42.000>with <00:00:43.000>second <00:00:44.000>intervals.
+
+7
+00:00:45.000 --> 00:00:50.000
+<00:00:45.000>This <00:00:45.100>text <00:00:45.200>should <00:00:45.300>appear <00:00:45.400>with <00:00:45.500>tenth <00:00:45.600>of <00:00:45.700>a <00:00:45.800>second <00:00:45.900>intervals.
+
+8
+00:00:50.000 --> 00:00:55.000
+<00:00:50.000>This <00:00:50.010>text <00:00:50.020>should <00:00:50.030>appear <00:00:50.040>with <00:00:50.050>100th <00:00:50.060>of <00:00:50.070>a <00:00:50.080>second <00:00:50.090>intervals.
+
+9
+00:00:55.000 --> 00:01:00.000
+<00:00:55.000>This <00:00:55.001>text <00:00:55.002>should <00:00:55.003>appear <00:00:55.004>with <00:00:55.005>1000th <00:00:55.006>of <00:00:55.007>a <00:00:55.008>second <00:00:55.009>intervals.
+
+10
+00:01:00.000 --> 00:01:10.000
+Test 3: WebVTT Formatting spans
+
+11
+00:01:10.000 --> 00:01:15.000
+This text should be bold
+This text should be italic
+This text should be underlined
+
+12
+00:01:15.000 --> 00:01:25.000
+Test 4: WebVTT Ruby spans
+
+13
+00:01:25.000 --> 00:01:30.000
+私わたし たち
+
+14
+00:01:30.000 --> 00:01:40.000
+Test 4: Ignoring malformed cues
+
+MALFORMEDCUE -->
+This text is from a malformed cue. It should not be processed.
+
+# 15
+# 00:01:30.000 --> 00:01:40.000
+# This text is from a malformed cue. It should not be processed.
+
+15
+00:01:40.000 --> 00:01:50.000
+Test 5: Cue alignment
+
+16
+00:01:50.000 --> 00:01:55.000 A:start
+This cue should be aligned to the start position.
+
+17
+00:01:50.000 --> 00:01:55.000 A:end
+This cue should be aligned to the end position.
+
+18
+00:01:50.000 --> 00:01:55.000 A:middle
+This cue should be aligned to the middle position.
+
+19
+00:01:55.000 --> 00:02:05.000
+Test 5.5: Cue alignment with BIDI/RTL text
+
+20
+00:02:05.000 --> 00:02:10.000 A:start
+بداية النص محاذاة
+(start)
+
+21
+00:02:05.000 --> 00:02:10.000 A:end
+نهاية النص محاذاة
+(end)
+
+22
+00:02:05.000 --> 00:02:10.000 A:middle
+محاذاة النص الأوسط
+(middle)
+
+23
+00:02:10.000 --> 00:02:20.000
+Test 6: Line position (percentage)
+
+24
+00:02:20.000 --> 00:02:20.250 L:1% A:start
+Line position: 1%
+
+25
+00:02:20.250 --> 00:02:20.50 L:2% A:start
+Line position: 2%
+
+26
+00:02:20.50 --> 00:02:20.750 L:3% A:start
+Line position: 3%
+
+27
+00:02:20.750 --> 00:02:21.000 L:4% A:start
+Line position: 4%
+
+28
+00:02:21.000 --> 00:02:21.250 L:5% A:start
+Line position: 5%
+
+29
+00:02:21.250 --> 00:02:21.50 L:6% A:start
+Line position: 6%
+
+30
+00:02:21.50 --> 00:02:21.750 L:7% A:start
+Line position: 7%
+
+31
+00:02:21.750 --> 00:02:22.000 L:8% A:start
+Line position: 8%
+
+32
+00:02:22.000 --> 00:02:22.250 L:9% A:start
+Line position: 9%
+
+33
+00:02:22.250 --> 00:02:22.50 L:10% A:start
+Line position: 10%
+
+34
+00:02:22.50 --> 00:02:22.750 L:11% A:start
+Line position: 11%
+
+35
+00:02:22.750 --> 00:02:23.000 L:12% A:start
+Line position: 12%
+
+36
+00:02:23.000 --> 00:02:23.250 L:13% A:start
+Line position: 13%
+
+37
+00:02:23.250 --> 00:02:23.50 L:14% A:start
+Line position: 14%
+
+38
+00:02:23.50 --> 00:02:23.750 L:15% A:start
+Line position: 15%
+
+39
+00:02:23.750 --> 00:02:24.000 L:16% A:start
+Line position: 16%
+
+40
+00:02:24.000 --> 00:02:24.250 L:17% A:start
+Line position: 17%
+
+41
+00:02:24.250 --> 00:02:24.50 L:18% A:start
+Line position: 18%
+
+42
+00:02:24.50 --> 00:02:24.750 L:19% A:start
+Line position: 19%
+
+43
+00:02:24.750 --> 00:02:25.000 L:20% A:start
+Line position: 20%
+
+44
+00:02:25.000 --> 00:02:25.250 L:21% A:start
+Line position: 21%
+
+45
+00:02:25.250 --> 00:02:25.50 L:22% A:start
+Line position: 22%
+
+46
+00:02:25.50 --> 00:02:25.750 L:23% A:start
+Line position: 23%
+
+47
+00:02:25.750 --> 00:02:26.000 L:24% A:start
+Line position: 24%
+
+48
+00:02:26.000 --> 00:02:26.250 L:25% A:start
+Line position: 25%
+
+49
+00:02:26.250 --> 00:02:26.50 L:26% A:start
+Line position: 26%
+
+50
+00:02:26.50 --> 00:02:26.750 L:27% A:start
+Line position: 27%
+
+51
+00:02:26.750 --> 00:02:27.000 L:28% A:start
+Line position: 28%
+
+52
+00:02:27.000 --> 00:02:27.250 L:29% A:start
+Line position: 29%
+
+53
+00:02:27.250 --> 00:02:27.50 L:30% A:start
+Line position: 30%
+
+54
+00:02:27.50 --> 00:02:27.750 L:31% A:start
+Line position: 31%
+
+55
+00:02:27.750 --> 00:02:28.000 L:32% A:start
+Line position: 32%
+
+56
+00:02:28.000 --> 00:02:28.250 L:33% A:start
+Line position: 33%
+
+57
+00:02:28.250 --> 00:02:28.50 L:34% A:start
+Line position: 34%
+
+58
+00:02:28.50 --> 00:02:28.750 L:35% A:start
+Line position: 35%
+
+59
+00:02:28.750 --> 00:02:29.000 L:36% A:start
+Line position: 36%
+
+60
+00:02:29.000 --> 00:02:29.250 L:37% A:start
+Line position: 37%
+
+61
+00:02:29.250 --> 00:02:29.50 L:38% A:start
+Line position: 38%
+
+62
+00:02:29.50 --> 00:02:29.750 L:39% A:start
+Line position: 39%
+
+63
+00:02:29.750 --> 00:02:30.000 L:40% A:start
+Line position: 40%
+
+64
+00:02:30.000 --> 00:02:30.250 L:41% A:start
+Line position: 41%
+
+65
+00:02:30.250 --> 00:02:30.50 L:42% A:start
+Line position: 42%
+
+66
+00:02:30.50 --> 00:02:30.750 L:43% A:start
+Line position: 43%
+
+67
+00:02:30.750 --> 00:02:31.000 L:44% A:start
+Line position: 44%
+
+68
+00:02:31.000 --> 00:02:31.250 L:45% A:start
+Line position: 45%
+
+69
+00:02:31.250 --> 00:02:31.50 L:46% A:start
+Line position: 46%
+
+70
+00:02:31.50 --> 00:02:31.750 L:47% A:start
+Line position: 47%
+
+71
+00:02:31.750 --> 00:02:32.000 L:48% A:start
+Line position: 48%
+
+72
+00:02:32.000 --> 00:02:32.250 L:49% A:start
+Line position: 49%
+
+73
+00:02:32.250 --> 00:02:32.50 L:50% A:start
+Line position: 50%
+
+74
+00:02:32.50 --> 00:02:32.750 L:51% A:start
+Line position: 51%
+
+75
+00:02:32.750 --> 00:02:33.000 L:52% A:start
+Line position: 52%
+
+76
+00:02:33.000 --> 00:02:33.250 L:53% A:start
+Line position: 53%
+
+77
+00:02:33.250 --> 00:02:33.50 L:54% A:start
+Line position: 54%
+
+78
+00:02:33.50 --> 00:02:33.750 L:55% A:start
+Line position: 55%
+
+79
+00:02:33.750 --> 00:02:34.000 L:56% A:start
+Line position: 56%
+
+80
+00:02:34.000 --> 00:02:34.250 L:57% A:start
+Line position: 57%
+
+81
+00:02:34.250 --> 00:02:34.50 L:58% A:start
+Line position: 58%
+
+82
+00:02:34.50 --> 00:02:34.750 L:59% A:start
+Line position: 59%
+
+83
+00:02:34.750 --> 00:02:35.000 L:60% A:start
+Line position: 60%
+
+84
+00:02:35.000 --> 00:02:35.250 L:61% A:start
+Line position: 61%
+
+85
+00:02:35.250 --> 00:02:35.50 L:62% A:start
+Line position: 62%
+
+86
+00:02:35.50 --> 00:02:35.750 L:63% A:start
+Line position: 63%
+
+87
+00:02:35.750 --> 00:02:36.000 L:64% A:start
+Line position: 64%
+
+88
+00:02:36.000 --> 00:02:36.250 L:65% A:start
+Line position: 65%
+
+89
+00:02:36.250 --> 00:02:36.50 L:66% A:start
+Line position: 66%
+
+90
+00:02:36.50 --> 00:02:36.750 L:67% A:start
+Line position: 67%
+
+91
+00:02:36.750 --> 00:02:37.000 L:68% A:start
+Line position: 68%
+
+92
+00:02:37.000 --> 00:02:37.250 L:69% A:start
+Line position: 69%
+
+93
+00:02:37.250 --> 00:02:37.50 L:70% A:start
+Line position: 70%
+
+94
+00:02:37.50 --> 00:02:37.750 L:71% A:start
+Line position: 71%
+
+95
+00:02:37.750 --> 00:02:38.000 L:72% A:start
+Line position: 72%
+
+96
+00:02:38.000 --> 00:02:38.250 L:73% A:start
+Line position: 73%
+
+97
+00:02:38.250 --> 00:02:38.50 L:74% A:start
+Line position: 74%
+
+98
+00:02:38.50 --> 00:02:38.750 L:75% A:start
+Line position: 75%
+
+99
+00:02:38.750 --> 00:02:39.000 L:76% A:start
+Line position: 76%
+
+100
+00:02:39.000 --> 00:02:39.250 L:77% A:start
+Line position: 77%
+
+101
+00:02:39.250 --> 00:02:39.50 L:78% A:start
+Line position: 78%
+
+102
+00:02:39.50 --> 00:02:39.750 L:79% A:start
+Line position: 79%
+
+103
+00:02:39.750 --> 00:02:40.000 L:80% A:start
+Line position: 80%
+
+104
+00:02:40.000 --> 00:02:40.250 L:81% A:start
+Line position: 81%
+
+105
+00:02:40.250 --> 00:02:40.50 L:82% A:start
+Line position: 82%
+
+106
+00:02:40.50 --> 00:02:40.750 L:83% A:start
+Line position: 83%
+
+107
+00:02:40.750 --> 00:02:41.000 L:84% A:start
+Line position: 84%
+
+108
+00:02:41.000 --> 00:02:41.250 L:85% A:start
+Line position: 85%
+
+109
+00:02:41.250 --> 00:02:41.50 L:86% A:start
+Line position: 86%
+
+110
+00:02:41.50 --> 00:02:41.750 L:87% A:start
+Line position: 87%
+
+111
+00:02:41.750 --> 00:02:42.000 L:88% A:start
+Line position: 88%
+
+112
+00:02:42.000 --> 00:02:42.250 L:89% A:start
+Line position: 89%
+
+113
+00:02:42.250 --> 00:02:42.50 L:90% A:start
+Line position: 90%
+
+114
+00:02:42.50 --> 00:02:42.750 L:91% A:start
+Line position: 91%
+
+115
+00:02:42.750 --> 00:02:43.000 L:92% A:start
+Line position: 92%
+
+116
+00:02:43.000 --> 00:02:43.250 L:93% A:start
+Line position: 93%
+
+117
+00:02:43.250 --> 00:02:43.50 L:94% A:start
+Line position: 94%
+
+118
+00:02:43.50 --> 00:02:43.750 L:95% A:start
+Line position: 95%
+
+119
+00:02:43.750 --> 00:02:44.000 L:96% A:start
+Line position: 96%
+
+120
+00:02:44.000 --> 00:02:44.250 L:97% A:start
+Line position: 97%
+
+121
+00:02:44.250 --> 00:02:44.50 L:98% A:start
+Line position: 98%
+
+122
+00:02:44.50 --> 00:02:44.750 L:99% A:start
+Line position: 99%
+
+123
+00:02:44.750 --> 00:02:45.000 L:100% A:start
+Line position: 100%
+
+124
+00:02:45.000 --> 00:02:55.000
+Test 7: Cue size (percentage)
+
+125
+00:02:55.000 --> 00:02:55.100 S:1% A:middle
+Size: 1%
+
+126
+00:02:55.100 --> 00:02:55.200 S:2% A:middle
+Size: 2%
+
+127
+00:02:55.200 --> 00:02:55.300 S:3% A:middle
+Size: 3%
+
+128
+00:02:55.300 --> 00:02:55.400 S:4% A:middle
+Size: 4%
+
+129
+00:02:55.400 --> 00:02:55.500 S:5% A:middle
+Size: 5%
+
+130
+00:02:55.500 --> 00:02:55.600 S:6% A:middle
+Size: 6%
+
+131
+00:02:55.600 --> 00:02:55.700 S:7% A:middle
+Size: 7%
+
+132
+00:02:55.700 --> 00:02:55.800 S:8% A:middle
+Size: 8%
+
+133
+00:02:55.800 --> 00:02:55.900 S:9% A:middle
+Size: 9%
+
+134
+00:02:55.900 --> 00:02:56.000 S:10% A:middle
+Size: 10%
+
+135
+00:02:56.000 --> 00:02:56.100 S:11% A:middle
+Size: 11%
+
+136
+00:02:56.100 --> 00:02:56.200 S:12% A:middle
+Size: 12%
+
+137
+00:02:56.200 --> 00:02:56.300 S:13% A:middle
+Size: 13%
+
+138
+00:02:56.300 --> 00:02:56.400 S:14% A:middle
+Size: 14%
+
+139
+00:02:56.400 --> 00:02:56.500 S:15% A:middle
+Size: 15%
+
+140
+00:02:56.500 --> 00:02:56.600 S:16% A:middle
+Size: 16%
+
+141
+00:02:56.600 --> 00:02:56.700 S:17% A:middle
+Size: 17%
+
+142
+00:02:56.700 --> 00:02:56.800 S:18% A:middle
+Size: 18%
+
+143
+00:02:56.800 --> 00:02:56.900 S:19% A:middle
+Size: 19%
+
+144
+00:02:56.900 --> 00:02:57.000 S:20% A:middle
+Size: 20%
+
+145
+00:02:57.000 --> 00:02:57.100 S:21% A:middle
+Size: 21%
+
+146
+00:02:57.100 --> 00:02:57.200 S:22% A:middle
+Size: 22%
+
+147
+00:02:57.200 --> 00:02:57.300 S:23% A:middle
+Size: 23%
+
+148
+00:02:57.300 --> 00:02:57.400 S:24% A:middle
+Size: 24%
+
+149
+00:02:57.400 --> 00:02:57.500 S:25% A:middle
+Size: 25%
+
+150
+00:02:57.500 --> 00:02:57.600 S:26% A:middle
+Size: 26%
+
+151
+00:02:57.600 --> 00:02:57.700 S:27% A:middle
+Size: 27%
+
+152
+00:02:57.700 --> 00:02:57.800 S:28% A:middle
+Size: 28%
+
+153
+00:02:57.800 --> 00:02:57.900 S:29% A:middle
+Size: 29%
+
+154
+00:02:57.900 --> 00:02:58.000 S:30% A:middle
+Size: 30%
+
+155
+00:02:58.000 --> 00:02:58.100 S:31% A:middle
+Size: 31%
+
+156
+00:02:58.100 --> 00:02:58.200 S:32% A:middle
+Size: 32%
+
+157
+00:02:58.200 --> 00:02:58.300 S:33% A:middle
+Size: 33%
+
+158
+00:02:58.300 --> 00:02:58.400 S:34% A:middle
+Size: 34%
+
+159
+00:02:58.400 --> 00:02:58.500 S:35% A:middle
+Size: 35%
+
+160
+00:02:58.500 --> 00:02:58.600 S:36% A:middle
+Size: 36%
+
+161
+00:02:58.600 --> 00:02:58.700 S:37% A:middle
+Size: 37%
+
+162
+00:02:58.700 --> 00:02:58.800 S:38% A:middle
+Size: 38%
+
+163
+00:02:58.800 --> 00:02:58.900 S:39% A:middle
+Size: 39%
+
+164
+00:02:58.900 --> 00:02:59.000 S:40% A:middle
+Size: 40%
+
+165
+00:02:59.000 --> 00:02:59.100 S:41% A:middle
+Size: 41%
+
+166
+00:02:59.100 --> 00:02:59.200 S:42% A:middle
+Size: 42%
+
+167
+00:02:59.200 --> 00:02:59.300 S:43% A:middle
+Size: 43%
+
+168
+00:02:59.300 --> 00:02:59.400 S:44% A:middle
+Size: 44%
+
+169
+00:02:59.400 --> 00:02:59.500 S:45% A:middle
+Size: 45%
+
+170
+00:02:59.500 --> 00:02:59.600 S:46% A:middle
+Size: 46%
+
+171
+00:02:59.600 --> 00:02:59.700 S:47% A:middle
+Size: 47%
+
+172
+00:02:59.700 --> 00:02:59.800 S:48% A:middle
+Size: 48%
+
+173
+00:02:59.800 --> 00:02:59.900 S:49% A:middle
+Size: 49%
+
+174
+00:02:59.900 --> 00:02:60.000 S:50% A:middle
+Size: 50%
+
+175
+00:03:00.000 --> 00:03:00.100 S:51% A:middle
+Size: 51%
+
+176
+00:03:00.100 --> 00:03:00.200 S:52% A:middle
+Size: 52%
+
+177
+00:03:00.200 --> 00:03:00.300 S:53% A:middle
+Size: 53%
+
+178
+00:03:00.300 --> 00:03:00.400 S:54% A:middle
+Size: 54%
+
+179
+00:03:00.400 --> 00:03:00.500 S:55% A:middle
+Size: 55%
+
+180
+00:03:00.500 --> 00:03:00.600 S:56% A:middle
+Size: 56%
+
+181
+00:03:00.600 --> 00:03:00.700 S:57% A:middle
+Size: 57%
+
+182
+00:03:00.700 --> 00:03:00.800 S:58% A:middle
+Size: 58%
+
+183
+00:03:00.800 --> 00:03:00.900 S:59% A:middle
+Size: 59%
+
+184
+00:03:00.900 --> 00:03:01.000 S:60% A:middle
+Size: 60%
+
+185
+00:03:01.000 --> 00:03:01.100 S:61% A:middle
+Size: 61%
+
+186
+00:03:01.100 --> 00:03:01.200 S:62% A:middle
+Size: 62%
+
+187
+00:03:01.200 --> 00:03:01.300 S:63% A:middle
+Size: 63%
+
+188
+00:03:01.300 --> 00:03:01.400 S:64% A:middle
+Size: 64%
+
+189
+00:03:01.400 --> 00:03:01.500 S:65% A:middle
+Size: 65%
+
+190
+00:03:01.500 --> 00:03:01.600 S:66% A:middle
+Size: 66%
+
+191
+00:03:01.600 --> 00:03:01.700 S:67% A:middle
+Size: 67%
+
+192
+00:03:01.700 --> 00:03:01.800 S:68% A:middle
+Size: 68%
+
+193
+00:03:01.800 --> 00:03:01.900 S:69% A:middle
+Size: 69%
+
+194
+00:03:01.900 --> 00:03:02.000 S:70% A:middle
+Size: 70%
+
+195
+00:03:02.000 --> 00:03:02.100 S:71% A:middle
+Size: 71%
+
+196
+00:03:02.100 --> 00:03:02.200 S:72% A:middle
+Size: 72%
+
+197
+00:03:02.200 --> 00:03:02.300 S:73% A:middle
+Size: 73%
+
+198
+00:03:02.300 --> 00:03:02.400 S:74% A:middle
+Size: 74%
+
+199
+00:03:02.400 --> 00:03:02.500 S:75% A:middle
+Size: 75%
+
+200
+00:03:02.500 --> 00:03:02.600 S:76% A:middle
+Size: 76%
+
+201
+00:03:02.600 --> 00:03:02.700 S:77% A:middle
+Size: 77%
+
+202
+00:03:02.700 --> 00:03:02.800 S:78% A:middle
+Size: 78%
+
+203
+00:03:02.800 --> 00:03:02.900 S:79% A:middle
+Size: 79%
+
+204
+00:03:02.900 --> 00:03:03.000 S:80% A:middle
+Size: 80%
+
+205
+00:03:03.000 --> 00:03:03.100 S:81% A:middle
+Size: 81%
+
+206
+00:03:03.100 --> 00:03:03.200 S:82% A:middle
+Size: 82%
+
+207
+00:03:03.200 --> 00:03:03.300 S:83% A:middle
+Size: 83%
+
+208
+00:03:03.300 --> 00:03:03.400 S:84% A:middle
+Size: 84%
+
+209
+00:03:03.400 --> 00:03:03.500 S:85% A:middle
+Size: 85%
+
+210
+00:03:03.500 --> 00:03:03.600 S:86% A:middle
+Size: 86%
+
+211
+00:03:03.600 --> 00:03:03.700 S:87% A:middle
+Size: 87%
+
+212
+00:03:03.700 --> 00:03:03.800 S:88% A:middle
+Size: 88%
+
+213
+00:03:03.800 --> 00:03:03.900 S:89% A:middle
+Size: 89%
+
+214
+00:03:03.900 --> 00:03:04.000 S:90% A:middle
+Size: 90%
+
+215
+00:03:04.000 --> 00:03:04.100 S:91% A:middle
+Size: 91%
+
+216
+00:03:04.100 --> 00:03:04.200 S:92% A:middle
+Size: 92%
+
+217
+00:03:04.200 --> 00:03:04.300 S:93% A:middle
+Size: 93%
+
+218
+00:03:04.300 --> 00:03:04.400 S:94% A:middle
+Size: 94%
+
+219
+00:03:04.400 --> 00:03:04.500 S:95% A:middle
+Size: 95%
+
+220
+00:03:04.500 --> 00:03:04.600 S:96% A:middle
+Size: 96%
+
+221
+00:03:04.600 --> 00:03:04.700 S:97% A:middle
+Size: 97%
+
+222
+00:03:04.700 --> 00:03:04.800 S:98% A:middle
+Size: 98%
+
+223
+00:03:04.800 --> 00:03:04.900 S:99% A:middle
+Size: 99%
+
+224
+00:03:04.900 --> 00:03:05.000 S:100% A:middle
+Size: 100%
+
+225
+00:03:05.000 --> 00:03:15.000
+Test 8: Text position (percentage)
+
+226
+00:03:15.000 --> 00:03:15.100 T:1% S:20% L:70% A:middle
+Text Position: 1%
+
+227
+00:03:15.100 --> 00:03:15.200 T:2% S:20% L:70% A:middle
+Text Position: 2%
+
+228
+00:03:15.200 --> 00:03:15.300 T:3% S:20% L:70% A:middle
+Text Position: 3%
+
+229
+00:03:15.300 --> 00:03:15.400 T:4% S:20% L:70% A:middle
+Text Position: 4%
+
+230
+00:03:15.400 --> 00:03:15.400 T:5% S:20% L:70% A:middle
+Text Position: 5%
+
+231
+00:03:15.400 --> 00:03:15.500 T:6% S:20% L:70% A:middle
+Text Position: 6%
+
+232
+00:03:15.500 --> 00:03:15.600 T:7% S:20% L:70% A:middle
+Text Position: 7%
+
+233
+00:03:15.600 --> 00:03:15.700 T:8% S:20% L:70% A:middle
+Text Position: 8%
+
+234
+00:03:15.700 --> 00:03:15.800 T:9% S:20% L:70% A:middle
+Text Position: 9%
+
+235
+00:03:15.800 --> 00:03:15.900 T:10% S:20% L:70% A:middle
+Text Position: 10%
+
+236
+00:03:15.900 --> 00:03:16.000 T:11% S:20% L:70% A:middle
+Text Position: 11%
+
+237
+00:03:16.000 --> 00:03:16.200 T:12% S:20% L:70% A:middle
+Text Position: 12%
+
+238
+00:03:16.200 --> 00:03:16.300 T:13% S:20% L:70% A:middle
+Text Position: 13%
+
+239
+00:03:16.300 --> 00:03:16.400 T:14% S:20% L:70% A:middle
+Text Position: 14%
+
+240
+00:03:16.400 --> 00:03:16.500 T:15% S:20% L:70% A:middle
+Text Position: 15%
+
+241
+00:03:16.500 --> 00:03:16.600 T:16% S:20% L:70% A:middle
+Text Position: 16%
+
+242
+00:03:16.600 --> 00:03:16.700 T:17% S:20% L:70% A:middle
+Text Position: 17%
+
+243
+00:03:16.700 --> 00:03:16.800 T:18% S:20% L:70% A:middle
+Text Position: 18%
+
+244
+00:03:16.800 --> 00:03:16.900 T:19% S:20% L:70% A:middle
+Text Position: 19%
+
+245
+00:03:16.900 --> 00:03:17.000 T:20% S:20% L:70% A:middle
+Text Position: 20%
+
+246
+00:03:17.000 --> 00:03:17.100 T:21% S:20% L:70% A:middle
+Text Position: 21%
+
+247
+00:03:17.100 --> 00:03:17.200 T:22% S:20% L:70% A:middle
+Text Position: 22%
+
+248
+00:03:17.200 --> 00:03:17.300 T:23% S:20% L:70% A:middle
+Text Position: 23%
+
+249
+00:03:17.300 --> 00:03:17.400 T:24% S:20% L:70% A:middle
+Text Position: 24%
+
+250
+00:03:17.400 --> 00:03:17.500 T:25% S:20% L:70% A:middle
+Text Position: 25%
+
+251
+00:03:17.500 --> 00:03:17.600 T:26% S:20% L:70% A:middle
+Text Position: 26%
+
+252
+00:03:17.600 --> 00:03:17.700 T:27% S:20% L:70% A:middle
+Text Position: 27%
+
+253
+00:03:17.700 --> 00:03:17.800 T:28% S:20% L:70% A:middle
+Text Position: 28%
+
+254
+00:03:17.800 --> 00:03:17.900 T:29% S:20% L:70% A:middle
+Text Position: 29%
+
+255
+00:03:17.900 --> 00:03:18.000 T:30% S:20% L:70% A:middle
+Text Position: 30%
+
+256
+00:03:18.000 --> 00:03:18.100 T:31% S:20% L:70% A:middle
+Text Position: 31%
+
+257
+00:03:18.100 --> 00:03:18.200 T:32% S:20% L:70% A:middle
+Text Position: 32%
+
+258
+00:03:18.200 --> 00:03:18.300 T:33% S:20% L:70% A:middle
+Text Position: 33%
+
+259
+00:03:18.300 --> 00:03:18.400 T:34% S:20% L:70% A:middle
+Text Position: 34%
+
+260
+00:03:18.400 --> 00:03:18.500 T:35% S:20% L:70% A:middle
+Text Position: 35%
+
+261
+00:03:18.500 --> 00:03:18.600 T:36% S:20% L:70% A:middle
+Text Position: 36%
+
+262
+00:03:18.600 --> 00:03:18.700 T:37% S:20% L:70% A:middle
+Text Position: 37%
+
+263
+00:03:18.700 --> 00:03:18.800 T:38% S:20% L:70% A:middle
+Text Position: 38%
+
+264
+00:03:18.800 --> 00:03:18.900 T:39% S:20% L:70% A:middle
+Text Position: 39%
+
+265
+00:03:18.900 --> 00:03:19.000 T:40% S:20% L:70% A:middle
+Text Position: 40%
+
+266
+00:03:19.000 --> 00:03:19.100 T:41% S:20% L:70% A:middle
+Text Position: 41%
+
+267
+00:03:19.100 --> 00:03:19.200 T:42% S:20% L:70% A:middle
+Text Position: 42%
+
+268
+00:03:19.200 --> 00:03:19.300 T:43% S:20% L:70% A:middle
+Text Position: 43%
+
+269
+00:03:19.300 --> 00:03:19.400 T:44% S:20% L:70% A:middle
+Text Position: 44%
+
+270
+00:03:19.400 --> 00:03:19.500 T:45% S:20% L:70% A:middle
+Text Position: 45%
+
+271
+00:03:19.500 --> 00:03:19.600 T:46% S:20% L:70% A:middle
+Text Position: 46%
+
+272
+00:03:19.600 --> 00:03:19.700 T:47% S:20% L:70% A:middle
+Text Position: 47%
+
+273
+00:03:19.700 --> 00:03:19.800 T:48% S:20% L:70% A:middle
+Text Position: 48%
+
+274
+00:03:19.800 --> 00:03:19.900 T:49% S:20% L:70% A:middle
+Text Position: 49%
+
+275
+00:03:19.900 --> 00:03:20.000 T:50% S:20% L:70% A:middle
+Text Position: 50%
+
+276
+00:03:20.000 --> 00:03:20.100 T:51% S:20% L:70% A:middle
+Text Position: 51%
+
+277
+00:03:20.100 --> 00:03:20.200 T:52% S:20% L:70% A:middle
+Text Position: 52%
+
+278
+00:03:20.200 --> 00:03:20.300 T:53% S:20% L:70% A:middle
+Text Position: 53%
+
+279
+00:03:20.300 --> 00:03:20.400 T:54% S:20% L:70% A:middle
+Text Position: 54%
+
+280
+00:03:20.400 --> 00:03:20.500 T:55% S:20% L:70% A:middle
+Text Position: 55%
+
+281
+00:03:20.500 --> 00:03:20.600 T:56% S:20% L:70% A:middle
+Text Position: 56%
+
+282
+00:03:20.600 --> 00:03:20.700 T:57% S:20% L:70% A:middle
+Text Position: 57%
+
+283
+00:03:20.700 --> 00:03:20.800 T:58% S:20% L:70% A:middle
+Text Position: 58%
+
+284
+00:03:20.800 --> 00:03:20.900 T:59% S:20% L:70% A:middle
+Text Position: 59%
+
+285
+00:03:20.900 --> 00:03:21.000 T:60% S:20% L:70% A:middle
+Text Position: 60%
+
+286
+00:03:21.000 --> 00:03:21.100 T:61% S:20% L:70% A:middle
+Text Position: 61%
+
+287
+00:03:21.100 --> 00:03:21.200 T:62% S:20% L:70% A:middle
+Text Position: 62%
+
+288
+00:03:21.200 --> 00:03:21.300 T:63% S:20% L:70% A:middle
+Text Position: 63%
+
+289
+00:03:21.300 --> 00:03:21.400 T:64% S:20% L:70% A:middle
+Text Position: 64%
+
+290
+00:03:21.400 --> 00:03:21.500 T:65% S:20% L:70% A:middle
+Text Position: 65%
+
+291
+00:03:21.500 --> 00:03:21.600 T:66% S:20% L:70% A:middle
+Text Position: 66%
+
+292
+00:03:21.600 --> 00:03:21.700 T:67% S:20% L:70% A:middle
+Text Position: 67%
+
+293
+00:03:21.700 --> 00:03:21.800 T:68% S:20% L:70% A:middle
+Text Position: 68%
+
+294
+00:03:21.800 --> 00:03:21.900 T:69% S:20% L:70% A:middle
+Text Position: 69%
+
+295
+00:03:21.900 --> 00:03:22.000 T:70% S:20% L:70% A:middle
+Text Position: 70%
+
+296
+00:03:22.000 --> 00:03:22.100 T:71% S:20% L:70% A:middle
+Text Position: 71%
+
+297
+00:03:22.100 --> 00:03:22.200 T:72% S:20% L:70% A:middle
+Text Position: 72%
+
+298
+00:03:22.200 --> 00:03:22.300 T:73% S:20% L:70% A:middle
+Text Position: 73%
+
+299
+00:03:22.300 --> 00:03:22.400 T:74% S:20% L:70% A:middle
+Text Position: 74%
+
+300
+00:03:22.400 --> 00:03:22.500 T:75% S:20% L:70% A:middle
+Text Position: 75%
+
+301
+00:03:22.500 --> 00:03:22.600 T:76% S:20% L:70% A:middle
+Text Position: 76%
+
+302
+00:03:22.600 --> 00:03:22.700 T:77% S:20% L:70% A:middle
+Text Position: 77%
+
+303
+00:03:22.700 --> 00:03:22.800 T:78% S:20% L:70% A:middle
+Text Position: 78%
+
+304
+00:03:22.800 --> 00:03:22.900 T:79% S:20% L:70% A:middle
+Text Position: 79%
+
+305
+00:03:22.900 --> 00:03:23.000 T:80% S:20% L:70% A:middle
+Text Position: 80%
+
+306
+00:03:23.000 --> 00:03:23.100 T:81% S:20% L:70% A:middle
+Text Position: 81%
+
+307
+00:03:23.100 --> 00:03:23.200 T:82% S:20% L:70% A:middle
+Text Position: 82%
+
+308
+00:03:23.200 --> 00:03:23.300 T:83% S:20% L:70% A:middle
+Text Position: 83%
+
+309
+00:03:23.300 --> 00:03:23.400 T:84% S:20% L:70% A:middle
+Text Position: 84%
+
+310
+00:03:23.400 --> 00:03:23.500 T:85% S:20% L:70% A:middle
+Text Position: 85%
+
+311
+00:03:23.500 --> 00:03:23.600 T:86% S:20% L:70% A:middle
+Text Position: 86%
+
+312
+00:03:23.600 --> 00:03:23.700 T:87% S:20% L:70% A:middle
+Text Position: 87%
+
+313
+00:03:23.700 --> 00:03:23.800 T:88% S:20% L:70% A:middle
+Text Position: 88%
+
+314
+00:03:23.800 --> 00:03:23.900 T:89% S:20% L:70% A:middle
+Text Position: 89%
+
+315
+00:03:23.900 --> 00:03:24.000 T:90% S:20% L:70% A:middle
+Text Position: 90%
+
+316
+00:03:24.000 --> 00:03:24.100 T:91% S:20% L:70% A:middle
+Text Position: 91%
+
+317
+00:03:24.100 --> 00:03:24.200 T:92% S:20% L:70% A:middle
+Text Position: 92%
+
+318
+00:03:24.200 --> 00:03:24.300 T:93% S:20% L:70% A:middle
+Text Position: 93%
+
+319
+00:03:24.300 --> 00:03:24.400 T:94% S:20% L:70% A:middle
+Text Position: 94%
+
+320
+00:03:24.400 --> 00:03:24.500 T:95% S:20% L:70% A:middle
+Text Position: 95%
+
+321
+00:03:24.500 --> 00:03:24.600 T:96% S:20% L:70% A:middle
+Text Position: 96%
+
+322
+00:03:24.600 --> 00:03:24.700 T:97% S:20% L:70% A:middle
+Text Position: 97%
+
+323
+00:03:24.700 --> 00:03:24.800 T:98% S:20% L:70% A:middle
+Text Position: 98%
+
+324
+00:03:24.800 --> 00:03:24.900 T:99% S:20% L:70% A:middle
+Text Position: 99%
+
+325
+00:03:24.900 --> 00:03:25.000 T:100% S:20% L:70% A:middle
+Text Position: 100%
+
+326
+00:03:25.000 --> 00:03:35.000
+Test 9: Text position, size, and line position
+
+327
+00:03:35.000 --> 00:03:35.100 T:1% S:1% L:1% A:middle
+1%
+
+328
+00:03:35.100 --> 00:03:35.200 T:2% S:2% L:2% A:middle
+2%
+
+329
+00:03:35.200 --> 00:03:35.300 T:3% S:3% L:3% A:middle
+3%
+
+330
+00:03:35.300 --> 00:03:35.400 T:4% S:4% L:4% A:middle
+4%
+
+331
+00:03:35.400 --> 00:03:35.500 T:5% S:5% L:5% A:middle
+5%
+
+332
+00:03:35.500 --> 00:03:35.600 T:6% S:6% L:6% A:middle
+6%
+
+333
+00:03:35.600 --> 00:03:35.700 T:7% S:7% L:7% A:middle
+7%
+
+334
+00:03:35.700 --> 00:03:35.800 T:8% S:8% L:8% A:middle
+8%
+
+335
+00:03:35.800 --> 00:03:35.900 T:9% S:9% L:9% A:middle
+9%
+
+336
+00:03:35.900 --> 00:03:36.000 T:10% S:10% L:10% A:middle
+10%
+
+337
+00:03:36.000 --> 00:03:36.100 T:11% S:11% L:11% A:middle
+11%
+
+338
+00:03:36.100 --> 00:03:36.200 T:12% S:12% L:12% A:middle
+12%
+
+339
+00:03:36.200 --> 00:03:36.300 T:13% S:13% L:13% A:middle
+13%
+
+340
+00:03:36.300 --> 00:03:36.400 T:14% S:14% L:14% A:middle
+14%
+
+341
+00:03:36.400 --> 00:03:36.500 T:15% S:15% L:15% A:middle
+15%
+
+342
+00:03:36.500 --> 00:03:36.600 T:16% S:16% L:16% A:middle
+16%
+
+343
+00:03:36.600 --> 00:03:36.700 T:17% S:17% L:17% A:middle
+17%
+
+344
+00:03:36.700 --> 00:03:36.800 T:18% S:18% L:18% A:middle
+18%
+
+345
+00:03:36.800 --> 00:03:36.900 T:19% S:19% L:19% A:middle
+19%
+
+346
+00:03:36.900 --> 00:03:37.000 T:20% S:20% L:20% A:middle
+20%
+
+347
+00:03:37.000 --> 00:03:37.100 T:21% S:21% L:21% A:middle
+21%
+
+348
+00:03:37.100 --> 00:03:37.200 T:22% S:22% L:22% A:middle
+22%
+
+349
+00:03:37.200 --> 00:03:37.300 T:23% S:23% L:23% A:middle
+23%
+
+350
+00:03:37.300 --> 00:03:37.400 T:24% S:24% L:24% A:middle
+24%
+
+351
+00:03:37.400 --> 00:03:37.500 T:25% S:25% L:25% A:middle
+25%
+
+352
+00:03:37.500 --> 00:03:37.600 T:26% S:26% L:26% A:middle
+26%
+
+353
+00:03:37.600 --> 00:03:37.700 T:27% S:27% L:27% A:middle
+27%
+
+354
+00:03:37.700 --> 00:03:37.800 T:28% S:28% L:28% A:middle
+28%
+
+355
+00:03:37.800 --> 00:03:37.900 T:29% S:29% L:29% A:middle
+29%
+
+356
+00:03:37.900 --> 00:03:38.000 T:30% S:30% L:30% A:middle
+30%
+
+357
+00:03:38.000 --> 00:03:38.100 T:31% S:31% L:31% A:middle
+31%
+
+358
+00:03:38.100 --> 00:03:38.200 T:32% S:32% L:32% A:middle
+32%
+
+359
+00:03:38.200 --> 00:03:38.300 T:33% S:33% L:33% A:middle
+33%
+
+360
+00:03:38.300 --> 00:03:38.400 T:34% S:34% L:34% A:middle
+34%
+
+361
+00:03:38.400 --> 00:03:38.500 T:35% S:35% L:35% A:middle
+35%
+
+362
+00:03:38.500 --> 00:03:38.600 T:36% S:36% L:36% A:middle
+36%
+
+363
+00:03:38.600 --> 00:03:38.700 T:37% S:37% L:37% A:middle
+37%
+
+364
+00:03:38.700 --> 00:03:38.800 T:38% S:38% L:38% A:middle
+38%
+
+365
+00:03:38.800 --> 00:03:38.900 T:39% S:39% L:39% A:middle
+39%
+
+366
+00:03:38.900 --> 00:03:39.000 T:40% S:40% L:40% A:middle
+40%
+
+367
+00:03:39.000 --> 00:03:39.100 T:41% S:41% L:41% A:middle
+41%
+
+368
+00:03:39.100 --> 00:03:39.200 T:42% S:42% L:42% A:middle
+42%
+
+369
+00:03:39.200 --> 00:03:39.300 T:43% S:43% L:43% A:middle
+43%
+
+370
+00:03:39.300 --> 00:03:39.400 T:44% S:44% L:44% A:middle
+44%
+
+371
+00:03:39.400 --> 00:03:39.500 T:45% S:45% L:45% A:middle
+45%
+
+372
+00:03:39.500 --> 00:03:39.600 T:46% S:46% L:46% A:middle
+46%
+
+373
+00:03:39.600 --> 00:03:39.700 T:47% S:47% L:47% A:middle
+47%
+
+374
+00:03:39.700 --> 00:03:39.800 T:48% S:48% L:48% A:middle
+48%
+
+375
+00:03:39.800 --> 00:03:39.900 T:49% S:49% L:49% A:middle
+49%
+
+376
+00:03:39.900 --> 00:03:40.000 T:50% S:50% L:50% A:middle
+50%
+
+377
+00:03:40.000 --> 00:03:40.100 T:51% S:51% L:51% A:middle
+51%
+
+378
+00:03:40.100 --> 00:03:40.200 T:52% S:52% L:52% A:middle
+52%
+
+379
+00:03:40.200 --> 00:03:40.300 T:53% S:53% L:53% A:middle
+53%
+
+380
+00:03:40.300 --> 00:03:40.400 T:54% S:54% L:54% A:middle
+54%
+
+381
+00:03:40.400 --> 00:03:40.500 T:55% S:55% L:55% A:middle
+55%
+
+382
+00:03:40.500 --> 00:03:40.600 T:56% S:56% L:56% A:middle
+56%
+
+383
+00:03:40.600 --> 00:03:40.700 T:57% S:57% L:57% A:middle
+57%
+
+384
+00:03:40.700 --> 00:03:40.800 T:58% S:58% L:58% A:middle
+58%
+
+385
+00:03:40.800 --> 00:03:40.900 T:59% S:59% L:59% A:middle
+59%
+
+386
+00:03:40.900 --> 00:03:41.000 T:60% S:60% L:60% A:middle
+60%
+
+387
+00:03:41.000 --> 00:03:41.100 T:61% S:61% L:61% A:middle
+61%
+
+388
+00:03:41.100 --> 00:03:41.200 T:62% S:62% L:62% A:middle
+62%
+
+389
+00:03:41.200 --> 00:03:41.300 T:63% S:63% L:63% A:middle
+63%
+
+390
+00:03:41.300 --> 00:03:41.400 T:64% S:64% L:64% A:middle
+64%
+
+391
+00:03:41.400 --> 00:03:41.500 T:65% S:65% L:65% A:middle
+65%
+
+392
+00:03:41.500 --> 00:03:41.600 T:66% S:66% L:66% A:middle
+66%
+
+393
+00:03:41.600 --> 00:03:41.700 T:67% S:67% L:67% A:middle
+67%
+
+394
+00:03:41.700 --> 00:03:41.800 T:68% S:68% L:68% A:middle
+68%
+
+395
+00:03:41.800 --> 00:03:41.900 T:69% S:69% L:69% A:middle
+69%
+
+396
+00:03:41.900 --> 00:03:42.000 T:70% S:70% L:70% A:middle
+70%
+
+397
+00:03:42.000 --> 00:03:42.100 T:71% S:71% L:71% A:middle
+71%
+
+398
+00:03:42.100 --> 00:03:42.200 T:72% S:72% L:72% A:middle
+72%
+
+399
+00:03:42.200 --> 00:03:42.300 T:73% S:73% L:73% A:middle
+73%
+
+400
+00:03:42.300 --> 00:03:42.400 T:74% S:74% L:74% A:middle
+74%
+
+401
+00:03:42.400 --> 00:03:42.500 T:75% S:75% L:75% A:middle
+75%
+
+402
+00:03:42.500 --> 00:03:42.600 T:76% S:76% L:76% A:middle
+76%
+
+403
+00:03:42.600 --> 00:03:42.700 T:77% S:77% L:77% A:middle
+77%
+
+404
+00:03:42.700 --> 00:03:42.800 T:78% S:78% L:78% A:middle
+78%
+
+405
+00:03:42.800 --> 00:03:42.900 T:79% S:79% L:79% A:middle
+79%
+
+406
+00:03:42.900 --> 00:03:43.000 T:80% S:80% L:80% A:middle
+80%
+
+407
+00:03:43.000 --> 00:03:43.100 T:81% S:81% L:81% A:middle
+81%
+
+408
+00:03:43.100 --> 00:03:43.200 T:82% S:82% L:82% A:middle
+82%
+
+409
+00:03:43.200 --> 00:03:43.300 T:83% S:83% L:83% A:middle
+83%
+
+410
+00:03:43.300 --> 00:03:43.400 T:84% S:84% L:84% A:middle
+84%
+
+411
+00:03:43.400 --> 00:03:43.500 T:85% S:85% L:85% A:middle
+85%
+
+412
+00:03:43.500 --> 00:03:43.600 T:86% S:86% L:86% A:middle
+86%
+
+413
+00:03:43.600 --> 00:03:43.700 T:87% S:87% L:87% A:middle
+87%
+
+414
+00:03:43.700 --> 00:03:43.800 T:88% S:88% L:88% A:middle
+88%
+
+415
+00:03:43.800 --> 00:03:43.900 T:89% S:89% L:89% A:middle
+89%
+
+416
+00:03:43.900 --> 00:03:44.000 T:90% S:90% L:90% A:middle
+90%
+
+417
+00:03:44.000 --> 00:03:44.100 T:91% S:91% L:91% A:middle
+91%
+
+418
+00:03:44.100 --> 00:03:44.200 T:92% S:92% L:92% A:middle
+92%
+
+419
+00:03:44.200 --> 00:03:44.300 T:93% S:93% L:93% A:middle
+93%
+
+420
+00:03:44.300 --> 00:03:44.400 T:94% S:94% L:94% A:middle
+94%
+
+421
+00:03:44.400 --> 00:03:44.500 T:95% S:95% L:95% A:middle
+95%
+
+422
+00:03:44.500 --> 00:03:44.600 T:96% S:96% L:96% A:middle
+96%
+
+423
+00:03:44.600 --> 00:03:44.700 T:97% S:97% L:97% A:middle
+97%
+
+424
+00:03:44.700 --> 00:03:44.800 T:98% S:98% L:98% A:middle
+98%
+
+425
+00:03:44.800 --> 00:03:44.900 T:99% S:99% L:99% A:middle
+99%
+
+426
+00:03:44.900 --> 00:03:46.000 T:100% S:100% L:100% A:middle
+100%
+
+427
+00:03:46.000 --> 00:03:55.000
+Test 10: Vertical and Vertical-LR cue direction
+
+428
+00:03:55.000 --> 00:04:00.000 D:vertical
+Vertical Cue 一二三四五六七八九十
+
+429
+00:03:55.000 --> 00:04:00.000 D:vertical-lr
+Vertical-LR Cue 一二三四五六七八九十
+
+430
+00:04:00.000 --> 00:04:10.000
+Test 11: Vertical cue direction text alignment
+
+431
+00:04:10.000 --> 00:04:12.500 D:vertical A:start
+A:Start 一二三四五六七八九十
+
+432
+00:04:12.500 --> 00:04:15.000 D:vertical A:middle
+A:Middle 一二三四五六七八九十
+
+433
+00:04:15.000 --> 00:04:17.500 D:vertical A:end
+Alignment:End 一二三四五六七八九十
+
+434
+00:04:17.500 --> 00:04:27.500
+Test 12: Vertical-LR cue direction text alignment
+
+435
+00:04:27.500 --> 00:04:30.000 D:vertical-lr A:start
+A:Start 一二三四五六七八九十
+
+436
+00:04:30.000 --> 00:04:32.500 D:vertical-lr A:middle
+A:Middle 一二三四五六七八九十
+
+437
+00:04:32.500 --> 00:04:35.000 D:vertical-lr A:end
+Alignment:End 一二三四五六七八九十
+
+438
+00:04:35.000 --> 00:04:45.000
+Test 13: Vertical cue line position
+
+439
+00:04:45.000 --> 00:04:45.100 D:vertical L:1%
+LPos:1%
+
+440
+00:04:45.100 --> 00:04:45.200 D:vertical L:2%
+LPos:2%
+
+441
+00:04:45.200 --> 00:04:45.300 D:vertical L:3%
+LPos:3%
+
+442
+00:04:45.300 --> 00:04:45.400 D:vertical L:4%
+LPos:4%
+
+443
+00:04:45.400 --> 00:04:45.500 D:vertical L:5%
+LPos:5%
+
+444
+00:04:45.500 --> 00:04:45.600 D:vertical L:6%
+LPos:6%
+
+445
+00:04:45.600 --> 00:04:45.700 D:vertical L:7%
+LPos:7%
+
+446
+00:04:45.700 --> 00:04:45.800 D:vertical L:8%
+LPos:8%
+
+447
+00:04:45.800 --> 00:04:45.900 D:vertical L:9%
+LPos:9%
+
+448
+00:04:45.900 --> 00:04:46.000 D:vertical L:10%
+LPos:10%
+
+449
+00:04:46.000 --> 00:04:46.100 D:vertical L:11%
+LPos:11%
+
+450
+00:04:46.100 --> 00:04:46.200 D:vertical L:12%
+LPos:12%
+
+451
+00:04:46.200 --> 00:04:46.300 D:vertical L:13%
+LPos:13%
+
+452
+00:04:46.300 --> 00:04:46.400 D:vertical L:14%
+LPos:14%
+
+453
+00:04:46.400 --> 00:04:46.500 D:vertical L:15%
+LPos:15%
+
+454
+00:04:46.500 --> 00:04:46.600 D:vertical L:16%
+LPos:16%
+
+455
+00:04:46.600 --> 00:04:46.700 D:vertical L:17%
+LPos:17%
+
+456
+00:04:46.700 --> 00:04:46.800 D:vertical L:18%
+LPos:18%
+
+457
+00:04:46.800 --> 00:04:46.900 D:vertical L:19%
+LPos:19%
+
+458
+00:04:46.900 --> 00:04:47.000 D:vertical L:20%
+LPos:20%
+
+459
+00:04:47.000 --> 00:04:47.100 D:vertical L:21%
+LPos:21%
+
+460
+00:04:47.100 --> 00:04:47.200 D:vertical L:22%
+LPos:22%
+
+461
+00:04:47.200 --> 00:04:47.300 D:vertical L:23%
+LPos:23%
+
+462
+00:04:47.300 --> 00:04:47.400 D:vertical L:24%
+LPos:24%
+
+463
+00:04:47.400 --> 00:04:47.500 D:vertical L:25%
+LPos:25%
+
+464
+00:04:47.500 --> 00:04:47.600 D:vertical L:26%
+LPos:26%
+
+465
+00:04:47.600 --> 00:04:47.700 D:vertical L:27%
+LPos:27%
+
+466
+00:04:47.700 --> 00:04:47.800 D:vertical L:28%
+LPos:28%
+
+467
+00:04:47.800 --> 00:04:47.900 D:vertical L:29%
+LPos:29%
+
+468
+00:04:47.900 --> 00:04:48.000 D:vertical L:30%
+LPos:30%
+
+469
+00:04:48.000 --> 00:04:48.100 D:vertical L:31%
+LPos:31%
+
+470
+00:04:48.100 --> 00:04:48.200 D:vertical L:32%
+LPos:32%
+
+471
+00:04:48.200 --> 00:04:48.300 D:vertical L:33%
+LPos:33%
+
+472
+00:04:48.300 --> 00:04:48.400 D:vertical L:34%
+LPos:34%
+
+473
+00:04:48.400 --> 00:04:48.500 D:vertical L:35%
+LPos:35%
+
+474
+00:04:48.500 --> 00:04:48.600 D:vertical L:36%
+LPos:36%
+
+475
+00:04:48.600 --> 00:04:48.700 D:vertical L:37%
+LPos:37%
+
+476
+00:04:48.700 --> 00:04:48.800 D:vertical L:38%
+LPos:38%
+
+477
+00:04:48.800 --> 00:04:48.900 D:vertical L:39%
+LPos:39%
+
+478
+00:04:48.900 --> 00:04:49.000 D:vertical L:40%
+LPos:40%
+
+479
+00:04:49.000 --> 00:04:49.100 D:vertical L:41%
+LPos:41%
+
+480
+00:04:49.100 --> 00:04:49.200 D:vertical L:42%
+LPos:42%
+
+481
+00:04:49.200 --> 00:04:49.300 D:vertical L:43%
+LPos:43%
+
+482
+00:04:49.300 --> 00:04:49.400 D:vertical L:44%
+LPos:44%
+
+483
+00:04:49.400 --> 00:04:49.500 D:vertical L:45%
+LPos:45%
+
+484
+00:04:49.500 --> 00:04:49.600 D:vertical L:46%
+LPos:46%
+
+485
+00:04:49.600 --> 00:04:49.700 D:vertical L:47%
+LPos:47%
+
+486
+00:04:49.700 --> 00:04:49.800 D:vertical L:48%
+LPos:48%
+
+487
+00:04:49.800 --> 00:04:49.900 D:vertical L:49%
+LPos:49%
+
+488
+00:04:49.900 --> 00:04:50.000 D:vertical L:50%
+LPos:50%
+
+489
+00:04:50.000 --> 00:04:50.100 D:vertical L:51%
+LPos:51%
+
+490
+00:04:50.100 --> 00:04:50.200 D:vertical L:52%
+LPos:52%
+
+491
+00:04:50.200 --> 00:04:50.300 D:vertical L:53%
+LPos:53%
+
+492
+00:04:50.300 --> 00:04:50.400 D:vertical L:54%
+LPos:54%
+
+493
+00:04:50.400 --> 00:04:50.500 D:vertical L:55%
+LPos:55%
+
+494
+00:04:50.500 --> 00:04:50.600 D:vertical L:56%
+LPos:56%
+
+495
+00:04:50.600 --> 00:04:50.700 D:vertical L:57%
+LPos:57%
+
+496
+00:04:50.700 --> 00:04:50.800 D:vertical L:58%
+LPos:58%
+
+497
+00:04:50.800 --> 00:04:50.900 D:vertical L:59%
+LPos:59%
+
+498
+00:04:50.900 --> 00:04:51.000 D:vertical L:60%
+LPos:60%
+
+499
+00:04:51.000 --> 00:04:51.100 D:vertical L:61%
+LPos:61%
+
+500
+00:04:51.100 --> 00:04:51.200 D:vertical L:62%
+LPos:62%
+
+501
+00:04:51.200 --> 00:04:51.300 D:vertical L:63%
+LPos:63%
+
+502
+00:04:51.300 --> 00:04:51.400 D:vertical L:64%
+LPos:64%
+
+503
+00:04:51.400 --> 00:04:51.500 D:vertical L:65%
+LPos:65%
+
+504
+00:04:51.500 --> 00:04:51.600 D:vertical L:66%
+LPos:66%
+
+505
+00:04:51.600 --> 00:04:51.700 D:vertical L:67%
+LPos:67%
+
+506
+00:04:51.700 --> 00:04:51.800 D:vertical L:68%
+LPos:68%
+
+507
+00:04:51.800 --> 00:04:51.900 D:vertical L:69%
+LPos:69%
+
+508
+00:04:51.900 --> 00:04:52.000 D:vertical L:70%
+LPos:70%
+
+509
+00:04:52.000 --> 00:04:52.100 D:vertical L:71%
+LPos:71%
+
+510
+00:04:52.100 --> 00:04:52.200 D:vertical L:72%
+LPos:72%
+
+511
+00:04:52.200 --> 00:04:52.300 D:vertical L:73%
+LPos:73%
+
+512
+00:04:52.300 --> 00:04:52.400 D:vertical L:74%
+LPos:74%
+
+513
+00:04:52.400 --> 00:04:52.500 D:vertical L:75%
+LPos:75%
+
+514
+00:04:52.500 --> 00:04:52.600 D:vertical L:76%
+LPos:76%
+
+515
+00:04:52.600 --> 00:04:52.700 D:vertical L:77%
+LPos:77%
+
+516
+00:04:52.700 --> 00:04:52.800 D:vertical L:78%
+LPos:78%
+
+517
+00:04:52.800 --> 00:04:52.900 D:vertical L:79%
+LPos:79%
+
+518
+00:04:52.900 --> 00:04:53.000 D:vertical L:80%
+LPos:80%
+
+519
+00:04:53.000 --> 00:04:53.100 D:vertical L:81%
+LPos:81%
+
+520
+00:04:53.100 --> 00:04:53.200 D:vertical L:82%
+LPos:82%
+
+521
+00:04:53.200 --> 00:04:53.300 D:vertical L:83%
+LPos:83%
+
+522
+00:04:53.300 --> 00:04:53.400 D:vertical L:84%
+LPos:84%
+
+523
+00:04:53.400 --> 00:04:53.500 D:vertical L:85%
+LPos:85%
+
+524
+00:04:53.500 --> 00:04:53.600 D:vertical L:86%
+LPos:86%
+
+525
+00:04:53.600 --> 00:04:53.700 D:vertical L:87%
+LPos:87%
+
+526
+00:04:53.700 --> 00:04:53.800 D:vertical L:88%
+LPos:88%
+
+527
+00:04:53.800 --> 00:04:53.900 D:vertical L:89%
+LPos:89%
+
+528
+00:04:53.900 --> 00:04:54.000 D:vertical L:90%
+LPos:90%
+
+529
+00:04:54.000 --> 00:04:54.100 D:vertical L:91%
+LPos:91%
+
+530
+00:04:54.100 --> 00:04:54.200 D:vertical L:92%
+LPos:92%
+
+531
+00:04:54.200 --> 00:04:54.300 D:vertical L:93%
+LPos:93%
+
+532
+00:04:54.300 --> 00:04:54.400 D:vertical L:94%
+LPos:94%
+
+533
+00:04:54.400 --> 00:04:54.500 D:vertical L:95%
+LPos:95%
+
+534
+00:04:54.500 --> 00:04:54.600 D:vertical L:96%
+LPos:96%
+
+535
+00:04:54.600 --> 00:04:54.700 D:vertical L:97%
+LPos:97%
+
+536
+00:04:54.700 --> 00:04:54.800 D:vertical L:98%
+LPos:98%
+
+537
+00:04:54.800 --> 00:04:54.900 D:vertical L:99%
+LPos:99%
+
+538
+00:04:54.900 --> 00:05:00.000 D:vertical L:100%
+LPos:100%
+
+539
+00:05:00.000 --> 00:05:10.000
+Test 14: Vertical-LR cue line position
+
+540
+00:05:10.000 --> 00:05:10.100 D:vertical-lr L:1%
+LPos:1%
+
+541
+00:05:10.100 --> 00:05:10.200 D:vertical-lr L:2%
+LPos:2%
+
+542
+00:05:10.200 --> 00:05:10.200 D:vertical-lr L:3%
+LPos:3%
+
+543
+00:05:10.200 --> 00:05:10.300 D:vertical-lr L:4%
+LPos:4%
+
+544
+00:05:10.300 --> 00:05:10.400 D:vertical-lr L:5%
+LPos:5%
+
+545
+00:05:10.400 --> 00:05:10.500 D:vertical-lr L:6%
+LPos:6%
+
+546
+00:05:10.500 --> 00:05:10.600 D:vertical-lr L:7%
+LPos:7%
+
+547
+00:05:10.600 --> 00:05:10.700 D:vertical-lr L:8%
+LPos:8%
+
+548
+00:05:10.700 --> 00:05:10.800 D:vertical-lr L:9%
+LPos:9%
+
+549
+00:05:10.800 --> 00:05:10.900 D:vertical-lr L:10%
+LPos:10%
+
+550
+00:05:10.900 --> 00:05:11.000 D:vertical-lr L:11%
+LPos:11%
+
+551
+00:05:11.000 --> 00:05:11.100 D:vertical-lr L:12%
+LPos:12%
+
+552
+00:05:11.100 --> 00:05:11.200 D:vertical-lr L:13%
+LPos:13%
+
+553
+00:05:11.200 --> 00:05:11.300 D:vertical-lr L:14%
+LPos:14%
+
+554
+00:05:11.300 --> 00:05:11.400 D:vertical-lr L:15%
+LPos:15%
+
+555
+00:05:11.400 --> 00:05:11.500 D:vertical-lr L:16%
+LPos:16%
+
+556
+00:05:11.500 --> 00:05:11.600 D:vertical-lr L:17%
+LPos:17%
+
+557
+00:05:11.600 --> 00:05:11.700 D:vertical-lr L:18%
+LPos:18%
+
+558
+00:05:11.700 --> 00:05:11.800 D:vertical-lr L:19%
+LPos:19%
+
+559
+00:05:11.800 --> 00:05:11.900 D:vertical-lr L:20%
+LPos:20%
+
+560
+00:05:11.900 --> 00:05:12.000 D:vertical-lr L:21%
+LPos:21%
+
+561
+00:05:12.000 --> 00:05:12.100 D:vertical-lr L:22%
+LPos:22%
+
+562
+00:05:12.100 --> 00:05:12.200 D:vertical-lr L:23%
+LPos:23%
+
+563
+00:05:12.200 --> 00:05:12.300 D:vertical-lr L:24%
+LPos:24%
+
+564
+00:05:12.300 --> 00:05:12.400 D:vertical-lr L:25%
+LPos:25%
+
+565
+00:05:12.400 --> 00:05:12.500 D:vertical-lr L:26%
+LPos:26%
+
+566
+00:05:12.500 --> 00:05:12.600 D:vertical-lr L:27%
+LPos:27%
+
+567
+00:05:12.600 --> 00:05:12.700 D:vertical-lr L:28%
+LPos:28%
+
+568
+00:05:12.700 --> 00:05:12.800 D:vertical-lr L:29%
+LPos:29%
+
+569
+00:05:12.800 --> 00:05:12.900 D:vertical-lr L:30%
+LPos:30%
+
+570
+00:05:12.900 --> 00:05:13.000 D:vertical-lr L:31%
+LPos:31%
+
+571
+00:05:13.000 --> 00:05:13.100 D:vertical-lr L:32%
+LPos:32%
+
+572
+00:05:13.100 --> 00:05:13.200 D:vertical-lr L:33%
+LPos:33%
+
+573
+00:05:13.200 --> 00:05:13.300 D:vertical-lr L:34%
+LPos:34%
+
+574
+00:05:13.300 --> 00:05:13.400 D:vertical-lr L:35%
+LPos:35%
+
+575
+00:05:13.400 --> 00:05:13.500 D:vertical-lr L:36%
+LPos:36%
+
+576
+00:05:13.500 --> 00:05:13.600 D:vertical-lr L:37%
+LPos:37%
+
+577
+00:05:13.600 --> 00:05:13.700 D:vertical-lr L:38%
+LPos:38%
+
+578
+00:05:13.700 --> 00:05:13.800 D:vertical-lr L:39%
+LPos:39%
+
+579
+00:05:13.800 --> 00:05:13.900 D:vertical-lr L:40%
+LPos:40%
+
+580
+00:05:13.900 --> 00:05:14.000 D:vertical-lr L:41%
+LPos:41%
+
+581
+00:05:14.000 --> 00:05:14.100 D:vertical-lr L:42%
+LPos:42%
+
+582
+00:05:14.100 --> 00:05:14.200 D:vertical-lr L:43%
+LPos:43%
+
+583
+00:05:14.200 --> 00:05:14.300 D:vertical-lr L:44%
+LPos:44%
+
+584
+00:05:14.300 --> 00:05:14.400 D:vertical-lr L:45%
+LPos:45%
+
+585
+00:05:14.400 --> 00:05:14.500 D:vertical-lr L:46%
+LPos:46%
+
+586
+00:05:14.500 --> 00:05:14.600 D:vertical-lr L:47%
+LPos:47%
+
+587
+00:05:14.600 --> 00:05:14.700 D:vertical-lr L:48%
+LPos:48%
+
+588
+00:05:14.700 --> 00:05:14.800 D:vertical-lr L:49%
+LPos:49%
+
+589
+00:05:14.800 --> 00:05:14.900 D:vertical-lr L:50%
+LPos:50%
+
+590
+00:05:14.900 --> 00:05:15.000 D:vertical-lr L:51%
+LPos:51%
+
+591
+00:05:15.000 --> 00:05:15.100 D:vertical-lr L:52%
+LPos:52%
+
+592
+00:05:15.100 --> 00:05:15.200 D:vertical-lr L:53%
+LPos:53%
+
+593
+00:05:15.200 --> 00:05:15.300 D:vertical-lr L:54%
+LPos:54%
+
+594
+00:05:15.300 --> 00:05:15.400 D:vertical-lr L:55%
+LPos:55%
+
+595
+00:05:15.400 --> 00:05:15.500 D:vertical-lr L:56%
+LPos:56%
+
+596
+00:05:15.500 --> 00:05:15.600 D:vertical-lr L:57%
+LPos:57%
+
+597
+00:05:15.600 --> 00:05:15.700 D:vertical-lr L:58%
+LPos:58%
+
+598
+00:05:15.700 --> 00:05:15.800 D:vertical-lr L:59%
+LPos:59%
+
+599
+00:05:15.800 --> 00:05:15.900 D:vertical-lr L:60%
+LPos:60%
+
+600
+00:05:15.900 --> 00:05:16.000 D:vertical-lr L:61%
+LPos:61%
+
+601
+00:05:16.000 --> 00:05:16.100 D:vertical-lr L:62%
+LPos:62%
+
+602
+00:05:16.100 --> 00:05:16.200 D:vertical-lr L:63%
+LPos:63%
+
+603
+00:05:16.200 --> 00:05:16.300 D:vertical-lr L:64%
+LPos:64%
+
+604
+00:05:16.300 --> 00:05:16.400 D:vertical-lr L:65%
+LPos:65%
+
+605
+00:05:16.400 --> 00:05:16.500 D:vertical-lr L:66%
+LPos:66%
+
+606
+00:05:16.500 --> 00:05:16.600 D:vertical-lr L:67%
+LPos:67%
+
+607
+00:05:16.600 --> 00:05:16.700 D:vertical-lr L:68%
+LPos:68%
+
+608
+00:05:16.700 --> 00:05:16.800 D:vertical-lr L:69%
+LPos:69%
+
+609
+00:05:16.800 --> 00:05:16.900 D:vertical-lr L:70%
+LPos:70%
+
+610
+00:05:16.900 --> 00:05:17.000 D:vertical-lr L:71%
+LPos:71%
+
+611
+00:05:17.000 --> 00:05:17.100 D:vertical-lr L:72%
+LPos:72%
+
+612
+00:05:17.100 --> 00:05:17.200 D:vertical-lr L:73%
+LPos:73%
+
+613
+00:05:17.200 --> 00:05:17.400 D:vertical-lr L:74%
+LPos:74%
+
+614
+00:05:17.400 --> 00:05:17.500 D:vertical-lr L:75%
+LPos:75%
+
+615
+00:05:17.500 --> 00:05:17.600 D:vertical-lr L:76%
+LPos:76%
+
+616
+00:05:17.600 --> 00:05:17.700 D:vertical-lr L:77%
+LPos:77%
+
+617
+00:05:17.700 --> 00:05:17.800 D:vertical-lr L:78%
+LPos:78%
+
+618
+00:05:17.800 --> 00:05:17.900 D:vertical-lr L:79%
+LPos:79%
+
+619
+00:05:17.900 --> 00:05:18.000 D:vertical-lr L:80%
+LPos:80%
+
+620
+00:05:18.000 --> 00:05:18.100 D:vertical-lr L:81%
+LPos:81%
+
+621
+00:05:18.100 --> 00:05:18.200 D:vertical-lr L:82%
+LPos:82%
+
+622
+00:05:18.200 --> 00:05:18.300 D:vertical-lr L:83%
+LPos:83%
+
+623
+00:05:18.300 --> 00:05:18.400 D:vertical-lr L:84%
+LPos:84%
+
+624
+00:05:18.400 --> 00:05:18.500 D:vertical-lr L:85%
+LPos:85%
+
+625
+00:05:18.500 --> 00:05:18.600 D:vertical-lr L:86%
+LPos:86%
+
+626
+00:05:18.600 --> 00:05:18.700 D:vertical-lr L:87%
+LPos:87%
+
+627
+00:05:18.700 --> 00:05:18.800 D:vertical-lr L:88%
+LPos:88%
+
+628
+00:05:18.800 --> 00:05:18.900 D:vertical-lr L:89%
+LPos:89%
+
+629
+00:05:18.900 --> 00:05:19.000 D:vertical-lr L:90%
+LPos:90%
+
+630
+00:05:19.000 --> 00:05:19.100 D:vertical-lr L:91%
+LPos:91%
+
+631
+00:05:19.100 --> 00:05:19.200 D:vertical-lr L:92%
+LPos:92%
+
+632
+00:05:19.200 --> 00:05:19.300 D:vertical-lr L:93%
+LPos:93%
+
+633
+00:05:19.300 --> 00:05:19.400 D:vertical-lr L:94%
+LPos:94%
+
+634
+00:05:19.400 --> 00:05:19.500 D:vertical-lr L:95%
+LPos:95%
+
+635
+00:05:19.500 --> 00:05:19.600 D:vertical-lr L:96%
+LPos:96%
+
+636
+00:05:19.600 --> 00:05:19.700 D:vertical-lr L:97%
+LPos:97%
+
+637
+00:05:19.700 --> 00:05:19.800 D:vertical-lr L:98%
+LPos:98%
+
+638
+00:05:19.800 --> 00:05:19.900 D:vertical-lr L:99%
+LPos:99%
+
+639
+00:05:19.900 --> 00:05:25.000 D:vertical-lr L:100%
+LPos:100%
+
+640
+00:05:25.000 --> 00:05:35.000
+Test 15: Vertical cue size
+
+641
+00:05:35.000 --> 00:05:35.100 D:vertical S:1%
+S:1%
+
+642
+00:05:35.100 --> 00:05:35.200 D:vertical S:2%
+S:2%
+
+643
+00:05:35.200 --> 00:05:35.300 D:vertical S:3%
+S:3%
+
+644
+00:05:35.300 --> 00:05:35.400 D:vertical S:4%
+S:4%
+
+645
+00:05:35.400 --> 00:05:35.500 D:vertical S:5%
+S:5%
+
+646
+00:05:35.500 --> 00:05:35.600 D:vertical S:6%
+S:6%
+
+647
+00:05:35.600 --> 00:05:35.700 D:vertical S:7%
+S:7%
+
+648
+00:05:35.700 --> 00:05:35.800 D:vertical S:8%
+S:8%
+
+649
+00:05:35.800 --> 00:05:35.900 D:vertical S:9%
+S:9%
+
+650
+00:05:35.900 --> 00:05:36.000 D:vertical S:10%
+S:10%
+
+651
+00:05:36.000 --> 00:05:36.100 D:vertical S:11%
+S:11%
+
+652
+00:05:36.100 --> 00:05:36.200 D:vertical S:12%
+S:12%
+
+653
+00:05:36.200 --> 00:05:36.300 D:vertical S:13%
+S:13%
+
+654
+00:05:36.300 --> 00:05:36.400 D:vertical S:14%
+S:14%
+
+655
+00:05:36.400 --> 00:05:36.500 D:vertical S:15%
+S:15%
+
+656
+00:05:36.500 --> 00:05:36.600 D:vertical S:16%
+S:16%
+
+657
+00:05:36.600 --> 00:05:36.700 D:vertical S:17%
+S:17%
+
+658
+00:05:36.700 --> 00:05:36.800 D:vertical S:18%
+S:18%
+
+659
+00:05:36.800 --> 00:05:36.900 D:vertical S:19%
+S:19%
+
+660
+00:05:36.900 --> 00:05:37.000 D:vertical S:20%
+S:20%
+
+661
+00:05:37.000 --> 00:05:37.100 D:vertical S:21%
+S:21%
+
+662
+00:05:37.100 --> 00:05:37.200 D:vertical S:22%
+S:22%
+
+663
+00:05:37.200 --> 00:05:37.300 D:vertical S:23%
+S:23%
+
+664
+00:05:37.300 --> 00:05:37.400 D:vertical S:24%
+S:24%
+
+665
+00:05:37.400 --> 00:05:37.500 D:vertical S:25%
+S:25%
+
+666
+00:05:37.500 --> 00:05:37.600 D:vertical S:26%
+S:26%
+
+667
+00:05:37.600 --> 00:05:37.700 D:vertical S:27%
+S:27%
+
+668
+00:05:37.700 --> 00:05:37.800 D:vertical S:28%
+S:28%
+
+669
+00:05:37.800 --> 00:05:37.900 D:vertical S:29%
+S:29%
+
+670
+00:05:37.900 --> 00:05:38.000 D:vertical S:30%
+S:30%
+
+671
+00:05:38.000 --> 00:05:38.100 D:vertical S:31%
+S:31%
+
+672
+00:05:38.100 --> 00:05:38.200 D:vertical S:32%
+S:32%
+
+673
+00:05:38.200 --> 00:05:38.300 D:vertical S:33%
+S:33%
+
+674
+00:05:38.300 --> 00:05:38.400 D:vertical S:34%
+S:34%
+
+675
+00:05:38.400 --> 00:05:38.500 D:vertical S:35%
+S:35%
+
+676
+00:05:38.500 --> 00:05:38.600 D:vertical S:36%
+S:36%
+
+677
+00:05:38.600 --> 00:05:38.700 D:vertical S:37%
+S:37%
+
+678
+00:05:38.700 --> 00:05:38.800 D:vertical S:38%
+S:38%
+
+679
+00:05:38.800 --> 00:05:38.900 D:vertical S:39%
+S:39%
+
+680
+00:05:38.900 --> 00:05:39.000 D:vertical S:40%
+S:40%
+
+681
+00:05:39.000 --> 00:05:39.100 D:vertical S:41%
+S:41%
+
+682
+00:05:39.100 --> 00:05:39.200 D:vertical S:42%
+S:42%
+
+683
+00:05:39.200 --> 00:05:39.300 D:vertical S:43%
+S:43%
+
+684
+00:05:39.300 --> 00:05:39.400 D:vertical S:44%
+S:44%
+
+685
+00:05:39.400 --> 00:05:39.500 D:vertical S:45%
+S:45%
+
+686
+00:05:39.500 --> 00:05:39.600 D:vertical S:46%
+S:46%
+
+687
+00:05:39.600 --> 00:05:39.700 D:vertical S:47%
+S:47%
+
+688
+00:05:39.700 --> 00:05:39.800 D:vertical S:48%
+S:48%
+
+689
+00:05:39.800 --> 00:05:39.900 D:vertical S:49%
+S:49%
+
+690
+00:05:39.900 --> 00:05:40.000 D:vertical S:50%
+S:50%
+
+691
+00:05:40.000 --> 00:05:40.100 D:vertical S:51%
+S:51%
+
+692
+00:05:40.100 --> 00:05:40.200 D:vertical S:52%
+S:52%
+
+693
+00:05:40.200 --> 00:05:40.300 D:vertical S:53%
+S:53%
+
+694
+00:05:40.300 --> 00:05:40.400 D:vertical S:54%
+S:54%
+
+695
+00:05:40.400 --> 00:05:40.500 D:vertical S:55%
+S:55%
+
+696
+00:05:40.500 --> 00:05:40.600 D:vertical S:56%
+S:56%
+
+697
+00:05:40.600 --> 00:05:40.700 D:vertical S:57%
+S:57%
+
+698
+00:05:40.700 --> 00:05:40.800 D:vertical S:58%
+S:58%
+
+699
+00:05:40.800 --> 00:05:40.900 D:vertical S:59%
+S:59%
+
+700
+00:05:40.900 --> 00:05:41.000 D:vertical S:60%
+S:60%
+
+701
+00:05:41.000 --> 00:05:41.100 D:vertical S:61%
+S:61%
+
+702
+00:05:41.100 --> 00:05:41.200 D:vertical S:62%
+S:62%
+
+703
+00:05:41.200 --> 00:05:41.300 D:vertical S:63%
+S:63%
+
+704
+00:05:41.300 --> 00:05:41.400 D:vertical S:64%
+S:64%
+
+705
+00:05:41.400 --> 00:05:41.500 D:vertical S:65%
+S:65%
+
+706
+00:05:41.500 --> 00:05:41.600 D:vertical S:66%
+S:66%
+
+707
+00:05:41.600 --> 00:05:41.700 D:vertical S:67%
+S:67%
+
+708
+00:05:41.700 --> 00:05:41.800 D:vertical S:68%
+S:68%
+
+709
+00:05:41.800 --> 00:05:41.900 D:vertical S:69%
+S:69%
+
+710
+00:05:41.900 --> 00:05:42.000 D:vertical S:70%
+S:70%
+
+711
+00:05:42.000 --> 00:05:42.100 D:vertical S:71%
+S:71%
+
+712
+00:05:42.100 --> 00:05:42.200 D:vertical S:72%
+S:72%
+
+713
+00:05:42.200 --> 00:05:42.300 D:vertical S:73%
+S:73%
+
+714
+00:05:42.300 --> 00:05:42.400 D:vertical S:74%
+S:74%
+
+715
+00:05:42.400 --> 00:05:42.500 D:vertical S:75%
+S:75%
+
+716
+00:05:42.500 --> 00:05:42.600 D:vertical S:76%
+S:76%
+
+717
+00:05:42.600 --> 00:05:42.700 D:vertical S:77%
+S:77%
+
+718
+00:05:42.700 --> 00:05:42.800 D:vertical S:78%
+S:78%
+
+719
+00:05:42.800 --> 00:05:42.900 D:vertical S:79%
+S:79%
+
+720
+00:05:42.900 --> 00:05:43.000 D:vertical S:80%
+S:80%
+
+721
+00:05:43.000 --> 00:05:43.100 D:vertical S:81%
+S:81%
+
+722
+00:05:43.100 --> 00:05:43.200 D:vertical S:82%
+S:82%
+
+723
+00:05:43.200 --> 00:05:43.300 D:vertical S:83%
+S:83%
+
+724
+00:05:43.300 --> 00:05:43.400 D:vertical S:84%
+S:84%
+
+725
+00:05:43.400 --> 00:05:43.500 D:vertical S:85%
+S:85%
+
+726
+00:05:43.500 --> 00:05:43.600 D:vertical S:86%
+S:86%
+
+727
+00:05:43.600 --> 00:05:43.700 D:vertical S:87%
+S:87%
+
+728
+00:05:43.700 --> 00:05:43.800 D:vertical S:88%
+S:88%
+
+729
+00:05:43.800 --> 00:05:43.900 D:vertical S:89%
+S:89%
+
+730
+00:05:43.900 --> 00:05:44.000 D:vertical S:90%
+S:90%
+
+731
+00:05:44.000 --> 00:05:44.100 D:vertical S:91%
+S:91%
+
+732
+00:05:44.100 --> 00:05:44.200 D:vertical S:92%
+S:92%
+
+733
+00:05:44.200 --> 00:05:44.300 D:vertical S:93%
+S:93%
+
+734
+00:05:44.300 --> 00:05:44.400 D:vertical S:94%
+S:94%
+
+735
+00:05:44.400 --> 00:05:44.500 D:vertical S:95%
+S:95%
+
+736
+00:05:44.500 --> 00:05:44.600 D:vertical S:96%
+S:96%
+
+737
+00:05:44.600 --> 00:05:44.700 D:vertical S:97%
+S:97%
+
+738
+00:05:44.700 --> 00:05:44.800 D:vertical S:98%
+S:98%
+
+739
+00:05:44.800 --> 00:05:44.900 D:vertical S:99%
+S:99%
+
+740
+00:05:44.900 --> 00:05:45.000 D:vertical S:100%
+S:100%
+
+741
+00:05:45.000 --> 00:05:55.000
+Test 15: Vertical-LR cue size
+
+742
+00:05:55.000 --> 00:05:55.100 D:vertical-lr S:1%
+S:1%
+
+743
+00:05:55.100 --> 00:05:55.200 D:vertical-lr S:2%
+S:2%
+
+744
+00:05:55.200 --> 00:05:55.300 D:vertical-lr S:3%
+S:3%
+
+745
+00:05:55.300 --> 00:05:55.400 D:vertical-lr S:4%
+S:4%
+
+746
+00:05:55.400 --> 00:05:55.500 D:vertical-lr S:5%
+S:5%
+
+747
+00:05:55.500 --> 00:05:55.600 D:vertical-lr S:6%
+S:6%
+
+748
+00:05:55.600 --> 00:05:55.700 D:vertical-lr S:7%
+S:7%
+
+749
+00:05:55.700 --> 00:05:55.800 D:vertical-lr S:8%
+S:8%
+
+750
+00:05:55.800 --> 00:05:55.900 D:vertical-lr S:9%
+S:9%
+
+751
+00:05:55.900 --> 00:05:56.000 D:vertical-lr S:10%
+S:10%
+
+752
+00:05:56.000 --> 00:05:56.100 D:vertical-lr S:11%
+S:11%
+
+753
+00:05:56.100 --> 00:05:56.200 D:vertical-lr S:12%
+S:12%
+
+754
+00:05:56.200 --> 00:05:56.300 D:vertical-lr S:13%
+S:13%
+
+755
+00:05:56.300 --> 00:05:56.400 D:vertical-lr S:14%
+S:14%
+
+756
+00:05:56.400 --> 00:05:56.500 D:vertical-lr S:15%
+S:15%
+
+757
+00:05:56.500 --> 00:05:56.600 D:vertical-lr S:16%
+S:16%
+
+758
+00:05:56.600 --> 00:05:56.700 D:vertical-lr S:17%
+S:17%
+
+759
+00:05:56.700 --> 00:05:56.800 D:vertical-lr S:18%
+S:18%
+
+760
+00:05:56.800 --> 00:05:56.900 D:vertical-lr S:19%
+S:19%
+
+761
+00:05:56.900 --> 00:05:57.000 D:vertical-lr S:20%
+S:20%
+
+762
+00:05:57.000 --> 00:05:57.100 D:vertical-lr S:21%
+S:21%
+
+763
+00:05:57.100 --> 00:05:57.200 D:vertical-lr S:22%
+S:22%
+
+764
+00:05:57.200 --> 00:05:57.300 D:vertical-lr S:23%
+S:23%
+
+765
+00:05:57.300 --> 00:05:57.400 D:vertical-lr S:24%
+S:24%
+
+766
+00:05:57.400 --> 00:05:57.500 D:vertical-lr S:25%
+S:25%
+
+767
+00:05:57.500 --> 00:05:57.600 D:vertical-lr S:26%
+S:26%
+
+768
+00:05:57.600 --> 00:05:57.700 D:vertical-lr S:27%
+S:27%
+
+769
+00:05:57.700 --> 00:05:57.800 D:vertical-lr S:28%
+S:28%
+
+770
+00:05:57.800 --> 00:05:57.900 D:vertical-lr S:29%
+S:29%
+
+771
+00:05:57.900 --> 00:05:58.000 D:vertical-lr S:30%
+S:30%
+
+772
+00:05:58.000 --> 00:05:58.100 D:vertical-lr S:31%
+S:31%
+
+773
+00:05:58.100 --> 00:05:58.200 D:vertical-lr S:32%
+S:32%
+
+774
+00:05:58.200 --> 00:05:58.300 D:vertical-lr S:33%
+S:33%
+
+775
+00:05:58.300 --> 00:05:58.400 D:vertical-lr S:34%
+S:34%
+
+776
+00:05:58.400 --> 00:05:58.500 D:vertical-lr S:35%
+S:35%
+
+777
+00:05:58.500 --> 00:05:58.600 D:vertical-lr S:36%
+S:36%
+
+778
+00:05:58.600 --> 00:05:58.700 D:vertical-lr S:37%
+S:37%
+
+779
+00:05:58.700 --> 00:05:58.800 D:vertical-lr S:38%
+S:38%
+
+780
+00:05:58.800 --> 00:05:58.900 D:vertical-lr S:39%
+S:39%
+
+781
+00:05:58.900 --> 00:05:59.000 D:vertical-lr S:40%
+S:40%
+
+782
+00:05:59.000 --> 00:05:59.100 D:vertical-lr S:41%
+S:41%
+
+783
+00:05:59.100 --> 00:05:59.200 D:vertical-lr S:42%
+S:42%
+
+784
+00:05:59.200 --> 00:05:59.300 D:vertical-lr S:43%
+S:43%
+
+785
+00:05:59.300 --> 00:05:59.400 D:vertical-lr S:44%
+S:44%
+
+786
+00:05:59.400 --> 00:05:59.500 D:vertical-lr S:45%
+S:45%
+
+787
+00:05:59.500 --> 00:05:59.600 D:vertical-lr S:46%
+S:46%
+
+788
+00:05:59.600 --> 00:05:59.700 D:vertical-lr S:47%
+S:47%
+
+789
+00:05:59.700 --> 00:05:59.800 D:vertical-lr S:48%
+S:48%
+
+790
+00:05:59.800 --> 00:05:59.900 D:vertical-lr S:49%
+S:49%
+
+791
+00:05:59.900 --> 00:05:60.000 D:vertical-lr S:50%
+S:50%
+
+792
+00:06:00.000 --> 00:06:00.100 D:vertical-lr S:51%
+S:51%
+
+793
+00:06:00.100 --> 00:06:00.200 D:vertical-lr S:52%
+S:52%
+
+794
+00:06:00.200 --> 00:06:00.300 D:vertical-lr S:53%
+S:53%
+
+795
+00:06:00.300 --> 00:06:00.400 D:vertical-lr S:54%
+S:54%
+
+796
+00:06:00.400 --> 00:06:00.500 D:vertical-lr S:55%
+S:55%
+
+797
+00:06:00.500 --> 00:06:00.600 D:vertical-lr S:56%
+S:56%
+
+798
+00:06:00.600 --> 00:06:00.700 D:vertical-lr S:57%
+S:57%
+
+799
+00:06:00.700 --> 00:06:00.800 D:vertical-lr S:58%
+S:58%
+
+800
+00:06:00.800 --> 00:06:00.900 D:vertical-lr S:59%
+S:59%
+
+801
+00:06:00.900 --> 00:06:01.000 D:vertical-lr S:60%
+S:60%
+
+802
+00:06:01.000 --> 00:06:01.100 D:vertical-lr S:61%
+S:61%
+
+803
+00:06:01.100 --> 00:06:01.200 D:vertical-lr S:62%
+S:62%
+
+804
+00:06:01.200 --> 00:06:01.300 D:vertical-lr S:63%
+S:63%
+
+805
+00:06:01.300 --> 00:06:01.400 D:vertical-lr S:64%
+S:64%
+
+806
+00:06:01.400 --> 00:06:01.500 D:vertical-lr S:65%
+S:65%
+
+807
+00:06:01.500 --> 00:06:01.600 D:vertical-lr S:66%
+S:66%
+
+808
+00:06:01.600 --> 00:06:01.700 D:vertical-lr S:67%
+S:67%
+
+809
+00:06:01.700 --> 00:06:01.800 D:vertical-lr S:68%
+S:68%
+
+810
+00:06:01.800 --> 00:06:01.900 D:vertical-lr S:69%
+S:69%
+
+811
+00:06:01.900 --> 00:06:02.000 D:vertical-lr S:70%
+S:70%
+
+812
+00:06:02.000 --> 00:06:02.100 D:vertical-lr S:71%
+S:71%
+
+813
+00:06:02.100 --> 00:06:02.200 D:vertical-lr S:72%
+S:72%
+
+814
+00:06:02.200 --> 00:06:02.300 D:vertical-lr S:73%
+S:73%
+
+815
+00:06:02.300 --> 00:06:02.400 D:vertical-lr S:74%
+S:74%
+
+816
+00:06:02.400 --> 00:06:02.500 D:vertical-lr S:75%
+S:75%
+
+817
+00:06:02.500 --> 00:06:02.600 D:vertical-lr S:76%
+S:76%
+
+818
+00:06:02.600 --> 00:06:02.700 D:vertical-lr S:77%
+S:77%
+
+819
+00:06:02.700 --> 00:06:02.800 D:vertical-lr S:78%
+S:78%
+
+820
+00:06:02.800 --> 00:06:02.900 D:vertical-lr S:79%
+S:79%
+
+821
+00:06:02.900 --> 00:06:03.000 D:vertical-lr S:80%
+S:80%
+
+822
+00:06:03.000 --> 00:06:03.100 D:vertical-lr S:81%
+S:81%
+
+823
+00:06:03.100 --> 00:06:03.200 D:vertical-lr S:82%
+S:82%
+
+824
+00:06:03.200 --> 00:06:03.300 D:vertical-lr S:83%
+S:83%
+
+825
+00:06:03.300 --> 00:06:03.400 D:vertical-lr S:84%
+S:84%
+
+826
+00:06:03.400 --> 00:06:03.500 D:vertical-lr S:85%
+S:85%
+
+827
+00:06:03.500 --> 00:06:03.600 D:vertical-lr S:86%
+S:86%
+
+828
+00:06:03.600 --> 00:06:03.700 D:vertical-lr S:87%
+S:87%
+
+829
+00:06:03.700 --> 00:06:03.800 D:vertical-lr S:88%
+S:88%
+
+830
+00:06:03.800 --> 00:06:03.900 D:vertical-lr S:89%
+S:89%
+
+831
+00:06:03.900 --> 00:06:04.000 D:vertical-lr S:90%
+S:90%
+
+832
+00:06:04.000 --> 00:06:04.100 D:vertical-lr S:91%
+S:91%
+
+833
+00:06:04.100 --> 00:06:04.200 D:vertical-lr S:92%
+S:92%
+
+834
+00:06:04.200 --> 00:06:04.300 D:vertical-lr S:93%
+S:93%
+
+835
+00:06:04.300 --> 00:06:04.400 D:vertical-lr S:94%
+S:94%
+
+836
+00:06:04.400 --> 00:06:04.500 D:vertical-lr S:95%
+S:95%
+
+837
+00:06:04.500 --> 00:06:04.600 D:vertical-lr S:96%
+S:96%
+
+838
+00:06:04.600 --> 00:06:04.700 D:vertical-lr S:97%
+S:97%
+
+839
+00:06:04.700 --> 00:06:04.800 D:vertical-lr S:98%
+S:98%
+
+840
+00:06:04.800 --> 00:06:04.900 D:vertical-lr S:99%
+S:99%
+
+841
+00:06:04.900 --> 00:06:10.000 D:vertical-lr S:100%
+S:100%
+
+842
+00:06:10.000 --> 00:06:20.000
+Test 16: Vertical text position
+
+843
+00:06:20.000 --> 00:06:20.100 D:vertical S:25% T:1%
+T:1%
+
+844
+00:06:20.100 --> 00:06:20.200 D:vertical S:25% T:2%
+T:2%
+
+845
+00:06:20.200 --> 00:06:20.300 D:vertical S:25% T:3%
+T:3%
+
+846
+00:06:20.300 --> 00:06:20.400 D:vertical S:25% T:4%
+T:4%
+
+847
+00:06:20.400 --> 00:06:20.500 D:vertical S:25% T:5%
+T:5%
+
+848
+00:06:20.500 --> 00:06:20.600 D:vertical S:25% T:6%
+T:6%
+
+849
+00:06:20.600 --> 00:06:20.700 D:vertical S:25% T:7%
+T:7%
+
+850
+00:06:20.700 --> 00:06:20.800 D:vertical S:25% T:8%
+T:8%
+
+851
+00:06:20.800 --> 00:06:20.900 D:vertical S:25% T:9%
+T:9%
+
+852
+00:06:20.900 --> 00:06:21.000 D:vertical S:25% T:10%
+T:10%
+
+853
+00:06:21.000 --> 00:06:21.100 D:vertical S:25% T:11%
+T:11%
+
+854
+00:06:21.100 --> 00:06:21.200 D:vertical S:25% T:12%
+T:12%
+
+855
+00:06:21.200 --> 00:06:21.300 D:vertical S:25% T:13%
+T:13%
+
+856
+00:06:21.300 --> 00:06:21.400 D:vertical S:25% T:14%
+T:14%
+
+857
+00:06:21.400 --> 00:06:21.500 D:vertical S:25% T:15%
+T:15%
+
+858
+00:06:21.500 --> 00:06:21.600 D:vertical S:25% T:16%
+T:16%
+
+859
+00:06:21.600 --> 00:06:21.700 D:vertical S:25% T:17%
+T:17%
+
+860
+00:06:21.700 --> 00:06:21.800 D:vertical S:25% T:18%
+T:18%
+
+861
+00:06:21.800 --> 00:06:21.900 D:vertical S:25% T:19%
+T:19%
+
+862
+00:06:21.900 --> 00:06:22.000 D:vertical S:25% T:20%
+T:20%
+
+863
+00:06:22.000 --> 00:06:22.100 D:vertical S:25% T:21%
+T:21%
+
+864
+00:06:22.100 --> 00:06:22.200 D:vertical S:25% T:22%
+T:22%
+
+865
+00:06:22.200 --> 00:06:22.300 D:vertical S:25% T:23%
+T:23%
+
+866
+00:06:22.300 --> 00:06:22.400 D:vertical S:25% T:24%
+T:24%
+
+867
+00:06:22.400 --> 00:06:22.500 D:vertical S:25% T:25%
+T:25%
+
+868
+00:06:22.500 --> 00:06:22.600 D:vertical S:25% T:26%
+T:26%
+
+869
+00:06:22.600 --> 00:06:22.700 D:vertical S:25% T:27%
+T:27%
+
+870
+00:06:22.700 --> 00:06:22.800 D:vertical S:25% T:28%
+T:28%
+
+871
+00:06:22.800 --> 00:06:22.900 D:vertical S:25% T:29%
+T:29%
+
+872
+00:06:22.900 --> 00:06:23.000 D:vertical S:25% T:30%
+T:30%
+
+873
+00:06:23.000 --> 00:06:23.100 D:vertical S:25% T:31%
+T:31%
+
+874
+00:06:23.100 --> 00:06:23.200 D:vertical S:25% T:32%
+T:32%
+
+875
+00:06:23.200 --> 00:06:23.300 D:vertical S:25% T:33%
+T:33%
+
+876
+00:06:23.300 --> 00:06:23.400 D:vertical S:25% T:34%
+T:34%
+
+877
+00:06:23.400 --> 00:06:23.500 D:vertical S:25% T:35%
+T:35%
+
+878
+00:06:23.500 --> 00:06:23.600 D:vertical S:25% T:36%
+T:36%
+
+879
+00:06:23.600 --> 00:06:23.700 D:vertical S:25% T:37%
+T:37%
+
+880
+00:06:23.700 --> 00:06:23.800 D:vertical S:25% T:38%
+T:38%
+
+881
+00:06:23.800 --> 00:06:23.900 D:vertical S:25% T:39%
+T:39%
+
+882
+00:06:23.900 --> 00:06:24.000 D:vertical S:25% T:40%
+T:40%
+
+883
+00:06:24.000 --> 00:06:24.100 D:vertical S:25% T:41%
+T:41%
+
+884
+00:06:24.100 --> 00:06:24.200 D:vertical S:25% T:42%
+T:42%
+
+885
+00:06:24.200 --> 00:06:24.300 D:vertical S:25% T:43%
+T:43%
+
+886
+00:06:24.300 --> 00:06:24.400 D:vertical S:25% T:44%
+T:44%
+
+887
+00:06:24.400 --> 00:06:24.500 D:vertical S:25% T:45%
+T:45%
+
+888
+00:06:24.500 --> 00:06:24.600 D:vertical S:25% T:46%
+T:46%
+
+889
+00:06:24.600 --> 00:06:24.700 D:vertical S:25% T:47%
+T:47%
+
+890
+00:06:24.700 --> 00:06:24.800 D:vertical S:25% T:48%
+T:48%
+
+891
+00:06:24.800 --> 00:06:24.900 D:vertical S:25% T:49%
+T:49%
+
+892
+00:06:24.900 --> 00:06:25.000 D:vertical S:25% T:50%
+T:50%
+
+893
+00:06:25.000 --> 00:06:25.100 D:vertical S:25% T:51%
+T:51%
+
+894
+00:06:25.100 --> 00:06:25.200 D:vertical S:25% T:52%
+T:52%
+
+895
+00:06:25.200 --> 00:06:25.300 D:vertical S:25% T:53%
+T:53%
+
+896
+00:06:25.300 --> 00:06:25.400 D:vertical S:25% T:54%
+T:54%
+
+897
+00:06:25.400 --> 00:06:25.500 D:vertical S:25% T:55%
+T:55%
+
+898
+00:06:25.500 --> 00:06:25.600 D:vertical S:25% T:56%
+T:56%
+
+899
+00:06:25.600 --> 00:06:25.700 D:vertical S:25% T:57%
+T:57%
+
+900
+00:06:25.700 --> 00:06:25.800 D:vertical S:25% T:58%
+T:58%
+
+901
+00:06:25.800 --> 00:06:25.900 D:vertical S:25% T:59%
+T:59%
+
+902
+00:06:25.900 --> 00:06:26.000 D:vertical S:25% T:60%
+T:60%
+
+903
+00:06:26.000 --> 00:06:26.100 D:vertical S:25% T:61%
+T:61%
+
+904
+00:06:26.100 --> 00:06:26.200 D:vertical S:25% T:62%
+T:62%
+
+905
+00:06:26.200 --> 00:06:26.300 D:vertical S:25% T:63%
+T:63%
+
+906
+00:06:26.300 --> 00:06:26.400 D:vertical S:25% T:64%
+T:64%
+
+907
+00:06:26.400 --> 00:06:26.500 D:vertical S:25% T:65%
+T:65%
+
+908
+00:06:26.500 --> 00:06:26.600 D:vertical S:25% T:66%
+T:66%
+
+909
+00:06:26.600 --> 00:06:26.700 D:vertical S:25% T:67%
+T:67%
+
+910
+00:06:26.700 --> 00:06:26.800 D:vertical S:25% T:68%
+T:68%
+
+911
+00:06:26.800 --> 00:06:26.900 D:vertical S:25% T:69%
+T:69%
+
+912
+00:06:26.900 --> 00:06:27.000 D:vertical S:25% T:70%
+T:70%
+
+913
+00:06:27.000 --> 00:06:27.100 D:vertical S:25% T:71%
+T:71%
+
+914
+00:06:27.100 --> 00:06:27.200 D:vertical S:25% T:72%
+T:72%
+
+915
+00:06:27.200 --> 00:06:27.300 D:vertical S:25% T:73%
+T:73%
+
+916
+00:06:27.300 --> 00:06:27.400 D:vertical S:25% T:74%
+T:74%
+
+917
+00:06:27.400 --> 00:06:27.500 D:vertical S:25% T:75%
+T:75%
+
+918
+00:06:27.500 --> 00:06:27.600 D:vertical S:25% T:76%
+T:76%
+
+919
+00:06:27.600 --> 00:06:27.700 D:vertical S:25% T:77%
+T:77%
+
+920
+00:06:27.700 --> 00:06:27.800 D:vertical S:25% T:78%
+T:78%
+
+921
+00:06:27.800 --> 00:06:27.900 D:vertical S:25% T:79%
+T:79%
+
+922
+00:06:27.900 --> 00:06:28.000 D:vertical S:25% T:80%
+T:80%
+
+923
+00:06:28.000 --> 00:06:28.100 D:vertical S:25% T:81%
+T:81%
+
+924
+00:06:28.100 --> 00:06:28.200 D:vertical S:25% T:82%
+T:82%
+
+925
+00:06:28.200 --> 00:06:28.300 D:vertical S:25% T:83%
+T:83%
+
+926
+00:06:28.300 --> 00:06:28.400 D:vertical S:25% T:84%
+T:84%
+
+927
+00:06:28.400 --> 00:06:28.500 D:vertical S:25% T:85%
+T:85%
+
+928
+00:06:28.500 --> 00:06:28.600 D:vertical S:25% T:86%
+T:86%
+
+929
+00:06:28.600 --> 00:06:28.700 D:vertical S:25% T:87%
+T:87%
+
+930
+00:06:28.700 --> 00:06:28.800 D:vertical S:25% T:88%
+T:88%
+
+931
+00:06:28.800 --> 00:06:28.900 D:vertical S:25% T:89%
+T:89%
+
+932
+00:06:28.900 --> 00:06:29.000 D:vertical S:25% T:90%
+T:90%
+
+933
+00:06:29.000 --> 00:06:29.100 D:vertical S:25% T:91%
+T:91%
+
+934
+00:06:29.100 --> 00:06:29.200 D:vertical S:25% T:92%
+T:92%
+
+935
+00:06:29.200 --> 00:06:29.300 D:vertical S:25% T:93%
+T:93%
+
+936
+00:06:29.300 --> 00:06:29.400 D:vertical S:25% T:94%
+T:94%
+
+937
+00:06:29.400 --> 00:06:29.500 D:vertical S:25% T:95%
+T:95%
+
+938
+00:06:29.500 --> 00:06:29.600 D:vertical S:25% T:96%
+T:96%
+
+939
+00:06:29.600 --> 00:06:29.700 D:vertical S:25% T:97%
+T:97%
+
+940
+00:06:29.700 --> 00:06:29.800 D:vertical S:25% T:98%
+T:98%
+
+941
+00:06:29.800 --> 00:06:29.900 D:vertical S:25% T:99%
+T:99%
+
+942
+00:06:29.900 --> 00:06:30.000 D:vertical S:25% T:100%
+T:100%
+
+943
+00:06:30.000 --> 00:06:40.000
+Test 17: Vertical-LR text position
+
+944
+00:06:40.000 --> 00:06:40.100 D:vertical-lr S:25% T:1%
+T:1%
+
+945
+00:06:40.100 --> 00:06:40.200 D:vertical-lr S:25% T:2%
+T:2%
+
+946
+00:06:40.200 --> 00:06:40.300 D:vertical-lr S:25% T:3%
+T:3%
+
+947
+00:06:40.300 --> 00:06:40.400 D:vertical-lr S:25% T:4%
+T:4%
+
+948
+00:06:40.400 --> 00:06:40.500 D:vertical-lr S:25% T:5%
+T:5%
+
+949
+00:06:40.500 --> 00:06:40.600 D:vertical-lr S:25% T:6%
+T:6%
+
+950
+00:06:40.600 --> 00:06:40.700 D:vertical-lr S:25% T:7%
+T:7%
+
+951
+00:06:40.700 --> 00:06:40.800 D:vertical-lr S:25% T:8%
+T:8%
+
+952
+00:06:40.800 --> 00:06:40.900 D:vertical-lr S:25% T:9%
+T:9%
+
+953
+00:06:40.900 --> 00:06:41.000 D:vertical-lr S:25% T:10%
+T:10%
+
+954
+00:06:41.000 --> 00:06:41.100 D:vertical-lr S:25% T:11%
+T:11%
+
+955
+00:06:41.100 --> 00:06:41.200 D:vertical-lr S:25% T:12%
+T:12%
+
+956
+00:06:41.200 --> 00:06:41.300 D:vertical-lr S:25% T:13%
+T:13%
+
+957
+00:06:41.300 --> 00:06:41.400 D:vertical-lr S:25% T:14%
+T:14%
+
+958
+00:06:41.400 --> 00:06:41.500 D:vertical-lr S:25% T:15%
+T:15%
+
+959
+00:06:41.500 --> 00:06:41.600 D:vertical-lr S:25% T:16%
+T:16%
+
+960
+00:06:41.600 --> 00:06:41.700 D:vertical-lr S:25% T:17%
+T:17%
+
+961
+00:06:41.700 --> 00:06:41.800 D:vertical-lr S:25% T:18%
+T:18%
+
+962
+00:06:41.800 --> 00:06:41.900 D:vertical-lr S:25% T:19%
+T:19%
+
+963
+00:06:41.900 --> 00:06:42.000 D:vertical-lr S:25% T:20%
+T:20%
+
+964
+00:06:42.000 --> 00:06:42.100 D:vertical-lr S:25% T:21%
+T:21%
+
+965
+00:06:42.100 --> 00:06:42.200 D:vertical-lr S:25% T:22%
+T:22%
+
+966
+00:06:42.200 --> 00:06:42.300 D:vertical-lr S:25% T:23%
+T:23%
+
+967
+00:06:42.300 --> 00:06:42.400 D:vertical-lr S:25% T:24%
+T:24%
+
+968
+00:06:42.400 --> 00:06:42.500 D:vertical-lr S:25% T:25%
+T:25%
+
+969
+00:06:42.500 --> 00:06:42.600 D:vertical-lr S:25% T:26%
+T:26%
+
+970
+00:06:42.600 --> 00:06:42.700 D:vertical-lr S:25% T:27%
+T:27%
+
+971
+00:06:42.700 --> 00:06:42.800 D:vertical-lr S:25% T:28%
+T:28%
+
+972
+00:06:42.800 --> 00:06:42.900 D:vertical-lr S:25% T:29%
+T:29%
+
+973
+00:06:42.900 --> 00:06:43.000 D:vertical-lr S:25% T:30%
+T:30%
+
+974
+00:06:43.000 --> 00:06:43.100 D:vertical-lr S:25% T:31%
+T:31%
+
+975
+00:06:43.100 --> 00:06:43.200 D:vertical-lr S:25% T:32%
+T:32%
+
+976
+00:06:43.200 --> 00:06:43.300 D:vertical-lr S:25% T:33%
+T:33%
+
+977
+00:06:43.300 --> 00:06:43.400 D:vertical-lr S:25% T:34%
+T:34%
+
+978
+00:06:43.400 --> 00:06:43.500 D:vertical-lr S:25% T:35%
+T:35%
+
+979
+00:06:43.500 --> 00:06:43.600 D:vertical-lr S:25% T:36%
+T:36%
+
+980
+00:06:43.600 --> 00:06:43.700 D:vertical-lr S:25% T:37%
+T:37%
+
+981
+00:06:43.700 --> 00:06:43.800 D:vertical-lr S:25% T:38%
+T:38%
+
+982
+00:06:43.800 --> 00:06:43.900 D:vertical-lr S:25% T:39%
+T:39%
+
+983
+00:06:43.900 --> 00:06:44.000 D:vertical-lr S:25% T:40%
+T:40%
+
+984
+00:06:44.000 --> 00:06:44.100 D:vertical-lr S:25% T:41%
+T:41%
+
+985
+00:06:44.100 --> 00:06:44.200 D:vertical-lr S:25% T:42%
+T:42%
+
+986
+00:06:44.200 --> 00:06:44.300 D:vertical-lr S:25% T:43%
+T:43%
+
+987
+00:06:44.300 --> 00:06:44.400 D:vertical-lr S:25% T:44%
+T:44%
+
+988
+00:06:44.400 --> 00:06:44.500 D:vertical-lr S:25% T:45%
+T:45%
+
+989
+00:06:44.500 --> 00:06:44.600 D:vertical-lr S:25% T:46%
+T:46%
+
+990
+00:06:44.600 --> 00:06:44.700 D:vertical-lr S:25% T:47%
+T:47%
+
+991
+00:06:44.700 --> 00:06:44.800 D:vertical-lr S:25% T:48%
+T:48%
+
+992
+00:06:44.800 --> 00:06:44.900 D:vertical-lr S:25% T:49%
+T:49%
+
+993
+00:06:44.900 --> 00:06:45.000 D:vertical-lr S:25% T:50%
+T:50%
+
+994
+00:06:45.000 --> 00:06:45.100 D:vertical-lr S:25% T:51%
+T:51%
+
+995
+00:06:45.100 --> 00:06:45.200 D:vertical-lr S:25% T:52%
+T:52%
+
+996
+00:06:45.200 --> 00:06:45.300 D:vertical-lr S:25% T:53%
+T:53%
+
+997
+00:06:45.300 --> 00:06:45.400 D:vertical-lr S:25% T:54%
+T:54%
+
+998
+00:06:45.400 --> 00:06:45.500 D:vertical-lr S:25% T:55%
+T:55%
+
+999
+00:06:45.500 --> 00:06:45.600 D:vertical-lr S:25% T:56%
+T:56%
+
+1000
+00:06:45.600 --> 00:06:45.700 D:vertical-lr S:25% T:57%
+T:57%
+
+1001
+00:06:45.700 --> 00:06:45.800 D:vertical-lr S:25% T:58%
+T:58%
+
+1002
+00:06:45.800 --> 00:06:45.900 D:vertical-lr S:25% T:59%
+T:59%
+
+1003
+00:06:45.900 --> 00:06:46.000 D:vertical-lr S:25% T:60%
+T:60%
+
+1004
+00:06:46.000 --> 00:06:46.100 D:vertical-lr S:25% T:61%
+T:61%
+
+1005
+00:06:46.100 --> 00:06:46.200 D:vertical-lr S:25% T:62%
+T:62%
+
+1006
+00:06:46.200 --> 00:06:46.300 D:vertical-lr S:25% T:63%
+T:63%
+
+1007
+00:06:46.300 --> 00:06:46.400 D:vertical-lr S:25% T:64%
+T:64%
+
+1008
+00:06:46.400 --> 00:06:46.500 D:vertical-lr S:25% T:65%
+T:65%
+
+1009
+00:06:46.500 --> 00:06:46.600 D:vertical-lr S:25% T:66%
+T:66%
+
+1010
+00:06:46.600 --> 00:06:46.700 D:vertical-lr S:25% T:67%
+T:67%
+
+1011
+00:06:46.700 --> 00:06:46.800 D:vertical-lr S:25% T:68%
+T:68%
+
+1012
+00:06:46.800 --> 00:06:46.900 D:vertical-lr S:25% T:69%
+T:69%
+
+1013
+00:06:46.900 --> 00:06:47.000 D:vertical-lr S:25% T:70%
+T:70%
+
+1014
+00:06:47.000 --> 00:06:47.100 D:vertical-lr S:25% T:71%
+T:71%
+
+1015
+00:06:47.100 --> 00:06:47.200 D:vertical-lr S:25% T:72%
+T:72%
+
+1016
+00:06:47.200 --> 00:06:47.300 D:vertical-lr S:25% T:73%
+T:73%
+
+1017
+00:06:47.300 --> 00:06:47.400 D:vertical-lr S:25% T:74%
+T:74%
+
+1018
+00:06:47.400 --> 00:06:47.500 D:vertical-lr S:25% T:75%
+T:75%
+
+1019
+00:06:47.500 --> 00:06:47.600 D:vertical-lr S:25% T:76%
+T:76%
+
+1020
+00:06:47.600 --> 00:06:47.700 D:vertical-lr S:25% T:77%
+T:77%
+
+1021
+00:06:47.700 --> 00:06:47.800 D:vertical-lr S:25% T:78%
+T:78%
+
+1022
+00:06:47.800 --> 00:06:47.900 D:vertical-lr S:25% T:79%
+T:79%
+
+1023
+00:06:47.900 --> 00:06:48.000 D:vertical-lr S:25% T:80%
+T:80%
+
+1024
+00:06:48.000 --> 00:06:48.100 D:vertical-lr S:25% T:81%
+T:81%
+
+1025
+00:06:48.100 --> 00:06:48.200 D:vertical-lr S:25% T:82%
+T:82%
+
+1026
+00:06:48.200 --> 00:06:48.300 D:vertical-lr S:25% T:83%
+T:83%
+
+1027
+00:06:48.300 --> 00:06:48.400 D:vertical-lr S:25% T:84%
+T:84%
+
+1028
+00:06:48.400 --> 00:06:48.500 D:vertical-lr S:25% T:85%
+T:85%
+
+1029
+00:06:48.500 --> 00:06:48.600 D:vertical-lr S:25% T:86%
+T:86%
+
+1030
+00:06:48.600 --> 00:06:48.700 D:vertical-lr S:25% T:87%
+T:87%
+
+1031
+00:06:48.700 --> 00:06:48.800 D:vertical-lr S:25% T:88%
+T:88%
+
+1032
+00:06:48.800 --> 00:06:48.900 D:vertical-lr S:25% T:89%
+T:89%
+
+1033
+00:06:48.900 --> 00:06:49.000 D:vertical-lr S:25% T:90%
+T:90%
+
+1034
+00:06:49.000 --> 00:06:49.100 D:vertical-lr S:25% T:91%
+T:91%
+
+1035
+00:06:49.100 --> 00:06:49.200 D:vertical-lr S:25% T:92%
+T:92%
+
+1036
+00:06:49.200 --> 00:06:49.300 D:vertical-lr S:25% T:93%
+T:93%
+
+1037
+00:06:49.300 --> 00:06:49.400 D:vertical-lr S:25% T:94%
+T:94%
+
+1038
+00:06:49.400 --> 00:06:49.500 D:vertical-lr S:25% T:95%
+T:95%
+
+1039
+00:06:49.500 --> 00:06:49.600 D:vertical-lr S:25% T:96%
+T:96%
+
+1040
+00:06:49.600 --> 00:06:49.700 D:vertical-lr S:25% T:97%
+T:97%
+
+1041
+00:06:49.700 --> 00:06:49.800 D:vertical-lr S:25% T:98%
+T:98%
+
+1042
+00:06:49.800 --> 00:06:49.900 D:vertical-lr S:25% T:99%
+T:99%
+
+1043
+00:06:49.900 --> 00:06:50.000 D:vertical-lr S:25% T:100%
+T:100%
+
+1044
+00:06:50.000 --> 00:07:00.000
+Test 18: Combined Ultimate Test!
+
+1045
+00:07:00.000 --> 00:07:00.100 D:vertical-lr L:1% S:1% T:1%
+1%
+
+1046
+00:07:00.000 --> 00:07:00.100 D:vertical L:1% S:1% T:1%
+1%
+
+1047
+00:07:00.000 --> 00:07:00.100 D:horizontal L:1% S:1% T:1%
+1%
+
+1048
+00:07:00.100 --> 00:07:00.200 D:vertical-lr L:2% S:2% T:2%
+2%
+
+1049
+00:07:00.100 --> 00:07:00.200 D:vertical L:2% S:2% T:2%
+2%
+
+1050
+00:07:00.100 --> 00:07:00.200 D:horizontal L:2% S:2% T:2%
+2%
+
+1051
+00:07:00.200 --> 00:07:00.300 D:vertical-lr L:3% S:3% T:3%
+3%
+
+1052
+00:07:00.200 --> 00:07:00.300 D:vertical L:3% S:3% T:3%
+3%
+
+1053
+00:07:00.200 --> 00:07:00.300 D:horizontal L:3% S:3% T:3%
+3%
+
+1054
+00:07:00.300 --> 00:07:00.400 D:vertical-lr L:4% S:4% T:4%
+4%
+
+1055
+00:07:00.300 --> 00:07:00.400 D:vertical L:4% S:4% T:4%
+4%
+
+1056
+00:07:00.300 --> 00:07:00.400 D:horizontal L:4% S:4% T:4%
+4%
+
+1057
+00:07:00.400 --> 00:07:00.500 D:vertical-lr L:5% S:5% T:5%
+5%
+
+1058
+00:07:00.400 --> 00:07:00.500 D:vertical L:5% S:5% T:5%
+5%
+
+1059
+00:07:00.400 --> 00:07:00.500 D:horizontal L:5% S:5% T:5%
+5%
+
+1060
+00:07:00.500 --> 00:07:00.600 D:vertical-lr L:6% S:6% T:6%
+6%
+
+1061
+00:07:00.500 --> 00:07:00.600 D:vertical L:6% S:6% T:6%
+6%
+
+1062
+00:07:00.500 --> 00:07:00.600 D:horizontal L:6% S:6% T:6%
+6%
+
+1063
+00:07:00.600 --> 00:07:00.700 D:vertical-lr L:7% S:7% T:7%
+7%
+
+1064
+00:07:00.600 --> 00:07:00.700 D:vertical L:7% S:7% T:7%
+7%
+
+1065
+00:07:00.600 --> 00:07:00.700 D:horizontal L:7% S:7% T:7%
+7%
+
+1066
+00:07:00.700 --> 00:07:00.700 D:vertical-lr L:8% S:8% T:8%
+8%
+
+1067
+00:07:00.700 --> 00:07:00.700 D:vertical L:8% S:8% T:8%
+8%
+
+1068
+00:07:00.700 --> 00:07:00.700 D:horizontal L:8% S:8% T:8%
+8%
+
+1069
+00:07:00.700 --> 00:07:00.900 D:vertical-lr L:9% S:9% T:9%
+9%
+
+1070
+00:07:00.700 --> 00:07:00.900 D:vertical L:9% S:9% T:9%
+9%
+
+1071
+00:07:00.700 --> 00:07:00.900 D:horizontal L:9% S:9% T:9%
+9%
+
+1072
+00:07:00.900 --> 00:07:00.900 D:vertical-lr L:10% S:10% T:10%
+10%
+
+1073
+00:07:00.900 --> 00:07:00.900 D:vertical L:10% S:10% T:10%
+10%
+
+1074
+00:07:00.900 --> 00:07:00.900 D:horizontal L:10% S:10% T:10%
+10%
+
+1075
+00:07:00.900 --> 00:07:01.000 D:vertical-lr L:11% S:11% T:11%
+11%
+
+1076
+00:07:00.900 --> 00:07:01.000 D:vertical L:11% S:11% T:11%
+11%
+
+1077
+00:07:00.900 --> 00:07:01.000 D:horizontal L:11% S:11% T:11%
+11%
+
+1078
+00:07:01.000 --> 00:07:01.200 D:vertical-lr L:12% S:12% T:12%
+12%
+
+1079
+00:07:01.000 --> 00:07:01.200 D:vertical L:12% S:12% T:12%
+12%
+
+1080
+00:07:01.000 --> 00:07:01.200 D:horizontal L:12% S:12% T:12%
+12%
+
+1081
+00:07:01.200 --> 00:07:01.300 D:vertical-lr L:13% S:13% T:13%
+13%
+
+1082
+00:07:01.200 --> 00:07:01.300 D:vertical L:13% S:13% T:13%
+13%
+
+1083
+00:07:01.200 --> 00:07:01.300 D:horizontal L:13% S:13% T:13%
+13%
+
+1084
+00:07:01.300 --> 00:07:01.400 D:vertical-lr L:14% S:14% T:14%
+14%
+
+1085
+00:07:01.300 --> 00:07:01.400 D:vertical L:14% S:14% T:14%
+14%
+
+1086
+00:07:01.300 --> 00:07:01.400 D:horizontal L:14% S:14% T:14%
+14%
+
+1087
+00:07:01.400 --> 00:07:01.500 D:vertical-lr L:15% S:15% T:15%
+15%
+
+1088
+00:07:01.400 --> 00:07:01.500 D:vertical L:15% S:15% T:15%
+15%
+
+1089
+00:07:01.400 --> 00:07:01.500 D:horizontal L:15% S:15% T:15%
+15%
+
+1090
+00:07:01.500 --> 00:07:01.600 D:vertical-lr L:16% S:16% T:16%
+16%
+
+1091
+00:07:01.500 --> 00:07:01.600 D:vertical L:16% S:16% T:16%
+16%
+
+1092
+00:07:01.500 --> 00:07:01.600 D:horizontal L:16% S:16% T:16%
+16%
+
+1093
+00:07:01.600 --> 00:07:01.700 D:vertical-lr L:17% S:17% T:17%
+17%
+
+1094
+00:07:01.600 --> 00:07:01.700 D:vertical L:17% S:17% T:17%
+17%
+
+1095
+00:07:01.600 --> 00:07:01.700 D:horizontal L:17% S:17% T:17%
+17%
+
+1096
+00:07:01.700 --> 00:07:01.800 D:vertical-lr L:18% S:18% T:18%
+18%
+
+1097
+00:07:01.700 --> 00:07:01.800 D:vertical L:18% S:18% T:18%
+18%
+
+1098
+00:07:01.700 --> 00:07:01.800 D:horizontal L:18% S:18% T:18%
+18%
+
+1099
+00:07:01.800 --> 00:07:01.900 D:vertical-lr L:19% S:19% T:19%
+19%
+
+1100
+00:07:01.800 --> 00:07:01.900 D:vertical L:19% S:19% T:19%
+19%
+
+1101
+00:07:01.800 --> 00:07:01.900 D:horizontal L:19% S:19% T:19%
+19%
+
+1102
+00:07:01.900 --> 00:07:02.000 D:vertical-lr L:20% S:20% T:20%
+20%
+
+1103
+00:07:01.900 --> 00:07:02.000 D:vertical L:20% S:20% T:20%
+20%
+
+1104
+00:07:01.900 --> 00:07:02.000 D:horizontal L:20% S:20% T:20%
+20%
+
+1105
+00:07:02.000 --> 00:07:02.100 D:vertical-lr L:21% S:21% T:21%
+21%
+
+1106
+00:07:02.000 --> 00:07:02.100 D:vertical L:21% S:21% T:21%
+21%
+
+1107
+00:07:02.000 --> 00:07:02.100 D:horizontal L:21% S:21% T:21%
+21%
+
+1108
+00:07:02.100 --> 00:07:02.200 D:vertical-lr L:22% S:22% T:22%
+22%
+
+1109
+00:07:02.100 --> 00:07:02.200 D:vertical L:22% S:22% T:22%
+22%
+
+1110
+00:07:02.100 --> 00:07:02.200 D:horizontal L:22% S:22% T:22%
+22%
+
+1111
+00:07:02.200 --> 00:07:02.300 D:vertical-lr L:23% S:23% T:23%
+23%
+
+1112
+00:07:02.200 --> 00:07:02.300 D:vertical L:23% S:23% T:23%
+23%
+
+1113
+00:07:02.200 --> 00:07:02.300 D:horizontal L:23% S:23% T:23%
+23%
+
+1114
+00:07:02.300 --> 00:07:02.400 D:vertical-lr L:24% S:24% T:24%
+24%
+
+1115
+00:07:02.300 --> 00:07:02.400 D:vertical L:24% S:24% T:24%
+24%
+
+1116
+00:07:02.300 --> 00:07:02.400 D:horizontal L:24% S:24% T:24%
+24%
+
+1117
+00:07:02.400 --> 00:07:02.500 D:vertical-lr L:25% S:25% T:25%
+25%
+
+1118
+00:07:02.400 --> 00:07:02.500 D:vertical L:25% S:25% T:25%
+25%
+
+1119
+00:07:02.400 --> 00:07:02.500 D:horizontal L:25% S:25% T:25%
+25%
+
+1120
+00:07:02.500 --> 00:07:02.600 D:vertical-lr L:26% S:26% T:26%
+26%
+
+1121
+00:07:02.500 --> 00:07:02.600 D:vertical L:26% S:26% T:26%
+26%
+
+1122
+00:07:02.500 --> 00:07:02.600 D:horizontal L:26% S:26% T:26%
+26%
+
+1123
+00:07:02.600 --> 00:07:02.700 D:vertical-lr L:27% S:27% T:27%
+27%
+
+1124
+00:07:02.600 --> 00:07:02.700 D:vertical L:27% S:27% T:27%
+27%
+
+1125
+00:07:02.600 --> 00:07:02.700 D:horizontal L:27% S:27% T:27%
+27%
+
+1126
+00:07:02.700 --> 00:07:02.800 D:vertical-lr L:28% S:28% T:28%
+28%
+
+1127
+00:07:02.700 --> 00:07:02.800 D:vertical L:28% S:28% T:28%
+28%
+
+1128
+00:07:02.700 --> 00:07:02.800 D:horizontal L:28% S:28% T:28%
+28%
+
+1129
+00:07:02.800 --> 00:07:02.900 D:vertical-lr L:29% S:29% T:29%
+29%
+
+1130
+00:07:02.800 --> 00:07:02.900 D:vertical L:29% S:29% T:29%
+29%
+
+1131
+00:07:02.800 --> 00:07:02.900 D:horizontal L:29% S:29% T:29%
+29%
+
+1132
+00:07:02.900 --> 00:07:03.000 D:vertical-lr L:30% S:30% T:30%
+30%
+
+1133
+00:07:02.900 --> 00:07:03.000 D:vertical L:30% S:30% T:30%
+30%
+
+1134
+00:07:02.900 --> 00:07:03.000 D:horizontal L:30% S:30% T:30%
+30%
+
+1135
+00:07:03.000 --> 00:07:03.100 D:vertical-lr L:31% S:31% T:31%
+31%
+
+1136
+00:07:03.000 --> 00:07:03.100 D:vertical L:31% S:31% T:31%
+31%
+
+1137
+00:07:03.000 --> 00:07:03.100 D:horizontal L:31% S:31% T:31%
+31%
+
+1138
+00:07:03.100 --> 00:07:03.200 D:vertical-lr L:32% S:32% T:32%
+32%
+
+1139
+00:07:03.100 --> 00:07:03.200 D:vertical L:32% S:32% T:32%
+32%
+
+1140
+00:07:03.100 --> 00:07:03.200 D:horizontal L:32% S:32% T:32%
+32%
+
+1141
+00:07:03.200 --> 00:07:03.300 D:vertical-lr L:33% S:33% T:33%
+33%
+
+1142
+00:07:03.200 --> 00:07:03.300 D:vertical L:33% S:33% T:33%
+33%
+
+1143
+00:07:03.200 --> 00:07:03.300 D:horizontal L:33% S:33% T:33%
+33%
+
+1144
+00:07:03.300 --> 00:07:03.400 D:vertical-lr L:34% S:34% T:34%
+34%
+
+1145
+00:07:03.300 --> 00:07:03.400 D:vertical L:34% S:34% T:34%
+34%
+
+1146
+00:07:03.300 --> 00:07:03.400 D:horizontal L:34% S:34% T:34%
+34%
+
+1147
+00:07:03.400 --> 00:07:03.500 D:vertical-lr L:35% S:35% T:35%
+35%
+
+1148
+00:07:03.400 --> 00:07:03.500 D:vertical L:35% S:35% T:35%
+35%
+
+1149
+00:07:03.400 --> 00:07:03.500 D:horizontal L:35% S:35% T:35%
+35%
+
+1150
+00:07:03.500 --> 00:07:03.600 D:vertical-lr L:36% S:36% T:36%
+36%
+
+1151
+00:07:03.500 --> 00:07:03.600 D:vertical L:36% S:36% T:36%
+36%
+
+1152
+00:07:03.500 --> 00:07:03.600 D:horizontal L:36% S:36% T:36%
+36%
+
+1153
+00:07:03.600 --> 00:07:03.700 D:vertical-lr L:37% S:37% T:37%
+37%
+
+1154
+00:07:03.600 --> 00:07:03.700 D:vertical L:37% S:37% T:37%
+37%
+
+1155
+00:07:03.600 --> 00:07:03.700 D:horizontal L:37% S:37% T:37%
+37%
+
+1156
+00:07:03.700 --> 00:07:03.800 D:vertical-lr L:38% S:38% T:38%
+38%
+
+1157
+00:07:03.700 --> 00:07:03.800 D:vertical L:38% S:38% T:38%
+38%
+
+1158
+00:07:03.700 --> 00:07:03.800 D:horizontal L:38% S:38% T:38%
+38%
+
+1159
+00:07:03.800 --> 00:07:03.900 D:vertical-lr L:39% S:39% T:39%
+39%
+
+1160
+00:07:03.800 --> 00:07:03.900 D:vertical L:39% S:39% T:39%
+39%
+
+1161
+00:07:03.800 --> 00:07:03.900 D:horizontal L:39% S:39% T:39%
+39%
+
+1162
+00:07:03.900 --> 00:07:04.000 D:vertical-lr L:40% S:40% T:40%
+40%
+
+1163
+00:07:03.900 --> 00:07:04.000 D:vertical L:40% S:40% T:40%
+40%
+
+1164
+00:07:03.900 --> 00:07:04.000 D:horizontal L:40% S:40% T:40%
+40%
+
+1165
+00:07:04.000 --> 00:07:04.100 D:vertical-lr L:41% S:41% T:41%
+41%
+
+1166
+00:07:04.000 --> 00:07:04.100 D:vertical L:41% S:41% T:41%
+41%
+
+1167
+00:07:04.000 --> 00:07:04.100 D:horizontal L:41% S:41% T:41%
+41%
+
+1168
+00:07:04.100 --> 00:07:04.200 D:vertical-lr L:42% S:42% T:42%
+42%
+
+1169
+00:07:04.100 --> 00:07:04.200 D:vertical L:42% S:42% T:42%
+42%
+
+1170
+00:07:04.100 --> 00:07:04.200 D:horizontal L:42% S:42% T:42%
+42%
+
+1171
+00:07:04.200 --> 00:07:04.300 D:vertical-lr L:43% S:43% T:43%
+43%
+
+1172
+00:07:04.200 --> 00:07:04.300 D:vertical L:43% S:43% T:43%
+43%
+
+1173
+00:07:04.200 --> 00:07:04.300 D:horizontal L:43% S:43% T:43%
+43%
+
+1174
+00:07:04.300 --> 00:07:04.400 D:vertical-lr L:44% S:44% T:44%
+44%
+
+1175
+00:07:04.300 --> 00:07:04.400 D:vertical L:44% S:44% T:44%
+44%
+
+1176
+00:07:04.300 --> 00:07:04.400 D:horizontal L:44% S:44% T:44%
+44%
+
+1177
+00:07:04.400 --> 00:07:04.500 D:vertical-lr L:45% S:45% T:45%
+45%
+
+1178
+00:07:04.400 --> 00:07:04.500 D:vertical L:45% S:45% T:45%
+45%
+
+1179
+00:07:04.400 --> 00:07:04.500 D:horizontal L:45% S:45% T:45%
+45%
+
+1180
+00:07:04.500 --> 00:07:04.600 D:vertical-lr L:46% S:46% T:46%
+46%
+
+1181
+00:07:04.500 --> 00:07:04.600 D:vertical L:46% S:46% T:46%
+46%
+
+1182
+00:07:04.500 --> 00:07:04.600 D:horizontal L:46% S:46% T:46%
+46%
+
+1183
+00:07:04.600 --> 00:07:04.600 D:vertical-lr L:47% S:47% T:47%
+47%
+
+1184
+00:07:04.600 --> 00:07:04.600 D:vertical L:47% S:47% T:47%
+47%
+
+1185
+00:07:04.600 --> 00:07:04.600 D:horizontal L:47% S:47% T:47%
+47%
+
+1186
+00:07:04.600 --> 00:07:04.700 D:vertical-lr L:48% S:48% T:48%
+48%
+
+1187
+00:07:04.600 --> 00:07:04.700 D:vertical L:48% S:48% T:48%
+48%
+
+1188
+00:07:04.600 --> 00:07:04.700 D:horizontal L:48% S:48% T:48%
+48%
+
+1189
+00:07:04.700 --> 00:07:04.800 D:vertical-lr L:49% S:49% T:49%
+49%
+
+1190
+00:07:04.700 --> 00:07:04.800 D:vertical L:49% S:49% T:49%
+49%
+
+1191
+00:07:04.700 --> 00:07:04.800 D:horizontal L:49% S:49% T:49%
+49%
+
+1192
+00:07:04.800 --> 00:07:04.900 D:vertical-lr L:50% S:50% T:50%
+50%
+
+1193
+00:07:04.800 --> 00:07:04.900 D:vertical L:50% S:50% T:50%
+50%
+
+1194
+00:07:04.800 --> 00:07:04.900 D:horizontal L:50% S:50% T:50%
+50%
+
+1195
+00:07:04.900 --> 00:07:05.000 D:vertical-lr L:51% S:51% T:51%
+51%
+
+1196
+00:07:04.900 --> 00:07:05.000 D:vertical L:51% S:51% T:51%
+51%
+
+1197
+00:07:04.900 --> 00:07:05.000 D:horizontal L:51% S:51% T:51%
+51%
+
+1198
+00:07:05.000 --> 00:07:05.100 D:vertical-lr L:52% S:52% T:52%
+52%
+
+1199
+00:07:05.000 --> 00:07:05.100 D:vertical L:52% S:52% T:52%
+52%
+
+1200
+00:07:05.000 --> 00:07:05.100 D:horizontal L:52% S:52% T:52%
+52%
+
+1201
+00:07:05.100 --> 00:07:05.200 D:vertical-lr L:53% S:53% T:53%
+53%
+
+1202
+00:07:05.100 --> 00:07:05.200 D:vertical L:53% S:53% T:53%
+53%
+
+1203
+00:07:05.100 --> 00:07:05.200 D:horizontal L:53% S:53% T:53%
+53%
+
+1204
+00:07:05.200 --> 00:07:05.300 D:vertical-lr L:54% S:54% T:54%
+54%
+
+1205
+00:07:05.200 --> 00:07:05.300 D:vertical L:54% S:54% T:54%
+54%
+
+1206
+00:07:05.200 --> 00:07:05.300 D:horizontal L:54% S:54% T:54%
+54%
+
+1207
+00:07:05.300 --> 00:07:05.400 D:vertical-lr L:55% S:55% T:55%
+55%
+
+1208
+00:07:05.300 --> 00:07:05.400 D:vertical L:55% S:55% T:55%
+55%
+
+1209
+00:07:05.300 --> 00:07:05.400 D:horizontal L:55% S:55% T:55%
+55%
+
+1210
+00:07:05.400 --> 00:07:05.500 D:vertical-lr L:56% S:56% T:56%
+56%
+
+1211
+00:07:05.400 --> 00:07:05.500 D:vertical L:56% S:56% T:56%
+56%
+
+1212
+00:07:05.400 --> 00:07:05.500 D:horizontal L:56% S:56% T:56%
+56%
+
+1213
+00:07:05.500 --> 00:07:05.600 D:vertical-lr L:57% S:57% T:57%
+57%
+
+1214
+00:07:05.500 --> 00:07:05.600 D:vertical L:57% S:57% T:57%
+57%
+
+1215
+00:07:05.500 --> 00:07:05.600 D:horizontal L:57% S:57% T:57%
+57%
+
+1216
+00:07:05.600 --> 00:07:05.700 D:vertical-lr L:58% S:58% T:58%
+58%
+
+1217
+00:07:05.600 --> 00:07:05.700 D:vertical L:58% S:58% T:58%
+58%
+
+1218
+00:07:05.600 --> 00:07:05.700 D:horizontal L:58% S:58% T:58%
+58%
+
+1219
+00:07:05.700 --> 00:07:05.800 D:vertical-lr L:59% S:59% T:59%
+59%
+
+1220
+00:07:05.700 --> 00:07:05.800 D:vertical L:59% S:59% T:59%
+59%
+
+1221
+00:07:05.700 --> 00:07:05.800 D:horizontal L:59% S:59% T:59%
+59%
+
+1222
+00:07:05.800 --> 00:07:05.900 D:vertical-lr L:60% S:60% T:60%
+60%
+
+1223
+00:07:05.800 --> 00:07:05.900 D:vertical L:60% S:60% T:60%
+60%
+
+1224
+00:07:05.800 --> 00:07:05.900 D:horizontal L:60% S:60% T:60%
+60%
+
+1225
+00:07:05.900 --> 00:07:06.000 D:vertical-lr L:61% S:61% T:61%
+61%
+
+1226
+00:07:05.900 --> 00:07:06.000 D:vertical L:61% S:61% T:61%
+61%
+
+1227
+00:07:05.900 --> 00:07:06.000 D:horizontal L:61% S:61% T:61%
+61%
+
+1228
+00:07:06.000 --> 00:07:06.100 D:vertical-lr L:62% S:62% T:62%
+62%
+
+1229
+00:07:06.000 --> 00:07:06.100 D:vertical L:62% S:62% T:62%
+62%
+
+1230
+00:07:06.000 --> 00:07:06.100 D:horizontal L:62% S:62% T:62%
+62%
+
+1231
+00:07:06.100 --> 00:07:06.200 D:vertical-lr L:63% S:63% T:63%
+63%
+
+1232
+00:07:06.100 --> 00:07:06.200 D:vertical L:63% S:63% T:63%
+63%
+
+1233
+00:07:06.100 --> 00:07:06.200 D:horizontal L:63% S:63% T:63%
+63%
+
+1234
+00:07:06.200 --> 00:07:06.300 D:vertical-lr L:64% S:64% T:64%
+64%
+
+1235
+00:07:06.200 --> 00:07:06.300 D:vertical L:64% S:64% T:64%
+64%
+
+1236
+00:07:06.200 --> 00:07:06.300 D:horizontal L:64% S:64% T:64%
+64%
+
+1237
+00:07:06.300 --> 00:07:06.400 D:vertical-lr L:65% S:65% T:65%
+65%
+
+1238
+00:07:06.300 --> 00:07:06.400 D:vertical L:65% S:65% T:65%
+65%
+
+1239
+00:07:06.300 --> 00:07:06.400 D:horizontal L:65% S:65% T:65%
+65%
+
+1240
+00:07:06.400 --> 00:07:06.500 D:vertical-lr L:66% S:66% T:66%
+66%
+
+1241
+00:07:06.400 --> 00:07:06.500 D:vertical L:66% S:66% T:66%
+66%
+
+1242
+00:07:06.400 --> 00:07:06.500 D:horizontal L:66% S:66% T:66%
+66%
+
+1243
+00:07:06.500 --> 00:07:06.600 D:vertical-lr L:67% S:67% T:67%
+67%
+
+1244
+00:07:06.500 --> 00:07:06.600 D:vertical L:67% S:67% T:67%
+67%
+
+1245
+00:07:06.500 --> 00:07:06.600 D:horizontal L:67% S:67% T:67%
+67%
+
+1246
+00:07:06.600 --> 00:07:06.700 D:vertical-lr L:68% S:68% T:68%
+68%
+
+1247
+00:07:06.600 --> 00:07:06.700 D:vertical L:68% S:68% T:68%
+68%
+
+1248
+00:07:06.600 --> 00:07:06.700 D:horizontal L:68% S:68% T:68%
+68%
+
+1249
+00:07:06.700 --> 00:07:06.800 D:vertical-lr L:69% S:69% T:69%
+69%
+
+1250
+00:07:06.700 --> 00:07:06.800 D:vertical L:69% S:69% T:69%
+69%
+
+1251
+00:07:06.700 --> 00:07:06.800 D:horizontal L:69% S:69% T:69%
+69%
+
+1252
+00:07:06.800 --> 00:07:06.900 D:vertical-lr L:70% S:70% T:70%
+70%
+
+1253
+00:07:06.800 --> 00:07:06.900 D:vertical L:70% S:70% T:70%
+70%
+
+1254
+00:07:06.800 --> 00:07:06.900 D:horizontal L:70% S:70% T:70%
+70%
+
+1255
+00:07:06.900 --> 00:07:07.000 D:vertical-lr L:71% S:71% T:71%
+71%
+
+1256
+00:07:06.900 --> 00:07:07.000 D:vertical L:71% S:71% T:71%
+71%
+
+1257
+00:07:06.900 --> 00:07:07.000 D:horizontal L:71% S:71% T:71%
+71%
+
+1258
+00:07:07.000 --> 00:07:07.100 D:vertical-lr L:72% S:72% T:72%
+72%
+
+1259
+00:07:07.000 --> 00:07:07.100 D:vertical L:72% S:72% T:72%
+72%
+
+1260
+00:07:07.000 --> 00:07:07.100 D:horizontal L:72% S:72% T:72%
+72%
+
+1261
+00:07:07.100 --> 00:07:07.200 D:vertical-lr L:73% S:73% T:73%
+73%
+
+1262
+00:07:07.100 --> 00:07:07.200 D:vertical L:73% S:73% T:73%
+73%
+
+1263
+00:07:07.100 --> 00:07:07.200 D:horizontal L:73% S:73% T:73%
+73%
+
+1264
+00:07:07.200 --> 00:07:07.300 D:vertical-lr L:74% S:74% T:74%
+74%
+
+1265
+00:07:07.200 --> 00:07:07.300 D:vertical L:74% S:74% T:74%
+74%
+
+1266
+00:07:07.200 --> 00:07:07.300 D:horizontal L:74% S:74% T:74%
+74%
+
+1267
+00:07:07.300 --> 00:07:07.400 D:vertical-lr L:75% S:75% T:75%
+75%
+
+1268
+00:07:07.300 --> 00:07:07.400 D:vertical L:75% S:75% T:75%
+75%
+
+1269
+00:07:07.300 --> 00:07:07.400 D:horizontal L:75% S:75% T:75%
+75%
+
+1270
+00:07:07.400 --> 00:07:07.500 D:vertical-lr L:76% S:76% T:76%
+76%
+
+1271
+00:07:07.400 --> 00:07:07.500 D:vertical L:76% S:76% T:76%
+76%
+
+1272
+00:07:07.400 --> 00:07:07.500 D:horizontal L:76% S:76% T:76%
+76%
+
+1273
+00:07:07.500 --> 00:07:07.600 D:vertical-lr L:77% S:77% T:77%
+77%
+
+1274
+00:07:07.500 --> 00:07:07.600 D:vertical L:77% S:77% T:77%
+77%
+
+1275
+00:07:07.500 --> 00:07:07.600 D:horizontal L:77% S:77% T:77%
+77%
+
+1276
+00:07:07.600 --> 00:07:07.700 D:vertical-lr L:78% S:78% T:78%
+78%
+
+1277
+00:07:07.600 --> 00:07:07.700 D:vertical L:78% S:78% T:78%
+78%
+
+1278
+00:07:07.600 --> 00:07:07.700 D:horizontal L:78% S:78% T:78%
+78%
+
+1279
+00:07:07.700 --> 00:07:07.800 D:vertical-lr L:79% S:79% T:79%
+79%
+
+1280
+00:07:07.700 --> 00:07:07.800 D:vertical L:79% S:79% T:79%
+79%
+
+1281
+00:07:07.700 --> 00:07:07.800 D:horizontal L:79% S:79% T:79%
+79%
+
+1282
+00:07:07.800 --> 00:07:07.900 D:vertical-lr L:80% S:80% T:80%
+80%
+
+1283
+00:07:07.800 --> 00:07:07.900 D:vertical L:80% S:80% T:80%
+80%
+
+1284
+00:07:07.800 --> 00:07:07.900 D:horizontal L:80% S:80% T:80%
+80%
+
+1285
+00:07:07.900 --> 00:07:08.000 D:vertical-lr L:81% S:81% T:81%
+81%
+
+1286
+00:07:07.900 --> 00:07:08.000 D:vertical L:81% S:81% T:81%
+81%
+
+1287
+00:07:07.900 --> 00:07:08.000 D:horizontal L:81% S:81% T:81%
+81%
+
+1288
+00:07:08.000 --> 00:07:08.100 D:vertical-lr L:82% S:82% T:82%
+82%
+
+1289
+00:07:08.000 --> 00:07:08.100 D:vertical L:82% S:82% T:82%
+82%
+
+1290
+00:07:08.000 --> 00:07:08.100 D:horizontal L:82% S:82% T:82%
+82%
+
+1291
+00:07:08.100 --> 00:07:08.200 D:vertical-lr L:83% S:83% T:83%
+83%
+
+1292
+00:07:08.100 --> 00:07:08.200 D:vertical L:83% S:83% T:83%
+83%
+
+1293
+00:07:08.100 --> 00:07:08.200 D:horizontal L:83% S:83% T:83%
+83%
+
+1294
+00:07:08.200 --> 00:07:08.300 D:vertical-lr L:84% S:84% T:84%
+84%
+
+1295
+00:07:08.200 --> 00:07:08.300 D:vertical L:84% S:84% T:84%
+84%
+
+1296
+00:07:08.200 --> 00:07:08.300 D:horizontal L:84% S:84% T:84%
+84%
+
+1297
+00:07:08.300 --> 00:07:08.400 D:vertical-lr L:85% S:85% T:85%
+85%
+
+1298
+00:07:08.300 --> 00:07:08.400 D:vertical L:85% S:85% T:85%
+85%
+
+1299
+00:07:08.300 --> 00:07:08.400 D:horizontal L:85% S:85% T:85%
+85%
+
+1300
+00:07:08.400 --> 00:07:08.500 D:vertical-lr L:86% S:86% T:86%
+86%
+
+1301
+00:07:08.400 --> 00:07:08.500 D:vertical L:86% S:86% T:86%
+86%
+
+1302
+00:07:08.400 --> 00:07:08.500 D:horizontal L:86% S:86% T:86%
+86%
+
+1303
+00:07:08.500 --> 00:07:08.600 D:vertical-lr L:87% S:87% T:87%
+87%
+
+1304
+00:07:08.500 --> 00:07:08.600 D:vertical L:87% S:87% T:87%
+87%
+
+1305
+00:07:08.500 --> 00:07:08.600 D:horizontal L:87% S:87% T:87%
+87%
+
+1306
+00:07:08.600 --> 00:07:08.700 D:vertical-lr L:88% S:88% T:88%
+88%
+
+1307
+00:07:08.600 --> 00:07:08.700 D:vertical L:88% S:88% T:88%
+88%
+
+1308
+00:07:08.600 --> 00:07:08.700 D:horizontal L:88% S:88% T:88%
+88%
+
+1309
+00:07:08.700 --> 00:07:08.800 D:vertical-lr L:89% S:89% T:89%
+89%
+
+1310
+00:07:08.700 --> 00:07:08.800 D:vertical L:89% S:89% T:89%
+89%
+
+1311
+00:07:08.700 --> 00:07:08.800 D:horizontal L:89% S:89% T:89%
+89%
+
+1312
+00:07:08.800 --> 00:07:08.900 D:vertical-lr L:90% S:90% T:90%
+90%
+
+1313
+00:07:08.800 --> 00:07:08.900 D:vertical L:90% S:90% T:90%
+90%
+
+1314
+00:07:08.800 --> 00:07:08.900 D:horizontal L:90% S:90% T:90%
+90%
+
+1315
+00:07:08.900 --> 00:07:09.000 D:vertical-lr L:91% S:91% T:91%
+91%
+
+1316
+00:07:08.900 --> 00:07:09.000 D:vertical L:91% S:91% T:91%
+91%
+
+1317
+00:07:08.900 --> 00:07:09.000 D:horizontal L:91% S:91% T:91%
+91%
+
+1318
+00:07:09.000 --> 00:07:09.100 D:vertical-lr L:92% S:92% T:92%
+92%
+
+1319
+00:07:09.000 --> 00:07:09.100 D:vertical L:92% S:92% T:92%
+92%
+
+1320
+00:07:09.000 --> 00:07:09.100 D:horizontal L:92% S:92% T:92%
+92%
+
+1321
+00:07:09.100 --> 00:07:09.200 D:vertical-lr L:93% S:93% T:93%
+93%
+
+1322
+00:07:09.100 --> 00:07:09.200 D:vertical L:93% S:93% T:93%
+93%
+
+1323
+00:07:09.100 --> 00:07:09.200 D:horizontal L:93% S:93% T:93%
+93%
+
+1324
+00:07:09.200 --> 00:07:09.300 D:vertical-lr L:94% S:94% T:94%
+94%
+
+1325
+00:07:09.200 --> 00:07:09.300 D:vertical L:94% S:94% T:94%
+94%
+
+1326
+00:07:09.200 --> 00:07:09.300 D:horizontal L:94% S:94% T:94%
+94%
+
+1327
+00:07:09.300 --> 00:07:09.400 D:vertical-lr L:95% S:95% T:95%
+95%
+
+1328
+00:07:09.300 --> 00:07:09.400 D:vertical L:95% S:95% T:95%
+95%
+
+1329
+00:07:09.300 --> 00:07:09.400 D:horizontal L:95% S:95% T:95%
+95%
+
+1330
+00:07:09.400 --> 00:07:09.500 D:vertical-lr L:96% S:96% T:96%
+96%
+
+1331
+00:07:09.400 --> 00:07:09.500 D:vertical L:96% S:96% T:96%
+96%
+
+1332
+00:07:09.400 --> 00:07:09.500 D:horizontal L:96% S:96% T:96%
+96%
+
+1333
+00:07:09.500 --> 00:07:09.600 D:vertical-lr L:97% S:97% T:97%
+97%
+
+1334
+00:07:09.500 --> 00:07:09.600 D:vertical L:97% S:97% T:97%
+97%
+
+1335
+00:07:09.500 --> 00:07:09.600 D:horizontal L:97% S:97% T:97%
+97%
+
+1336
+00:07:09.600 --> 00:07:09.700 D:vertical-lr L:98% S:98% T:98%
+98%
+
+1337
+00:07:09.600 --> 00:07:09.700 D:vertical L:98% S:98% T:98%
+98%
+
+1338
+00:07:09.600 --> 00:07:09.700 D:horizontal L:98% S:98% T:98%
+98%
+
+1339
+00:07:09.700 --> 00:07:09.800 D:vertical-lr L:99% S:99% T:99%
+99%
+
+1340
+00:07:09.700 --> 00:07:09.800 D:vertical L:99% S:99% T:99%
+99%
+
+1341
+00:07:09.700 --> 00:07:09.800 D:horizontal L:99% S:99% T:99%
+99%
+
+1342
+00:07:09.800 --> 00:07:09.900 D:vertical-lr L:100% S:100% T:100%
+100%
+
+1343
+00:07:09.800 --> 00:07:09.900 D:vertical L:100% S:100% T:100%
+100%
+
+1344
+00:07:09.800 --> 00:07:09.900 D:horizontal L:100% S:100% T:100%
+100%
\ No newline at end of file
diff --git a/video/arduino-de.srt b/video/arduino-de.vtt
similarity index 55%
rename from video/arduino-de.srt
rename to video/arduino-de.vtt
index 567eee5..fad5ef3 100644
--- a/video/arduino-de.srt
+++ b/video/arduino-de.vtt
@@ -1,107 +1,109 @@
+WEBVTT
+
1
-00:00:13,000 --> 00:00:016,100
-Ich hörte von dieser arduino Projekt, und ich sah es online -
+00:00:13.000 --> 00:00:016.100
+Ich hörte von dieser arduino Projekt, und ich sah es online -
2
-00:00:16,100 --> 00:00:20,100
+00:00:16.100 --> 00:00:20.100
- und ich sagte "Wow! eine Menge Leute fangen an, darüber zu reden. Ich check it out!"
3
-00:00:20,100 --> 00:00:29,000
+00:00:20.100 --> 00:00:29.000
Da wollten wir ein Werkzeug für unsere Schüler, die moderner als das, was auf dem Markt in diesem Augenblick war zu machen.
4
-00:00:30,100 --> 00:00:38,306
+00:00:30.100 --> 00:00:38.306
Für mich war es ein Fall von - das ist ein Tool, das sehe ich mit mich könnte, und deshalb konnte ich in tatsächlich helfen, um es aus zu einer weiteren Welt zu glauben.
5
-00:00:38,500 --> 00:00:44,500
+00:00:38.500 --> 00:00:44.500
Open-Source-Hardware um wieder die Kontrolle über, was in Sachen.
6
-00:00:49,500 --> 00:00:55,000
+00:00:49.500 --> 00:00:55.000
Wir wollten, dass es Open Source sein - so könnte jeder kommen, helfen und dazu beizutragen.
7
-00:00:55,000 --> 00:01:02,000
+00:00:55.000 --> 00:01:02.000
Für mich ist das darum, den Menschen die nötigen Informationen, um die Hardware für sich selbst neu zu erstellen.
8
-00:01:02,000 --> 00:01:08,000
-Ich bin wirklich begeistert von arduino ... weil ich wirklich über Verständnis mehr darüber, wie die Welt funktioniert.
+00:01:02.000 --> 00:01:08.000
+Ich bin wirklich begeistert von arduino... weil ich wirklich über Verständnis mehr darüber, wie die Welt funktioniert.
9
-00:01:08,700 --> 00:01:14,463
+00:01:08.700 --> 00:01:14.463
Mein Berater sagte, dass ... es käme der Tag, wenn es mehr Computer als Menschen.
10
-00:01:14,463 --> 00:01:20,100
+00:01:14.463 --> 00:01:20.100
Wenn jeder Computer hatte eine Tastatur und Maus ... und einem Bildschirm, wäre es unmöglich, unser Leben zu Befehl.
11
-00:01:24,100 --> 00:01:29,300
+00:01:24.100 --> 00:01:29.300
Dann begannen wir, um herauszufinden, dass es einen Weg, um ein sehr schönes Ökosystem von Menschen bekommen ...
12
-00:01:29,300 --> 00:01:33,400
+00:01:29.300 --> 00:01:33.400
... teil und machen Erweiterungen ... So Derivate ... und hilft ...
13
-00:01:33,400 --> 00:01:41,400
+00:01:33.400 --> 00:01:41.400
Es ist also eine Art, wie ich bin einen Schritt auf eine Leiter. Und dann bin ich anderen Menschen helfen weiter gehen die Leiter hinauf.
14
-00:01:41,400 --> 00:01:47,700
+00:01:41.400 --> 00:01:47.700
So ... mussten wir die Schnittstelle zu vereinfachen. Und das ist der Beginn einer völlig neuen Art der Betrachtung ... Berechnung.
15
-00:01:47,700 --> 00:01:52,000
-Arduino: The Documentary
+00:01:47.700 --> 00:01:52.000
+Arduino: The Documentary
16
-00:01:52,000 --> 00:01:55,800
-Das allererste, was ich versuchte, mit einem arduino einen 3D-Drucker laufen war zu tun.
+00:01:52.000 --> 00:01:55.800
+Das allererste, was ich versuchte, mit einem arduino einen 3D-Drucker laufen war zu tun.
17
-00:01:55,800 --> 00:01:58,500
+00:01:55.800 --> 00:01:58.500
... so dass ich bewegte Kuchen und Torten ... Singen
18
-00:01:58,500 --> 00:02:00,100
-Also machen wir mit den Puppen Arduino ...
+00:01:58.500 --> 00:02:00.100
+Also machen wir mit den Puppen Arduino ...
19
-00:02:00,100 --> 00:02:04,120
+00:02:00.100 --> 00:02:04.120
Einer meiner Studenten baut eine Gitarre.
20
-00:02:04,120 --> 00:02:06,200
+00:02:04.120 --> 00:02:06.200
Hier gibt's a. .. hier ist ein Roboter ...
21
-00:02:06,200 --> 00:02:10,400
+00:02:06.200 --> 00:02:10.400
Wenn ich kippen, sehen Sie das Licht nach links geht - wenn ich dich gehen ...
22
-00:02:10,400 --> 00:02:13,400
+00:02:10.400 --> 00:02:13.400
Viele Menschen haben gebaut Hubschrauber mit vier oder sechs Motoren.
23
-00:02:16,300 --> 00:02:22,100
-Plötzlich kam eine Meldung sagt uns, dass arduino ...
+00:02:16.300 --> 00:02:22.100
+Plötzlich kam eine Meldung sagt uns, dass arduino ...
24
-00:02:22,100 --> 00:02:25,000
+00:02:22.100 --> 00:02:25.000
Und natürlich sagte ich OK ... das ist einfach zu viel.
25
-00:02:25,000 --> 00:02:29,000
+00:02:25.000 --> 00:02:29.000
Es endete als ein 17-Jähriger machte den Satelliten.
26
-00:02:30,000 --> 00:02:45,000
-Arduino: The Documentary
+00:02:30.000 --> 00:02:45.000
+Arduino: The Documentary
27
-00:02:45,000 --> 00:02:50,000
+00:02:45.000 --> 00:02:50.000
www.arduinothedocumentary.org
\ No newline at end of file
diff --git a/video/arduino-en.srt b/video/arduino-en.srt
deleted file mode 100644
index 37bbf77..0000000
--- a/video/arduino-en.srt
+++ /dev/null
@@ -1,107 +0,0 @@
-1
-00:00:13,000 --> 00:00:16,100 D:vertical L:90% T:50% S:140%
-I heard about this arduino project, and I saw it online -
-
-2
-00:00:16,100 --> 00:00:20,100
-- and I said 'Wow! a lot of people are starting to talk about this. I should check it out!'
-
-3
-00:00:20,100 --> 00:00:29,000
-Because we wanted to make a tool for our students that was more modern than what was available on the market at that moment.
-
-4
-00:00:30,100 --> 00:00:38,306
-For me it was a case of - this is a tool that I could see using myself , and therefore I could believe in actually helping to get it out to a wider world.
-
-5
-00:00:38,500 --> 00:00:44,500
-Open source hardware means to take back control of what is inside things.
-
-6
-00:00:49,500 --> 00:00:55,000
-We wanted it to be open source - so everybody could come out, help, and contribute.
-
-7
-00:00:55,000 --> 00:01:02,000
-For me, that's about giving people the information they need to recreate the piece of hardware for themselves.
-
-8
-00:01:02,000 --> 00:01:08,000
-I'm really passionate about arduino ... because I really care about understanding more about how the world works.
-
-9
-00:01:08,700 --> 00:01:14,463
-My advisors said that... there would come the day when there is more computers than people.
-
-10
-00:01:14,463 --> 00:01:20,100
-If every computer had a keyboard and mouse... and a screen, it would be impossible to command our lives.
-
-11
-00:01:24,100 --> 00:01:29,300
-Then we started to figure out that there was a way to get a very nice ecosystem of people...
-
-12
-00:01:29,300 --> 00:01:33,400
-...participating, and making extensions... making derivatives... and helping...
-
-13
-00:01:33,400 --> 00:01:41,400
-So it's kind of like I'm taking one step up a ladder. And then I'm helping other people go further up the ladder.
-
-14
-00:01:41,400 --> 00:01:47,700
-So... we had to simplify the interface. And that's the beginning of a completely new way of looking at... computation.
-
-15
-00:01:47,700 --> 00:01:52,000
-Arduino: The Documentary
-
-16
-00:01:52,000 --> 00:01:55,800
-The very first thing I tried to do with an arduino was run a 3D printer.
-
-17
-00:01:55,800 --> 00:01:58,500
-...so I make moving cakes and singing cakes...
-
-18
-00:01:58,500 --> 00:02:00,100
-So we make puppets with the arduino ...
-
-19
-00:02:00,100 --> 00:02:04,120
-One of my students is building a guitar.
-
-20
-00:02:04,120 --> 00:02:06,200
-Here's a... here's a robot...
-
-21
-00:02:06,200 --> 00:02:10,400
-If I tilt, you see the light goes left - if I go up...
-
-22
-00:02:10,400 --> 00:02:13,400
-Many people have built helicopters with four or six motors.
-
-23
-00:02:16,300 --> 00:02:22,100
-Suddenly a message arrived telling us that arduino ...
-
-24
-00:02:22,100 --> 00:02:25,000
-And obviously I said OK... this is just too much.
-
-25
-00:02:25,000 --> 00:02:29,000
-It ended up being a 17 year old who made the satellite.
-
-26
-00:02:30,000 --> 00:02:45,000
-Arduino: The Documentary
-
-27
-00:02:45,000 --> 00:02:50,000
-www.arduinothedocumentary.org
\ No newline at end of file
diff --git a/video/arduino-en.vtt b/video/arduino-en.vtt
new file mode 100644
index 0000000..fed8bad
--- /dev/null
+++ b/video/arduino-en.vtt
@@ -0,0 +1,109 @@
+WEBVTT
+
+1
+00:00:13.000 --> 00:00:16.100
+I heard about this arduino project, and I saw it online -
+
+2
+00:00:16.100 --> 00:00:20.100
+- and I said 'Wow! <00:00:18.100>a lot of people are starting to talk about this. I should check it out!'
+
+3
+00:00:20.100 --> 00:00:29.000
+Because we wanted to make a tool for our students that was more modern than what was available on the market at that moment.
+
+4
+00:00:30.100 --> 00:00:38.306
+For me it was a case of - this is a tool that I could see using myself , and therefore I could believe in actually helping to get it out to a wider world.
+
+5
+00:00:38.500 --> 00:00:44.500
+Open source hardware means to take back control of what is inside things.
+
+6
+00:00:49.500 --> 00:00:55.000
+We wanted it to be open source - so everybody could come out, help, and contribute.
+
+7
+00:00:55.000 --> 00:01:02.000
+For me, that's about giving people the information they need to recreate the piece of hardware for themselves.
+
+8
+00:01:02.000 --> 00:01:08.000
+I'm really passionate about arduino... because I really care about understanding more about how the world works.
+
+9
+00:01:08.700 --> 00:01:14.463
+My advisors said that... there would come the day when there is more computers than people.
+
+10
+00:01:14.463 --> 00:01:20.100
+If every computer had a keyboard and mouse... and a screen, it would be impossible to command our lives.
+
+11
+00:01:24.100 --> 00:01:29.300
+Then we started to figure out that there was a way to get a very nice ecosystem of people...
+
+12
+00:01:29.300 --> 00:01:33.400
+...participating, and making extensions... making derivatives... and helping...
+
+13
+00:01:33.400 --> 00:01:41.400
+So it's kind of like I'm taking one step up a ladder. And then I'm helping other people go further up the ladder.
+
+14
+00:01:41.400 --> 00:01:47.700
+So... we had to simplify the interface. And that's the beginning of a completely new way of looking at... computation.
+
+15
+00:01:47.700 --> 00:01:52.000
+Arduino: The Documentary
+
+16
+00:01:52.000 --> 00:01:55.800
+The very first thing I tried to do with an arduino was run a 3D printer.
+
+17
+00:01:55.800 --> 00:01:58.500
+...so I make moving cakes and singing cakes...
+
+18
+00:01:58.500 --> 00:02:00.100
+So we make puppets with the arduino...
+
+19
+00:02:00.100 --> 00:02:04.120
+One of my students is building a guitar.
+
+20
+00:02:04.120 --> 00:02:06.200
+Here's a... here's a robot...
+
+21
+00:02:06.200 --> 00:02:10.400
+If I tilt, you see the light goes left - if I go up...
+
+22
+00:02:10.400 --> 00:02:13.400
+Many people have built helicopters with four or six motors.
+
+23
+00:02:16.300 --> 00:02:22.100
+Suddenly a message arrived telling us that arduino...
+
+24
+00:02:22.100 --> 00:02:25.000
+And obviously I said OK... this is just too much.
+
+25
+00:02:25.000 --> 00:02:29.000
+It ended up being a 17 year old who made the satellite.
+
+26
+00:02:30.000 --> 00:02:45.000
+Arduino: The Documentary
+
+27
+00:02:45.000 --> 00:02:50.000
+www.arduinothedocumentary.org
\ No newline at end of file
diff --git a/video/arduino-ja.srt b/video/arduino-ja.vtt
similarity index 56%
rename from video/arduino-ja.srt
rename to video/arduino-ja.vtt
index 25d9378..8e2e56b 100644
--- a/video/arduino-ja.srt
+++ b/video/arduino-ja.vtt
@@ -1,111 +1,113 @@
+WEBVTT
+
1
-00:00:00,000 --> 00:00:10,100
-NB: As a speaker of Japanese, I am fully aware that these subtitles range from subtly to horribly wrong, but I was too lazy to do anything but a machine translation.
+00:00:00.000 --> 00:00:10.100
+NB: As a Japanese speaker, I am fully aware that these subtitles range from subtly to horribly wrong, but I was too lazy to do anything but a machine translation.
2
-00:00:13,000 --> 00:00:016,100
-私はこのarduino のプロジェクトについて聞いて、私はそれをオンラインで見て-
+00:00:13.000 --> 00:00:16.100 D:vertical-lr
+私はこのarduinoのプロジェクトについて聞いて、私はそれをオンラインで見て-
3
-00:00:16,100 --> 00:00:20,100
+00:00:16.100 --> 00:00:20.100 D:vertical
- そして、私と同時期に、うわー!多くの人々がこの話を始めている。私はそれをチェックアウトする必要があります!
4
-00:00:20,100 --> 00:00:29,000
+00:00:20.100 --> 00:00:29.000 D:vertical
我々はよりその時点で、市場で入手可能であったものよりも現代だった我々の学生のためのツールを作りたいと思ってからです。
5
-00:00:30,100 --> 00:00:38,306
+00:00:30.100 --> 00:00:38.306 D:vertical
これは、私は、自分自身を使用して見ることができたので、私は実際にはより広い世界にそれを取得するうえで信じることができたツールです-私にとっては、そうだった。
6
-00:00:38,500 --> 00:00:44,500
+00:00:38.500 --> 00:00:44.500 D:vertical
オープンソースハードウェアは、物事の内側にあるものの制御を取り戻すことを意味します。
7
-00:00:49,500 --> 00:00:55,000
+00:00:49.500 --> 00:00:55.000 D:vertical
我々はオープンソースになることを望んでいた-ので、誰もが、助けて、出てくる可能性がと貢献しています。
8
-00:00:55,000 --> 00:01:02,000
+00:00:55.000 --> 00:01:02.000 D:vertical
私にとっては、そのことについて人々に自分自身のためのハードウェアの一部を再作成するために必要な情報を与えている。
9
-00:01:02,000 --> 00:01:08,000
-私は本当にarduino の情熱よ...私は本当にに関する詳細については、世界はどのように動作するか理解する気だ。
+00:01:02.000 --> 00:01:08.000 D:vertical
+私は本当にarduinoの情熱よ...私は本当にに関する詳細については、世界はどのように動作するか理解する気だ。
10
-00:01:08,700 --> 00:01:14,463
+00:01:08.700 --> 00:01:14.463 D:vertical
私のアドバイザーによると...人以上のコンピュータがあるときに日が来るだろう。
11
-00:01:14,463 --> 00:01:20,100
+00:01:14.463 --> 00:01:20.100 D:vertical
すべてのコンピュータは、キーボードとマウスを持っていた場合は...と画面には、それが私たちの生活を指揮することは不可能です。
12
-00:01:24,100 --> 00:01:29,300
+00:01:24.100 --> 00:01:29.300 D:vertical
その後、我々は人々の非常に素晴らしい生態系を取得する方法があったことを把握し始めた...
13
-00:01:29,300 --> 00:01:33,400
+00:01:29.300 --> 00:01:33.400 D:vertical
...参加し、拡張機能を作る...デリバティブを作る...と支援...
14
-00:01:33,400 --> 00:01:41,400
+00:01:33.400 --> 00:01:41.400 D:vertical
だから、私は梯子の上の一歩を取っているようなものだ。そして、私が手伝いをしている他の人が梯子の上、さらに進んでください。
15
-00:01:41,400 --> 00:01:47,700
+00:01:41.400 --> 00:01:47.700 D:vertical
だから... ...我々はインターフェイスを簡素化していた。そして、それは見ての全く新しい方法の始まりです...計算。
16
-00:01:47,700 --> 00:01:52,000
-アードウイーノ: ザ・ドキュメンタリー
+00:01:47.700 --> 00:01:52.000 D:vertical-lr
+アードウイーノ: ザ・ドキュメンタリー
17
-00:01:52,000 --> 00:01:55,800
-arduino は3Dプリンタを実行されたと私が試した、非常に最初にすること。
+00:01:52.000 --> 00:01:55.800 D:vertical
+arduinoは3Dプリンタを実行されたと私が試した、非常に最初にすること。
18
-00:01:55,800 --> 00:01:58,500
+00:01:55.800 --> 00:01:58.500 D:vertical
...ので、私はケーキと歌のケーキを移動する...
19
-00:01:58,500 --> 00:02:00,100
-そこで、arduino ので人形を作る...
+00:01:58.500 --> 00:02:00.100 D:vertical
+そこで、arduinoので人形を作る...
20
-00:02:00,100 --> 00:02:04,120
+00:02:00.100 --> 00:02:04.120 D:vertical
私の生徒の一人はギターを構築しています。
21
-00:02:04,120 --> 00:02:06,200
+00:02:04.120 --> 00:02:06.200 D:vertical
これはロボットです。
22
-00:02:06,200 --> 00:02:10,400
+00:02:06.200 --> 00:02:10.400 D:vertical
私が傾けている場合は、光が左に行くを参照してください-私が行けば...
23
-00:02:10,400 --> 00:02:13,400
+00:02:10.400 --> 00:02:13.400 D:vertical
多くの人々が4つまたは6つのモータとヘリコプター組み込まれています。
24
-00:02:16,300 --> 00:02:22,100
-突然のメッセージが私たちをことを伝える到着arduino の...
+00:02:16.300 --> 00:02:22.100 D:vertical
+突然のメッセージが私たちをことを伝える到着arduinoの...
25
-00:02:22,100 --> 00:02:25,000
+00:02:22.100 --> 00:02:25.000 D:vertical
そして、明らかに私はOKだ...これはあまりにも似ています。
26
-00:02:25,000 --> 00:02:29,000
+00:02:25.000 --> 00:02:29.000 D:vertical
それが17年には、衛星を行ったユーザーを古いものになってしまいました。
27
-00:02:30,000 --> 00:02:45,000
-アードウイーノ: ザ・ドキュメンタリー
+00:02:30.000 --> 00:02:45.000 D:vertical
+アードウイーノ: ザ・ドキュメンタリー
28
-00:02:45,000 --> 00:02:50,000
+00:02:45.000 --> 00:02:50.000
www.arduinothedocumentary.org
\ No newline at end of file
diff --git a/video/example.vtt b/video/example.vtt
new file mode 100644
index 0000000..6968582
--- /dev/null
+++ b/video/example.vtt
@@ -0,0 +1,40 @@
+WEBVTT
+
+00:11.000 --> 00:13.000
+We are in New York City
+
+00:13.000 --> 00:16.000
+We're actually at the Lucern Hotel, just down the street
+
+00:16.000 --> 00:18.000
+from the American Museum of Natural History
+
+00:18.000 --> 00:20.000
+and with me is Neil DeGrasse Tyson
+
+00:20.000 --> 00:22.000
+Astrophysicist, Director of the Hayden Planetarium
+
+00:22.000 --> 00:24.000
+at the AMNH.
+
+00:24.000 --> 00:26.000
+Thank you for walking down here.
+
+00:27.000 --> 00:30.000
+And I want to do a follow-up on the last conversation we did.
+
+00:30.000 --> 00:31.500 A:end S:50%
+When we e-mailed—
+
+00:30.500 --> 00:32.500 A:start S:50%
+Didn't we talk about enough in that conversation?
+
+00:32.000 --> 00:35.500 A:end S:50%
+No! No no no no; 'cos 'cos obviously 'cos
+
+00:32.500 --> 00:33.500 A:start S:50%
+Laughs
+
+00:35.500 --> 00:38.000
+You know I'm so excited my glasses are falling off here.