mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
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
This commit is contained in:
parent
e594eccd4d
commit
51df2dce46
3 changed files with 51 additions and 25 deletions
|
|
@ -14,6 +14,12 @@ STYLE
|
||||||
color: peachpuff;
|
color: peachpuff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
STYLE
|
||||||
|
::cue(v[voice="LaGord"]) { background-color: lime }
|
||||||
|
|
||||||
|
STYLE
|
||||||
|
::cue(v[voice="The Frog"]) { font-weight: bold }
|
||||||
|
|
||||||
STYLE
|
STYLE
|
||||||
::cue(v){text-decoration:underline}
|
::cue(v){text-decoration:underline}
|
||||||
|
|
||||||
|
|
@ -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;
|
||||||
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 {
|
||||||
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 {
|
||||||
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;
|
||||||
/* 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;
|
||||||
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";
|
||||||
|
|
@ -71,6 +69,10 @@ import java.util.regex.Pattern;
|
||||||
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;
|
||||||
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;
|
||||||
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;
|
||||||
}
|
}
|
||||||
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;
|
||||||
* Find end of tag (>). The position returned is the position of the > plus one (exclusive).
|
* Find end of tag (>). The position returned is the position of the > 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;
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 &lt: and &gt; of a start or end tag
|
* @param tagExpression Characters between &lt: and &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: '<>'
|
|
||||||
*/
|
*/
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue