mirror of
https://github.com/samsonjs/media.git
synced 2026-03-26 09:35:47 +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;
|
||||
}
|
||||
|
||||
STYLE
|
||||
::cue(v[voice="LaGord"]) { background-color: lime }
|
||||
|
||||
STYLE
|
||||
::cue(v[voice="The Frog"]) { font-weight: bold }
|
||||
|
||||
STYLE
|
||||
::cue(v){text-decoration:underline}
|
||||
|
||||
|
|
@ -26,4 +32,8 @@ id2
|
|||
This is the second subtitle.
|
||||
|
||||
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.text.Cue;
|
||||
|
||||
import android.graphics.Typeface;
|
||||
import android.test.InstrumentationTestCase;
|
||||
import android.text.Layout.Alignment;
|
||||
import android.text.Spanned;
|
||||
import android.text.style.BackgroundColorSpan;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.text.style.UnderlineSpan;
|
||||
|
||||
import java.io.IOException;
|
||||
|
|
@ -155,7 +157,7 @@ public class WebvttParserTest extends InstrumentationTestCase {
|
|||
WebvttSubtitle subtitle = parser.decode(bytes, bytes.length);
|
||||
|
||||
// Test event count.
|
||||
assertEquals(6, subtitle.getEventTimeCount());
|
||||
assertEquals(8, subtitle.getEventTimeCount());
|
||||
|
||||
// Test cues.
|
||||
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 cue2 = subtitle.getCues(2345000).get(0);
|
||||
Cue cue3 = subtitle.getCues(20000000).get(0);
|
||||
Cue cue4 = subtitle.getCues(25000000).get(0);
|
||||
Spanned s1 = (Spanned) cue1.text;
|
||||
Spanned s2 = (Spanned) cue2.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(), BackgroundColorSpan.class).length);
|
||||
assertEquals(2, s2.getSpans(0, s2.length(), ForegroundColorSpan.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,
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@ import java.util.regex.Pattern;
|
|||
/* package */ final class WebvttCueParser {
|
||||
|
||||
public static final String UNIVERSAL_CUE_ID = "";
|
||||
public static final String CUE_ID_PREFIX = "#";
|
||||
public static final Pattern CUE_HEADER_PATTERN = Pattern
|
||||
.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_SEMI_COLON = ';';
|
||||
private static final char CHAR_SPACE = ' ';
|
||||
private static final String SPACE = " ";
|
||||
|
||||
private static final String ENTITY_LESS_THAN = "lt";
|
||||
private static final String ENTITY_GREATER_THAN = "gt";
|
||||
|
|
@ -70,6 +68,10 @@ import java.util.regex.Pattern;
|
|||
private static final String TAG_CLASS = "c";
|
||||
private static final String TAG_VOICE = "v";
|
||||
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_ITALIC = Typeface.ITALIC;
|
||||
|
|
@ -153,7 +155,6 @@ import java.util.regex.Pattern;
|
|||
Map<String, WebvttCssStyle> styleMap) {
|
||||
SpannableStringBuilder spannedText = new SpannableStringBuilder();
|
||||
Stack<StartTag> startTagStack = new Stack<>();
|
||||
String[] tagTokens;
|
||||
int pos = 0;
|
||||
while (pos < markup.length()) {
|
||||
char curr = markup.charAt(pos);
|
||||
|
|
@ -167,10 +168,10 @@ import java.util.regex.Pattern;
|
|||
boolean isClosingTag = markup.charAt(ltPos + 1) == CHAR_SLASH;
|
||||
pos = findEndOfTag(markup, ltPos + 1);
|
||||
boolean isVoidTag = markup.charAt(pos - 2) == CHAR_SLASH;
|
||||
|
||||
tagTokens = tokenizeTag(markup.substring(
|
||||
ltPos + (isClosingTag ? 2 : 1), isVoidTag ? pos - 2 : pos - 1));
|
||||
if (tagTokens == null || !isSupportedTag(tagTokens[0])) {
|
||||
String fullTagExpression = markup.substring(ltPos + (isClosingTag ? 2 : 1),
|
||||
isVoidTag ? pos - 2 : pos - 1);
|
||||
String tagName = getTagName(fullTagExpression);
|
||||
if (tagName == null || !isSupportedTag(tagName)) {
|
||||
continue;
|
||||
}
|
||||
if (isClosingTag) {
|
||||
|
|
@ -181,9 +182,10 @@ import java.util.regex.Pattern;
|
|||
}
|
||||
startTag = startTagStack.pop();
|
||||
applySpansForTag(startTag, spannedText, styleMap);
|
||||
} while(!startTag.name.equals(tagTokens[0]));
|
||||
} while(!startTag.name.equals(tagName));
|
||||
} 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;
|
||||
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).
|
||||
*
|
||||
* @param markup The WebVTT cue markup to be parsed.
|
||||
* @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).
|
||||
* @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).
|
||||
*/
|
||||
private static int findEndOfTag(String markup, int startPos) {
|
||||
int idx = markup.indexOf(CHAR_GREATER_THAN, startPos);
|
||||
|
|
@ -376,6 +378,11 @@ import java.util.regex.Pattern;
|
|||
return;
|
||||
}
|
||||
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,
|
||||
|
|
@ -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
|
||||
* @return an array of <code>String</code>s with the tag name at pos 0 followed by style classes
|
||||
* or null if it's an empty tag: '<>'
|
||||
* @param tagExpression Characters between &lt: and &gt; of a start or end tag.
|
||||
* @return The name of tag.
|
||||
*/
|
||||
private static String[] tokenizeTag(String fullTagExpression) {
|
||||
fullTagExpression = fullTagExpression.replace("\\s+", " ").trim();
|
||||
if (fullTagExpression.length() == 0) {
|
||||
private static String getTagName(String tagExpression) {
|
||||
tagExpression = tagExpression.trim();
|
||||
if (tagExpression.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (fullTagExpression.contains(SPACE)) {
|
||||
fullTagExpression = fullTagExpression.substring(0, fullTagExpression.indexOf(SPACE));
|
||||
}
|
||||
return fullTagExpression.split("\\.");
|
||||
return tagExpression.split("[ \\.]")[0];
|
||||
}
|
||||
|
||||
private static String getVoiceName(String fullTagExpression) {
|
||||
return fullTagExpression.trim().substring(fullTagExpression.indexOf(" ")).trim();
|
||||
}
|
||||
|
||||
private static final class StartTag {
|
||||
|
||||
public final String name;
|
||||
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.name = name;
|
||||
this.voiceName = voiceName;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue