diff --git a/library/src/androidTest/java/com/google/android/exoplayer/text/webvtt/WebvttCueParserTest.java b/library/src/androidTest/java/com/google/android/exoplayer/text/webvtt/WebvttCueParserTest.java index a959ff7019..f5a7af9790 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer/text/webvtt/WebvttCueParserTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer/text/webvtt/WebvttCueParserTest.java @@ -27,7 +27,7 @@ import android.text.style.UnderlineSpan; public final class WebvttCueParserTest extends InstrumentationTestCase { public void testParseStrictValidClassesAndTrailingTokens() throws Exception { - Spanned text = WebvttCueParser.parse("" + Spanned text = WebvttCueParser.parseCueText("" + "This is text with html tags"); assertEquals("This is text with html tags", text.toString()); @@ -48,7 +48,7 @@ public final class WebvttCueParserTest extends InstrumentationTestCase { } public void testParseStrictValidUnsupportedTagsStrippedOut() throws Exception { - Spanned text = WebvttCueParser.parse( + Spanned text = WebvttCueParser.parseCueText( "This is text with " + "html tags"); @@ -58,7 +58,7 @@ public final class WebvttCueParserTest extends InstrumentationTestCase { } public void testParseWellFormedUnclosedEndAtCueEnd() throws Exception { - Spanned text = WebvttCueParser.parse( + Spanned text = WebvttCueParser.parseCueText( "An unclosed u tag with italic inside"); assertEquals("An unclosed u tag with italic inside", text.toString()); @@ -76,7 +76,7 @@ public final class WebvttCueParserTest extends InstrumentationTestCase { } public void testParseWellFormedUnclosedEndAtParent() throws Exception { - Spanned text = WebvttCueParser.parse( + Spanned text = WebvttCueParser.parseCueText( "An unclosed u tag with underline and italic inside"); assertEquals("An unclosed u tag with underline and italic inside", text.toString()); @@ -95,7 +95,7 @@ public final class WebvttCueParserTest extends InstrumentationTestCase { } public void testParseMalformedNestedElements() throws Exception { - Spanned text = WebvttCueParser.parse( + Spanned text = WebvttCueParser.parseCueText( "An unclosed u tag with italic inside"); assertEquals("An unclosed u tag with italic inside", text.toString()); @@ -121,7 +121,7 @@ public final class WebvttCueParserTest extends InstrumentationTestCase { } public void testParseCloseNonExistingTag() throws Exception { - Spanned text = WebvttCueParser.parse("blahblahblahblah"); + Spanned text = WebvttCueParser.parseCueText("blahblahblahblah"); assertEquals("blahblahblahblah", text.toString()); StyleSpan[] spans = getSpans(text, StyleSpan.class); @@ -132,42 +132,42 @@ public final class WebvttCueParserTest extends InstrumentationTestCase { } public void testParseEmptyTagName() throws Exception { - Spanned text = WebvttCueParser.parse("An unclosed u tag with <>italic inside"); + Spanned text = WebvttCueParser.parseCueText("An unclosed u tag with <>italic inside"); assertEquals("An unclosed u tag with italic inside", text.toString()); } public void testParseEntities() throws Exception { - Spanned text = WebvttCueParser.parse("& > <  "); + Spanned text = WebvttCueParser.parseCueText("& > <  "); assertEquals("& > < ", text.toString()); } public void testParseEntitiesUnsupported() throws Exception { - Spanned text = WebvttCueParser.parse("&noway; &sure;"); + Spanned text = WebvttCueParser.parseCueText("&noway; &sure;"); assertEquals(" ", text.toString()); } public void testParseEntitiesNotTerminated() throws Exception { - Spanned text = WebvttCueParser.parse("& here comes text"); + Spanned text = WebvttCueParser.parseCueText("& here comes text"); assertEquals("& here comes text", text.toString()); } public void testParseEntitiesNotTerminatedUnsupported() throws Exception { - Spanned text = WebvttCueParser.parse("&surenot here comes text"); + Spanned text = WebvttCueParser.parseCueText("&surenot here comes text"); assertEquals(" here comes text", text.toString()); } public void testParseEntitiesNotTerminatedNoSpace() throws Exception { - Spanned text = WebvttCueParser.parse("&surenot"); + Spanned text = WebvttCueParser.parseCueText("&surenot"); assertEquals("&surenot", text.toString()); } public void testParseVoidTag() throws Exception { - Spanned text = WebvttCueParser.parse("here comes
text
"); + Spanned text = WebvttCueParser.parseCueText("here comes
text
"); assertEquals("here comes text", text.toString()); } public void testParseMultipleTagsOfSameKind() { - Spanned text = WebvttCueParser.parse("blah blah blah foo"); + Spanned text = WebvttCueParser.parseCueText("blah blah blah foo"); assertEquals("blah blah blah foo", text.toString()); StyleSpan[] spans = getSpans(text, StyleSpan.class); @@ -181,7 +181,7 @@ public final class WebvttCueParserTest extends InstrumentationTestCase { } public void testParseInvalidVoidSlash() { - Spanned text = WebvttCueParser.parse("blah blah"); + Spanned text = WebvttCueParser.parseCueText("blah blah"); assertEquals("blah blah", text.toString()); StyleSpan[] spans = getSpans(text, StyleSpan.class); @@ -189,37 +189,37 @@ public final class WebvttCueParserTest extends InstrumentationTestCase { } public void testParseMonkey() throws Exception { - Spanned text = WebvttCueParser.parse( + Spanned text = WebvttCueParser.parseCueText( "< u>An unclosed u tag with <<<<< i>italic
inside"); assertEquals("An unclosed u tag with italic inside", text.toString()); - text = WebvttCueParser.parse(">>>>>>>>>An unclosed u tag with <<<<< italic" - + " inside"); + text = WebvttCueParser.parseCueText(">>>>>>>>>An unclosed u tag with <<<<< italic" + + " inside"); assertEquals(">>>>>>>>>An unclosed u tag with inside", text.toString()); } public void testParseCornerCases() throws Exception { - Spanned text = WebvttCueParser.parse(">"); + Spanned text = WebvttCueParser.parseCueText(">"); assertEquals(">", text.toString()); - text = WebvttCueParser.parse("<"); + text = WebvttCueParser.parseCueText("<"); assertEquals("", text.toString()); - text = WebvttCueParser.parse("><<<<<<<<<<"); + text = WebvttCueParser.parseCueText("<<<<<<>><<<<<<<<<<"); assertEquals(">", text.toString()); - text = WebvttCueParser.parse("<>"); + text = WebvttCueParser.parseCueText("<>"); assertEquals("", text.toString()); - text = WebvttCueParser.parse("&"); + text = WebvttCueParser.parseCueText("&"); assertEquals("&", text.toString()); - text = WebvttCueParser.parse("&&&&&&&"); + text = WebvttCueParser.parseCueText("&&&&&&&"); assertEquals("&&&&&&&", text.toString()); } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/WebvttExtractor.java b/library/src/main/java/com/google/android/exoplayer/hls/WebvttExtractor.java index f1bb1e0661..1406807ede 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/WebvttExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/WebvttExtractor.java @@ -25,6 +25,7 @@ import com.google.android.exoplayer.extractor.PositionHolder; import com.google.android.exoplayer.extractor.SeekMap; import com.google.android.exoplayer.extractor.TrackOutput; import com.google.android.exoplayer.extractor.ts.PtsTimestampAdjuster; +import com.google.android.exoplayer.text.webvtt.WebvttCueParser; import com.google.android.exoplayer.text.webvtt.WebvttParserUtil; import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.ParsableByteArray; @@ -137,7 +138,7 @@ import java.util.regex.Pattern; } // Find the first cue header and parse the start time. - Matcher cueHeaderMatcher = WebvttParserUtil.findNextCueHeader(webvttData); + Matcher cueHeaderMatcher = WebvttCueParser.findNextCueHeader(webvttData); if (cueHeaderMatcher == null) { // No cues found. Don't output a sample, but still output a corresponding track. buildTrackOutput(0); diff --git a/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttCueParser.java b/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttCueParser.java index e459978d77..e0322a3d85 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttCueParser.java +++ b/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttCueParser.java @@ -15,7 +15,11 @@ */ package com.google.android.exoplayer.text.webvtt; +import com.google.android.exoplayer.text.Cue; +import com.google.android.exoplayer.util.ParsableByteArray; + import android.graphics.Typeface; +import android.text.Layout.Alignment; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.style.StyleSpan; @@ -23,11 +27,19 @@ import android.text.style.UnderlineSpan; import android.util.Log; import java.util.Stack; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Parser for webvtt cue text. (https://w3c.github.io/webvtt/#cue-text) */ -/* package */ final class WebvttCueParser { +public final class WebvttCueParser { + + public static final Pattern CUE_HEADER_PATTERN = Pattern + .compile("^(\\S+)\\s+-->\\s+(\\S+)(.*)?$"); + + private static final Pattern COMMENT = Pattern.compile("^NOTE((\u0020|\u0009).*)?$"); + private static final Pattern CUE_SETTING_PATTERN = Pattern.compile("(\\S+?):(\\S+)"); private static final char CHAR_LESS_THAN = '<'; private static final char CHAR_GREATER_THAN = '>'; @@ -54,9 +66,102 @@ import java.util.Stack; private static final String TAG = "WebvttCueParser"; - private WebvttCueParser() {} + private StringBuilder textBuilder; + private PositionHolder positionHolder; - public static Spanned parse(String markup) { + public WebvttCueParser() { + positionHolder = new PositionHolder(); + textBuilder = new StringBuilder(); + } + + /** + * Parses the next valid Webvtt cue in a parsable array, including timestamps, settings and text. + * + * @param webvttData parsable Webvtt file data. + * @return a {@link WebvttCue} instance if cue content is found. {@code null} otherwise. + */ + public WebvttCue parseNextValidCue(ParsableByteArray webvttData) { + Matcher cueHeaderMatcher; + while ((cueHeaderMatcher = findNextCueHeader(webvttData)) != null) { + WebvttCue currentCue = parseCue(cueHeaderMatcher, webvttData); + if (currentCue != null) { + return currentCue; + } + } + return null; + } + + private WebvttCue parseCue(Matcher cueHeaderMatcher, ParsableByteArray webvttData) { + long cueStartTime; + long cueEndTime; + try { + // Parse the cue start and end times. + cueStartTime = WebvttParserUtil.parseTimestampUs(cueHeaderMatcher.group(1)); + cueEndTime = WebvttParserUtil.parseTimestampUs(cueHeaderMatcher.group(2)); + } catch (NumberFormatException e) { + Log.w(TAG, "Skipping cue with bad header: " + cueHeaderMatcher.group()); + return null; + } + + // Default cue settings. + Alignment cueTextAlignment = null; + float cueLine = Cue.DIMEN_UNSET; + int cueLineType = Cue.TYPE_UNSET; + int cueLineAnchor = Cue.TYPE_UNSET; + float cuePosition = Cue.DIMEN_UNSET; + int cuePositionAnchor = Cue.TYPE_UNSET; + float cueWidth = Cue.DIMEN_UNSET; + + // Parse the cue settings list. + Matcher cueSettingMatcher = CUE_SETTING_PATTERN.matcher(cueHeaderMatcher.group(3)); + while (cueSettingMatcher.find()) { + String name = cueSettingMatcher.group(1); + String value = cueSettingMatcher.group(2); + try { + if ("line".equals(name)) { + parseLineAttribute(value, positionHolder); + cueLine = positionHolder.position; + cueLineType = positionHolder.lineType; + cueLineAnchor = positionHolder.positionAnchor; + } else if ("align".equals(name)) { + cueTextAlignment = parseTextAlignment(value); + } else if ("position".equals(name)) { + parsePositionAttribute(value, positionHolder); + cuePosition = positionHolder.position; + cuePositionAnchor = positionHolder.positionAnchor; + } else if ("size".equals(name)) { + cueWidth = WebvttParserUtil.parsePercentage(value); + } else { + Log.w(TAG, "Unknown cue setting " + name + ":" + value); + } + } catch (NumberFormatException e) { + Log.w(TAG, "Skipping bad cue setting: " + cueSettingMatcher.group()); + } + } + + if (cuePosition != Cue.DIMEN_UNSET && cuePositionAnchor == Cue.TYPE_UNSET) { + // Computed position alignment should be derived from the text alignment if it has not been + // set explicitly. + cuePositionAnchor = alignmentToAnchor(cueTextAlignment); + } + + // Parse the cue text. + textBuilder.setLength(0); + String line; + while ((line = webvttData.readLine()) != null && !line.isEmpty()) { + if (textBuilder.length() > 0) { + textBuilder.append("\n"); + } + textBuilder.append(line.trim()); + } + + CharSequence cueText = parseCueText(textBuilder.toString()); + + return new WebvttCue(cueStartTime, cueEndTime, cueText, cueTextAlignment, cueLine, + cueLineType, cueLineAnchor, cuePosition, cuePositionAnchor, cueWidth); + } + + /* package */ static Spanned parseCueText(String markup) { SpannableStringBuilder spannedText = new SpannableStringBuilder(); Stack startTagStack = new Stack<>(); String[] tagTokens; @@ -121,6 +226,126 @@ import java.util.Stack; return spannedText; } + /** + * Reads lines up to and including the next WebVTT cue header. + * + * @param input The input from which lines should be read. + * @return A {@link Matcher} for the WebVTT cue header, or null if the end of the input was + * reached without a cue header being found. In the case that a cue header is found, groups 1, + * 2 and 3 of the returned matcher contain the start time, end time and settings list. + */ + public static Matcher findNextCueHeader(ParsableByteArray input) { + String line; + while ((line = input.readLine()) != null) { + if (COMMENT.matcher(line).matches()) { + // Skip until the end of the comment block. + while ((line = input.readLine()) != null && !line.isEmpty()) {} + } else { + Matcher cueHeaderMatcher = WebvttCueParser.CUE_HEADER_PATTERN.matcher(line); + if (cueHeaderMatcher.matches()) { + return cueHeaderMatcher; + } + } + } + return null; + } + + private static final class PositionHolder { + + public float position; + public int positionAnchor; + public int lineType; + + } + + // Internal methods + + private static void parseLineAttribute(String s, PositionHolder out) + throws NumberFormatException { + int lineAnchor; + int commaPosition = s.indexOf(','); + if (commaPosition != -1) { + lineAnchor = parsePositionAnchor(s.substring(commaPosition + 1)); + s = s.substring(0, commaPosition); + } else { + lineAnchor = Cue.TYPE_UNSET; + } + float line; + int lineType; + if (s.endsWith("%")) { + line = WebvttParserUtil.parsePercentage(s); + lineType = Cue.LINE_TYPE_FRACTION; + } else { + line = Integer.parseInt(s); + lineType = Cue.LINE_TYPE_NUMBER; + } + out.position = line; + out.positionAnchor = lineAnchor; + out.lineType = lineType; + } + + private static void parsePositionAttribute(String s, PositionHolder out) + throws NumberFormatException { + int positionAnchor; + int commaPosition = s.indexOf(','); + if (commaPosition != -1) { + positionAnchor = parsePositionAnchor(s.substring(commaPosition + 1)); + s = s.substring(0, commaPosition); + } else { + positionAnchor = Cue.TYPE_UNSET; + } + out.position = WebvttParserUtil.parsePercentage(s); + out.positionAnchor = positionAnchor; + out.lineType = Cue.TYPE_UNSET; + } + + private static int parsePositionAnchor(String s) { + switch (s) { + case "start": + return Cue.ANCHOR_TYPE_START; + case "middle": + return Cue.ANCHOR_TYPE_MIDDLE; + case "end": + return Cue.ANCHOR_TYPE_END; + default: + Log.w(TAG, "Invalid anchor value: " + s); + return Cue.TYPE_UNSET; + } + } + + private static Alignment parseTextAlignment(String s) { + switch (s) { + case "start": + case "left": + return Alignment.ALIGN_NORMAL; + case "middle": + return Alignment.ALIGN_CENTER; + case "end": + case "right": + return Alignment.ALIGN_OPPOSITE; + default: + Log.w(TAG, "Invalid alignment value: " + s); + return null; + } + } + + private static int alignmentToAnchor(Alignment alignment) { + if (alignment == null) { + return Cue.TYPE_UNSET; + } + switch (alignment) { + case ALIGN_NORMAL: + return Cue.ANCHOR_TYPE_START; + case ALIGN_CENTER: + return Cue.ANCHOR_TYPE_MIDDLE; + case ALIGN_OPPOSITE: + return Cue.ANCHOR_TYPE_END; + default: + Log.w(TAG, "Unrecognized alignment: " + alignment); + return Cue.ANCHOR_TYPE_START; + } + } + /** * Find end of tag (>). The position returned is the position of the > plus one (exclusive). * diff --git a/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttParser.java b/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttParser.java index cedbfa9736..90ec748fc1 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttParser.java +++ b/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttParser.java @@ -16,18 +16,13 @@ package com.google.android.exoplayer.text.webvtt; import com.google.android.exoplayer.ParserException; -import com.google.android.exoplayer.text.Cue; import com.google.android.exoplayer.text.SubtitleParser; import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.ParsableByteArray; -import android.text.Layout.Alignment; import android.text.TextUtils; -import android.util.Log; import java.util.ArrayList; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * A simple WebVTT parser. @@ -36,16 +31,12 @@ import java.util.regex.Pattern; */ public final class WebvttParser implements SubtitleParser { - private static final String TAG = "WebvttParser"; - - private static final Pattern CUE_SETTING = Pattern.compile("(\\S+?):(\\S+)"); - - private final PositionHolder positionHolder; - private final StringBuilder textBuilder; + private final WebvttCueParser cueParser; + private final ParsableByteArray parsableWebvttData; public WebvttParser() { - positionHolder = new PositionHolder(); - textBuilder = new StringBuilder(); + cueParser = new WebvttCueParser(); + parsableWebvttData = new ParsableByteArray(); } @Override @@ -55,190 +46,20 @@ public final class WebvttParser implements SubtitleParser { @Override public final WebvttSubtitle parse(byte[] bytes, int offset, int length) throws ParserException { - ParsableByteArray webvttData = new ParsableByteArray(bytes, offset + length); - webvttData.setPosition(offset); + parsableWebvttData.reset(bytes, offset + length); + parsableWebvttData.setPosition(offset); // Validate the first line of the header, and skip the remainder. - WebvttParserUtil.validateWebvttHeaderLine(webvttData); - while (!TextUtils.isEmpty(webvttData.readLine())) {} + WebvttParserUtil.validateWebvttHeaderLine(parsableWebvttData); + while (!TextUtils.isEmpty(parsableWebvttData.readLine())) {} - // Process the cues and text. + // Extract Cues ArrayList subtitles = new ArrayList<>(); - Matcher cueHeaderMatcher; - while ((cueHeaderMatcher = WebvttParserUtil.findNextCueHeader(webvttData)) != null) { - long cueStartTime; - long cueEndTime; - try { - // Parse the cue start and end times. - cueStartTime = WebvttParserUtil.parseTimestampUs(cueHeaderMatcher.group(1)); - cueEndTime = WebvttParserUtil.parseTimestampUs(cueHeaderMatcher.group(2)); - } catch (NumberFormatException e) { - Log.w(TAG, "Skipping cue with bad header: " + cueHeaderMatcher.group()); - continue; - } - - // Default cue settings. - Alignment cueTextAlignment = null; - float cueLine = Cue.DIMEN_UNSET; - int cueLineType = Cue.TYPE_UNSET; - int cueLineAnchor = Cue.TYPE_UNSET; - float cuePosition = Cue.DIMEN_UNSET; - int cuePositionAnchor = Cue.TYPE_UNSET; - float cueWidth = Cue.DIMEN_UNSET; - - // Parse the cue settings list. - Matcher cueSettingMatcher = CUE_SETTING.matcher(cueHeaderMatcher.group(3)); - while (cueSettingMatcher.find()) { - String name = cueSettingMatcher.group(1); - String value = cueSettingMatcher.group(2); - try { - if ("line".equals(name)) { - parseLineAttribute(value, positionHolder); - cueLine = positionHolder.position; - cueLineType = positionHolder.lineType; - cueLineAnchor = positionHolder.positionAnchor; - } else if ("align".equals(name)) { - cueTextAlignment = parseTextAlignment(value); - } else if ("position".equals(name)) { - parsePositionAttribute(value, positionHolder); - cuePosition = positionHolder.position; - cuePositionAnchor = positionHolder.positionAnchor; - } else if ("size".equals(name)) { - cueWidth = parsePercentage(value); - } else { - Log.w(TAG, "Unknown cue setting " + name + ":" + value); - } - } catch (NumberFormatException e) { - Log.w(TAG, "Skipping bad cue setting: " + cueSettingMatcher.group()); - } - } - - if (cuePosition != Cue.DIMEN_UNSET && cuePositionAnchor == Cue.TYPE_UNSET) { - // Computed position alignment should be derived from the text alignment if it has not been - // set explicitly. - cuePositionAnchor = alignmentToAnchor(cueTextAlignment); - } - - // Parse the cue text. - textBuilder.setLength(0); - String line; - while ((line = webvttData.readLine()) != null && !line.isEmpty()) { - if (textBuilder.length() > 0) { - textBuilder.append("\n"); - } - textBuilder.append(line.trim()); - } - - CharSequence cueText = WebvttCueParser.parse(textBuilder.toString()); - - WebvttCue cue = new WebvttCue(cueStartTime, cueEndTime, cueText, cueTextAlignment, cueLine, - cueLineType, cueLineAnchor, cuePosition, cuePositionAnchor, cueWidth); - subtitles.add(cue); + WebvttCue currentWebvttCue; + while ((currentWebvttCue = cueParser.parseNextValidCue(parsableWebvttData)) != null) { + subtitles.add(currentWebvttCue); } - return new WebvttSubtitle(subtitles); } - private static void parseLineAttribute(String s, PositionHolder out) - throws NumberFormatException { - int lineAnchor; - int commaPosition = s.indexOf(","); - if (commaPosition != -1) { - lineAnchor = parsePositionAnchor(s.substring(commaPosition + 1)); - s = s.substring(0, commaPosition); - } else { - lineAnchor = Cue.TYPE_UNSET; - } - float line; - int lineType; - if (s.endsWith("%")) { - line = parsePercentage(s); - lineType = Cue.LINE_TYPE_FRACTION; - } else { - line = Integer.parseInt(s); - lineType = Cue.LINE_TYPE_NUMBER; - } - out.position = line; - out.positionAnchor = lineAnchor; - out.lineType = lineType; - } - - private static void parsePositionAttribute(String s, PositionHolder out) - throws NumberFormatException { - int positionAnchor; - int commaPosition = s.indexOf(","); - if (commaPosition != -1) { - positionAnchor = parsePositionAnchor(s.substring(commaPosition + 1)); - s = s.substring(0, commaPosition); - } else { - positionAnchor = Cue.TYPE_UNSET; - } - out.position = parsePercentage(s); - out.positionAnchor = positionAnchor; - out.lineType = Cue.TYPE_UNSET; - } - - private static float parsePercentage(String s) throws NumberFormatException { - if (!s.endsWith("%")) { - throw new NumberFormatException("Percentages must end with %"); - } - s = s.substring(0, s.length() - 1); - return Float.parseFloat(s) / 100; - } - - private static int parsePositionAnchor(String s) { - switch (s) { - case "start": - return Cue.ANCHOR_TYPE_START; - case "middle": - return Cue.ANCHOR_TYPE_MIDDLE; - case "end": - return Cue.ANCHOR_TYPE_END; - default: - Log.w(TAG, "Invalid anchor value: " + s); - return Cue.TYPE_UNSET; - } - } - - private static Alignment parseTextAlignment(String s) { - switch (s) { - case "start": - case "left": - return Alignment.ALIGN_NORMAL; - case "middle": - return Alignment.ALIGN_CENTER; - case "end": - case "right": - return Alignment.ALIGN_OPPOSITE; - default: - Log.w(TAG, "Invalid alignment value: " + s); - return null; - } - } - - private static int alignmentToAnchor(Alignment alignment) { - if (alignment == null) { - return Cue.TYPE_UNSET; - } - switch (alignment) { - case ALIGN_NORMAL: - return Cue.ANCHOR_TYPE_START; - case ALIGN_CENTER: - return Cue.ANCHOR_TYPE_MIDDLE; - case ALIGN_OPPOSITE: - return Cue.ANCHOR_TYPE_END; - default: - Log.w(TAG, "Unrecognized alignment: " + alignment); - return Cue.ANCHOR_TYPE_START; - } - } - - private static final class PositionHolder { - - public float position; - public int positionAnchor; - public int lineType; - - } - } diff --git a/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttParserUtil.java b/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttParserUtil.java index bcf96b18e6..4c1a90531a 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttParserUtil.java +++ b/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttParserUtil.java @@ -18,7 +18,6 @@ package com.google.android.exoplayer.text.webvtt; import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.util.ParsableByteArray; -import java.util.regex.Matcher; import java.util.regex.Pattern; /** @@ -27,8 +26,6 @@ import java.util.regex.Pattern; public final class WebvttParserUtil { private static final Pattern HEADER = Pattern.compile("^\uFEFF?WEBVTT((\u0020|\u0009).*)?$"); - private static final Pattern COMMENT = Pattern.compile("^NOTE((\u0020|\u0009).*)?$"); - private static final Pattern CUE_HEADER = Pattern.compile("^(\\S+)\\s+-->\\s+(\\S+)(.*)?$"); private WebvttParserUtil() {} @@ -45,30 +42,6 @@ public final class WebvttParserUtil { } } - /** - * Reads lines up to and including the next WebVTT cue header. - * - * @param input The input from which lines should be read. - * @return A {@link Matcher} for the WebVTT cue header, or null if the end of the input was - * reached without a cue header being found. In the case that a cue header is found, groups 1, - * 2 and 3 of the returned matcher contain the start time, end time and settings list. - */ - public static Matcher findNextCueHeader(ParsableByteArray input) { - String line; - while ((line = input.readLine()) != null) { - if (COMMENT.matcher(line).matches()) { - // Skip until the end of the comment block. - while ((line = input.readLine()) != null && !line.isEmpty()) {} - } else { - Matcher cueHeaderMatcher = CUE_HEADER.matcher(line); - if (cueHeaderMatcher.matches()) { - return cueHeaderMatcher; - } - } - } - return null; - } - /** * Parses a WebVTT timestamp. * @@ -86,4 +59,17 @@ public final class WebvttParserUtil { return (value * 1000 + Long.parseLong(parts[1])) * 1000; } + /** + * Parses a percentage and returns a scaled float. + * @param s contains the number to parse. + * @return a float scaled number. 1.0 represents 100%. + * @throws NumberFormatException if the number format is invalid or does not end with '%'. + */ + public static float parsePercentage(String s) throws NumberFormatException { + if (!s.endsWith("%")) { + throw new NumberFormatException("Percentages must end with %"); + } + return Float.parseFloat(s.substring(0, s.length() - 1)) / 100; + } + }