Commit 51df2dce by aquilescanta Committed by Oliver Woodman

Add support for voice selection in WebVTT CSS

Allow styling <v Someone>Hello</v> with ::cue(v[voice="Someone"]) { ... }.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=119748009
parent e594eccd
...@@ -15,6 +15,12 @@ STYLE ...@@ -15,6 +15,12 @@ STYLE
} }
STYLE STYLE
::cue(v[voice="LaGord"]) { background-color: lime }
STYLE
::cue(v[voice="The Frog"]) { font-weight: bold }
STYLE
::cue(v){text-decoration:underline} ::cue(v){text-decoration:underline}
id1 id1
...@@ -26,4 +32,8 @@ id2 ...@@ -26,4 +32,8 @@ id2
This is the second subtitle. This is the second subtitle.
00:20.000 --> 00:21.000 00:20.000 --> 00:21.000
This is a <v Mary>reference by element</v> This is a <v.clazz Mary>reference by element</v>
00:25.000 --> 00:28.000
<v LaGord>You are an idiot</v>
<v The Frog>You don't have the guts</v>
...@@ -19,11 +19,13 @@ import com.google.android.exoplayer.ParserException; ...@@ -19,11 +19,13 @@ import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.testutil.TestUtil; import com.google.android.exoplayer.testutil.TestUtil;
import com.google.android.exoplayer.text.Cue; import com.google.android.exoplayer.text.Cue;
import android.graphics.Typeface;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import android.text.Layout.Alignment; import android.text.Layout.Alignment;
import android.text.Spanned; import android.text.Spanned;
import android.text.style.BackgroundColorSpan; import android.text.style.BackgroundColorSpan;
import android.text.style.ForegroundColorSpan; import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.text.style.UnderlineSpan; import android.text.style.UnderlineSpan;
import java.io.IOException; import java.io.IOException;
...@@ -155,7 +157,7 @@ public class WebvttParserTest extends InstrumentationTestCase { ...@@ -155,7 +157,7 @@ public class WebvttParserTest extends InstrumentationTestCase {
WebvttSubtitle subtitle = parser.decode(bytes, bytes.length); WebvttSubtitle subtitle = parser.decode(bytes, bytes.length);
// Test event count. // Test event count.
assertEquals(6, subtitle.getEventTimeCount()); assertEquals(8, subtitle.getEventTimeCount());
// Test cues. // Test cues.
assertCue(subtitle, 0, 0, 1234000, "This is the first subtitle."); assertCue(subtitle, 0, 0, 1234000, "This is the first subtitle.");
...@@ -164,13 +166,18 @@ public class WebvttParserTest extends InstrumentationTestCase { ...@@ -164,13 +166,18 @@ public class WebvttParserTest extends InstrumentationTestCase {
Cue cue1 = subtitle.getCues(0).get(0); Cue cue1 = subtitle.getCues(0).get(0);
Cue cue2 = subtitle.getCues(2345000).get(0); Cue cue2 = subtitle.getCues(2345000).get(0);
Cue cue3 = subtitle.getCues(20000000).get(0); Cue cue3 = subtitle.getCues(20000000).get(0);
Cue cue4 = subtitle.getCues(25000000).get(0);
Spanned s1 = (Spanned) cue1.text; Spanned s1 = (Spanned) cue1.text;
Spanned s2 = (Spanned) cue2.text; Spanned s2 = (Spanned) cue2.text;
Spanned s3 = (Spanned) cue3.text; Spanned s3 = (Spanned) cue3.text;
Spanned s4 = (Spanned) cue4.text;
assertEquals(1, s1.getSpans(0, s1.length(), ForegroundColorSpan.class).length); assertEquals(1, s1.getSpans(0, s1.length(), ForegroundColorSpan.class).length);
assertEquals(1, s1.getSpans(0, s1.length(), BackgroundColorSpan.class).length); assertEquals(1, s1.getSpans(0, s1.length(), BackgroundColorSpan.class).length);
assertEquals(2, s2.getSpans(0, s2.length(), ForegroundColorSpan.class).length); assertEquals(2, s2.getSpans(0, s2.length(), ForegroundColorSpan.class).length);
assertEquals(1, s3.getSpans(10, s3.length(), UnderlineSpan.class).length); assertEquals(1, s3.getSpans(10, s3.length(), UnderlineSpan.class).length);
assertEquals(2, s4.getSpans(0, 16, BackgroundColorSpan.class).length);
assertEquals(1, s4.getSpans(17, s4.length(), StyleSpan.class).length);
assertEquals(Typeface.BOLD, s4.getSpans(17, s4.length(), StyleSpan.class)[0].getStyle());
} }
private static void assertCue(WebvttSubtitle subtitle, int eventTimeIndex, long startTimeUs, private static void assertCue(WebvttSubtitle subtitle, int eventTimeIndex, long startTimeUs,
......
...@@ -45,7 +45,6 @@ import java.util.regex.Pattern; ...@@ -45,7 +45,6 @@ import java.util.regex.Pattern;
/* package */ final class WebvttCueParser { /* package */ final class WebvttCueParser {
public static final String UNIVERSAL_CUE_ID = ""; public static final String UNIVERSAL_CUE_ID = "";
public static final String CUE_ID_PREFIX = "#";
public static final Pattern CUE_HEADER_PATTERN = Pattern public static final Pattern CUE_HEADER_PATTERN = Pattern
.compile("^(\\S+)\\s+-->\\s+(\\S+)(.*)?$"); .compile("^(\\S+)\\s+-->\\s+(\\S+)(.*)?$");
...@@ -57,7 +56,6 @@ import java.util.regex.Pattern; ...@@ -57,7 +56,6 @@ import java.util.regex.Pattern;
private static final char CHAR_AMPERSAND = '&'; private static final char CHAR_AMPERSAND = '&';
private static final char CHAR_SEMI_COLON = ';'; private static final char CHAR_SEMI_COLON = ';';
private static final char CHAR_SPACE = ' '; private static final char CHAR_SPACE = ' ';
private static final String SPACE = " ";
private static final String ENTITY_LESS_THAN = "lt"; private static final String ENTITY_LESS_THAN = "lt";
private static final String ENTITY_GREATER_THAN = "gt"; private static final String ENTITY_GREATER_THAN = "gt";
...@@ -70,6 +68,10 @@ import java.util.regex.Pattern; ...@@ -70,6 +68,10 @@ import java.util.regex.Pattern;
private static final String TAG_CLASS = "c"; private static final String TAG_CLASS = "c";
private static final String TAG_VOICE = "v"; private static final String TAG_VOICE = "v";
private static final String TAG_LANG = "lang"; private static final String TAG_LANG = "lang";
private static final String CUE_ID_PREFIX = "#";
private static final String CUE_VOICE_PREFIX = "v[voice=\"";
private static final String CUE_VOICE_SUFFIX = "\"]";
private static final int STYLE_BOLD = Typeface.BOLD; private static final int STYLE_BOLD = Typeface.BOLD;
private static final int STYLE_ITALIC = Typeface.ITALIC; private static final int STYLE_ITALIC = Typeface.ITALIC;
...@@ -153,7 +155,6 @@ import java.util.regex.Pattern; ...@@ -153,7 +155,6 @@ import java.util.regex.Pattern;
Map<String, WebvttCssStyle> styleMap) { Map<String, WebvttCssStyle> styleMap) {
SpannableStringBuilder spannedText = new SpannableStringBuilder(); SpannableStringBuilder spannedText = new SpannableStringBuilder();
Stack<StartTag> startTagStack = new Stack<>(); Stack<StartTag> startTagStack = new Stack<>();
String[] tagTokens;
int pos = 0; int pos = 0;
while (pos < markup.length()) { while (pos < markup.length()) {
char curr = markup.charAt(pos); char curr = markup.charAt(pos);
...@@ -167,10 +168,10 @@ import java.util.regex.Pattern; ...@@ -167,10 +168,10 @@ import java.util.regex.Pattern;
boolean isClosingTag = markup.charAt(ltPos + 1) == CHAR_SLASH; boolean isClosingTag = markup.charAt(ltPos + 1) == CHAR_SLASH;
pos = findEndOfTag(markup, ltPos + 1); pos = findEndOfTag(markup, ltPos + 1);
boolean isVoidTag = markup.charAt(pos - 2) == CHAR_SLASH; boolean isVoidTag = markup.charAt(pos - 2) == CHAR_SLASH;
String fullTagExpression = markup.substring(ltPos + (isClosingTag ? 2 : 1),
tagTokens = tokenizeTag(markup.substring( isVoidTag ? pos - 2 : pos - 1);
ltPos + (isClosingTag ? 2 : 1), isVoidTag ? pos - 2 : pos - 1)); String tagName = getTagName(fullTagExpression);
if (tagTokens == null || !isSupportedTag(tagTokens[0])) { if (tagName == null || !isSupportedTag(tagName)) {
continue; continue;
} }
if (isClosingTag) { if (isClosingTag) {
...@@ -181,9 +182,10 @@ import java.util.regex.Pattern; ...@@ -181,9 +182,10 @@ import java.util.regex.Pattern;
} }
startTag = startTagStack.pop(); startTag = startTagStack.pop();
applySpansForTag(startTag, spannedText, styleMap); applySpansForTag(startTag, spannedText, styleMap);
} while(!startTag.name.equals(tagTokens[0])); } while(!startTag.name.equals(tagName));
} else if (!isVoidTag) { } else if (!isVoidTag) {
startTagStack.push(new StartTag(tagTokens[0], spannedText.length())); startTagStack.push(new StartTag(tagName, spannedText.length(),
TAG_VOICE.equals(tagName) ? getVoiceName(fullTagExpression) : null));
} }
break; break;
case CHAR_AMPERSAND: case CHAR_AMPERSAND:
...@@ -309,8 +311,8 @@ import java.util.regex.Pattern; ...@@ -309,8 +311,8 @@ import java.util.regex.Pattern;
* Find end of tag (&gt;). The position returned is the position of the &gt; plus one (exclusive). * Find end of tag (&gt;). The position returned is the position of the &gt; plus one (exclusive).
* *
* @param markup The WebVTT cue markup to be parsed. * @param markup The WebVTT cue markup to be parsed.
* @param startPos the position from where to start searching for the end of tag. * @param startPos The position from where to start searching for the end of tag.
* @return the position of the end of tag plus 1 (one). * @return The position of the end of tag plus 1 (one).
*/ */
private static int findEndOfTag(String markup, int startPos) { private static int findEndOfTag(String markup, int startPos) {
int idx = markup.indexOf(CHAR_GREATER_THAN, startPos); int idx = markup.indexOf(CHAR_GREATER_THAN, startPos);
...@@ -376,6 +378,11 @@ import java.util.regex.Pattern; ...@@ -376,6 +378,11 @@ import java.util.regex.Pattern;
return; return;
} }
applyStyleToText(spannedText, styleForTag, start, end); applyStyleToText(spannedText, styleForTag, start, end);
if (startTag.voiceName != null) {
WebvttCssStyle styleForVoice = styleMap.get(CUE_VOICE_PREFIX + startTag.voiceName
+ CUE_VOICE_SUFFIX);
applyStyleToText(spannedText, styleForVoice, start, end);
}
} }
private static void applyStyleToText(SpannableStringBuilder spannedText, private static void applyStyleToText(SpannableStringBuilder spannedText,
...@@ -428,31 +435,33 @@ import java.util.regex.Pattern; ...@@ -428,31 +435,33 @@ import java.util.regex.Pattern;
} }
/** /**
* Tokenizes a tag expression into tag name (pos 0) and classes (pos 1..n). * Gets the tag name for the given tag contents.
* *
* @param fullTagExpression characters between &amp;lt: and &amp;gt; of a start or end tag * @param tagExpression Characters between &amp;lt: and &amp;gt; of a start or end tag.
* @return an array of <code>String</code>s with the tag name at pos 0 followed by style classes * @return The name of tag.
* or null if it's an empty tag: '&lt;&gt;'
*/ */
private static String[] tokenizeTag(String fullTagExpression) { private static String getTagName(String tagExpression) {
fullTagExpression = fullTagExpression.replace("\\s+", " ").trim(); tagExpression = tagExpression.trim();
if (fullTagExpression.length() == 0) { if (tagExpression.isEmpty()) {
return null; return null;
} }
if (fullTagExpression.contains(SPACE)) { return tagExpression.split("[ \\.]")[0];
fullTagExpression = fullTagExpression.substring(0, fullTagExpression.indexOf(SPACE)); }
}
return fullTagExpression.split("\\."); private static String getVoiceName(String fullTagExpression) {
return fullTagExpression.trim().substring(fullTagExpression.indexOf(" ")).trim();
} }
private static final class StartTag { private static final class StartTag {
public final String name; public final String name;
public final int position; public final int position;
public final String voiceName;
public StartTag(String name, int position) { public StartTag(String name, int position, String voiceName) {
this.position = position; this.position = position;
this.name = name; this.name = name;
this.voiceName = voiceName;
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment