From 0ead2af22c91591d488197feb69300481b0f86d3 Mon Sep 17 00:00:00 2001 From: Arnold Szabo Date: Thu, 21 Jan 2021 22:45:07 +0100 Subject: [PATCH 1/5] Add support for SSA (V4+) PrimaryColour style --- demos/main/src/main/assets/media.exolist.json | 7 ++++ .../exoplayer2/text/ssa/SsaDecoder.java | 13 ++++++- .../android/exoplayer2/text/ssa/SsaStyle.java | 34 ++++++++++++++++--- .../android/exoplayer2/util/ColorParser.java | 21 ++++++++++++ .../exoplayer2/util/ColorParserTest.java | 5 +++ 5 files changed, 75 insertions(+), 5 deletions(-) diff --git a/demos/main/src/main/assets/media.exolist.json b/demos/main/src/main/assets/media.exolist.json index b515eca98a..57b063dbb2 100644 --- a/demos/main/src/main/assets/media.exolist.json +++ b/demos/main/src/main/assets/media.exolist.json @@ -506,6 +506,13 @@ "subtitle_mime_type": "text/x-ssa", "subtitle_language": "en" }, + { + "name": "SubStation Alpha colors", + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/video-avc-baseline-480.mp4", + "subtitle_uri": "https://drive.google.com/uc?export=download&id=13EdW4Qru-vQerUlwS_Ht5Cely_Tn0tQe", + "subtitle_mime_type": "text/x-ssa", + "subtitle_language": "en" + }, { "name": "MPEG-4 Timed Text", "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mp4/dizzy-with-tx3g.mp4" diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java index f44db4924f..fa66c49dfe 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java @@ -19,6 +19,8 @@ import static com.google.android.exoplayer2.text.Cue.LINE_TYPE_FRACTION; import static com.google.android.exoplayer2.util.Util.castNonNull; import android.text.Layout; +import android.text.SpannableString; +import android.text.style.ForegroundColorSpan; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.text.Cue; @@ -301,8 +303,17 @@ public final class SsaDecoder extends SimpleSubtitleDecoder { SsaStyle.Overrides styleOverrides, float screenWidth, float screenHeight) { - Cue.Builder cue = new Cue.Builder().setText(text); + SpannableString spannableText = new SpannableString(text); + Cue.Builder cue = new Cue.Builder().setText(spannableText); + // Apply primary color. + if (style != null) { + if (style.primaryColor != SsaStyle.SSA_COLOR_UNKNOWN) { + spannableText.setSpan(new ForegroundColorSpan(style.primaryColor), + 0, spannableText.length(), SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + // Apply alignment. @SsaStyle.SsaAlignment int alignment; if (styleOverrides.alignment != SsaStyle.SSA_ALIGNMENT_UNKNOWN) { alignment = styleOverrides.alignment; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java index 0cba339034..f2c0eb630c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java @@ -21,10 +21,12 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.graphics.PointF; import android.text.TextUtils; +import androidx.annotation.ColorInt; import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.ColorParser; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; import java.lang.annotation.Documented; @@ -83,12 +85,16 @@ import java.util.regex.Pattern; public static final int SSA_ALIGNMENT_TOP_CENTER = 8; public static final int SSA_ALIGNMENT_TOP_RIGHT = 9; + public static final int SSA_COLOR_UNKNOWN = -1; + public final String name; @SsaAlignment public final int alignment; + @ColorInt public int primaryColor; - private SsaStyle(String name, @SsaAlignment int alignment) { + private SsaStyle(String name, @SsaAlignment int alignment, @ColorInt int primaryColor) { this.name = name; this.alignment = alignment; + this.primaryColor = primaryColor; } @Nullable @@ -105,7 +111,9 @@ import java.util.regex.Pattern; } try { return new SsaStyle( - styleValues[format.nameIndex].trim(), parseAlignment(styleValues[format.alignmentIndex])); + styleValues[format.nameIndex].trim(), + parseAlignment(styleValues[format.alignmentIndex]), + parsePrimaryColor(styleValues[format.primaryColorIndex])); } catch (RuntimeException e) { Log.w(TAG, "Skipping malformed 'Style:' line: '" + styleLine + "'", e); return null; @@ -144,6 +152,16 @@ import java.util.regex.Pattern; } } + @ColorInt + private static int parsePrimaryColor(String primaryColorStr) { + try { + return ColorParser.parseSsaColor(primaryColorStr); + } catch (IllegalArgumentException ex) { + Log.w(TAG, "Failed parsing color value: " + primaryColorStr); + } + return SSA_COLOR_UNKNOWN; + } + /** * Represents a {@code Format:} line from the {@code [V4+ Styles]} section * @@ -154,11 +172,13 @@ import java.util.regex.Pattern; public final int nameIndex; public final int alignmentIndex; + public final int primaryColorIndex; public final int length; - private Format(int nameIndex, int alignmentIndex, int length) { + private Format(int nameIndex, int alignmentIndex, int primaryColorIndex, int length) { this.nameIndex = nameIndex; this.alignmentIndex = alignmentIndex; + this.primaryColorIndex = primaryColorIndex; this.length = length; } @@ -171,6 +191,7 @@ import java.util.regex.Pattern; public static Format fromFormatLine(String styleFormatLine) { int nameIndex = C.INDEX_UNSET; int alignmentIndex = C.INDEX_UNSET; + int primaryColorIndex = C.INDEX_UNSET; String[] keys = TextUtils.split(styleFormatLine.substring(SsaDecoder.FORMAT_LINE_PREFIX.length()), ","); for (int i = 0; i < keys.length; i++) { @@ -181,9 +202,14 @@ import java.util.regex.Pattern; case "alignment": alignmentIndex = i; break; + case "primarycolour": + primaryColorIndex = i; + break; } } - return nameIndex != C.INDEX_UNSET ? new Format(nameIndex, alignmentIndex, keys.length) : null; + return nameIndex != C.INDEX_UNSET + ? new Format(nameIndex, alignmentIndex, primaryColorIndex, keys.length) + : null; } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/ColorParser.java b/library/core/src/main/java/com/google/android/exoplayer2/util/ColorParser.java index 85ef43f669..697c1695e8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/ColorParser.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/ColorParser.java @@ -67,6 +67,27 @@ public final class ColorParser { return parseColorInternal(colorExpression, true); } + /** + * Parses a SSA V4+ color expression. + * + * @param colorExpression The color expression. + * @return The parsed ARGB color. + */ + @ColorInt + public static int parseSsaColor(String colorExpression) { + // SSA V4+ color format is &HAABBGGRR. + if (colorExpression.length() != 10 || !"&H".equals(colorExpression.substring(0, 2))) { + throw new IllegalArgumentException(); + } + // Convert &HAABBGGRR to #RRGGBBAA. + String rgba = new StringBuilder() + .append(colorExpression.substring(2)) + .append("#") + .reverse() + .toString(); + return parseColorInternal(rgba, true); + } + @ColorInt private static int parseColorInternal(String colorExpression, boolean alphaHasFloatFormat) { Assertions.checkArgument(!TextUtils.isEmpty(colorExpression)); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/util/ColorParserTest.java b/library/core/src/test/java/com/google/android/exoplayer2/util/ColorParserTest.java index c2f165dec1..a15ef95627 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/util/ColorParserTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/util/ColorParserTest.java @@ -18,8 +18,10 @@ package com.google.android.exoplayer2.util; import static android.graphics.Color.BLACK; import static android.graphics.Color.RED; import static android.graphics.Color.WHITE; +import static android.graphics.Color.YELLOW; import static android.graphics.Color.argb; import static android.graphics.Color.parseColor; +import static com.google.android.exoplayer2.util.ColorParser.parseSsaColor; import static com.google.android.exoplayer2.util.ColorParser.parseTtmlColor; import static com.google.common.truth.Truth.assertThat; @@ -64,6 +66,9 @@ public final class ColorParserTest { // Hex colors in ColorParser are RGBA, where-as {@link Color#parseColor} takes ARGB. assertThat(parseTtmlColor("#FFFFFF00")).isEqualTo(parseColor("#00FFFFFF")); assertThat(parseTtmlColor("#12345678")).isEqualTo(parseColor("#78123456")); + // SSA colors are in &HAABBGGRR format. + assertThat(parseSsaColor("&HFF0000FF")).isEqualTo(RED); + assertThat(parseSsaColor("&HFF00FFFF")).isEqualTo(YELLOW); } @Test From 1364c01e09733b49eaaf48ce446cae43e453ec42 Mon Sep 17 00:00:00 2001 From: Arnold Szabo Date: Mon, 25 Jan 2021 23:35:10 +0100 Subject: [PATCH 2/5] Improve support of SSA (V4+) PrimaryColour style --- .../exoplayer2/text/ssa/SsaDecoder.java | 4 +- .../android/exoplayer2/text/ssa/SsaStyle.java | 31 ++++++++++++--- .../android/exoplayer2/util/ColorParser.java | 38 ++++++++++++++----- .../exoplayer2/util/ColorParserTest.java | 20 ++++++++-- 4 files changed, 72 insertions(+), 21 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java index fa66c49dfe..c14767667a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java @@ -308,8 +308,8 @@ public final class SsaDecoder extends SimpleSubtitleDecoder { // Apply primary color. if (style != null) { - if (style.primaryColor != SsaStyle.SSA_COLOR_UNKNOWN) { - spannableText.setSpan(new ForegroundColorSpan(style.primaryColor), + if (style.primaryColor.isSet) { + spannableText.setSpan(new ForegroundColorSpan(style.primaryColor.value), 0, spannableText.length(), SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java index f2c0eb630c..bc6c729eb2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java @@ -89,9 +89,9 @@ import java.util.regex.Pattern; public final String name; @SsaAlignment public final int alignment; - @ColorInt public int primaryColor; + public SsaColor primaryColor; - private SsaStyle(String name, @SsaAlignment int alignment, @ColorInt int primaryColor) { + private SsaStyle(String name, @SsaAlignment int alignment, SsaColor primaryColor) { this.name = name; this.alignment = alignment; this.primaryColor = primaryColor; @@ -152,14 +152,33 @@ import java.util.regex.Pattern; } } - @ColorInt - private static int parsePrimaryColor(String primaryColorStr) { + private static SsaColor parsePrimaryColor(String primaryColorStr) { try { - return ColorParser.parseSsaColor(primaryColorStr); + return SsaColor.from(ColorParser.parseSsaColor(primaryColorStr.trim())); } catch (IllegalArgumentException ex) { Log.w(TAG, "Failed parsing color value: " + primaryColorStr); } - return SSA_COLOR_UNKNOWN; + return SsaColor.UNSET; + } + + /** + * Represents an SSA V4+ style color in ARGB format. + */ + /* package */ static final class SsaColor { + + public static SsaColor UNSET = new SsaColor(0, false); + + public final @ColorInt int value; + public final boolean isSet; + + private SsaColor(@ColorInt int value, boolean isSet) { + this.value = value; + this.isSet = isSet; + } + + public static SsaColor from(@ColorInt int value) { + return new SsaColor(value, true); + } } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/ColorParser.java b/library/core/src/main/java/com/google/android/exoplayer2/util/ColorParser.java index 697c1695e8..7722962262 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/ColorParser.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/ColorParser.java @@ -75,17 +75,35 @@ public final class ColorParser { */ @ColorInt public static int parseSsaColor(String colorExpression) { - // SSA V4+ color format is &HAABBGGRR. - if (colorExpression.length() != 10 || !"&H".equals(colorExpression.substring(0, 2))) { - throw new IllegalArgumentException(); + // The SSA V4+ color can be represented in hex (&HAABBGGRR) or in decimal format (byte order + // AABBGGRR) and in both cases the alpha channel's value needs to be inverted as in case of SSA + // the 0xFF alpha value means transparent and 0x00 means opaque which is the opposite from the + // @ColorInt representation. + int abgr; + try { + // Parse color from hex format (&HAABBGGRR). + if (colorExpression.startsWith("&H")) { + StringBuilder rgbaStringBuilder = new StringBuilder(colorExpression); + if (colorExpression.length() < 10) { + // Add leading zeros if necessary. + while (rgbaStringBuilder.length() != 10) { + rgbaStringBuilder.insert(2, "0"); + } + } + abgr = (int) Long.parseLong(colorExpression.substring(2), 16); + } else { + // Parse color from decimal format (bytes order AABBGGRR). + abgr = (int) Long.parseLong(colorExpression); + } + } catch (NumberFormatException ex) { + throw new IllegalArgumentException(ex); } - // Convert &HAABBGGRR to #RRGGBBAA. - String rgba = new StringBuilder() - .append(colorExpression.substring(2)) - .append("#") - .reverse() - .toString(); - return parseColorInternal(rgba, true); + // Convert ABGR to ARGB. + int a = ((abgr >> 24) & 0xFF) ^ 0xFF; // Flip alpha. + int b = (abgr >> 16) & 0xFF; + int g = (abgr >> 8) & 0xFF; + int r = abgr & 0xff; + return Color.argb(a, r, g, b); } @ColorInt diff --git a/library/core/src/test/java/com/google/android/exoplayer2/util/ColorParserTest.java b/library/core/src/test/java/com/google/android/exoplayer2/util/ColorParserTest.java index a15ef95627..ab302e333d 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/util/ColorParserTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/util/ColorParserTest.java @@ -16,6 +16,8 @@ package com.google.android.exoplayer2.util; import static android.graphics.Color.BLACK; +import static android.graphics.Color.BLUE; +import static android.graphics.Color.GREEN; import static android.graphics.Color.RED; import static android.graphics.Color.WHITE; import static android.graphics.Color.YELLOW; @@ -66,9 +68,21 @@ public final class ColorParserTest { // Hex colors in ColorParser are RGBA, where-as {@link Color#parseColor} takes ARGB. assertThat(parseTtmlColor("#FFFFFF00")).isEqualTo(parseColor("#00FFFFFF")); assertThat(parseTtmlColor("#12345678")).isEqualTo(parseColor("#78123456")); - // SSA colors are in &HAABBGGRR format. - assertThat(parseSsaColor("&HFF0000FF")).isEqualTo(RED); - assertThat(parseSsaColor("&HFF00FFFF")).isEqualTo(YELLOW); + } + + @Test + public void ssaColorParsing() { + // Hex format (&HAABBGGRR). + assertThat(parseSsaColor("&H000000FF")).isEqualTo(RED); + assertThat(parseSsaColor("&H0000FFFF")).isEqualTo(YELLOW); + assertThat(parseSsaColor("&H400000FF")).isEqualTo(parseColor("#BFFF0000")); + // Leading zeros. + assertThat(parseSsaColor("&HFF")).isEqualTo(RED); + assertThat(parseSsaColor("&HFF00")).isEqualTo(GREEN); + assertThat(parseSsaColor("&HFF0000")).isEqualTo(BLUE); + // Decimal format (AABBGGRR byte order). + assertThat(parseSsaColor(/*#000000FF*/"255")).isEqualTo(parseColor("#FFFF0000")); + assertThat(parseSsaColor(/*#FF0000FF*/"4278190335")).isEqualTo(parseColor("#00FF0000")); } @Test From 2241320535b1e2038a07bbf89ec4a80f2ef3456e Mon Sep 17 00:00:00 2001 From: Arnold Szabo Date: Mon, 25 Jan 2021 23:36:50 +0100 Subject: [PATCH 3/5] Remove unused static variable --- .../java/com/google/android/exoplayer2/text/ssa/SsaStyle.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java index bc6c729eb2..6b29904ed6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java @@ -85,8 +85,6 @@ import java.util.regex.Pattern; public static final int SSA_ALIGNMENT_TOP_CENTER = 8; public static final int SSA_ALIGNMENT_TOP_RIGHT = 9; - public static final int SSA_COLOR_UNKNOWN = -1; - public final String name; @SsaAlignment public final int alignment; public SsaColor primaryColor; From f8a47bc86d1134013770532291be09377ad95a7b Mon Sep 17 00:00:00 2001 From: Arnold Szabo Date: Tue, 26 Jan 2021 21:37:01 +0100 Subject: [PATCH 4/5] Modify the SsaColor to be more similar to the Optional class. --- .../exoplayer2/text/ssa/SsaDecoder.java | 4 ++-- .../android/exoplayer2/text/ssa/SsaStyle.java | 20 +++++++++++++++---- .../android/exoplayer2/util/ColorParser.java | 2 +- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java index c14767667a..cd139a45dd 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java @@ -308,8 +308,8 @@ public final class SsaDecoder extends SimpleSubtitleDecoder { // Apply primary color. if (style != null) { - if (style.primaryColor.isSet) { - spannableText.setSpan(new ForegroundColorSpan(style.primaryColor.value), + if (style.primaryColor.isSet()) { + spannableText.setSpan(new ForegroundColorSpan(style.primaryColor.getColor()), 0, spannableText.length(), SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java index 6b29904ed6..26c725a1b8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java @@ -31,6 +31,7 @@ import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; import java.lang.annotation.Documented; import java.lang.annotation.Retention; +import java.util.NoSuchElementException; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -166,14 +167,25 @@ import java.util.regex.Pattern; public static SsaColor UNSET = new SsaColor(0, false); - public final @ColorInt int value; - public final boolean isSet; + private final @ColorInt int color; + private final boolean isSet; - private SsaColor(@ColorInt int value, boolean isSet) { - this.value = value; + private SsaColor(@ColorInt int color, boolean isSet) { + this.color = color; this.isSet = isSet; } + public @ColorInt int getColor() { + if (!isSet) { + throw new NoSuchElementException("No color is present"); + } + return color; + } + + public boolean isSet() { + return isSet; + } + public static SsaColor from(@ColorInt int value) { return new SsaColor(value, true); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/ColorParser.java b/library/core/src/main/java/com/google/android/exoplayer2/util/ColorParser.java index 7722962262..e8b2f1e77f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/ColorParser.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/ColorParser.java @@ -90,7 +90,7 @@ public final class ColorParser { rgbaStringBuilder.insert(2, "0"); } } - abgr = (int) Long.parseLong(colorExpression.substring(2), 16); + abgr = (int) Long.parseLong(rgbaStringBuilder.substring(2), 16); } else { // Parse color from decimal format (bytes order AABBGGRR). abgr = (int) Long.parseLong(colorExpression); From 28a3921a6a2d8c2b952e5ec9e54a80ba2b05ada6 Mon Sep 17 00:00:00 2001 From: Arnold Szabo Date: Tue, 26 Jan 2021 22:57:10 +0100 Subject: [PATCH 5/5] Add color decoding tests to SsaDecoderTest, remove SubStation Alpha colors" from media.exolist.json. --- demos/main/src/main/assets/media.exolist.json | 7 --- .../exoplayer2/text/ssa/SsaDecoderTest.java | 44 +++++++++++++++++++ testdata/src/test/assets/media/ssa/colors | 24 ++++++++++ 3 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 testdata/src/test/assets/media/ssa/colors diff --git a/demos/main/src/main/assets/media.exolist.json b/demos/main/src/main/assets/media.exolist.json index 57b063dbb2..b515eca98a 100644 --- a/demos/main/src/main/assets/media.exolist.json +++ b/demos/main/src/main/assets/media.exolist.json @@ -506,13 +506,6 @@ "subtitle_mime_type": "text/x-ssa", "subtitle_language": "en" }, - { - "name": "SubStation Alpha colors", - "uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/video-avc-baseline-480.mp4", - "subtitle_uri": "https://drive.google.com/uc?export=download&id=13EdW4Qru-vQerUlwS_Ht5Cely_Tn0tQe", - "subtitle_mime_type": "text/x-ssa", - "subtitle_language": "en" - }, { "name": "MPEG-4 Timed Text", "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mp4/dizzy-with-tx3g.mp4" diff --git a/library/core/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java index c7833fab04..4a8fd55b64 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java @@ -18,7 +18,10 @@ package com.google.android.exoplayer2.text.ssa; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; +import android.graphics.Color; import android.text.Layout; +import android.text.SpannableString; +import android.text.style.ForegroundColorSpan; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.testutil.TestUtil; @@ -44,6 +47,7 @@ public final class SsaDecoderTest { private static final String INVALID_TIMECODES = "media/ssa/invalid_timecodes"; private static final String INVALID_POSITIONS = "media/ssa/invalid_positioning"; private static final String POSITIONS_WITHOUT_PLAYRES = "media/ssa/positioning_without_playres"; + private static final String COLORS = "media/ssa/colors"; @Test public void decodeEmpty() throws IOException { @@ -267,6 +271,46 @@ public final class SsaDecoderTest { assertTypicalCue3(subtitle, 0); } + @Test + public void decodeColors() throws IOException { + SsaDecoder decoder = new SsaDecoder(); + byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), COLORS); + Subtitle subtitle = decoder.decode(bytes, bytes.length, false); + assertThat(subtitle.getEventTimeCount()).isEqualTo(12); + // &H000000FF (AABBGGRR) -> #FFFF0000 (AARRGGBB) + Cue firstCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0))); + ForegroundColorSpan firstSpan = getSpan(firstCue, ForegroundColorSpan.class); + assertThat(firstSpan.getForegroundColor()).isEqualTo(Color.RED); + // &H0000FFFF (AABBGGRR) -> #FFFFFF00 (AARRGGBB) + Cue secondCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2))); + ForegroundColorSpan secondSpan = getSpan(secondCue, ForegroundColorSpan.class); + assertThat(secondSpan.getForegroundColor()).isEqualTo(Color.YELLOW); + // &HFF00 (GGRR) -> #FF00FF00 (AARRGGBB) + Cue thirdClue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(4))); + ForegroundColorSpan thirdSpan = getSpan(thirdClue, ForegroundColorSpan.class); + assertThat(thirdSpan.getForegroundColor()).isEqualTo(Color.GREEN); + // &H400000FF (AABBGGRR) -> #BFFF0000 (AARRGGBB) -> -1073807360 + Cue forthClue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(6))); + ForegroundColorSpan forthSpan = getSpan(forthClue, ForegroundColorSpan.class); + assertThat(forthSpan.getForegroundColor()).isEqualTo(-1073807360); + // 16711680 (AABBGGRR) -> &H00FF0000 (AABBGGRR) -> #FF0000FF (AARRGGBB) -> -16776961 + Cue fifthClue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(8))); + ForegroundColorSpan fifthSpan = getSpan(fifthClue, ForegroundColorSpan.class); + assertThat(fifthSpan.getForegroundColor()).isEqualTo(-16776961); + // 2164195328 (AABBGGRR) -> &H80FF0000 (AABBGGRR) -> #7F0000FF (AARRGGBB) -> 2130706687 + Cue sixthClue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(10))); + ForegroundColorSpan sixthSpan = getSpan(sixthClue, ForegroundColorSpan.class); + assertThat(sixthSpan.getForegroundColor()).isEqualTo(2130706687); + } + + private static T getSpan(Cue cue, Class clazz) { + return getSpan(cue, 0, cue.text.length(), clazz); + } + + private static T getSpan(Cue cue, int start, int end, Class clazz) { + return SpannableString.valueOf(cue.text).getSpans(start, end, clazz)[0]; + } + private static void assertTypicalCue1(Subtitle subtitle, int eventIndex) { assertThat(subtitle.getEventTime(eventIndex)).isEqualTo(0); assertThat(subtitle.getCues(subtitle.getEventTime(eventIndex)).get(0).text.toString()) diff --git a/testdata/src/test/assets/media/ssa/colors b/testdata/src/test/assets/media/ssa/colors new file mode 100644 index 0000000000..1655603e7a --- /dev/null +++ b/testdata/src/test/assets/media/ssa/colors @@ -0,0 +1,24 @@ +[Script Info] +Title: Coloring +Script Type: V4.00+ +PlayResX: 1280 +PlayResY: 720 + +[V4+ Styles] +Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding +Style: PrimaryColourStyleHexRed ,Roboto,50,&H000000FF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,3,0,2,50,50,70,1 +Style: PrimaryColourStyleHexYellow ,Roboto,50,&H0000FFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,3,0,2,50,50,70,1 +Style: PrimaryColourStyleHexGreen ,Roboto,50,&HFF00 ,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,3,0,2,50,50,70,1 +Style: PrimaryColourStyleHexAlpha ,Roboto,50,&H400000FF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,3,0,2,50,50,70,1 +Style: PrimaryColourStyleDecimal ,Roboto,50,16711680 ,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,3,0,2,50,50,70,1 +Style: PrimaryColourStyleDecimalAlpha ,Roboto,50,2164195328,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,3,0,2,50,50,70,1 + + +[Events] +Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text +Dialogue: 0,0:00:00.95,0:00:03.11,PrimaryColourStyleHexRed ,Arnold,0,0,0,,First line in RED (&H000000FF). +Dialogue: 0,0:00:04.50,0:00:07.50,PrimaryColourStyleHexYellow ,Arnold,0,0,0,,Second line in YELLOW (&H0000FFFF). +Dialogue: 0,0:00:08.50,0:00:11.50,PrimaryColourStyleHexGreen ,Arnold,0,0,0,,Third line in GREEN (leading zeros &HFF00). +Dialogue: 0,0:00:12.50,0:00:15.50,PrimaryColourStyleHexAlpha ,Arnold,0,0,0,,Fourth line in RED with alpha (&H400000FF). +Dialogue: 0,0:00:16.50,0:00:19.50,PrimaryColourStyleDecimal ,Arnold,0,0,0,,Fifth line in BLUE (16711680). +Dialogue: 0,0:00:20.70,0:00:23.00,PrimaryColourStyleDecimalAlpha ,Arnold,0,0,0,,Sixth line in BLUE with alpha (2164195328). \ No newline at end of file