Add support for SSA (V4+) PrimaryColour style

This commit is contained in:
Arnold Szabo 2021-01-21 22:45:07 +01:00
parent c40d1c6620
commit 0ead2af22c
5 changed files with 75 additions and 5 deletions

View file

@ -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"

View file

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

View file

@ -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;
}
}

View file

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

View file

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