Merge pull request #10150 from egor-n:dev-v2-8435-outlinecolour
PiperOrigin-RevId: 444787307
This commit is contained in:
commit
9369348d6f
@ -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.
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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.
|
@ -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
|
Loading…
x
Reference in New Issue
Block a user