Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for SSA Underline and Strikeout #8851

Merged
merged 1 commit into from Apr 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion demos/main/src/main/assets/media.exolist.json
Expand Up @@ -509,7 +509,7 @@
{
"name": "SubStation Alpha positioning",
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/video-avc-baseline-480.mp4",
"subtitle_uri": "https://storage.googleapis.com/exoplayer-test-media-1/ssa/test-subs-position.ass",
"subtitle_uri": "https://drive.google.com/uc?export=download&id=16IrvtynQ6-ANRpRX7hU6xEQeFU91LmXl",
"subtitle_mime_type": "text/x-ssa",
"subtitle_language": "en"
},
Expand Down
Expand Up @@ -22,7 +22,9 @@
import android.text.Layout;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.text.style.UnderlineSpan;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.text.Cue;
Expand Down Expand Up @@ -340,6 +342,20 @@ private static Cue createCue(
/* end= */ spannableText.length(),
SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);
}
if (style.underline) {
spannableText.setSpan(
new UnderlineSpan(),
/* start= */ 0,
/* end= */ spannableText.length(),
SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);
}
if (style.strikeout) {
spannableText.setSpan(
new StrikethroughSpan(),
/* start= */ 0,
/* end= */ spannableText.length(),
SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}

@SsaStyle.SsaAlignment int alignment;
Expand Down
Expand Up @@ -22,6 +22,7 @@

import android.graphics.Color;
import android.graphics.PointF;
import android.os.Binder;
import android.text.TextUtils;
import androidx.annotation.ColorInt;
import androidx.annotation.IntDef;
Expand Down Expand Up @@ -95,20 +96,26 @@
public final float fontSize;
public final boolean bold;
public final boolean italic;
public final boolean underline;
public final boolean strikeout;

private SsaStyle(
String name,
@SsaAlignment int alignment,
@Nullable @ColorInt Integer primaryColor,
float fontSize,
boolean bold,
boolean italic) {
boolean italic,
boolean underline,
boolean strikeout) {
this.name = name;
this.alignment = alignment;
this.primaryColor = primaryColor;
this.fontSize = fontSize;
this.bold = bold;
this.italic = italic;
this.underline = underline;
this.strikeout = strikeout;
}

@Nullable
Expand Down Expand Up @@ -136,10 +143,16 @@ public static SsaStyle fromStyleLine(String styleLine, Format format) {
? parseFontSize(styleValues[format.fontSizeIndex].trim())
: Cue.DIMEN_UNSET,
format.boldIndex != C.INDEX_UNSET
? parseBoldOrItalic(styleValues[format.boldIndex].trim())
? parseBooleanData(styleValues[format.boldIndex].trim())
: false,
format.italicIndex != C.INDEX_UNSET
? parseBoldOrItalic(styleValues[format.italicIndex].trim())
? parseBooleanData(styleValues[format.italicIndex].trim())
: false,
format.underlineIndex != C.INDEX_UNSET
? parseBooleanData(styleValues[format.underlineIndex].trim())
: false,
format.strikeoutIndex != C.INDEX_UNSET
? parseBooleanData(styleValues[format.strikeoutIndex].trim())
: false);
} catch (RuntimeException e) {
Log.w(TAG, "Skipping malformed 'Style:' line: '" + styleLine + "'", e);
Expand Down Expand Up @@ -226,12 +239,12 @@ private static float parseFontSize(String fontSize) {
}
}

private static boolean parseBoldOrItalic(String boldOrItalic) {
private static boolean parseBooleanData(String booleanData) {
try {
int value = Integer.parseInt(boldOrItalic);
int value = Integer.parseInt(booleanData);
return value == 1 || value == -1;
} catch (NumberFormatException e) {
Log.w(TAG, "Failed to parse bold/italic: '" + boldOrItalic + "'", e);
Log.w(TAG, "Failed to parse boolean data: '" + booleanData + "'", e);
return false;
}
}
Expand All @@ -250,6 +263,8 @@ private static boolean parseBoldOrItalic(String boldOrItalic) {
public final int fontSizeIndex;
public final int boldIndex;
public final int italicIndex;
public final int underlineIndex;
public final int strikeoutIndex;
public final int length;

private Format(
Expand All @@ -259,13 +274,17 @@ private Format(
int fontSizeIndex,
int boldIndex,
int italicIndex,
int underlineIndex,
int strikeoutIndex,
int length) {
this.nameIndex = nameIndex;
this.alignmentIndex = alignmentIndex;
this.primaryColorIndex = primaryColorIndex;
this.fontSizeIndex = fontSizeIndex;
this.boldIndex = boldIndex;
this.italicIndex = italicIndex;
this.underlineIndex = underlineIndex;
this.strikeoutIndex = strikeoutIndex;
this.length = length;
}

Expand All @@ -282,6 +301,8 @@ public static Format fromFormatLine(String styleFormatLine) {
int fontSizeIndex = C.INDEX_UNSET;
int boldIndex = C.INDEX_UNSET;
int italicIndex = C.INDEX_UNSET;
int underlineIndex = C.INDEX_UNSET;
int strikeoutIndex = C.INDEX_UNSET;
String[] keys =
TextUtils.split(styleFormatLine.substring(SsaDecoder.FORMAT_LINE_PREFIX.length()), ",");
for (int i = 0; i < keys.length; i++) {
Expand All @@ -304,6 +325,12 @@ public static Format fromFormatLine(String styleFormatLine) {
case "italic":
italicIndex = i;
break;
case "underline":
underlineIndex = i;
break;
case "strikeout":
strikeoutIndex = i;
break;
}
}
return nameIndex != C.INDEX_UNSET
Expand All @@ -314,6 +341,8 @@ public static Format fromFormatLine(String styleFormatLine) {
fontSizeIndex,
boldIndex,
italicIndex,
underlineIndex,
strikeoutIndex,
keys.length)
: null;
}
Expand Down
Expand Up @@ -50,6 +50,8 @@ public final class SsaDecoderTest {
private static final String STYLE_COLORS = "media/ssa/style_colors";
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_UNDERLINE = "media/ssa/style_underline";
private static final String STYLE_STRIKEOUT = "media/ssa/style_strikeout";

@Test
public void decodeEmpty() throws IOException {
Expand Down Expand Up @@ -356,6 +358,38 @@ public void decodeBoldItalic() throws IOException {
SpannedSubject.assertThat(thirdCueText).hasBoldItalicSpanBetween(0, thirdCueText.length());
}

@Test
public void decodeUnderline() throws IOException {
SsaDecoder decoder = new SsaDecoder();
byte[] bytes =
TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), STYLE_UNDERLINE);
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).hasUnderlineSpanBetween(0, firstCueText.length());
Spanned secondCueText =
(Spanned) Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2))).text;
SpannedSubject.assertThat(secondCueText).hasNoUnderlineSpanBetween(0, secondCueText.length());
}

@Test
public void decodeStrikeout() throws IOException {
SsaDecoder decoder = new SsaDecoder();
byte[] bytes =
TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), STYLE_STRIKEOUT);
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).hasStrikethroughSpanBetween(0, firstCueText.length());
Spanned secondCueText =
(Spanned) Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2))).text;
SpannedSubject.assertThat(secondCueText).hasNoStrikethroughSpanBetween(0, secondCueText.length());
}

private static void assertTypicalCue1(Subtitle subtitle, int eventIndex) {
assertThat(subtitle.getEventTime(eventIndex)).isEqualTo(0);
assertThat(subtitle.getCues(subtitle.getEventTime(eventIndex)).get(0).text.toString())
Expand Down
16 changes: 16 additions & 0 deletions testdata/src/test/assets/media/ssa/style_strikeout
@@ -0,0 +1,16 @@
[Script Info]
Title: SSA/ASS Test
Original Script: Abel
Script Type: V4.00+
PlayResX: 1280
PlayResY: 720

[V4+ Styles]
Format: Name ,Strikeout
Style: WithStrikeout ,-1
Style: WithoutStrikeout ,0

[Events]
Format: Start ,End ,Style ,Text
Dialogue: 0:00:01.00,0:00:03.00,WithStrikeout ,First line with Strikeout.
Dialogue: 0:00:05.00,0:00:07.00,WithoutStrikeout ,Second line without Strikeout.
16 changes: 16 additions & 0 deletions testdata/src/test/assets/media/ssa/style_underline
@@ -0,0 +1,16 @@
[Script Info]
Title: SSA/ASS Test
Original Script: Abel
Script Type: V4.00+
PlayResX: 1280
PlayResY: 720

[V4+ Styles]
Format: Name ,Underline
Style: WithUnderline ,-1
Style: WithoutUnderline ,0

[Events]
Format: Start ,End ,Style ,Text
Dialogue: 0:00:01.00,0:00:03.00,WithUnderline ,First line with Underline.
Dialogue: 0:00:05.00,0:00:07.00,WithoutUnderline ,Second line without Underline.