From a12bde4f57002a9adf5da6c01b2f601a6edf92e9 Mon Sep 17 00:00:00 2001 From: ibaker Date: Thu, 12 Oct 2023 01:40:05 -0700 Subject: [PATCH] Migrate `SubtitleParser` tests to incremental `parse()` methods All the production code is already calling these new incremental methods, migrating the tests allows us to remove the old `List`-returning methods in a follow-up change. #minor-release PiperOrigin-RevId: 572822828 --- .../extractor/text/ssa/SsaParserTest.java | 79 ++++++++++--- .../text/subrip/SubripParserTest.java | 66 ++++++++--- .../extractor/text/tx3g/Tx3gParserTest.java | 109 +++++++----------- .../text/webvtt/Mp4WebvttParserTest.java | 39 ++++--- 4 files changed, 179 insertions(+), 114 deletions(-) diff --git a/libraries/extractor/src/test/java/androidx/media3/extractor/text/ssa/SsaParserTest.java b/libraries/extractor/src/test/java/androidx/media3/extractor/text/ssa/SsaParserTest.java index f2bde68ae43..914fc7673a2 100644 --- a/libraries/extractor/src/test/java/androidx/media3/extractor/text/ssa/SsaParserTest.java +++ b/libraries/extractor/src/test/java/androidx/media3/extractor/text/ssa/SsaParserTest.java @@ -24,6 +24,8 @@ import android.text.Spanned; import androidx.media3.common.text.Cue; import androidx.media3.extractor.text.CuesWithTiming; +import androidx.media3.extractor.text.SubtitleParser; +import androidx.media3.extractor.text.SubtitleParser.OutputOptions; import androidx.media3.test.utils.TestUtil; import androidx.media3.test.utils.truth.SpannedSubject; import androidx.test.core.app.ApplicationProvider; @@ -71,7 +73,7 @@ public void cuesReplacementBehaviorIsMerge() throws IOException { public void parseEmpty() throws IOException { SsaParser parser = new SsaParser(); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), EMPTY); - List allCues = parser.parse(bytes); + ImmutableList allCues = parseAllCues(parser, bytes); assertThat(allCues).isEmpty(); } @@ -81,7 +83,7 @@ public void parseEmptyStyleLine() throws IOException { SsaParser parser = new SsaParser(); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), EMPTY_STYLE_LINE); - List allCues = parser.parse(bytes); + ImmutableList allCues = parseAllCues(parser, bytes); assertThat(allCues).hasSize(1); Cue cue = Iterables.getOnlyElement(allCues.get(0).cues); @@ -101,7 +103,7 @@ public void parseEmptyStyleLine() throws IOException { public void parseTypical() throws IOException { SsaParser parser = new SsaParser(); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TYPICAL); - List allCues = parser.parse(bytes); + ImmutableList allCues = parseAllCues(parser, bytes); assertThat(allCues).hasSize(3); // Check position, line, anchors & alignment are set from Alignment Style (2 - bottom-center). @@ -122,6 +124,34 @@ public void parseTypical() throws IOException { assertTypicalCue3(allCues.get(2)); } + @Test + public void parseTypical_onlyCuesAfterTime() throws IOException { + SsaParser parser = new SsaParser(); + byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TYPICAL); + List cues = new ArrayList<>(); + parser.parse(bytes, OutputOptions.onlyCuesAfter(/* startTimeUs= */ 1_000_000), cues::add); + + assertThat(cues).hasSize(2); + assertTypicalCue2(cues.get(0)); + assertTypicalCue3(cues.get(1)); + } + + @Test + public void parseTypical_cuesAfterTimeThenCuesBefore() throws IOException { + SsaParser parser = new SsaParser(); + byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TYPICAL); + List cues = new ArrayList<>(); + parser.parse( + bytes, + OutputOptions.cuesAfterThenRemainingCuesBefore(/* startTimeUs= */ 1_000_000), + cues::add); + + assertThat(cues).hasSize(3); + assertTypicalCue2(cues.get(0)); + assertTypicalCue3(cues.get(1)); + assertTypicalCue1(cues.get(2)); + } + @Test public void parseTypicalWithInitializationData() throws IOException { byte[] headerBytes = @@ -134,7 +164,7 @@ public void parseTypicalWithInitializationData() throws IOException { SsaParser parser = new SsaParser(initializationData); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TYPICAL_DIALOGUE_ONLY); - List allCues = parser.parse(bytes); + ImmutableList allCues = parseAllCues(parser, bytes); assertThat(allCues).hasSize(3); assertTypicalCue1(allCues.get(0)); @@ -155,8 +185,13 @@ public void parseTypicalWithInitializationDataAtOffsetIntoDialogueAndRestrictedL SsaParser parser = new SsaParser(initializationData); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TYPICAL_DIALOGUE_ONLY); - ImmutableList allCues = - parser.parse(bytes, /* offset= */ 10, /* length= */ bytes.length - 30); + List allCues = new ArrayList<>(); + parser.parse( + bytes, + /* offset= */ 10, + /* length= */ bytes.length - 30, + OutputOptions.allCues(), + allCues::add); assertThat(allCues).hasSize(2); // Because of the offset, we skip the first line of dialogue @@ -173,7 +208,7 @@ public void parseTypicalUtf16le() throws IOException { SsaParser parser = new SsaParser(); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TYPICAL_UTF16LE); - List allCues = parser.parse(bytes); + ImmutableList allCues = parseAllCues(parser, bytes); assertThat(allCues).hasSize(3); // Check position, line, anchors & alignment are set from Alignment Style (2 - bottom-center). @@ -199,7 +234,7 @@ public void parseTypicalUtf16be() throws IOException { SsaParser parser = new SsaParser(); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TYPICAL_UTF16BE); - List allCues = parser.parse(bytes); + ImmutableList allCues = parseAllCues(parser, bytes); assertThat(allCues).hasSize(3); // Check position, line, anchors & alignment are set from Alignment Style (2 - bottom-center). @@ -225,7 +260,7 @@ public void parseOverlappingTimecodes() throws IOException { SsaParser parser = new SsaParser(); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), OVERLAPPING_TIMECODES); - List allCues = parser.parse(bytes); + ImmutableList allCues = parseAllCues(parser, bytes); String firstSubtitleText = "First subtitle - end overlaps second"; String secondSubtitleText = "Second subtitle - beginning overlaps first"; @@ -289,7 +324,7 @@ public void parseOverlappingTimecodes() throws IOException { public void parsePositions() throws IOException { SsaParser parser = new SsaParser(); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), POSITIONS); - List allCues = parser.parse(bytes); + ImmutableList allCues = parseAllCues(parser, bytes); // Check \pos() sets position & line Cue firstCue = Iterables.getOnlyElement(allCues.get(0).cues); @@ -343,7 +378,7 @@ public void parseInvalidPositions() throws IOException { SsaParser parser = new SsaParser(); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), INVALID_POSITIONS); - List allCues = parser.parse(bytes); + ImmutableList allCues = parseAllCues(parser, bytes); // Negative parameter to \pos() - fall back to the positions implied by middle-left alignment. Cue firstCue = Iterables.getOnlyElement(allCues.get(0).cues); @@ -380,7 +415,7 @@ public void parsePositionsWithMissingPlayResY() throws IOException { byte[] bytes = TestUtil.getByteArray( ApplicationProvider.getApplicationContext(), POSITIONS_WITHOUT_PLAYRES); - List allCues = parser.parse(bytes); + ImmutableList allCues = parseAllCues(parser, bytes); // The dialogue line has a valid \pos() override, but it's ignored because PlayResY isn't // set (so we don't know the denominator). @@ -396,7 +431,7 @@ public void parseInvalidTimecodes() throws IOException { SsaParser parser = new SsaParser(); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), INVALID_TIMECODES); - List allCues = parser.parse(bytes); + ImmutableList allCues = parseAllCues(parser, bytes); assertThat(allCues).hasSize(1); assertTypicalCue3(Iterables.getOnlyElement(allCues)); @@ -407,7 +442,7 @@ public void parsePrimaryColor() throws IOException { SsaParser parser = new SsaParser(); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), STYLE_PRIMARY_COLOR); - List allCues = parser.parse(bytes); + ImmutableList allCues = parseAllCues(parser, bytes); assertThat(allCues).hasSize(7); // &H000000FF (AABBGGRR) -> #FFFF0000 (AARRGGBB) Spanned firstCueText = (Spanned) Iterables.getOnlyElement(allCues.get(0).cues).text; @@ -449,7 +484,7 @@ public void parseOutlineColor() throws IOException { SsaParser parser = new SsaParser(); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), STYLE_OUTLINE_COLOR); - List allCues = parser.parse(bytes); + ImmutableList allCues = parseAllCues(parser, bytes); assertThat(allCues).hasSize(2); Spanned firstCueText = (Spanned) Iterables.getOnlyElement(allCues.get(0).cues).text; SpannedSubject.assertThat(firstCueText) @@ -467,7 +502,7 @@ public void parseFontSize() throws IOException { SsaParser parser = new SsaParser(); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), STYLE_FONT_SIZE); - List allCues = parser.parse(bytes); + ImmutableList allCues = parseAllCues(parser, bytes); assertThat(allCues).hasSize(2); Cue firstCue = Iterables.getOnlyElement(allCues.get(0).cues); @@ -483,7 +518,7 @@ public void parseBoldItalic() throws IOException { SsaParser parser = new SsaParser(); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), STYLE_BOLD_ITALIC); - List allCues = parser.parse(bytes); + ImmutableList allCues = parseAllCues(parser, bytes); assertThat(allCues).hasSize(3); Spanned firstCueText = (Spanned) Iterables.getOnlyElement(allCues.get(0).cues).text; @@ -499,7 +534,7 @@ public void parseUnderline() throws IOException { SsaParser parser = new SsaParser(); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), STYLE_UNDERLINE); - List allCues = parser.parse(bytes); + ImmutableList allCues = parseAllCues(parser, bytes); assertThat(allCues).hasSize(2); Spanned firstCueText = (Spanned) Iterables.getOnlyElement(allCues.get(0).cues).text; @@ -513,7 +548,7 @@ public void parseStrikeout() throws IOException { SsaParser parser = new SsaParser(); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), STYLE_STRIKEOUT); - List allCues = parser.parse(bytes); + ImmutableList allCues = parseAllCues(parser, bytes); assertThat(allCues).hasSize(2); Spanned firstCueText = (Spanned) Iterables.getOnlyElement(allCues.get(0).cues).text; @@ -523,6 +558,12 @@ public void parseStrikeout() throws IOException { .hasNoStrikethroughSpanBetween(0, secondCueText.length()); } + private static ImmutableList parseAllCues(SubtitleParser parser, byte[] data) { + ImmutableList.Builder cues = ImmutableList.builder(); + parser.parse(data, OutputOptions.allCues(), cues::add); + return cues.build(); + } + private static void assertTypicalCue1(CuesWithTiming cuesWithTiming) { assertThat(cuesWithTiming.startTimeUs).isEqualTo(0); assertThat(cuesWithTiming.durationUs).isEqualTo(1230000); diff --git a/libraries/extractor/src/test/java/androidx/media3/extractor/text/subrip/SubripParserTest.java b/libraries/extractor/src/test/java/androidx/media3/extractor/text/subrip/SubripParserTest.java index c75de19fa1d..ee04b90fc1f 100644 --- a/libraries/extractor/src/test/java/androidx/media3/extractor/text/subrip/SubripParserTest.java +++ b/libraries/extractor/src/test/java/androidx/media3/extractor/text/subrip/SubripParserTest.java @@ -20,11 +20,14 @@ import androidx.media3.common.text.Cue; import androidx.media3.extractor.text.CuesWithTiming; +import androidx.media3.extractor.text.SubtitleParser; +import androidx.media3.extractor.text.SubtitleParser.OutputOptions; import androidx.media3.test.utils.TestUtil; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.common.collect.ImmutableList; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; @@ -60,7 +63,7 @@ public void parseEmpty() throws IOException { SubripParser parser = new SubripParser(); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), EMPTY_FILE); - List allCues = parser.parse(bytes); + ImmutableList allCues = parseAllCues(parser, bytes); assertThat(allCues).isEmpty(); } @@ -70,7 +73,7 @@ public void parseTypical() throws IOException { SubripParser parser = new SubripParser(); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TYPICAL_FILE); - List allCues = parser.parse(bytes); + ImmutableList allCues = parseAllCues(parser, bytes); assertThat(allCues).hasSize(3); assertTypicalCue1(allCues.get(0)); @@ -83,7 +86,8 @@ public void parseTypicalAtOffsetAndRestrictedLength() throws IOException { SubripParser parser = new SubripParser(); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TYPICAL_FILE); - ImmutableList allCues = parser.parse(bytes, 10, bytes.length - 15); + List allCues = new ArrayList<>(); + parser.parse(bytes, 10, bytes.length - 15, OutputOptions.allCues(), allCues::add); assertThat(allCues).hasSize(2); // Because of the offset, we skip the first line of dialogue @@ -93,6 +97,36 @@ public void parseTypicalAtOffsetAndRestrictedLength() throws IOException { assertThat(thirdCue.text.toString()).isEqualTo("This is the third subti"); } + @Test + public void parseTypical_onlyCuesAfterTime() throws IOException { + SubripParser parser = new SubripParser(); + byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TYPICAL_FILE); + + List cues = new ArrayList<>(); + parser.parse(bytes, OutputOptions.onlyCuesAfter(/* startTimeUs= */ 1_000_000), cues::add); + + assertThat(cues).hasSize(2); + assertTypicalCue2(cues.get(0)); + assertTypicalCue3(cues.get(1)); + } + + @Test + public void parseTypical_cuesAfterTimeThenCuesBefore() throws IOException { + SubripParser parser = new SubripParser(); + byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TYPICAL_FILE); + + List cues = new ArrayList<>(); + parser.parse( + bytes, + OutputOptions.cuesAfterThenRemainingCuesBefore(/* startTimeUs= */ 1_000_000), + cues::add); + + assertThat(cues).hasSize(3); + assertTypicalCue2(cues.get(0)); + assertTypicalCue3(cues.get(1)); + assertTypicalCue1(cues.get(2)); + } + @Test public void parseTypicalWithByteOrderMark() throws IOException { SubripParser parser = new SubripParser(); @@ -100,7 +134,7 @@ public void parseTypicalWithByteOrderMark() throws IOException { TestUtil.getByteArray( ApplicationProvider.getApplicationContext(), TYPICAL_WITH_BYTE_ORDER_MARK); - List allCues = parser.parse(bytes); + ImmutableList allCues = parseAllCues(parser, bytes); assertThat(allCues).hasSize(3); assertTypicalCue1(allCues.get(0)); @@ -115,7 +149,7 @@ public void parseTypicalExtraBlankLine() throws IOException { TestUtil.getByteArray( ApplicationProvider.getApplicationContext(), TYPICAL_EXTRA_BLANK_LINE); - List allCues = parser.parse(bytes); + ImmutableList allCues = parseAllCues(parser, bytes); assertThat(allCues).hasSize(3); assertTypicalCue1(allCues.get(0)); @@ -131,7 +165,7 @@ public void parseTypicalMissingTimecode() throws IOException { TestUtil.getByteArray( ApplicationProvider.getApplicationContext(), TYPICAL_MISSING_TIMECODE); - List allCues = parser.parse(bytes); + ImmutableList allCues = parseAllCues(parser, bytes); assertThat(allCues).hasSize(2); assertTypicalCue1(allCues.get(0)); @@ -146,7 +180,7 @@ public void parseTypicalMissingSequence() throws IOException { TestUtil.getByteArray( ApplicationProvider.getApplicationContext(), TYPICAL_MISSING_SEQUENCE); - List allCues = parser.parse(bytes); + ImmutableList allCues = parseAllCues(parser, bytes); assertThat(allCues).hasSize(2); assertTypicalCue1(allCues.get(0)); @@ -161,7 +195,7 @@ public void parseTypicalNegativeTimestamps() throws IOException { TestUtil.getByteArray( ApplicationProvider.getApplicationContext(), TYPICAL_NEGATIVE_TIMESTAMPS); - List allCues = parser.parse(bytes); + ImmutableList allCues = parseAllCues(parser, bytes); assertThat(allCues).hasSize(1); assertTypicalCue3(allCues.get(0)); @@ -174,7 +208,7 @@ public void parseTypicalUnexpectedEnd() throws IOException { byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TYPICAL_UNEXPECTED_END); - List allCues = parser.parse(bytes); + ImmutableList allCues = parseAllCues(parser, bytes); assertThat(allCues).hasSize(2); assertTypicalCue1(allCues.get(0)); @@ -187,7 +221,7 @@ public void parseTypicalUtf16LittleEndian() throws IOException { byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TYPICAL_UTF16LE); - List allCues = parser.parse(bytes); + ImmutableList allCues = parseAllCues(parser, bytes); assertThat(allCues).hasSize(3); assertTypicalCue1(allCues.get(0)); @@ -201,7 +235,7 @@ public void parseTypicalUtf16BigEndian() throws IOException { byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TYPICAL_UTF16BE); - List allCues = parser.parse(bytes); + ImmutableList allCues = parseAllCues(parser, bytes); assertThat(allCues).hasSize(3); assertTypicalCue1(allCues.get(0)); @@ -215,7 +249,7 @@ public void parseCueWithTag() throws IOException { byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TYPICAL_WITH_TAGS); - List allCues = parser.parse(bytes); + ImmutableList allCues = parseAllCues(parser, bytes); assertThat(allCues).isNotNull(); assertThat(allCues.get(0).cues.get(0).text.toString()).isEqualTo("This is the first subtitle."); @@ -244,7 +278,7 @@ public void parseTypicalNoHoursAndMillis() throws IOException { TestUtil.getByteArray( ApplicationProvider.getApplicationContext(), TYPICAL_NO_HOURS_AND_MILLIS); - List allCues = parser.parse(bytes); + ImmutableList allCues = parseAllCues(parser, bytes); assertThat(allCues).hasSize(3); assertTypicalCue1(allCues.get(0)); @@ -254,6 +288,12 @@ public void parseTypicalNoHoursAndMillis() throws IOException { assertTypicalCue3(allCues.get(2)); } + private static ImmutableList parseAllCues(SubtitleParser parser, byte[] data) { + ImmutableList.Builder cues = ImmutableList.builder(); + parser.parse(data, OutputOptions.allCues(), cues::add); + return cues.build(); + } + private static void assertTypicalCue1(CuesWithTiming cuesWithTiming) { assertThat(cuesWithTiming.startTimeUs).isEqualTo(0); assertThat(cuesWithTiming.cues.get(0).text.toString()).isEqualTo("This is the first subtitle."); diff --git a/libraries/extractor/src/test/java/androidx/media3/extractor/text/tx3g/Tx3gParserTest.java b/libraries/extractor/src/test/java/androidx/media3/extractor/text/tx3g/Tx3gParserTest.java index bae64b0c524..48ba6f5a079 100644 --- a/libraries/extractor/src/test/java/androidx/media3/extractor/text/tx3g/Tx3gParserTest.java +++ b/libraries/extractor/src/test/java/androidx/media3/extractor/text/tx3g/Tx3gParserTest.java @@ -20,16 +20,20 @@ import static com.google.common.truth.Truth.assertThat; import android.graphics.Color; +import android.text.Spanned; import android.text.SpannedString; import androidx.media3.common.C; import androidx.media3.common.text.Cue; import androidx.media3.extractor.text.CuesWithTiming; +import androidx.media3.extractor.text.SubtitleParser; import androidx.media3.test.utils.TestUtil; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; @@ -67,7 +71,9 @@ public void parseNoSubtitle() throws Exception { Tx3gParser parser = new Tx3gParser(ImmutableList.of()); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), NO_SUBTITLE); - CuesWithTiming cuesWithTiming = Iterables.getOnlyElement(parser.parse(bytes)); + List allCues = new ArrayList<>(/* initialCapacity= */ 1); + parser.parse(bytes, SubtitleParser.OutputOptions.allCues(), allCues::add); + CuesWithTiming cuesWithTiming = Iterables.getOnlyElement(allCues); assertThat(cuesWithTiming.cues).isEmpty(); assertThat(cuesWithTiming.startTimeUs).isEqualTo(C.TIME_UNSET); @@ -81,13 +87,9 @@ public void parseJustText() throws Exception { byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), SAMPLE_JUST_TEXT); - CuesWithTiming cuesWithTiming = Iterables.getOnlyElement(parser.parse(bytes)); - Cue singleCue = Iterables.getOnlyElement(cuesWithTiming.cues); + Cue singleCue = parseToSingleCue(parser, bytes); SpannedString text = new SpannedString(singleCue.text); - assertThat(cuesWithTiming.startTimeUs).isEqualTo(C.TIME_UNSET); - assertThat(cuesWithTiming.durationUs).isEqualTo(C.TIME_UNSET); - assertThat(cuesWithTiming.endTimeUs).isEqualTo(C.TIME_UNSET); assertThat(text.toString()).isEqualTo("CC Test"); assertThat(text).hasNoSpans(); assertFractionalLinePosition(singleCue, 0.85f); @@ -99,13 +101,9 @@ public void parseWithStyl() throws Exception { byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), SAMPLE_WITH_STYL); - CuesWithTiming cuesWithTiming = Iterables.getOnlyElement(parser.parse(bytes)); - Cue singleCue = Iterables.getOnlyElement(cuesWithTiming.cues); - SpannedString text = new SpannedString(singleCue.text); + Cue singleCue = parseToSingleCue(parser, bytes); + Spanned text = (Spanned) singleCue.text; - assertThat(cuesWithTiming.startTimeUs).isEqualTo(C.TIME_UNSET); - assertThat(cuesWithTiming.durationUs).isEqualTo(C.TIME_UNSET); - assertThat(cuesWithTiming.endTimeUs).isEqualTo(C.TIME_UNSET); assertThat(text.toString()).isEqualTo("CC Test"); assertThat(text).hasBoldItalicSpanBetween(0, 6); assertThat(text).hasUnderlineSpanBetween(0, 6); @@ -127,9 +125,8 @@ public void parseWithStyl_startTooLarge_noSpanAdded() throws Exception { TestUtil.getByteArray( ApplicationProvider.getApplicationContext(), SAMPLE_WITH_STYL_START_TOO_LARGE); - CuesWithTiming cuesWithTiming = Iterables.getOnlyElement(parser.parse(bytes)); - Cue singleCue = Iterables.getOnlyElement(cuesWithTiming.cues); - SpannedString text = new SpannedString(singleCue.text); + Cue singleCue = parseToSingleCue(parser, bytes); + Spanned text = (Spanned) singleCue.text; assertThat(text.toString()).isEqualTo("CC 🙂"); assertThat(text).hasNoSpans(); @@ -150,9 +147,8 @@ public void parseWithStyl_endTooLarge_clippedToEndOfText() throws Exception { TestUtil.getByteArray( ApplicationProvider.getApplicationContext(), SAMPLE_WITH_STYL_END_TOO_LARGE); - CuesWithTiming cuesWithTiming = Iterables.getOnlyElement(parser.parse(bytes)); - Cue singleCue = Iterables.getOnlyElement(cuesWithTiming.cues); - SpannedString text = new SpannedString(singleCue.text); + Cue singleCue = parseToSingleCue(parser, bytes); + Spanned text = (Spanned) singleCue.text; assertThat(text.toString()).isEqualTo("CC 🙂"); assertThat(text).hasBoldItalicSpanBetween(0, 5); @@ -168,13 +164,9 @@ public void parseWithStylAllDefaults() throws Exception { TestUtil.getByteArray( ApplicationProvider.getApplicationContext(), SAMPLE_WITH_STYL_ALL_DEFAULTS); - CuesWithTiming cuesWithTiming = Iterables.getOnlyElement(parser.parse(bytes)); - Cue singleCue = Iterables.getOnlyElement(cuesWithTiming.cues); - SpannedString text = new SpannedString(singleCue.text); + Cue singleCue = parseToSingleCue(parser, bytes); + Spanned text = (Spanned) singleCue.text; - assertThat(cuesWithTiming.startTimeUs).isEqualTo(C.TIME_UNSET); - assertThat(cuesWithTiming.durationUs).isEqualTo(C.TIME_UNSET); - assertThat(cuesWithTiming.endTimeUs).isEqualTo(C.TIME_UNSET); assertThat(text.toString()).isEqualTo("CC Test"); assertThat(text).hasNoSpans(); assertFractionalLinePosition(singleCue, 0.85f); @@ -186,13 +178,9 @@ public void parseUtf16BeNoStyl() throws Exception { byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), SAMPLE_UTF16_BE_NO_STYL); - CuesWithTiming cuesWithTiming = Iterables.getOnlyElement(parser.parse(bytes)); - Cue singleCue = Iterables.getOnlyElement(cuesWithTiming.cues); - SpannedString text = new SpannedString(singleCue.text); + Cue singleCue = parseToSingleCue(parser, bytes); + Spanned text = (Spanned) singleCue.text; - assertThat(cuesWithTiming.startTimeUs).isEqualTo(C.TIME_UNSET); - assertThat(cuesWithTiming.durationUs).isEqualTo(C.TIME_UNSET); - assertThat(cuesWithTiming.endTimeUs).isEqualTo(C.TIME_UNSET); assertThat(text.toString()).isEqualTo("你好"); assertThat(text).hasNoSpans(); assertFractionalLinePosition(singleCue, 0.85f); @@ -203,13 +191,8 @@ public void parseUtf16LeNoStyl() throws Exception { Tx3gParser parser = new Tx3gParser(ImmutableList.of()); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), SAMPLE_UTF16_LE_NO_STYL); - CuesWithTiming cuesWithTiming = Iterables.getOnlyElement(parser.parse(bytes)); - Cue singleCue = Iterables.getOnlyElement(cuesWithTiming.cues); - SpannedString text = new SpannedString(singleCue.text); - - assertThat(cuesWithTiming.startTimeUs).isEqualTo(C.TIME_UNSET); - assertThat(cuesWithTiming.durationUs).isEqualTo(C.TIME_UNSET); - assertThat(cuesWithTiming.endTimeUs).isEqualTo(C.TIME_UNSET); + Cue singleCue = parseToSingleCue(parser, bytes); + Spanned text = (Spanned) singleCue.text; assertThat(text.toString()).isEqualTo("你好"); assertThat(text).hasNoSpans(); @@ -223,13 +206,9 @@ public void parseWithMultipleStyl() throws Exception { TestUtil.getByteArray( ApplicationProvider.getApplicationContext(), SAMPLE_WITH_MULTIPLE_STYL); - CuesWithTiming cuesWithTiming = Iterables.getOnlyElement(parser.parse(bytes)); - Cue singleCue = Iterables.getOnlyElement(cuesWithTiming.cues); - SpannedString text = new SpannedString(singleCue.text); + Cue singleCue = parseToSingleCue(parser, bytes); + Spanned text = (Spanned) singleCue.text; - assertThat(cuesWithTiming.startTimeUs).isEqualTo(C.TIME_UNSET); - assertThat(cuesWithTiming.durationUs).isEqualTo(C.TIME_UNSET); - assertThat(cuesWithTiming.endTimeUs).isEqualTo(C.TIME_UNSET); assertThat(text.toString()).isEqualTo("Line 2\nLine 3"); assertThat(text).hasItalicSpanBetween(0, 5); assertThat(text).hasUnderlineSpanBetween(7, 12); @@ -245,13 +224,9 @@ public void parseWithOtherExtension() throws Exception { TestUtil.getByteArray( ApplicationProvider.getApplicationContext(), SAMPLE_WITH_OTHER_EXTENSION); - CuesWithTiming cuesWithTiming = Iterables.getOnlyElement(parser.parse(bytes)); - Cue singleCue = Iterables.getOnlyElement(cuesWithTiming.cues); - SpannedString text = new SpannedString(singleCue.text); + Cue singleCue = parseToSingleCue(parser, bytes); + Spanned text = (Spanned) singleCue.text; - assertThat(cuesWithTiming.startTimeUs).isEqualTo(C.TIME_UNSET); - assertThat(cuesWithTiming.durationUs).isEqualTo(C.TIME_UNSET); - assertThat(cuesWithTiming.endTimeUs).isEqualTo(C.TIME_UNSET); assertThat(text.toString()).isEqualTo("CC Test"); assertThat(text).hasBoldSpanBetween(0, 6); assertThat(text).hasForegroundColorSpanBetween(0, 6).withColor(Color.GREEN); @@ -266,13 +241,9 @@ public void initializationDecodeWithStyl() throws Exception { byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), SAMPLE_WITH_STYL); - CuesWithTiming cuesWithTiming = Iterables.getOnlyElement(parser.parse(bytes)); - Cue singleCue = Iterables.getOnlyElement(cuesWithTiming.cues); - SpannedString text = new SpannedString(singleCue.text); + Cue singleCue = parseToSingleCue(parser, bytes); + Spanned text = (Spanned) singleCue.text; - assertThat(cuesWithTiming.startTimeUs).isEqualTo(C.TIME_UNSET); - assertThat(cuesWithTiming.durationUs).isEqualTo(C.TIME_UNSET); - assertThat(cuesWithTiming.endTimeUs).isEqualTo(C.TIME_UNSET); assertThat(text.toString()).isEqualTo("CC Test"); assertThat(text).hasBoldItalicSpanBetween(0, 7); assertThat(text).hasUnderlineSpanBetween(0, 7); @@ -291,13 +262,9 @@ public void initializationDecodeWithTbox() throws Exception { byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), SAMPLE_WITH_TBOX); - CuesWithTiming cuesWithTiming = Iterables.getOnlyElement(parser.parse(bytes)); - Cue singleCue = Iterables.getOnlyElement(cuesWithTiming.cues); - SpannedString text = new SpannedString(singleCue.text); + Cue singleCue = parseToSingleCue(parser, bytes); + Spanned text = (Spanned) singleCue.text; - assertThat(cuesWithTiming.startTimeUs).isEqualTo(C.TIME_UNSET); - assertThat(cuesWithTiming.durationUs).isEqualTo(C.TIME_UNSET); - assertThat(cuesWithTiming.endTimeUs).isEqualTo(C.TIME_UNSET); assertThat(text.toString()).isEqualTo("CC Test"); assertThat(text).hasBoldItalicSpanBetween(0, 7); assertThat(text).hasUnderlineSpanBetween(0, 7); @@ -315,13 +282,9 @@ public void initializationAllDefaultsDecodeWithStyl() throws Exception { byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), SAMPLE_WITH_STYL); - CuesWithTiming cuesWithTiming = Iterables.getOnlyElement(parser.parse(bytes)); - Cue singleCue = Iterables.getOnlyElement(cuesWithTiming.cues); - SpannedString text = new SpannedString(singleCue.text); + Cue singleCue = parseToSingleCue(parser, bytes); + Spanned text = (Spanned) singleCue.text; - assertThat(cuesWithTiming.startTimeUs).isEqualTo(C.TIME_UNSET); - assertThat(cuesWithTiming.durationUs).isEqualTo(C.TIME_UNSET); - assertThat(cuesWithTiming.endTimeUs).isEqualTo(C.TIME_UNSET); assertThat(text.toString()).isEqualTo("CC Test"); assertThat(text).hasBoldItalicSpanBetween(0, 6); assertThat(text).hasUnderlineSpanBetween(0, 6); @@ -334,4 +297,16 @@ private static void assertFractionalLinePosition(Cue cue, float expectedFraction assertThat(cue.lineAnchor).isEqualTo(Cue.ANCHOR_TYPE_START); assertThat(cue.line).isWithin(1e-6f).of(expectedFraction); } + + private static Cue parseToSingleCue(SubtitleParser parser, byte[] data) { + List result = new ArrayList<>(/* initialCapacity= */ 1); + parser.parse(data, SubtitleParser.OutputOptions.allCues(), result::add); + CuesWithTiming cuesWithTiming = Iterables.getOnlyElement(result); + + assertThat(cuesWithTiming.startTimeUs).isEqualTo(C.TIME_UNSET); + assertThat(cuesWithTiming.durationUs).isEqualTo(C.TIME_UNSET); + assertThat(cuesWithTiming.endTimeUs).isEqualTo(C.TIME_UNSET); + + return Iterables.getOnlyElement(cuesWithTiming.cues); + } } diff --git a/libraries/extractor/src/test/java/androidx/media3/extractor/text/webvtt/Mp4WebvttParserTest.java b/libraries/extractor/src/test/java/androidx/media3/extractor/text/webvtt/Mp4WebvttParserTest.java index f21623d56c1..e36790fc257 100644 --- a/libraries/extractor/src/test/java/androidx/media3/extractor/text/webvtt/Mp4WebvttParserTest.java +++ b/libraries/extractor/src/test/java/androidx/media3/extractor/text/webvtt/Mp4WebvttParserTest.java @@ -22,9 +22,13 @@ import androidx.media3.common.C; import androidx.media3.common.text.Cue; import androidx.media3.extractor.text.CuesWithTiming; +import androidx.media3.extractor.text.SubtitleParser; +import androidx.media3.extractor.text.SubtitleParser.OutputOptions; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; import com.google.common.truth.Expect; +import java.util.ArrayList; import java.util.List; import org.junit.Rule; import org.junit.Test; @@ -171,7 +175,7 @@ public void cueReplacementBehaviorIsReplace() { @Test public void singleCueSample() { Mp4WebvttParser parser = new Mp4WebvttParser(); - List result = parser.parse(SINGLE_CUE_SAMPLE); + CuesWithTiming result = parseToSingleCuesWithTiming(parser, SINGLE_CUE_SAMPLE); // Line feed must be trimmed by the decoder Cue expectedCue = WebvttCueParser.newCueForText("Hello World"); assertMp4WebvttSubtitleEquals(result, expectedCue); @@ -180,7 +184,7 @@ public void singleCueSample() { @Test public void twoCuesSample() { Mp4WebvttParser parser = new Mp4WebvttParser(); - List result = parser.parse(DOUBLE_CUE_SAMPLE); + CuesWithTiming result = parseToSingleCuesWithTiming(parser, DOUBLE_CUE_SAMPLE); Cue firstExpectedCue = WebvttCueParser.newCueForText("Hello World"); Cue secondExpectedCue = WebvttCueParser.newCueForText("Bye Bye"); assertMp4WebvttSubtitleEquals(result, firstExpectedCue, secondExpectedCue); @@ -190,13 +194,12 @@ public void twoCuesSample() { public void noCueSample() { Mp4WebvttParser parser = new Mp4WebvttParser(); - List result = parser.parse(NO_CUE_SAMPLE); + CuesWithTiming result = parseToSingleCuesWithTiming(parser, NO_CUE_SAMPLE); - assertThat(result).hasSize(1); - assertThat(result.get(0).cues).isEmpty(); - assertThat(result.get(0).startTimeUs).isEqualTo(C.TIME_UNSET); - assertThat(result.get(0).durationUs).isEqualTo(C.TIME_UNSET); - assertThat(result.get(0).endTimeUs).isEqualTo(C.TIME_UNSET); + assertThat(result.cues).isEmpty(); + assertThat(result.startTimeUs).isEqualTo(C.TIME_UNSET); + assertThat(result.durationUs).isEqualTo(C.TIME_UNSET); + assertThat(result.endTimeUs).isEqualTo(C.TIME_UNSET); } // Negative tests. @@ -204,23 +207,29 @@ public void noCueSample() { @Test public void sampleWithIncompleteHeader() { Mp4WebvttParser parser = new Mp4WebvttParser(); - assertThrows(IllegalArgumentException.class, () -> parser.parse(INCOMPLETE_HEADER_SAMPLE)); + assertThrows( + IllegalArgumentException.class, + () -> parser.parse(INCOMPLETE_HEADER_SAMPLE, OutputOptions.allCues(), c -> {})); } // Util methods + private static CuesWithTiming parseToSingleCuesWithTiming(SubtitleParser parser, byte[] data) { + List result = new ArrayList<>(/* initialCapacity= */ 1); + parser.parse(data, OutputOptions.allCues(), result::add); + return Iterables.getOnlyElement(result); + } + /** * Asserts that the Subtitle's cues (which are all part of the event at t=0) are equal to the * expected Cues. * - * @param cuesWithTimings The list of {@link CuesWithTiming} to check. + * @param cuesWithTiming The {@link CuesWithTiming} to check. * @param expectedCues The expected {@link Cue}s. */ - private void assertMp4WebvttSubtitleEquals( - List cuesWithTimings, Cue... expectedCues) { - assertThat(cuesWithTimings).hasSize(1); - assertThat(cuesWithTimings.get(0).startTimeUs).isEqualTo(C.TIME_UNSET); - ImmutableList allCues = cuesWithTimings.get(0).cues; + private void assertMp4WebvttSubtitleEquals(CuesWithTiming cuesWithTiming, Cue... expectedCues) { + assertThat(cuesWithTiming.startTimeUs).isEqualTo(C.TIME_UNSET); + ImmutableList allCues = cuesWithTiming.cues; assertThat(allCues).hasSize(expectedCues.length); for (int i = 0; i < allCues.size(); i++) { assertCuesEqual(expectedCues[i], allCues.get(i));