From 741a9c2ef73c48ad504528e77c4b33e201c86747 Mon Sep 17 00:00:00 2001 From: Arnold Szabo Date: Sun, 21 Feb 2021 18:22:04 +0100 Subject: [PATCH 1/3] Add support for font size in SSA v4+ styling. --- demos/main/src/main/assets/media.exolist.json | 7 ++++ .../exoplayer2/text/ssa/SsaDecoder.java | 5 +++ .../android/exoplayer2/text/ssa/SsaStyle.java | 34 ++++++++++++++++--- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/demos/main/src/main/assets/media.exolist.json b/demos/main/src/main/assets/media.exolist.json index b515eca98a..9a61ba673c 100644 --- a/demos/main/src/main/assets/media.exolist.json +++ b/demos/main/src/main/assets/media.exolist.json @@ -506,6 +506,13 @@ "subtitle_mime_type": "text/x-ssa", "subtitle_language": "en" }, + { + "name": "SubStation Alpha font size", + "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" diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java index b8e047dbcb..032df6c2ab 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java @@ -314,6 +314,11 @@ public final class SsaDecoder extends SimpleSubtitleDecoder { /* end= */ spannableText.length(), SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); } + if (style.fontSize != null && screenHeight != Cue.DIMEN_UNSET) { + cue.setTextSize( + style.fontSize / screenHeight, + Cue.TEXT_SIZE_TYPE_FRACTIONAL_IGNORE_PADDING); + } } @SsaStyle.SsaAlignment int alignment; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java index bd378cccec..34d5528917 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java @@ -90,12 +90,17 @@ import java.util.regex.Pattern; public final String name; @SsaAlignment public final int alignment; @Nullable @ColorInt public final Integer primaryColor; + public final Integer fontSize; private SsaStyle( - String name, @SsaAlignment int alignment, @Nullable @ColorInt Integer primaryColor) { + String name, + @SsaAlignment int alignment, + @Nullable @ColorInt Integer primaryColor, + Integer fontSize) { this.name = name; this.alignment = alignment; this.primaryColor = primaryColor; + this.fontSize = fontSize; } @Nullable @@ -114,7 +119,8 @@ import java.util.regex.Pattern; return new SsaStyle( styleValues[format.nameIndex].trim(), parseAlignment(styleValues[format.alignmentIndex].trim()), - parseColor(styleValues[format.primaryColorIndex].trim())); + parseColor(styleValues[format.primaryColorIndex].trim()), + parseFontSize(styleValues[format.fontSizeIndex].trim())); } catch (RuntimeException e) { Log.w(TAG, "Skipping malformed 'Style:' line: '" + styleLine + "'", e); return null; @@ -191,6 +197,15 @@ import java.util.regex.Pattern; return Color.argb(a, r, g, b); } + private static Integer parseFontSize(String fontSize) { + try { + return Integer.parseInt(fontSize); + } catch (NumberFormatException e) { + Log.w(TAG, "Failed to parse font size: '" + fontSize + "'", e); + return null; + } + } + /** * Represents a {@code Format:} line from the {@code [V4+ Styles]} section * @@ -202,12 +217,19 @@ import java.util.regex.Pattern; public final int nameIndex; public final int alignmentIndex; public final int primaryColorIndex; + public final int fontSizeIndex; public final int length; - private Format(int nameIndex, int alignmentIndex, int primaryColorIndex, int length) { + private Format( + int nameIndex, + int alignmentIndex, + int primaryColorIndex, + int fontSizeIndex, + int length) { this.nameIndex = nameIndex; this.alignmentIndex = alignmentIndex; this.primaryColorIndex = primaryColorIndex; + this.fontSizeIndex = fontSizeIndex; this.length = length; } @@ -221,6 +243,7 @@ import java.util.regex.Pattern; int nameIndex = C.INDEX_UNSET; int alignmentIndex = C.INDEX_UNSET; int primaryColorIndex = C.INDEX_UNSET; + int fontSizeIndex = C.INDEX_UNSET; String[] keys = TextUtils.split(styleFormatLine.substring(SsaDecoder.FORMAT_LINE_PREFIX.length()), ","); for (int i = 0; i < keys.length; i++) { @@ -234,10 +257,13 @@ import java.util.regex.Pattern; case "primarycolour": primaryColorIndex = i; break; + case "fontsize": + fontSizeIndex = i; + break; } } return nameIndex != C.INDEX_UNSET - ? new Format(nameIndex, alignmentIndex, primaryColorIndex, keys.length) + ? new Format(nameIndex, alignmentIndex, primaryColorIndex, fontSizeIndex, keys.length) : null; } } From 9c35d76184c9359e3da80b101791010fd11b6096 Mon Sep 17 00:00:00 2001 From: Arnold Szabo Date: Mon, 22 Feb 2021 20:33:12 +0100 Subject: [PATCH 2/3] Parse font size into float, and fallback to Cue.DIMEN_UNSET instead of null. --- demos/main/src/main/assets/media.exolist.json | 7 ------- .../exoplayer2/text/ssa/SsaDecoder.java | 2 +- .../android/exoplayer2/text/ssa/SsaStyle.java | 11 +++++----- .../exoplayer2/text/ssa/SsaDecoderTest.java | 20 +++++++++++++++++-- .../assets/media/ssa/{colors => style_colors} | 0 .../src/test/assets/media/ssa/style_font_size | 18 +++++++++++++++++ 6 files changed, 43 insertions(+), 15 deletions(-) rename testdata/src/test/assets/media/ssa/{colors => style_colors} (100%) create mode 100644 testdata/src/test/assets/media/ssa/style_font_size diff --git a/demos/main/src/main/assets/media.exolist.json b/demos/main/src/main/assets/media.exolist.json index 9a61ba673c..b515eca98a 100644 --- a/demos/main/src/main/assets/media.exolist.json +++ b/demos/main/src/main/assets/media.exolist.json @@ -506,13 +506,6 @@ "subtitle_mime_type": "text/x-ssa", "subtitle_language": "en" }, - { - "name": "SubStation Alpha font size", - "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" diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java index 032df6c2ab..d8d1523b29 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java @@ -314,7 +314,7 @@ public final class SsaDecoder extends SimpleSubtitleDecoder { /* end= */ spannableText.length(), SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); } - if (style.fontSize != null && screenHeight != Cue.DIMEN_UNSET) { + if (style.fontSize != Cue.DIMEN_UNSET && screenHeight != Cue.DIMEN_UNSET) { cue.setTextSize( style.fontSize / screenHeight, Cue.TEXT_SIZE_TYPE_FRACTIONAL_IGNORE_PADDING); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java index 34d5528917..554e576710 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java @@ -27,6 +27,7 @@ import androidx.annotation.ColorInt; import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; @@ -90,13 +91,13 @@ import java.util.regex.Pattern; public final String name; @SsaAlignment public final int alignment; @Nullable @ColorInt public final Integer primaryColor; - public final Integer fontSize; + public final float fontSize; private SsaStyle( String name, @SsaAlignment int alignment, @Nullable @ColorInt Integer primaryColor, - Integer fontSize) { + float fontSize) { this.name = name; this.alignment = alignment; this.primaryColor = primaryColor; @@ -197,12 +198,12 @@ import java.util.regex.Pattern; return Color.argb(a, r, g, b); } - private static Integer parseFontSize(String fontSize) { + private static float parseFontSize(String fontSize) { try { - return Integer.parseInt(fontSize); + return Float.parseFloat(fontSize); } catch (NumberFormatException e) { Log.w(TAG, "Failed to parse font size: '" + fontSize + "'", e); - return null; + return Cue.DIMEN_UNSET; } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java index a734019d09..8dc98d3335 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java @@ -47,7 +47,8 @@ public final class SsaDecoderTest { private static final String INVALID_TIMECODES = "media/ssa/invalid_timecodes"; 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 COLORS = "media/ssa/colors"; + private static final String STYLE_COLORS = "media/ssa/style_colors"; + private static final String STYLE_FONT_SIZE = "media/ssa/style_font_size"; @Test public void decodeEmpty() throws IOException { @@ -274,7 +275,7 @@ public final class SsaDecoderTest { @Test public void decodeColors() throws IOException { SsaDecoder decoder = new SsaDecoder(); - byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), COLORS); + byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), STYLE_COLORS); Subtitle subtitle = decoder.decode(bytes, bytes.length, false); assertThat(subtitle.getEventTimeCount()).isEqualTo(14); // &H000000FF (AABBGGRR) -> #FFFF0000 (AARRGGBB) @@ -319,6 +320,21 @@ public final class SsaDecoderTest { .hasNoForegroundColorSpanBetween(0, seventhCueText.length()); } + @Test + public void decodeFontSize() throws IOException{ + SsaDecoder decoder = new SsaDecoder(); + byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), STYLE_FONT_SIZE); + Subtitle subtitle = decoder.decode(bytes, bytes.length, false); + assertThat(subtitle.getEventTimeCount()).isEqualTo(4); + + Cue firstCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0))); + assertThat(firstCue.textSize).isEqualTo(30f/720f); + assertThat(firstCue.textSizeType).isEqualTo(Cue.TEXT_SIZE_TYPE_FRACTIONAL_IGNORE_PADDING); + Cue secondCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2 ))); + assertThat(secondCue.textSize).isEqualTo(72.2f/720f); + assertThat(secondCue.textSizeType).isEqualTo(Cue.TEXT_SIZE_TYPE_FRACTIONAL_IGNORE_PADDING); + } + private static void assertTypicalCue1(Subtitle subtitle, int eventIndex) { assertThat(subtitle.getEventTime(eventIndex)).isEqualTo(0); assertThat(subtitle.getCues(subtitle.getEventTime(eventIndex)).get(0).text.toString()) diff --git a/testdata/src/test/assets/media/ssa/colors b/testdata/src/test/assets/media/ssa/style_colors similarity index 100% rename from testdata/src/test/assets/media/ssa/colors rename to testdata/src/test/assets/media/ssa/style_colors diff --git a/testdata/src/test/assets/media/ssa/style_font_size b/testdata/src/test/assets/media/ssa/style_font_size new file mode 100644 index 0000000000..d43a8b4390 --- /dev/null +++ b/testdata/src/test/assets/media/ssa/style_font_size @@ -0,0 +1,18 @@ +[Script Info] +Title: SSA/ASS Test +Original Script: Arnold Szabo +Script Type: V4.00+ +PlayResX: 1280 +PlayResY: 720 + +[V4+ Styles] +Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding +Style: FontSizeSmall ,Roboto,30, &H000000FF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,3,0,2,50,50,70,1 +Style: FontSizeBig ,Roboto,72.2,&H000000FF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,3,0,2,50,50,70,1 + + + +[Events] +Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text +Dialogue: 0,0:00:00.95,0:00:03.11,FontSizeSmall ,Arnold,0,0,0,,First line with font size 30. +Dialogue: 0,0:00:08.50,0:00:11.50,FontSizeBig ,Arnold,0,0,0,,Second line with font size 72.2. \ No newline at end of file From dafe710ab393c367cabebac162bb569cb8182d3a Mon Sep 17 00:00:00 2001 From: Arnold Szabo Date: Mon, 22 Feb 2021 20:36:06 +0100 Subject: [PATCH 3/3] Remove extra space from SsaDecoderTest.java. --- .../com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java index 8dc98d3335..5ab8f8ed1d 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java @@ -330,7 +330,7 @@ public final class SsaDecoderTest { Cue firstCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0))); assertThat(firstCue.textSize).isEqualTo(30f/720f); assertThat(firstCue.textSizeType).isEqualTo(Cue.TEXT_SIZE_TYPE_FRACTIONAL_IGNORE_PADDING); - Cue secondCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2 ))); + Cue secondCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2))); assertThat(secondCue.textSize).isEqualTo(72.2f/720f); assertThat(secondCue.textSizeType).isEqualTo(Cue.TEXT_SIZE_TYPE_FRACTIONAL_IGNORE_PADDING); }