diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 3746e3650d..2cfba47972 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -38,6 +38,9 @@ * Fix a bug that caused multiple ads in an ad pod to be skipped when one ad in the ad pod was skipped. * Fix passing an ads response to the `ImaAdsLoader` builder. +* Text + * Allow tx3g subtitles with `styl` boxes with start and/or end offsets + that lie outside the length of the cue text. ### 2.12.1 (2020-10-23) ### diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java index 4ce0ea8df5..907607f859 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java @@ -31,6 +31,7 @@ import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.SimpleSubtitleDecoder; import com.google.android.exoplayer2.text.Subtitle; import com.google.android.exoplayer2.text.SubtitleDecoderException; +import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; import com.google.common.base.Charsets; @@ -43,6 +44,8 @@ import java.util.List; */ public final class Tx3gDecoder extends SimpleSubtitleDecoder { + private static final String TAG = "Tx3gDecoder"; + private static final char BOM_UTF16_BE = '\uFEFF'; private static final char BOM_UTF16_LE = '\uFFFE'; @@ -185,6 +188,16 @@ public final class Tx3gDecoder extends SimpleSubtitleDecoder { int fontFace = parsableByteArray.readUnsignedByte(); parsableByteArray.skipBytes(1); // font size int colorRgba = parsableByteArray.readInt(); + + if (end > cueText.length()) { + Log.w( + TAG, "Truncating styl end (" + end + ") to cueText.length() (" + cueText.length() + ")."); + end = cueText.length(); + } + if (start >= end) { + Log.w(TAG, "Ignoring styl with start (" + start + ") >= end (" + end + ")."); + return; + } attachFontFace(cueText, fontFace, defaultFontFace, start, end, SPAN_PRIORITY_HIGH); attachColor(cueText, colorRgba, defaultColorRgba, start, end, SPAN_PRIORITY_HIGH); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoderTest.java index ca84f901d8..b64466cc00 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoderTest.java @@ -38,6 +38,10 @@ public final class Tx3gDecoderTest { private static final String NO_SUBTITLE = "media/tx3g/no_subtitle"; private static final String SAMPLE_JUST_TEXT = "media/tx3g/sample_just_text"; private static final String SAMPLE_WITH_STYL = "media/tx3g/sample_with_styl"; + private static final String SAMPLE_WITH_STYL_START_TOO_LARGE = + "media/tx3g/sample_with_styl_start_too_large"; + private static final String SAMPLE_WITH_STYL_END_TOO_LARGE = + "media/tx3g/sample_with_styl_end_too_large"; private static final String SAMPLE_WITH_STYL_ALL_DEFAULTS = "media/tx3g/sample_with_styl_all_defaults"; private static final String SAMPLE_UTF16_BE_NO_STYL = "media/tx3g/sample_utf16_be_no_styl"; @@ -90,6 +94,52 @@ public final class Tx3gDecoderTest { assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f); } + /** + * The 7-byte sample contains a 4-byte emoji. The start index (6) and end index (7) are valid as + * byte offsets, but not a UTF-16 code-unit offset, so they're both truncated to 5 (the length of + * the resulting the string in Java) and the spans end up empty (so we don't add them). + * + *
https://github.com/google/ExoPlayer/pull/8133 + */ + @Test + public void decodeWithStyl_startTooLarge_noSpanAdded() throws Exception { + Tx3gDecoder decoder = new Tx3gDecoder(ImmutableList.of()); + byte[] bytes = + TestUtil.getByteArray( + ApplicationProvider.getApplicationContext(), SAMPLE_WITH_STYL_START_TOO_LARGE); + + Subtitle subtitle = decoder.decode(bytes, bytes.length, false); + SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text); + + assertThat(text.toString()).isEqualTo("CC 🙂"); + assertThat(text).hasNoSpans(); + assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f); + } + + /** + * The 7-byte sample contains a 4-byte emoji. The end index (6) is valid as a byte offset, but not + * a UTF-16 code-unit offset, so it's truncated to 5 (the length of the resulting the string in + * Java). + * + *
https://github.com/google/ExoPlayer/pull/8133 + */ + @Test + public void decodeWithStyl_endTooLarge_clippedToEndOfText() throws Exception { + Tx3gDecoder decoder = new Tx3gDecoder(ImmutableList.of()); + byte[] bytes = + TestUtil.getByteArray( + ApplicationProvider.getApplicationContext(), SAMPLE_WITH_STYL_END_TOO_LARGE); + + Subtitle subtitle = decoder.decode(bytes, bytes.length, false); + SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text); + + assertThat(text.toString()).isEqualTo("CC 🙂"); + assertThat(text).hasBoldItalicSpanBetween(0, 5); + assertThat(text).hasUnderlineSpanBetween(0, 5); + assertThat(text).hasForegroundColorSpanBetween(0, 5).withColor(Color.GREEN); + assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f); + } + @Test public void decodeWithStylAllDefaults() throws Exception { Tx3gDecoder decoder = new Tx3gDecoder(ImmutableList.of()); diff --git a/testdata/src/test/assets/media/tx3g/sample_with_styl_end_too_large b/testdata/src/test/assets/media/tx3g/sample_with_styl_end_too_large new file mode 100644 index 0000000000..35c60e0607 Binary files /dev/null and b/testdata/src/test/assets/media/tx3g/sample_with_styl_end_too_large differ diff --git a/testdata/src/test/assets/media/tx3g/sample_with_styl_start_too_large b/testdata/src/test/assets/media/tx3g/sample_with_styl_start_too_large new file mode 100644 index 0000000000..972e90092f Binary files /dev/null and b/testdata/src/test/assets/media/tx3g/sample_with_styl_start_too_large differ