From aaf57c76cf20fda30d1169dc7dc5f2aae7ca39ff Mon Sep 17 00:00:00 2001 From: bachinger Date: Wed, 19 Jun 2019 10:41:54 +0100 Subject: [PATCH] allow multiple style rules in a STYLE block of a webvtt file PiperOrigin-RevId: 253959976 --- .../exoplayer2/text/webvtt/CssParser.java | 64 +++++++++++-------- .../exoplayer2/text/webvtt/WebvttDecoder.java | 5 +- .../src/test/assets/webvtt/with_css_styles | 2 - .../exoplayer2/text/webvtt/CssParserTest.java | 64 ++++++++++++------- 4 files changed, 78 insertions(+), 57 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/CssParser.java b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/CssParser.java index 193b92678b..c5d0526eb5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/CssParser.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/CssParser.java @@ -20,6 +20,8 @@ import android.text.TextUtils; import com.google.android.exoplayer2.util.ColorParser; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; +import java.util.ArrayList; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -35,8 +37,8 @@ import java.util.regex.Pattern; private static final String PROPERTY_TEXT_DECORATION = "text-decoration"; private static final String VALUE_BOLD = "bold"; private static final String VALUE_UNDERLINE = "underline"; - private static final String BLOCK_START = "{"; - private static final String BLOCK_END = "}"; + private static final String RULE_START = "{"; + private static final String RULE_END = "}"; private static final String PROPERTY_FONT_STYLE = "font-style"; private static final String VALUE_ITALIC = "italic"; @@ -53,38 +55,46 @@ import java.util.regex.Pattern; /** * Takes a CSS style block and consumes up to the first empty line. Attempts to parse the contents - * of the style block and returns a {@link WebvttCssStyle} instance if successful, or {@code null} - * otherwise. + * of the style block and returns a list of {@link WebvttCssStyle} instances if successful. If + * parsing fails, it returns a list including only the styles which have been successfully parsed + * up to the style rule which was malformed. * * @param input The input from which the style block should be read. - * @return A {@link WebvttCssStyle} that represents the parsed block, or {@code null} if parsing - * failed. + * @return A list of {@link WebvttCssStyle}s that represents the parsed block, or a list + * containing the styles up to the parsing failure. */ - @Nullable - public WebvttCssStyle parseBlock(ParsableByteArray input) { + public List parseBlock(ParsableByteArray input) { stringBuilder.setLength(0); int initialInputPosition = input.getPosition(); skipStyleBlock(input); styleInput.reset(input.data, input.getPosition()); styleInput.setPosition(initialInputPosition); - String selector = parseSelector(styleInput, stringBuilder); - if (selector == null || !BLOCK_START.equals(parseNextToken(styleInput, stringBuilder))) { - return null; - } - WebvttCssStyle style = new WebvttCssStyle(); - applySelectorToStyle(style, selector); - String token = null; - boolean blockEndFound = false; - while (!blockEndFound) { - int position = styleInput.getPosition(); - token = parseNextToken(styleInput, stringBuilder); - blockEndFound = token == null || BLOCK_END.equals(token); - if (!blockEndFound) { - styleInput.setPosition(position); - parseStyleDeclaration(styleInput, style, stringBuilder); + + List styles = new ArrayList<>(); + String selector; + while ((selector = parseSelector(styleInput, stringBuilder)) != null) { + if (!RULE_START.equals(parseNextToken(styleInput, stringBuilder))) { + return styles; + } + WebvttCssStyle style = new WebvttCssStyle(); + applySelectorToStyle(style, selector); + String token = null; + boolean blockEndFound = false; + while (!blockEndFound) { + int position = styleInput.getPosition(); + token = parseNextToken(styleInput, stringBuilder); + blockEndFound = token == null || RULE_END.equals(token); + if (!blockEndFound) { + styleInput.setPosition(position); + parseStyleDeclaration(styleInput, style, stringBuilder); + } + } + // Check that the style rule ended correctly. + if (RULE_END.equals(token)) { + styles.add(style); } } - return BLOCK_END.equals(token) ? style : null; // Check that the style block ended correctly. + return styles; } /** @@ -110,7 +120,7 @@ import java.util.regex.Pattern; if (token == null) { return null; } - if (BLOCK_START.equals(token)) { + if (RULE_START.equals(token)) { input.setPosition(position); return ""; } @@ -159,7 +169,7 @@ import java.util.regex.Pattern; String token = parseNextToken(input, stringBuilder); if (";".equals(token)) { // The style declaration is well formed. - } else if (BLOCK_END.equals(token)) { + } else if (RULE_END.equals(token)) { // The style declaration is well formed and we can go on, but the closing bracket had to be // fed back. input.setPosition(position); @@ -255,7 +265,7 @@ import java.util.regex.Pattern; // Syntax error. return null; } - if (BLOCK_END.equals(token) || ";".equals(token)) { + if (RULE_END.equals(token) || ";".equals(token)) { input.setPosition(position); expressionEndFound = true; } else { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoder.java index 06d3c14970..fe3c86bd1e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoder.java @@ -80,10 +80,7 @@ public final class WebvttDecoder extends SimpleSubtitleDecoder { throw new SubtitleDecoderException("A style block was found after the first cue."); } parsableWebvttData.readLine(); // Consume the "STYLE" header. - WebvttCssStyle styleBlock = cssParser.parseBlock(parsableWebvttData); - if (styleBlock != null) { - definedStyles.add(styleBlock); - } + definedStyles.addAll(cssParser.parseBlock(parsableWebvttData)); } else if (event == EVENT_CUE) { if (cueParser.parseCue(parsableWebvttData, webvttCueBuilder, definedStyles)) { subtitles.add(webvttCueBuilder.build()); diff --git a/library/core/src/test/assets/webvtt/with_css_styles b/library/core/src/test/assets/webvtt/with_css_styles index 2056d50adf..c2d47d8192 100644 --- a/library/core/src/test/assets/webvtt/with_css_styles +++ b/library/core/src/test/assets/webvtt/with_css_styles @@ -13,8 +13,6 @@ STYLE ::cue(#id2) { color: peachpuff; } - -STYLE ::cue(v[voice="LaGord"]) { background-color: lime } STYLE diff --git a/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/CssParserTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/CssParserTest.java index fb9e1a875f..72be083181 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/CssParserTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/CssParserTest.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; +import java.util.List; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -87,21 +88,32 @@ public final class CssParserTest { @Test public void testParseMethodSimpleInput() { - String styleBlock1 = " ::cue { color : black; background-color: PapayaWhip }"; WebvttCssStyle expectedStyle = new WebvttCssStyle(); + String styleBlock1 = " ::cue { color : black; background-color: PapayaWhip }"; expectedStyle.setFontColor(0xFF000000); expectedStyle.setBackgroundColor(0xFFFFEFD5); - assertParserProduces(expectedStyle, styleBlock1); + assertParserProduces(styleBlock1, expectedStyle); String styleBlock2 = " ::cue { color : black }\n\n::cue { color : invalid }"; expectedStyle = new WebvttCssStyle(); expectedStyle.setFontColor(0xFF000000); - assertParserProduces(expectedStyle, styleBlock2); + assertParserProduces(styleBlock2, expectedStyle); - String styleBlock3 = " \n::cue {\n background-color\n:#00fFFe}"; + String styleBlock3 = "::cue {\n background-color\n:#00fFFe}"; expectedStyle = new WebvttCssStyle(); expectedStyle.setBackgroundColor(0xFF00FFFE); - assertParserProduces(expectedStyle, styleBlock3); + assertParserProduces(styleBlock3, expectedStyle); + } + + @Test + public void testParseMethodMultipleRulesInBlockInput() { + String styleBlock = + "::cue {\n background-color\n:#00fFFe} \n::cue {\n background-color\n:#00000000}\n"; + WebvttCssStyle expectedStyle = new WebvttCssStyle(); + expectedStyle.setBackgroundColor(0xFF00FFFE); + WebvttCssStyle secondExpectedStyle = new WebvttCssStyle(); + secondExpectedStyle.setBackgroundColor(0x000000); + assertParserProduces(styleBlock, expectedStyle, secondExpectedStyle); } @Test @@ -116,7 +128,7 @@ public final class CssParserTest { expectedStyle.setFontFamily("courier"); expectedStyle.setBold(true); - assertParserProduces(expectedStyle, styleBlock); + assertParserProduces(styleBlock, expectedStyle); } @Test @@ -128,7 +140,7 @@ public final class CssParserTest { expectedStyle.setBackgroundColor(0x190A0B0C); expectedStyle.setFontColor(0xFF010101); - assertParserProduces(expectedStyle, styleBlock); + assertParserProduces(styleBlock, expectedStyle); } @Test @@ -203,25 +215,29 @@ public final class CssParserTest { assertThat(input.readLine()).isEqualTo(expectedLine); } - private void assertParserProduces(WebvttCssStyle expected, - String styleBlock){ + private void assertParserProduces(String styleBlock, WebvttCssStyle... expectedStyles) { ParsableByteArray input = new ParsableByteArray(Util.getUtf8Bytes(styleBlock)); - WebvttCssStyle actualElem = parser.parseBlock(input); - assertThat(actualElem.hasBackgroundColor()).isEqualTo(expected.hasBackgroundColor()); - if (expected.hasBackgroundColor()) { - assertThat(actualElem.getBackgroundColor()).isEqualTo(expected.getBackgroundColor()); + List styles = parser.parseBlock(input); + assertThat(styles.size()).isEqualTo(expectedStyles.length); + for (int i = 0; i < expectedStyles.length; i++) { + WebvttCssStyle expected = expectedStyles[i]; + WebvttCssStyle actualElem = styles.get(i); + assertThat(actualElem.hasBackgroundColor()).isEqualTo(expected.hasBackgroundColor()); + if (expected.hasBackgroundColor()) { + assertThat(actualElem.getBackgroundColor()).isEqualTo(expected.getBackgroundColor()); + } + assertThat(actualElem.hasFontColor()).isEqualTo(expected.hasFontColor()); + if (expected.hasFontColor()) { + assertThat(actualElem.getFontColor()).isEqualTo(expected.getFontColor()); + } + assertThat(actualElem.getFontFamily()).isEqualTo(expected.getFontFamily()); + assertThat(actualElem.getFontSize()).isEqualTo(expected.getFontSize()); + assertThat(actualElem.getFontSizeUnit()).isEqualTo(expected.getFontSizeUnit()); + assertThat(actualElem.getStyle()).isEqualTo(expected.getStyle()); + assertThat(actualElem.isLinethrough()).isEqualTo(expected.isLinethrough()); + assertThat(actualElem.isUnderline()).isEqualTo(expected.isUnderline()); + assertThat(actualElem.getTextAlign()).isEqualTo(expected.getTextAlign()); } - assertThat(actualElem.hasFontColor()).isEqualTo(expected.hasFontColor()); - if (expected.hasFontColor()) { - assertThat(actualElem.getFontColor()).isEqualTo(expected.getFontColor()); - } - assertThat(actualElem.getFontFamily()).isEqualTo(expected.getFontFamily()); - assertThat(actualElem.getFontSize()).isEqualTo(expected.getFontSize()); - assertThat(actualElem.getFontSizeUnit()).isEqualTo(expected.getFontSizeUnit()); - assertThat(actualElem.getStyle()).isEqualTo(expected.getStyle()); - assertThat(actualElem.isLinethrough()).isEqualTo(expected.isLinethrough()); - assertThat(actualElem.isUnderline()).isEqualTo(expected.isUnderline()); - assertThat(actualElem.getTextAlign()).isEqualTo(expected.getTextAlign()); } }