mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Add support for SSA (V4+) PrimaryColour style
This commit is contained in:
parent
c40d1c6620
commit
0ead2af22c
5 changed files with 75 additions and 5 deletions
|
|
@ -506,6 +506,13 @@
|
||||||
"subtitle_mime_type": "text/x-ssa",
|
"subtitle_mime_type": "text/x-ssa",
|
||||||
"subtitle_language": "en"
|
"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",
|
"name": "MPEG-4 Timed Text",
|
||||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mp4/dizzy-with-tx3g.mp4"
|
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mp4/dizzy-with-tx3g.mp4"
|
||||||
|
|
|
||||||
|
|
@ -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 static com.google.android.exoplayer2.util.Util.castNonNull;
|
||||||
|
|
||||||
import android.text.Layout;
|
import android.text.Layout;
|
||||||
|
import android.text.SpannableString;
|
||||||
|
import android.text.style.ForegroundColorSpan;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.text.Cue;
|
import com.google.android.exoplayer2.text.Cue;
|
||||||
|
|
@ -301,8 +303,17 @@ public final class SsaDecoder extends SimpleSubtitleDecoder {
|
||||||
SsaStyle.Overrides styleOverrides,
|
SsaStyle.Overrides styleOverrides,
|
||||||
float screenWidth,
|
float screenWidth,
|
||||||
float screenHeight) {
|
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;
|
@SsaStyle.SsaAlignment int alignment;
|
||||||
if (styleOverrides.alignment != SsaStyle.SSA_ALIGNMENT_UNKNOWN) {
|
if (styleOverrides.alignment != SsaStyle.SSA_ALIGNMENT_UNKNOWN) {
|
||||||
alignment = styleOverrides.alignment;
|
alignment = styleOverrides.alignment;
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,12 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||||
|
|
||||||
import android.graphics.PointF;
|
import android.graphics.PointF;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import androidx.annotation.ColorInt;
|
||||||
import androidx.annotation.IntDef;
|
import androidx.annotation.IntDef;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
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.Log;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.lang.annotation.Documented;
|
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_CENTER = 8;
|
||||||
public static final int SSA_ALIGNMENT_TOP_RIGHT = 9;
|
public static final int SSA_ALIGNMENT_TOP_RIGHT = 9;
|
||||||
|
|
||||||
|
public static final int SSA_COLOR_UNKNOWN = -1;
|
||||||
|
|
||||||
public final String name;
|
public final String name;
|
||||||
@SsaAlignment public final int alignment;
|
@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.name = name;
|
||||||
this.alignment = alignment;
|
this.alignment = alignment;
|
||||||
|
this.primaryColor = primaryColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|
@ -105,7 +111,9 @@ import java.util.regex.Pattern;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return new SsaStyle(
|
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) {
|
} 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;
|
||||||
|
|
@ -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
|
* 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 nameIndex;
|
||||||
public final int alignmentIndex;
|
public final int alignmentIndex;
|
||||||
|
public final int primaryColorIndex;
|
||||||
public final int length;
|
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.nameIndex = nameIndex;
|
||||||
this.alignmentIndex = alignmentIndex;
|
this.alignmentIndex = alignmentIndex;
|
||||||
|
this.primaryColorIndex = primaryColorIndex;
|
||||||
this.length = length;
|
this.length = length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,6 +191,7 @@ import java.util.regex.Pattern;
|
||||||
public static Format fromFormatLine(String styleFormatLine) {
|
public static Format fromFormatLine(String styleFormatLine) {
|
||||||
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;
|
||||||
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++) {
|
||||||
|
|
@ -181,9 +202,14 @@ import java.util.regex.Pattern;
|
||||||
case "alignment":
|
case "alignment":
|
||||||
alignmentIndex = i;
|
alignmentIndex = i;
|
||||||
break;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,27 @@ public final class ColorParser {
|
||||||
return parseColorInternal(colorExpression, true);
|
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
|
@ColorInt
|
||||||
private static int parseColorInternal(String colorExpression, boolean alphaHasFloatFormat) {
|
private static int parseColorInternal(String colorExpression, boolean alphaHasFloatFormat) {
|
||||||
Assertions.checkArgument(!TextUtils.isEmpty(colorExpression));
|
Assertions.checkArgument(!TextUtils.isEmpty(colorExpression));
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,10 @@ package com.google.android.exoplayer2.util;
|
||||||
import static android.graphics.Color.BLACK;
|
import static android.graphics.Color.BLACK;
|
||||||
import static android.graphics.Color.RED;
|
import static android.graphics.Color.RED;
|
||||||
import static android.graphics.Color.WHITE;
|
import static android.graphics.Color.WHITE;
|
||||||
|
import static android.graphics.Color.YELLOW;
|
||||||
import static android.graphics.Color.argb;
|
import static android.graphics.Color.argb;
|
||||||
import static android.graphics.Color.parseColor;
|
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.android.exoplayer2.util.ColorParser.parseTtmlColor;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
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.
|
// Hex colors in ColorParser are RGBA, where-as {@link Color#parseColor} takes ARGB.
|
||||||
assertThat(parseTtmlColor("#FFFFFF00")).isEqualTo(parseColor("#00FFFFFF"));
|
assertThat(parseTtmlColor("#FFFFFF00")).isEqualTo(parseColor("#00FFFFFF"));
|
||||||
assertThat(parseTtmlColor("#12345678")).isEqualTo(parseColor("#78123456"));
|
assertThat(parseTtmlColor("#12345678")).isEqualTo(parseColor("#78123456"));
|
||||||
|
// SSA colors are in &HAABBGGRR format.
|
||||||
|
assertThat(parseSsaColor("&HFF0000FF")).isEqualTo(RED);
|
||||||
|
assertThat(parseSsaColor("&HFF00FFFF")).isEqualTo(YELLOW);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue