allow multiple style rules in a STYLE block of a webvtt file

PiperOrigin-RevId: 253959976
This commit is contained in:
bachinger 2019-06-19 10:41:54 +01:00 committed by Oliver Woodman
parent 1952988f84
commit aaf57c76cf
4 changed files with 78 additions and 57 deletions

View File

@ -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<WebvttCssStyle> 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<WebvttCssStyle> 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 {

View File

@ -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());

View File

@ -13,8 +13,6 @@ STYLE
::cue(#id2) {
color: peachpuff;
}
STYLE
::cue(v[voice="LaGord"]) { background-color: lime }
STYLE

View File

@ -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<WebvttCssStyle> 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());
}
}