| 1 | package com.longtailvideo.plugins.captions { |
|---|
| 2 | |
|---|
| 3 | |
|---|
| 4 | import com.longtailvideo.jwplayer.utils.Strings; |
|---|
| 5 | import flash.external.ExternalInterface; |
|---|
| 6 | |
|---|
| 7 | |
|---|
| 8 | /** Parse styling and contents of W3C Timed Text XML files. **/ |
|---|
| 9 | public class DFXP { |
|---|
| 10 | |
|---|
| 11 | |
|---|
| 12 | /** Name of the main XMl node. **/ |
|---|
| 13 | public static const NAME:String = 'tt'; |
|---|
| 14 | |
|---|
| 15 | |
|---|
| 16 | /** Parse stylesheets from the head. */ |
|---|
| 17 | public static function parseStyles(data:XML,defaults:Object):Object { |
|---|
| 18 | var styles:Object = {}; |
|---|
| 19 | for each (var i:XML in data.children()) { |
|---|
| 20 | if (i.localName() == "head") { |
|---|
| 21 | for each (var node:XML in i.children()[0].children()) { |
|---|
| 22 | if (node.localName() == 'style') { |
|---|
| 23 | // Set the defaults. |
|---|
| 24 | var rules:Object = {}; |
|---|
| 25 | for(var rule:String in defaults) { |
|---|
| 26 | rules[rule] = defaults[rule]; |
|---|
| 27 | } |
|---|
| 28 | // Loop through all attributes for overrides. |
|---|
| 29 | for each (var attrib:XML in node.attributes()) { |
|---|
| 30 | var name:String = attrib.name(); |
|---|
| 31 | if (name.indexOf("::") > -1) { |
|---|
| 32 | name = name.substring(name.indexOf("::") + 2); |
|---|
| 33 | } |
|---|
| 34 | rules[name] = attrib.toString(); |
|---|
| 35 | } |
|---|
| 36 | // Save to listing |
|---|
| 37 | if (node.@id) { |
|---|
| 38 | styles[node.@id] = rules; |
|---|
| 39 | } |
|---|
| 40 | } |
|---|
| 41 | } |
|---|
| 42 | } |
|---|
| 43 | } |
|---|
| 44 | return styles; |
|---|
| 45 | }; |
|---|
| 46 | |
|---|
| 47 | |
|---|
| 48 | /** Parse captions from the TT XML, returning a list with {begin:Number,text:String} objects. **/ |
|---|
| 49 | public static function parseCaptions(data:XML,style:Object):Array { |
|---|
| 50 | var styles:Object = DFXP.parseStyles(data,style); |
|---|
| 51 | var array:Array = new Array({begin:0,text:''}); |
|---|
| 52 | for each (var i:XML in data.children()) { |
|---|
| 53 | if (i.localName() == "body") { |
|---|
| 54 | for each (var j:XML in i.children()) { |
|---|
| 55 | for each (var k:XML in j.children()) { |
|---|
| 56 | // Paragraphs are single captions. They live inside dividers. |
|---|
| 57 | if (k.localName() == 'p') { |
|---|
| 58 | var entry:Object = DFXP.parseCaption(k); |
|---|
| 59 | array.push(entry); |
|---|
| 60 | // Amend global styling. |
|---|
| 61 | if(entry.style && styles[entry.style]) { |
|---|
| 62 | entry.style = styles[entry.style]; |
|---|
| 63 | } else { |
|---|
| 64 | entry.style = style; |
|---|
| 65 | } |
|---|
| 66 | // Convert inline styles to HTML. |
|---|
| 67 | while (entry.text.indexOf("<span") > -1) { |
|---|
| 68 | entry.text = DFXP.parseSpan(entry.text,styles,entry.style); |
|---|
| 69 | } |
|---|
| 70 | // End with a new, empty caption, accounting for duration or end set. |
|---|
| 71 | if (entry['end']) { |
|---|
| 72 | array.push({begin:entry['end'],text:''}); |
|---|
| 73 | delete entry['end']; |
|---|
| 74 | } else if (entry['dur']) { |
|---|
| 75 | array.push({begin:entry['begin']+entry['dur'],text:''}); |
|---|
| 76 | delete entry['dur']; |
|---|
| 77 | } |
|---|
| 78 | } |
|---|
| 79 | } |
|---|
| 80 | } |
|---|
| 81 | } |
|---|
| 82 | } |
|---|
| 83 | return array; |
|---|
| 84 | }; |
|---|
| 85 | |
|---|
| 86 | |
|---|
| 87 | /** Parse a single captions entry. **/ |
|---|
| 88 | private static function parseCaption(data:XML):Object { |
|---|
| 89 | var pattern:RegExp = /(\n)+/; |
|---|
| 90 | var entry:Object = { |
|---|
| 91 | begin:Strings.seconds(data.@begin), |
|---|
| 92 | dur:Strings.seconds(data.@dur), |
|---|
| 93 | end:Strings.seconds(data.@end), |
|---|
| 94 | style:data.@style.toString(), |
|---|
| 95 | text:data.children().toXMLString().replace(/\n/g,' ') |
|---|
| 96 | }; |
|---|
| 97 | return entry; |
|---|
| 98 | }; |
|---|
| 99 | |
|---|
| 100 | |
|---|
| 101 | /** Convert a span entry into HTML. **/ |
|---|
| 102 | private static function parseSpan(text:String,styles:Object,defaults:Object):String { |
|---|
| 103 | var rules:Object = {}; |
|---|
| 104 | var newtext:String = ''; |
|---|
| 105 | // Find the span bounds and convert to XML. |
|---|
| 106 | var left:Number = text.indexOf("<span "); |
|---|
| 107 | var right:Number = text.indexOf("</span>",left); |
|---|
| 108 | if (left > -1 && right > -1) { |
|---|
| 109 | var span:XML = new XML(text.substring(left,right+7)); |
|---|
| 110 | // Use style if defined, else set defaults. |
|---|
| 111 | var style:String = span.@style; |
|---|
| 112 | if(style && styles[style]) { |
|---|
| 113 | for(var i:String in styles[style]) { rules[i] = styles[style][i]; } |
|---|
| 114 | } else { |
|---|
| 115 | for(var j:String in defaults) { rules[j] = defaults[j]; } |
|---|
| 116 | } |
|---|
| 117 | // Override style with inline declarations |
|---|
| 118 | for each (var attrib:XML in span.@*) { |
|---|
| 119 | var name:String = attrib.localName().toString(); |
|---|
| 120 | if (rules[name]) { |
|---|
| 121 | rules[name] = attrib.toString(); |
|---|
| 122 | } |
|---|
| 123 | } |
|---|
| 124 | // Wrap plain text with font and b/i/u tags. |
|---|
| 125 | newtext = '<font family="'+rules.fontFamily+'" size="'+rules.fontSize+'" color="'+rules.color+'">'; |
|---|
| 126 | newtext += text.substring(text.indexOf('>',left)+1, right); |
|---|
| 127 | newtext += "</font>"; |
|---|
| 128 | if(rules.fontWeight == 'bold') { newtext = '<b>'+newtext+'</b>'; } |
|---|
| 129 | if(rules.fontStyle == 'italic') { newtext = '<i>'+newtext+'</i>'; } |
|---|
| 130 | if(rules.textDecoration == 'underline') { newtext = '<u>'+newtext+'</u>'; } |
|---|
| 131 | } |
|---|
| 132 | return text.substr(0,left)+newtext+text.substr(right+7); |
|---|
| 133 | }; |
|---|
| 134 | |
|---|
| 135 | |
|---|
| 136 | } |
|---|
| 137 | |
|---|
| 138 | |
|---|
| 139 | } |
|---|