From 388b8d1d88598b4201a0b5c49c1124f656a7a55b Mon Sep 17 00:00:00 2001 From: Egor Neliuba Date: Sun, 3 Apr 2022 11:31:32 +0300 Subject: [PATCH 1/2] Add support for SSA OutlineColour (only background) OutlineColour should be treated as the background color if BorderStyle=3. Since currently BorderStyle is ignored, we can always treat OutlineColor as the background color. --- .../exoplayer2/text/ssa/SsaDecoder.java | 8 ++++++++ .../android/exoplayer2/text/ssa/SsaStyle.java | 14 ++++++++++++++ .../exoplayer2/text/ssa/SsaDecoderTest.java | 7 ++++++- .../src/test/assets/media/ssa/style_colors | 18 ++++++++++-------- 4 files changed, 38 insertions(+), 9 deletions(-) diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java b/library/extractor/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java index c6df62bb5f5..c02e2338ab4 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java @@ -21,6 +21,7 @@ import android.graphics.Typeface; import android.text.Layout; import android.text.SpannableString; +import android.text.style.BackgroundColorSpan; import android.text.style.ForegroundColorSpan; import android.text.style.StrikethroughSpan; import android.text.style.StyleSpan; @@ -319,6 +320,13 @@ private static Cue createCue( /* end= */ spannableText.length(), SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); } + if (style.outlineColor != null) { + spannableText.setSpan( + new BackgroundColorSpan(style.outlineColor), + /* start= */ 0, + /* end= */ spannableText.length(), + SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); + } if (style.fontSize != Cue.DIMEN_UNSET && screenHeight != Cue.DIMEN_UNSET) { cue.setTextSize( style.fontSize / screenHeight, Cue.TEXT_SIZE_TYPE_FRACTIONAL_IGNORE_PADDING); diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java b/library/extractor/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java index 8e09291312c..92821c02d9a 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java @@ -95,6 +95,7 @@ public final String name; public final @SsaAlignment int alignment; @Nullable @ColorInt public final Integer primaryColor; + @Nullable @ColorInt public final Integer outlineColor; public final float fontSize; public final boolean bold; public final boolean italic; @@ -105,6 +106,7 @@ private SsaStyle( String name, @SsaAlignment int alignment, @Nullable @ColorInt Integer primaryColor, + @Nullable @ColorInt Integer outlineColor, float fontSize, boolean bold, boolean italic, @@ -113,6 +115,7 @@ private SsaStyle( this.name = name; this.alignment = alignment; this.primaryColor = primaryColor; + this.outlineColor = outlineColor; this.fontSize = fontSize; this.bold = bold; this.italic = italic; @@ -141,6 +144,9 @@ public static SsaStyle fromStyleLine(String styleLine, Format format) { format.primaryColorIndex != C.INDEX_UNSET ? parseColor(styleValues[format.primaryColorIndex].trim()) : null, + format.outlineColorIndex != C.INDEX_UNSET + ? parseColor(styleValues[format.outlineColorIndex].trim()) + : null, format.fontSizeIndex != C.INDEX_UNSET ? parseFontSize(styleValues[format.fontSizeIndex].trim()) : Cue.DIMEN_UNSET, @@ -257,6 +263,7 @@ private static boolean parseBooleanValue(String booleanValue) { public final int nameIndex; public final int alignmentIndex; public final int primaryColorIndex; + public final int outlineColorIndex; public final int fontSizeIndex; public final int boldIndex; public final int italicIndex; @@ -268,6 +275,7 @@ private Format( int nameIndex, int alignmentIndex, int primaryColorIndex, + int outlineColorIndex, int fontSizeIndex, int boldIndex, int italicIndex, @@ -277,6 +285,7 @@ private Format( this.nameIndex = nameIndex; this.alignmentIndex = alignmentIndex; this.primaryColorIndex = primaryColorIndex; + this.outlineColorIndex = outlineColorIndex; this.fontSizeIndex = fontSizeIndex; this.boldIndex = boldIndex; this.italicIndex = italicIndex; @@ -295,6 +304,7 @@ public static Format fromFormatLine(String styleFormatLine) { int nameIndex = C.INDEX_UNSET; int alignmentIndex = C.INDEX_UNSET; int primaryColorIndex = C.INDEX_UNSET; + int outlineColorIndex = C.INDEX_UNSET; int fontSizeIndex = C.INDEX_UNSET; int boldIndex = C.INDEX_UNSET; int italicIndex = C.INDEX_UNSET; @@ -313,6 +323,9 @@ public static Format fromFormatLine(String styleFormatLine) { case "primarycolour": primaryColorIndex = i; break; + case "outlinecolour": + outlineColorIndex = i; + break; case "fontsize": fontSizeIndex = i; break; @@ -335,6 +348,7 @@ public static Format fromFormatLine(String styleFormatLine) { nameIndex, alignmentIndex, primaryColorIndex, + outlineColorIndex, fontSizeIndex, boldIndex, italicIndex, diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java index 4b3b8cef64e..a4001a2d4b0 100644 --- a/library/extractor/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java @@ -301,7 +301,7 @@ public void decodeColors() throws IOException { SsaDecoder decoder = new SsaDecoder(); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), STYLE_COLORS); Subtitle subtitle = decoder.decode(bytes, bytes.length, false); - assertThat(subtitle.getEventTimeCount()).isEqualTo(14); + assertThat(subtitle.getEventTimeCount()).isEqualTo(16); // &H000000FF (AABBGGRR) -> #FFFF0000 (AARRGGBB) Spanned firstCueText = (Spanned) Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0))).text; @@ -342,6 +342,11 @@ public void decodeColors() throws IOException { (Spanned) Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(12))).text; SpannedSubject.assertThat(seventhCueText) .hasNoForegroundColorSpanBetween(0, seventhCueText.length()); + Spanned eighthCueText = + (Spanned) Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(14))).text; + SpannedSubject.assertThat(eighthCueText) + .hasBackgroundColorSpanBetween(0, eighthCueText.length()) + .withColor(Color.BLUE); } @Test diff --git a/testdata/src/test/assets/media/ssa/style_colors b/testdata/src/test/assets/media/ssa/style_colors index a224e7ed4d8..fe31e66380d 100644 --- a/testdata/src/test/assets/media/ssa/style_colors +++ b/testdata/src/test/assets/media/ssa/style_colors @@ -5,14 +5,15 @@ PlayResX: 1280 PlayResY: 720 [V4+ Styles] -Format: Name ,PrimaryColour -Style: PrimaryColourStyleHexRed ,&H000000FF -Style: PrimaryColourStyleHexYellow ,&H0000FFFF -Style: PrimaryColourStyleHexGreen ,&HFF00 -Style: PrimaryColourStyleHexAlpha ,&HA00000FF -Style: PrimaryColourStyleDecimal ,16711680 -Style: PrimaryColourStyleDecimalAlpha,2164195328 -Style: PrimaryColourStyleInvalid ,blue +Format: Name ,PrimaryColour,OutlineColour +Style: PrimaryColourStyleHexRed ,&H000000FF ,&H00000000 +Style: PrimaryColourStyleHexYellow ,&H0000FFFF ,&H00000000 +Style: PrimaryColourStyleHexGreen ,&HFF00 ,&H00000000 +Style: PrimaryColourStyleHexAlpha ,&HA00000FF ,&H00000000 +Style: PrimaryColourStyleDecimal ,16711680 ,&H00000000 +Style: PrimaryColourStyleDecimalAlpha,2164195328 ,&H00000000 +Style: PrimaryColourStyleInvalid ,blue ,&H00000000 +Style: OutlineColourStyleBlue ,&H00000000 ,&H00FF0000 [Events] @@ -24,3 +25,4 @@ Dialogue: 0:00:07.00,0:00:08.00,PrimaryColourStyleHexAlpha ,Fourth line in RE Dialogue: 0:00:09.00,0:00:10.00,PrimaryColourStyleDecimal ,Fifth line in BLUE (16711680). Dialogue: 0:00:11.00,0:00:12.00,PrimaryColourStyleDecimalAlpha,Sixth line in BLUE with alpha (2164195328). Dialogue: 0:00:13.00,0:00:14.00,PrimaryColourInvalid ,Seventh line with invalid color. +Dialogue: 0:00:15.00,0:00:16.00,OutlineColourStyleBlue ,Eighth line with BLUE (&H00FF0000) outline. From 2bd348effd6a6f14a892439aa9f0ef9b22789062 Mon Sep 17 00:00:00 2001 From: Egor Neliuba Date: Mon, 4 Apr 2022 18:26:31 +0300 Subject: [PATCH 2/2] Consider BorderStyle value before applying OutlineColour as background --- .../exoplayer2/text/ssa/SsaDecoder.java | 2 +- .../android/exoplayer2/text/ssa/SsaStyle.java | 67 ++++++++++++++++++- .../exoplayer2/text/ssa/SsaDecoderTest.java | 6 +- .../src/test/assets/media/ssa/style_colors | 20 +++--- 4 files changed, 82 insertions(+), 13 deletions(-) diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java b/library/extractor/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java index c02e2338ab4..ba6b70499e0 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java @@ -320,7 +320,7 @@ private static Cue createCue( /* end= */ spannableText.length(), SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); } - if (style.outlineColor != null) { + if (style.borderStyle == SsaStyle.SSA_BORDER_STYLE_BOX && style.outlineColor != null) { spannableText.setSpan( new BackgroundColorSpan(style.outlineColor), /* start= */ 0, diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java b/library/extractor/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java index 92821c02d9a..47663069612 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java @@ -92,6 +92,31 @@ public static final int SSA_ALIGNMENT_TOP_CENTER = 8; public static final int SSA_ALIGNMENT_TOP_RIGHT = 9; + /** + * The SSA/ASS BorderStyle. + * + *

Allowed values: + * + *

+ */ + @Target(TYPE_USE) + @IntDef({ + SSA_BORDER_STYLE_UNKNOWN, + SSA_BORDER_STYLE_OUTLINE, + SSA_BORDER_STYLE_BOX, + }) + @Documented + @Retention(SOURCE) + public @interface SsaBorderStyle {} + + public static final int SSA_BORDER_STYLE_UNKNOWN = -1; + public static final int SSA_BORDER_STYLE_OUTLINE = 1; + public static final int SSA_BORDER_STYLE_BOX = 3; + public final String name; public final @SsaAlignment int alignment; @Nullable @ColorInt public final Integer primaryColor; @@ -101,6 +126,7 @@ public final boolean italic; public final boolean underline; public final boolean strikeout; + public final @SsaBorderStyle int borderStyle; private SsaStyle( String name, @@ -111,7 +137,8 @@ private SsaStyle( boolean bold, boolean italic, boolean underline, - boolean strikeout) { + boolean strikeout, + int borderStyle) { this.name = name; this.alignment = alignment; this.primaryColor = primaryColor; @@ -121,6 +148,7 @@ private SsaStyle( this.italic = italic; this.underline = underline; this.strikeout = strikeout; + this.borderStyle = borderStyle; } @Nullable @@ -157,7 +185,10 @@ && parseBooleanValue(styleValues[format.italicIndex].trim()), format.underlineIndex != C.INDEX_UNSET && parseBooleanValue(styleValues[format.underlineIndex].trim()), format.strikeoutIndex != C.INDEX_UNSET - && parseBooleanValue(styleValues[format.strikeoutIndex].trim())); + && parseBooleanValue(styleValues[format.strikeoutIndex].trim()), + format.borderStyleIndex != C.INDEX_UNSET + ? parseBorderStyle(styleValues[format.borderStyleIndex].trim()) + : SSA_BORDER_STYLE_UNKNOWN); } catch (RuntimeException e) { Log.w(TAG, "Skipping malformed 'Style:' line: '" + styleLine + "'", e); return null; @@ -195,6 +226,30 @@ private static boolean isValidAlignment(@SsaAlignment int alignment) { } } + private static @SsaBorderStyle int parseBorderStyle(String borderStyleStr) { + try { + @SsaBorderStyle int borderStyle = Integer.parseInt(borderStyleStr.trim()); + if (isValidBorderStyle(borderStyle)) { + return borderStyle; + } + } catch (NumberFormatException e) { + // Swallow the exception and return UNKNOWN below. + } + Log.w(TAG, "Ignoring unknown BorderStyle: " + borderStyleStr); + return SSA_BORDER_STYLE_UNKNOWN; + } + + private static boolean isValidBorderStyle(@SsaBorderStyle int alignment) { + switch (alignment) { + case SSA_BORDER_STYLE_OUTLINE: + case SSA_BORDER_STYLE_BOX: + return true; + case SSA_BORDER_STYLE_UNKNOWN: + default: + return false; + } + } + /** * Parses a SSA V4+ color expression. * @@ -269,6 +324,7 @@ private static boolean parseBooleanValue(String booleanValue) { public final int italicIndex; public final int underlineIndex; public final int strikeoutIndex; + public final int borderStyleIndex; public final int length; private Format( @@ -281,6 +337,7 @@ private Format( int italicIndex, int underlineIndex, int strikeoutIndex, + int borderStyleIndex, int length) { this.nameIndex = nameIndex; this.alignmentIndex = alignmentIndex; @@ -291,6 +348,7 @@ private Format( this.italicIndex = italicIndex; this.underlineIndex = underlineIndex; this.strikeoutIndex = strikeoutIndex; + this.borderStyleIndex = borderStyleIndex; this.length = length; } @@ -310,6 +368,7 @@ public static Format fromFormatLine(String styleFormatLine) { int italicIndex = C.INDEX_UNSET; int underlineIndex = C.INDEX_UNSET; int strikeoutIndex = C.INDEX_UNSET; + int borderStyleIndex = C.INDEX_UNSET; String[] keys = TextUtils.split(styleFormatLine.substring(SsaDecoder.FORMAT_LINE_PREFIX.length()), ","); for (int i = 0; i < keys.length; i++) { @@ -341,6 +400,9 @@ public static Format fromFormatLine(String styleFormatLine) { case "strikeout": strikeoutIndex = i; break; + case "borderstyle": + borderStyleIndex = i; + break; } } return nameIndex != C.INDEX_UNSET @@ -354,6 +416,7 @@ public static Format fromFormatLine(String styleFormatLine) { italicIndex, underlineIndex, strikeoutIndex, + borderStyleIndex, keys.length) : null; } diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java index a4001a2d4b0..60eb6008c97 100644 --- a/library/extractor/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java @@ -301,7 +301,7 @@ public void decodeColors() throws IOException { SsaDecoder decoder = new SsaDecoder(); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), STYLE_COLORS); Subtitle subtitle = decoder.decode(bytes, bytes.length, false); - assertThat(subtitle.getEventTimeCount()).isEqualTo(16); + assertThat(subtitle.getEventTimeCount()).isEqualTo(18); // &H000000FF (AABBGGRR) -> #FFFF0000 (AARRGGBB) Spanned firstCueText = (Spanned) Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0))).text; @@ -342,11 +342,15 @@ public void decodeColors() throws IOException { (Spanned) Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(12))).text; SpannedSubject.assertThat(seventhCueText) .hasNoForegroundColorSpanBetween(0, seventhCueText.length()); + // OutlineColour should be treated as background only when BorderStyle=3 Spanned eighthCueText = (Spanned) Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(14))).text; SpannedSubject.assertThat(eighthCueText) .hasBackgroundColorSpanBetween(0, eighthCueText.length()) .withColor(Color.BLUE); + Spanned ninthCueText = + (Spanned) Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(16))).text; + SpannedSubject.assertThat(ninthCueText).hasNoBackgroundColorSpanBetween(0, ninthCueText.length()); } @Test diff --git a/testdata/src/test/assets/media/ssa/style_colors b/testdata/src/test/assets/media/ssa/style_colors index fe31e66380d..a0ec2240c0d 100644 --- a/testdata/src/test/assets/media/ssa/style_colors +++ b/testdata/src/test/assets/media/ssa/style_colors @@ -5,15 +5,16 @@ PlayResX: 1280 PlayResY: 720 [V4+ Styles] -Format: Name ,PrimaryColour,OutlineColour -Style: PrimaryColourStyleHexRed ,&H000000FF ,&H00000000 -Style: PrimaryColourStyleHexYellow ,&H0000FFFF ,&H00000000 -Style: PrimaryColourStyleHexGreen ,&HFF00 ,&H00000000 -Style: PrimaryColourStyleHexAlpha ,&HA00000FF ,&H00000000 -Style: PrimaryColourStyleDecimal ,16711680 ,&H00000000 -Style: PrimaryColourStyleDecimalAlpha,2164195328 ,&H00000000 -Style: PrimaryColourStyleInvalid ,blue ,&H00000000 -Style: OutlineColourStyleBlue ,&H00000000 ,&H00FF0000 +Format: Name ,PrimaryColour,OutlineColour,BorderStyle +Style: PrimaryColourStyleHexRed ,&H000000FF ,&H00000000 ,3 +Style: PrimaryColourStyleHexYellow ,&H0000FFFF ,&H00000000 ,3 +Style: PrimaryColourStyleHexGreen ,&HFF00 ,&H00000000 ,3 +Style: PrimaryColourStyleHexAlpha ,&HA00000FF ,&H00000000 ,3 +Style: PrimaryColourStyleDecimal ,16711680 ,&H00000000 ,3 +Style: PrimaryColourStyleDecimalAlpha,2164195328 ,&H00000000 ,3 +Style: PrimaryColourStyleInvalid ,blue ,&H00000000 ,3 +Style: OutlineColourStyleBlue ,&H00000000 ,&H00FF0000 ,3 +Style: OutlineColourStyleIgnored ,&H00000000 ,&H00FF0000 ,1 [Events] @@ -26,3 +27,4 @@ Dialogue: 0:00:09.00,0:00:10.00,PrimaryColourStyleDecimal ,Fifth line in BLU Dialogue: 0:00:11.00,0:00:12.00,PrimaryColourStyleDecimalAlpha,Sixth line in BLUE with alpha (2164195328). Dialogue: 0:00:13.00,0:00:14.00,PrimaryColourInvalid ,Seventh line with invalid color. Dialogue: 0:00:15.00,0:00:16.00,OutlineColourStyleBlue ,Eighth line with BLUE (&H00FF0000) outline. +Dialogue: 0:00:17.00,0:00:18.00,OutlineColourStyleIgnored ,Ninth line with ignored outline because BorderStyle is not 3.