WebVTT CSS Styling -- Support for element selectors
This CL allows style blocks to reference elements. For example: we could style a cue with text "Sometimes <b>bold</b> is not enough" with the style block ::cue(b) { ... }. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=119734779
This commit is contained in:
parent
f963c626af
commit
c2beffc6c5
@ -14,6 +14,9 @@ STYLE
|
|||||||
color: peachpuff;
|
color: peachpuff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
STYLE
|
||||||
|
::cue(v){text-decoration:underline}
|
||||||
|
|
||||||
id1
|
id1
|
||||||
00:00.000 --> 00:01.234
|
00:00.000 --> 00:01.234
|
||||||
This is the first subtitle.
|
This is the first subtitle.
|
||||||
@ -21,3 +24,6 @@ This is the first subtitle.
|
|||||||
id2
|
id2
|
||||||
00:02.345 --> 00:03.456
|
00:02.345 --> 00:03.456
|
||||||
This is the second subtitle.
|
This is the second subtitle.
|
||||||
|
|
||||||
|
00:20.000 --> 00:21.000
|
||||||
|
This is a <v Mary>reference by element</v>
|
||||||
|
@ -21,6 +21,8 @@ import android.text.Spanned;
|
|||||||
import android.text.style.StyleSpan;
|
import android.text.style.StyleSpan;
|
||||||
import android.text.style.UnderlineSpan;
|
import android.text.style.UnderlineSpan;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit test for {@link WebvttCueParser}.
|
* Unit test for {@link WebvttCueParser}.
|
||||||
*/
|
*/
|
||||||
@ -221,7 +223,8 @@ public final class WebvttCueParserTest extends InstrumentationTestCase {
|
|||||||
|
|
||||||
private static Spanned parseCueText(String string) {
|
private static Spanned parseCueText(String string) {
|
||||||
WebvttCue.Builder builder = new WebvttCue.Builder();
|
WebvttCue.Builder builder = new WebvttCue.Builder();
|
||||||
WebvttCueParser.parseCueText(string, builder);
|
WebvttCueParser.parseCueText(null, string, builder,
|
||||||
|
Collections.<String, WebvttCssStyle>emptyMap());
|
||||||
return (Spanned) builder.build().text;
|
return (Spanned) builder.build().text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ 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.UnderlineSpan;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -154,7 +155,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(4, subtitle.getEventTimeCount());
|
assertEquals(6, 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.");
|
||||||
@ -162,11 +163,14 @@ 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);
|
||||||
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;
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void assertCue(WebvttSubtitle subtitle, int eventTimeIndex, long startTimeUs,
|
private static void assertCue(WebvttSubtitle subtitle, int eventTimeIndex, long startTimeUs,
|
||||||
|
@ -86,20 +86,22 @@ import java.util.Map;
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a string containing the selector. Empty string is the universal selector, and null
|
* Returns a string containing the selector. {@link WebvttCueParser#UNIVERSAL_CUE_ID} is the
|
||||||
* means syntax error.
|
* universal selector, and null means syntax error.
|
||||||
*
|
*
|
||||||
* <p>Expected inputs are:
|
* <p>Expected inputs are:
|
||||||
* ::cue
|
* <ul>
|
||||||
* ::cue(#id)
|
* <li>::cue
|
||||||
* ::cue(elem)
|
* <li>::cue(#id)
|
||||||
* ::cue(.class)
|
* <li>::cue(elem)
|
||||||
* ::cue(elem.class)
|
* <li>::cue(.class)
|
||||||
* ::cue(v[voice="Someone"])
|
* <li>::cue(elem.class)
|
||||||
|
* <li>::cue(v[voice="Someone"])
|
||||||
|
* </ul>
|
||||||
*
|
*
|
||||||
* @param input From which the selector is obtained.
|
* @param input From which the selector is obtained.
|
||||||
* @return A string containing the target, {@link WebvttCue#UNIVERSAL_CUE_ID} if targets all cues
|
* @return A string containing the target, {@link WebvttCueParser#UNIVERSAL_CUE_ID} if the
|
||||||
* and null if an error was encountered.
|
* selector is universal (targets all cues) or null if an error was encountered.
|
||||||
*/
|
*/
|
||||||
private static String parseSelector(ParsableByteArray input, StringBuilder stringBuilder) {
|
private static String parseSelector(ParsableByteArray input, StringBuilder stringBuilder) {
|
||||||
skipWhitespaceAndComments(input);
|
skipWhitespaceAndComments(input);
|
||||||
@ -117,7 +119,7 @@ import java.util.Map;
|
|||||||
}
|
}
|
||||||
if ("{".equals(token)) {
|
if ("{".equals(token)) {
|
||||||
input.setPosition(position);
|
input.setPosition(position);
|
||||||
return WebvttCue.UNIVERSAL_CUE_ID;
|
return WebvttCueParser.UNIVERSAL_CUE_ID;
|
||||||
}
|
}
|
||||||
String target = null;
|
String target = null;
|
||||||
if ("(".equals(token)) {
|
if ("(".equals(token)) {
|
||||||
|
@ -22,6 +22,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
import com.google.android.exoplayer.util.Util;
|
import com.google.android.exoplayer.util.Util;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -82,7 +83,8 @@ public final class Mp4WebvttParser extends SubtitleParser {
|
|||||||
if (boxType == TYPE_sttg) {
|
if (boxType == TYPE_sttg) {
|
||||||
WebvttCueParser.parseCueSettingsList(boxPayload, builder);
|
WebvttCueParser.parseCueSettingsList(boxPayload, builder);
|
||||||
} else if (boxType == TYPE_payl) {
|
} else if (boxType == TYPE_payl) {
|
||||||
WebvttCueParser.parseCueText(boxPayload.trim(), builder);
|
WebvttCueParser.parseCueText(null, boxPayload.trim(), builder,
|
||||||
|
Collections.<String, WebvttCssStyle>emptyMap());
|
||||||
} else {
|
} else {
|
||||||
// Other VTTCueBox children are still not supported and are ignored.
|
// Other VTTCueBox children are still not supported and are ignored.
|
||||||
}
|
}
|
||||||
|
@ -18,32 +18,14 @@ package com.google.android.exoplayer.text.webvtt;
|
|||||||
import com.google.android.exoplayer.text.Cue;
|
import com.google.android.exoplayer.text.Cue;
|
||||||
|
|
||||||
import android.text.Layout.Alignment;
|
import android.text.Layout.Alignment;
|
||||||
import android.text.Spannable;
|
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.Spanned;
|
|
||||||
import android.text.style.AbsoluteSizeSpan;
|
|
||||||
import android.text.style.AlignmentSpan;
|
|
||||||
import android.text.style.BackgroundColorSpan;
|
|
||||||
import android.text.style.ForegroundColorSpan;
|
|
||||||
import android.text.style.RelativeSizeSpan;
|
|
||||||
import android.text.style.StrikethroughSpan;
|
|
||||||
import android.text.style.StyleSpan;
|
|
||||||
import android.text.style.TypefaceSpan;
|
|
||||||
import android.text.style.UnderlineSpan;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A representation of a WebVTT cue.
|
* A representation of a WebVTT cue.
|
||||||
*/
|
*/
|
||||||
/* package */ final class WebvttCue extends Cue {
|
/* package */ final class WebvttCue extends Cue {
|
||||||
|
|
||||||
public static final String UNIVERSAL_CUE_ID = "";
|
|
||||||
public static final String CUE_ID_PREFIX = "#";
|
|
||||||
|
|
||||||
public final String id;
|
|
||||||
public final long startTime;
|
public final long startTime;
|
||||||
public final long endTime;
|
public final long endTime;
|
||||||
|
|
||||||
@ -52,15 +34,13 @@ import java.util.Map;
|
|||||||
}
|
}
|
||||||
|
|
||||||
public WebvttCue(long startTime, long endTime, CharSequence text) {
|
public WebvttCue(long startTime, long endTime, CharSequence text) {
|
||||||
this(null, startTime, endTime, text, null, Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.TYPE_UNSET,
|
this(startTime, endTime, text, null, Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.TYPE_UNSET,
|
||||||
Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET);
|
Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebvttCue(String id, long startTime, long endTime, CharSequence text,
|
public WebvttCue(long startTime, long endTime, CharSequence text, Alignment textAlignment,
|
||||||
Alignment textAlignment, float line, int lineType, int lineAnchor, float position,
|
float line, int lineType, int lineAnchor, float position, int positionAnchor, float width) {
|
||||||
int positionAnchor, float width) {
|
|
||||||
super(text, textAlignment, line, lineType, lineAnchor, position, positionAnchor, width);
|
super(text, textAlignment, line, lineType, lineAnchor, position, positionAnchor, width);
|
||||||
this.id = id;
|
|
||||||
this.startTime = startTime;
|
this.startTime = startTime;
|
||||||
this.endTime = endTime;
|
this.endTime = endTime;
|
||||||
}
|
}
|
||||||
@ -83,7 +63,6 @@ import java.util.Map;
|
|||||||
|
|
||||||
private static final String TAG = "WebvttCueBuilder";
|
private static final String TAG = "WebvttCueBuilder";
|
||||||
|
|
||||||
private String id;
|
|
||||||
private long startTime;
|
private long startTime;
|
||||||
private long endTime;
|
private long endTime;
|
||||||
private SpannableStringBuilder text;
|
private SpannableStringBuilder text;
|
||||||
@ -117,25 +96,13 @@ import java.util.Map;
|
|||||||
// Construction methods.
|
// Construction methods.
|
||||||
|
|
||||||
public WebvttCue build() {
|
public WebvttCue build() {
|
||||||
return build(Collections.<String, WebvttCssStyle>emptyMap());
|
|
||||||
}
|
|
||||||
|
|
||||||
public WebvttCue build(Map<String, WebvttCssStyle> styleMap) {
|
|
||||||
// TODO: Add support for inner spans.
|
|
||||||
maybeApplyStyleToText(styleMap.get(UNIVERSAL_CUE_ID), 0, text.length());
|
|
||||||
maybeApplyStyleToText(styleMap.get(CUE_ID_PREFIX + id), 0, text.length());
|
|
||||||
if (position != Cue.DIMEN_UNSET && positionAnchor == Cue.TYPE_UNSET) {
|
if (position != Cue.DIMEN_UNSET && positionAnchor == Cue.TYPE_UNSET) {
|
||||||
derivePositionAnchorFromAlignment();
|
derivePositionAnchorFromAlignment();
|
||||||
}
|
}
|
||||||
return new WebvttCue(id, startTime, endTime, text, textAlignment, line, lineType, lineAnchor,
|
return new WebvttCue(startTime, endTime, text, textAlignment, line, lineType, lineAnchor,
|
||||||
position, positionAnchor, width);
|
position, positionAnchor, width);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setId(String id) {
|
|
||||||
this.id = id;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setStartTime(long time) {
|
public Builder setStartTime(long time) {
|
||||||
startTime = time;
|
startTime = time;
|
||||||
return this;
|
return this;
|
||||||
@ -209,54 +176,6 @@ import java.util.Map;
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void maybeApplyStyleToText(WebvttCssStyle style, int start, int end) {
|
|
||||||
if (style == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (style.getStyle() != WebvttCssStyle.UNSPECIFIED) {
|
|
||||||
text.setSpan(new StyleSpan(style.getStyle()), start, end,
|
|
||||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
}
|
|
||||||
if (style.isLinethrough()) {
|
|
||||||
text.setSpan(new StrikethroughSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
}
|
|
||||||
if (style.isUnderline()) {
|
|
||||||
text.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
}
|
|
||||||
if (style.hasFontColor()) {
|
|
||||||
text.setSpan(new ForegroundColorSpan(style.getFontColor()), start, end,
|
|
||||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
}
|
|
||||||
if (style.hasBackgroundColor()) {
|
|
||||||
text.setSpan(new BackgroundColorSpan(style.getBackgroundColor()), start, end,
|
|
||||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
}
|
|
||||||
if (style.getFontFamily() != null) {
|
|
||||||
text.setSpan(new TypefaceSpan(style.getFontFamily()), start, end,
|
|
||||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
}
|
|
||||||
if (style.getTextAlign() != null) {
|
|
||||||
text.setSpan(new AlignmentSpan.Standard(style.getTextAlign()), start, end,
|
|
||||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
}
|
|
||||||
if (style.getFontSizeUnit() != WebvttCssStyle.UNSPECIFIED) {
|
|
||||||
switch (style.getFontSizeUnit()) {
|
|
||||||
case WebvttCssStyle.FONT_SIZE_UNIT_PIXEL:
|
|
||||||
text.setSpan(new AbsoluteSizeSpan((int) style.getFontSize(), true), start, end,
|
|
||||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
break;
|
|
||||||
case WebvttCssStyle.FONT_SIZE_UNIT_EM:
|
|
||||||
text.setSpan(new RelativeSizeSpan(style.getFontSize()), start, end,
|
|
||||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
break;
|
|
||||||
case WebvttCssStyle.FONT_SIZE_UNIT_PERCENT:
|
|
||||||
text.setSpan(new RelativeSizeSpan(style.getFontSize() / 100), start, end,
|
|
||||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -20,12 +20,21 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
|
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.text.Layout.Alignment;
|
import android.text.Layout.Alignment;
|
||||||
|
import android.text.Spannable;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
|
import android.text.style.AbsoluteSizeSpan;
|
||||||
|
import android.text.style.AlignmentSpan;
|
||||||
|
import android.text.style.BackgroundColorSpan;
|
||||||
|
import android.text.style.ForegroundColorSpan;
|
||||||
|
import android.text.style.RelativeSizeSpan;
|
||||||
|
import android.text.style.StrikethroughSpan;
|
||||||
import android.text.style.StyleSpan;
|
import android.text.style.StyleSpan;
|
||||||
|
import android.text.style.TypefaceSpan;
|
||||||
import android.text.style.UnderlineSpan;
|
import android.text.style.UnderlineSpan;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
@ -35,6 +44,8 @@ import java.util.regex.Pattern;
|
|||||||
*/
|
*/
|
||||||
/* package */ final class WebvttCueParser {
|
/* 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
|
public static final Pattern CUE_HEADER_PATTERN = Pattern
|
||||||
.compile("^(\\S+)\\s+-->\\s+(\\S+)(.*)?$");
|
.compile("^(\\S+)\\s+-->\\s+(\\S+)(.*)?$");
|
||||||
|
|
||||||
@ -76,22 +87,24 @@ import java.util.regex.Pattern;
|
|||||||
*
|
*
|
||||||
* @param webvttData Parsable WebVTT file data.
|
* @param webvttData Parsable WebVTT file data.
|
||||||
* @param builder Builder for WebVTT Cues.
|
* @param builder Builder for WebVTT Cues.
|
||||||
|
* @param styleMap Maps selector to style as referenced by the CSS ::cue pseudo-element.
|
||||||
* @return True if a valid Cue was found, false otherwise.
|
* @return True if a valid Cue was found, false otherwise.
|
||||||
*/
|
*/
|
||||||
/* package */ boolean parseCue(ParsableByteArray webvttData, WebvttCue.Builder builder) {
|
/* package */ boolean parseCue(ParsableByteArray webvttData, WebvttCue.Builder builder,
|
||||||
|
Map<String, WebvttCssStyle> styleMap) {
|
||||||
String firstLine = webvttData.readLine();
|
String firstLine = webvttData.readLine();
|
||||||
Matcher cueHeaderMatcher = WebvttCueParser.CUE_HEADER_PATTERN.matcher(firstLine);
|
Matcher cueHeaderMatcher = WebvttCueParser.CUE_HEADER_PATTERN.matcher(firstLine);
|
||||||
if (cueHeaderMatcher.matches()) {
|
if (cueHeaderMatcher.matches()) {
|
||||||
// We have found the timestamps in the first line. No id present.
|
// We have found the timestamps in the first line. No id present.
|
||||||
return parseCue(cueHeaderMatcher, webvttData, builder, textBuilder);
|
return parseCue(null, cueHeaderMatcher, webvttData, builder, textBuilder, styleMap);
|
||||||
} else {
|
} else {
|
||||||
// The first line is not the timestamps, but could be the cue id.
|
// The first line is not the timestamps, but could be the cue id.
|
||||||
String secondLine = webvttData.readLine();
|
String secondLine = webvttData.readLine();
|
||||||
cueHeaderMatcher = WebvttCueParser.CUE_HEADER_PATTERN.matcher(secondLine);
|
cueHeaderMatcher = WebvttCueParser.CUE_HEADER_PATTERN.matcher(secondLine);
|
||||||
if (cueHeaderMatcher.matches()) {
|
if (cueHeaderMatcher.matches()) {
|
||||||
// We can do the rest of the parsing, including the id.
|
// We can do the rest of the parsing, including the id.
|
||||||
builder.setId(firstLine.trim());
|
return parseCue(firstLine.trim(), cueHeaderMatcher, webvttData, builder, textBuilder,
|
||||||
return parseCue(cueHeaderMatcher, webvttData, builder, textBuilder);
|
styleMap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -131,10 +144,13 @@ import java.util.regex.Pattern;
|
|||||||
/**
|
/**
|
||||||
* Parses the text payload of a WebVTT Cue and applies modifications on {@link WebvttCue.Builder}.
|
* Parses the text payload of a WebVTT Cue and applies modifications on {@link WebvttCue.Builder}.
|
||||||
*
|
*
|
||||||
|
* @param id Id of the cue, {@code null} if it is not present.
|
||||||
* @param markup The markup text to be parsed.
|
* @param markup The markup text to be parsed.
|
||||||
|
* @param styleMap Maps selector to style as referenced by the CSS ::cue pseudo-element.
|
||||||
* @param builder Target builder.
|
* @param builder Target builder.
|
||||||
*/
|
*/
|
||||||
/* package */ static void parseCueText(String markup, WebvttCue.Builder builder) {
|
/* package */ static void parseCueText(String id, String markup, WebvttCue.Builder builder,
|
||||||
|
Map<String, WebvttCssStyle> styleMap) {
|
||||||
SpannableStringBuilder spannedText = new SpannableStringBuilder();
|
SpannableStringBuilder spannedText = new SpannableStringBuilder();
|
||||||
Stack<StartTag> startTagStack = new Stack<>();
|
Stack<StartTag> startTagStack = new Stack<>();
|
||||||
String[] tagTokens;
|
String[] tagTokens;
|
||||||
@ -164,7 +180,7 @@ import java.util.regex.Pattern;
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
startTag = startTagStack.pop();
|
startTag = startTagStack.pop();
|
||||||
applySpansForTag(startTag, spannedText);
|
applySpansForTag(startTag, spannedText, styleMap);
|
||||||
} while(!startTag.name.equals(tagTokens[0]));
|
} while(!startTag.name.equals(tagTokens[0]));
|
||||||
} else if (!isVoidTag) {
|
} else if (!isVoidTag) {
|
||||||
startTagStack.push(new StartTag(tagTokens[0], spannedText.length()));
|
startTagStack.push(new StartTag(tagTokens[0], spannedText.length()));
|
||||||
@ -194,13 +210,15 @@ import java.util.regex.Pattern;
|
|||||||
}
|
}
|
||||||
// apply unclosed tags
|
// apply unclosed tags
|
||||||
while (!startTagStack.isEmpty()) {
|
while (!startTagStack.isEmpty()) {
|
||||||
applySpansForTag(startTagStack.pop(), spannedText);
|
applySpansForTag(startTagStack.pop(), spannedText, styleMap);
|
||||||
}
|
}
|
||||||
|
applyStyleToText(spannedText, styleMap.get(UNIVERSAL_CUE_ID), 0, spannedText.length());
|
||||||
|
applyStyleToText(spannedText, styleMap.get(CUE_ID_PREFIX + id), 0, spannedText.length());
|
||||||
builder.setText(spannedText);
|
builder.setText(spannedText);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean parseCue(Matcher cueHeaderMatcher, ParsableByteArray webvttData,
|
private static boolean parseCue(String id, Matcher cueHeaderMatcher, ParsableByteArray webvttData,
|
||||||
WebvttCue.Builder builder, StringBuilder textBuilder) {
|
WebvttCue.Builder builder, StringBuilder textBuilder, Map<String, WebvttCssStyle> styleMap) {
|
||||||
try {
|
try {
|
||||||
// Parse the cue start and end times.
|
// Parse the cue start and end times.
|
||||||
builder.setStartTime(WebvttParserUtil.parseTimestampUs(cueHeaderMatcher.group(1)))
|
builder.setStartTime(WebvttParserUtil.parseTimestampUs(cueHeaderMatcher.group(1)))
|
||||||
@ -221,7 +239,7 @@ import java.util.regex.Pattern;
|
|||||||
}
|
}
|
||||||
textBuilder.append(line.trim());
|
textBuilder.append(line.trim());
|
||||||
}
|
}
|
||||||
parseCueText(textBuilder.toString(), builder);
|
parseCueText(id, textBuilder.toString(), builder, styleMap);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,22 +351,79 @@ import java.util.regex.Pattern;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void applySpansForTag(StartTag startTag, SpannableStringBuilder spannedText) {
|
private static void applySpansForTag(StartTag startTag, SpannableStringBuilder spannedText,
|
||||||
|
Map<String, WebvttCssStyle> styleMap) {
|
||||||
|
WebvttCssStyle styleForTag = styleMap.get(startTag.name);
|
||||||
|
int start = startTag.position;
|
||||||
|
int end = spannedText.length();
|
||||||
switch(startTag.name) {
|
switch(startTag.name) {
|
||||||
case TAG_BOLD:
|
case TAG_BOLD:
|
||||||
spannedText.setSpan(new StyleSpan(STYLE_BOLD), startTag.position,
|
spannedText.setSpan(new StyleSpan(STYLE_BOLD), start, end,
|
||||||
spannedText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
return;
|
|
||||||
case TAG_ITALIC:
|
|
||||||
spannedText.setSpan(new StyleSpan(STYLE_ITALIC), startTag.position,
|
|
||||||
spannedText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
return;
|
|
||||||
case TAG_UNDERLINE:
|
|
||||||
spannedText.setSpan(new UnderlineSpan(), startTag.position,
|
|
||||||
spannedText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
break;
|
break;
|
||||||
|
case TAG_ITALIC:
|
||||||
|
spannedText.setSpan(new StyleSpan(STYLE_ITALIC), start, end,
|
||||||
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
break;
|
||||||
|
case TAG_UNDERLINE:
|
||||||
|
spannedText.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
break;
|
||||||
|
case TAG_CLASS:
|
||||||
|
case TAG_LANG:
|
||||||
|
case TAG_VOICE:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
applyStyleToText(spannedText, styleForTag, start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void applyStyleToText(SpannableStringBuilder spannedText,
|
||||||
|
WebvttCssStyle style, int start, int end) {
|
||||||
|
if (style == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (style.getStyle() != WebvttCssStyle.UNSPECIFIED) {
|
||||||
|
spannedText.setSpan(new StyleSpan(style.getStyle()), start, end,
|
||||||
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
}
|
||||||
|
if (style.isLinethrough()) {
|
||||||
|
spannedText.setSpan(new StrikethroughSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
}
|
||||||
|
if (style.isUnderline()) {
|
||||||
|
spannedText.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
}
|
||||||
|
if (style.hasFontColor()) {
|
||||||
|
spannedText.setSpan(new ForegroundColorSpan(style.getFontColor()), start, end,
|
||||||
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
}
|
||||||
|
if (style.hasBackgroundColor()) {
|
||||||
|
spannedText.setSpan(new BackgroundColorSpan(style.getBackgroundColor()), start, end,
|
||||||
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
}
|
||||||
|
if (style.getFontFamily() != null) {
|
||||||
|
spannedText.setSpan(new TypefaceSpan(style.getFontFamily()), start, end,
|
||||||
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
}
|
||||||
|
if (style.getTextAlign() != null) {
|
||||||
|
spannedText.setSpan(new AlignmentSpan.Standard(style.getTextAlign()), start, end,
|
||||||
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
}
|
||||||
|
if (style.getFontSizeUnit() != WebvttCssStyle.UNSPECIFIED) {
|
||||||
|
switch (style.getFontSizeUnit()) {
|
||||||
|
case WebvttCssStyle.FONT_SIZE_UNIT_PIXEL:
|
||||||
|
spannedText.setSpan(new AbsoluteSizeSpan((int) style.getFontSize(), true), start, end,
|
||||||
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
break;
|
||||||
|
case WebvttCssStyle.FONT_SIZE_UNIT_EM:
|
||||||
|
spannedText.setSpan(new RelativeSizeSpan(style.getFontSize()), start, end,
|
||||||
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
break;
|
||||||
|
case WebvttCssStyle.FONT_SIZE_UNIT_PERCENT:
|
||||||
|
spannedText.setSpan(new RelativeSizeSpan(style.getFontSize() / 100), start, end,
|
||||||
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,8 +76,8 @@ public final class WebvttParser extends SubtitleParser {
|
|||||||
parsableWebvttData.readLine(); // Consume the "STYLE" header.
|
parsableWebvttData.readLine(); // Consume the "STYLE" header.
|
||||||
cssParser.parseBlock(parsableWebvttData, styleMap);
|
cssParser.parseBlock(parsableWebvttData, styleMap);
|
||||||
} else if (eventFound == CUE_FOUND) {
|
} else if (eventFound == CUE_FOUND) {
|
||||||
if (cueParser.parseCue(parsableWebvttData, webvttCueBuilder)) {
|
if (cueParser.parseCue(parsableWebvttData, webvttCueBuilder, styleMap)) {
|
||||||
subtitles.add(webvttCueBuilder.build(styleMap));
|
subtitles.add(webvttCueBuilder.build());
|
||||||
webvttCueBuilder.reset();
|
webvttCueBuilder.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user