Merge pull request #10150 from egor-n:dev-v2-8435-outlinecolour

PiperOrigin-RevId: 444787307
This commit is contained in:
Ian Baker 2022-05-09 10:15:05 +01:00
commit 9369348d6f
6 changed files with 134 additions and 6 deletions

View File

@ -26,6 +26,10 @@
* Ad playback / IMA: * Ad playback / IMA:
* Decrease ad polling rate from every 100ms to every 200ms, to line up with * Decrease ad polling rate from every 100ms to every 200ms, to line up with
Media Rating Council (MRC) recommendations. Media Rating Council (MRC) recommendations.
* Text:
* SSA: Support `OutlineColour` style setting when `BorderStyle == 3` (i.e.
`OutlineColour` sets the background of the cue)
([#8435](https://github.com/google/ExoPlayer/issues/8435)).
* Extractors: * Extractors:
* Matroska: Parse `DiscardPadding` for Opus tracks. * Matroska: Parse `DiscardPadding` for Opus tracks.
* Parse bitrates from `esds` boxes. * Parse bitrates from `esds` boxes.

View File

@ -21,6 +21,7 @@ import static androidx.media3.common.util.Util.castNonNull;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.text.Layout; import android.text.Layout;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.style.BackgroundColorSpan;
import android.text.style.ForegroundColorSpan; import android.text.style.ForegroundColorSpan;
import android.text.style.StrikethroughSpan; import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan; import android.text.style.StyleSpan;
@ -321,6 +322,13 @@ public final class SsaDecoder extends SimpleSubtitleDecoder {
/* end= */ spannableText.length(), /* end= */ spannableText.length(),
SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);
} }
if (style.borderStyle == SsaStyle.SSA_BORDER_STYLE_BOX && 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) { if (style.fontSize != Cue.DIMEN_UNSET && screenHeight != Cue.DIMEN_UNSET) {
cue.setTextSize( cue.setTextSize(
style.fontSize / screenHeight, Cue.TEXT_SIZE_TYPE_FRACTIONAL_IGNORE_PADDING); style.fontSize / screenHeight, Cue.TEXT_SIZE_TYPE_FRACTIONAL_IGNORE_PADDING);

View File

@ -92,32 +92,64 @@ import java.util.regex.Pattern;
public static final int SSA_ALIGNMENT_TOP_CENTER = 8; public static final int SSA_ALIGNMENT_TOP_CENTER = 8;
public static final int SSA_ALIGNMENT_TOP_RIGHT = 9; public static final int SSA_ALIGNMENT_TOP_RIGHT = 9;
/**
* The SSA/ASS BorderStyle.
*
* <p>Allowed values:
*
* <ul>
* <li>{@link #SSA_BORDER_STYLE_UNKNOWN}
* <li>{@link #SSA_BORDER_STYLE_OUTLINE}
* <li>{@link #SSA_BORDER_STYLE_BOX}
* </ul>
*/
@Target(TYPE_USE)
@IntDef({
SSA_BORDER_STYLE_UNKNOWN,
SSA_BORDER_STYLE_OUTLINE,
SSA_BORDER_STYLE_BOX,
})
@Documented
@Retention(SOURCE)
public @interface SsaBorderStyle {}
// The numbering follows the ASS (v4+) spec.
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 String name;
public final @SsaAlignment int alignment; public final @SsaAlignment int alignment;
@Nullable @ColorInt public final Integer primaryColor; @Nullable @ColorInt public final Integer primaryColor;
@Nullable @ColorInt public final Integer outlineColor;
public final float fontSize; public final float fontSize;
public final boolean bold; public final boolean bold;
public final boolean italic; public final boolean italic;
public final boolean underline; public final boolean underline;
public final boolean strikeout; public final boolean strikeout;
public final @SsaBorderStyle int borderStyle;
private SsaStyle( private SsaStyle(
String name, String name,
@SsaAlignment int alignment, @SsaAlignment int alignment,
@Nullable @ColorInt Integer primaryColor, @Nullable @ColorInt Integer primaryColor,
@Nullable @ColorInt Integer outlineColor,
float fontSize, float fontSize,
boolean bold, boolean bold,
boolean italic, boolean italic,
boolean underline, boolean underline,
boolean strikeout) { boolean strikeout,
@SsaBorderStyle int borderStyle) {
this.name = name; this.name = name;
this.alignment = alignment; this.alignment = alignment;
this.primaryColor = primaryColor; this.primaryColor = primaryColor;
this.outlineColor = outlineColor;
this.fontSize = fontSize; this.fontSize = fontSize;
this.bold = bold; this.bold = bold;
this.italic = italic; this.italic = italic;
this.underline = underline; this.underline = underline;
this.strikeout = strikeout; this.strikeout = strikeout;
this.borderStyle = borderStyle;
} }
@Nullable @Nullable
@ -141,6 +173,9 @@ import java.util.regex.Pattern;
format.primaryColorIndex != C.INDEX_UNSET format.primaryColorIndex != C.INDEX_UNSET
? parseColor(styleValues[format.primaryColorIndex].trim()) ? parseColor(styleValues[format.primaryColorIndex].trim())
: null, : null,
format.outlineColorIndex != C.INDEX_UNSET
? parseColor(styleValues[format.outlineColorIndex].trim())
: null,
format.fontSizeIndex != C.INDEX_UNSET format.fontSizeIndex != C.INDEX_UNSET
? parseFontSize(styleValues[format.fontSizeIndex].trim()) ? parseFontSize(styleValues[format.fontSizeIndex].trim())
: Cue.DIMEN_UNSET, : Cue.DIMEN_UNSET,
@ -151,7 +186,10 @@ import java.util.regex.Pattern;
format.underlineIndex != C.INDEX_UNSET format.underlineIndex != C.INDEX_UNSET
&& parseBooleanValue(styleValues[format.underlineIndex].trim()), && parseBooleanValue(styleValues[format.underlineIndex].trim()),
format.strikeoutIndex != C.INDEX_UNSET 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) { } catch (RuntimeException e) {
Log.w(TAG, "Skipping malformed 'Style:' line: '" + styleLine + "'", e); Log.w(TAG, "Skipping malformed 'Style:' line: '" + styleLine + "'", e);
return null; return null;
@ -189,6 +227,30 @@ import java.util.regex.Pattern;
} }
} }
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. * Parses a SSA V4+ color expression.
* *
@ -257,31 +319,37 @@ import java.util.regex.Pattern;
public final int nameIndex; public final int nameIndex;
public final int alignmentIndex; public final int alignmentIndex;
public final int primaryColorIndex; public final int primaryColorIndex;
public final int outlineColorIndex;
public final int fontSizeIndex; public final int fontSizeIndex;
public final int boldIndex; public final int boldIndex;
public final int italicIndex; public final int italicIndex;
public final int underlineIndex; public final int underlineIndex;
public final int strikeoutIndex; public final int strikeoutIndex;
public final int borderStyleIndex;
public final int length; public final int length;
private Format( private Format(
int nameIndex, int nameIndex,
int alignmentIndex, int alignmentIndex,
int primaryColorIndex, int primaryColorIndex,
int outlineColorIndex,
int fontSizeIndex, int fontSizeIndex,
int boldIndex, int boldIndex,
int italicIndex, int italicIndex,
int underlineIndex, int underlineIndex,
int strikeoutIndex, int strikeoutIndex,
int borderStyleIndex,
int length) { int length) {
this.nameIndex = nameIndex; this.nameIndex = nameIndex;
this.alignmentIndex = alignmentIndex; this.alignmentIndex = alignmentIndex;
this.primaryColorIndex = primaryColorIndex; this.primaryColorIndex = primaryColorIndex;
this.outlineColorIndex = outlineColorIndex;
this.fontSizeIndex = fontSizeIndex; this.fontSizeIndex = fontSizeIndex;
this.boldIndex = boldIndex; this.boldIndex = boldIndex;
this.italicIndex = italicIndex; this.italicIndex = italicIndex;
this.underlineIndex = underlineIndex; this.underlineIndex = underlineIndex;
this.strikeoutIndex = strikeoutIndex; this.strikeoutIndex = strikeoutIndex;
this.borderStyleIndex = borderStyleIndex;
this.length = length; this.length = length;
} }
@ -295,11 +363,13 @@ import java.util.regex.Pattern;
int nameIndex = C.INDEX_UNSET; int nameIndex = C.INDEX_UNSET;
int alignmentIndex = C.INDEX_UNSET; int alignmentIndex = C.INDEX_UNSET;
int primaryColorIndex = C.INDEX_UNSET; int primaryColorIndex = C.INDEX_UNSET;
int outlineColorIndex = C.INDEX_UNSET;
int fontSizeIndex = C.INDEX_UNSET; int fontSizeIndex = C.INDEX_UNSET;
int boldIndex = C.INDEX_UNSET; int boldIndex = C.INDEX_UNSET;
int italicIndex = C.INDEX_UNSET; int italicIndex = C.INDEX_UNSET;
int underlineIndex = C.INDEX_UNSET; int underlineIndex = C.INDEX_UNSET;
int strikeoutIndex = C.INDEX_UNSET; int strikeoutIndex = C.INDEX_UNSET;
int borderStyleIndex = C.INDEX_UNSET;
String[] keys = String[] keys =
TextUtils.split(styleFormatLine.substring(SsaDecoder.FORMAT_LINE_PREFIX.length()), ","); TextUtils.split(styleFormatLine.substring(SsaDecoder.FORMAT_LINE_PREFIX.length()), ",");
for (int i = 0; i < keys.length; i++) { for (int i = 0; i < keys.length; i++) {
@ -313,6 +383,9 @@ import java.util.regex.Pattern;
case "primarycolour": case "primarycolour":
primaryColorIndex = i; primaryColorIndex = i;
break; break;
case "outlinecolour":
outlineColorIndex = i;
break;
case "fontsize": case "fontsize":
fontSizeIndex = i; fontSizeIndex = i;
break; break;
@ -328,6 +401,9 @@ import java.util.regex.Pattern;
case "strikeout": case "strikeout":
strikeoutIndex = i; strikeoutIndex = i;
break; break;
case "borderstyle":
borderStyleIndex = i;
break;
} }
} }
return nameIndex != C.INDEX_UNSET return nameIndex != C.INDEX_UNSET
@ -335,11 +411,13 @@ import java.util.regex.Pattern;
nameIndex, nameIndex,
alignmentIndex, alignmentIndex,
primaryColorIndex, primaryColorIndex,
outlineColorIndex,
fontSizeIndex, fontSizeIndex,
boldIndex, boldIndex,
italicIndex, italicIndex,
underlineIndex, underlineIndex,
strikeoutIndex, strikeoutIndex,
borderStyleIndex,
keys.length) keys.length)
: null; : null;
} }

View File

@ -48,7 +48,8 @@ public final class SsaDecoderTest {
private static final String INVALID_TIMECODES = "media/ssa/invalid_timecodes"; private static final String INVALID_TIMECODES = "media/ssa/invalid_timecodes";
private static final String INVALID_POSITIONS = "media/ssa/invalid_positioning"; 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 POSITIONS_WITHOUT_PLAYRES = "media/ssa/positioning_without_playres";
private static final String STYLE_COLORS = "media/ssa/style_colors"; private static final String STYLE_PRIMARY_COLOR = "media/ssa/style_primary_color";
private static final String STYLE_OUTLINE_COLOR = "media/ssa/style_outline_color";
private static final String STYLE_FONT_SIZE = "media/ssa/style_font_size"; private static final String STYLE_FONT_SIZE = "media/ssa/style_font_size";
private static final String STYLE_BOLD_ITALIC = "media/ssa/style_bold_italic"; private static final String STYLE_BOLD_ITALIC = "media/ssa/style_bold_italic";
private static final String STYLE_UNDERLINE = "media/ssa/style_underline"; private static final String STYLE_UNDERLINE = "media/ssa/style_underline";
@ -297,9 +298,10 @@ public final class SsaDecoderTest {
} }
@Test @Test
public void decodeColors() throws IOException { public void decodePrimaryColor() throws IOException {
SsaDecoder decoder = new SsaDecoder(); SsaDecoder decoder = new SsaDecoder();
byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), STYLE_COLORS); byte[] bytes =
TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), STYLE_PRIMARY_COLOR);
Subtitle subtitle = decoder.decode(bytes, bytes.length, false); Subtitle subtitle = decoder.decode(bytes, bytes.length, false);
assertThat(subtitle.getEventTimeCount()).isEqualTo(14); assertThat(subtitle.getEventTimeCount()).isEqualTo(14);
// &H000000FF (AABBGGRR) -> #FFFF0000 (AARRGGBB) // &H000000FF (AABBGGRR) -> #FFFF0000 (AARRGGBB)
@ -344,6 +346,26 @@ public final class SsaDecoderTest {
.hasNoForegroundColorSpanBetween(0, seventhCueText.length()); .hasNoForegroundColorSpanBetween(0, seventhCueText.length());
} }
@Test
public void decodeOutlineColor() throws IOException {
SsaDecoder decoder = new SsaDecoder();
byte[] bytes =
TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), STYLE_OUTLINE_COLOR);
Subtitle subtitle = decoder.decode(bytes, bytes.length, false);
assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
Spanned firstCueText =
(Spanned) Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0))).text;
SpannedSubject.assertThat(firstCueText)
.hasBackgroundColorSpanBetween(0, firstCueText.length())
.withColor(Color.BLUE);
// OutlineColour should be treated as background only when BorderStyle=3
Spanned secondCueText =
(Spanned) Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2))).text;
SpannedSubject.assertThat(secondCueText)
.hasNoBackgroundColorSpanBetween(0, secondCueText.length());
}
@Test @Test
public void decodeFontSize() throws IOException { public void decodeFontSize() throws IOException {
SsaDecoder decoder = new SsaDecoder(); SsaDecoder decoder = new SsaDecoder();

View File

@ -0,0 +1,16 @@
[Script Info]
Title: OutlineColour settings
Script Type: V4.00+
PlayResX: 1280
PlayResY: 720
[V4+ Styles]
Format: Name ,OutlineColour,BorderStyle
Style: OutlineColourStyleBlue ,&H00FF0000 ,3
Style: OutlineColourStyleIgnored ,&H00FF0000 ,1
[Events]
Format: Start ,End ,Style ,Text
Dialogue: 0:00:01.00,0:00:02.00,OutlineColourStyleBlue ,Line with BLUE (&H00FF0000) outline.
Dialogue: 0:00:03.00,0:00:04.00,OutlineColourStyleIgnored ,Line with ignored outline because BorderStyle is not 3.

View File

@ -1,5 +1,5 @@
[Script Info] [Script Info]
Title: Coloring Title: PrimaryColour settings
Script Type: V4.00+ Script Type: V4.00+
PlayResX: 1280 PlayResX: 1280
PlayResY: 720 PlayResY: 720