Change LegacySubtitleUtil
handling of SubtitleParser.OutputOptions
If the `Subtitle` has 'active' cues at `OutputOptions.startTimeUs`, this change ensures these are emitted in a `CuesWithTiming` with `CuesWithTiming.startTimeUs = OutputOptions.startTimeUs`. If `OutputOptions.outputAllCues` is also set, then another `CuesWithTiming` is emitted at the end that covers the 'first part' of the active cues, and ends at `OutputOptions.startTimeUs`. As well as adding some more tests to `LegacySubtitleUtilWebvttTest`, this change also adds more tests for `TtmlParser` handling of `OutputOptions`, which transitively tests the behaviour of `LegacySubtitleUtil`. #minor-release PiperOrigin-RevId: 573151016
This commit is contained in:
parent
2421ba4d8f
commit
f9ece88a25
@ -15,12 +15,11 @@
|
||||
*/
|
||||
package androidx.media3.extractor.text;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.text.Cue;
|
||||
import androidx.media3.common.util.Consumer;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.extractor.text.SubtitleParser.OutputOptions;
|
||||
import java.util.List;
|
||||
|
||||
/** Utility methods for working with legacy {@link Subtitle} objects. */
|
||||
@ -37,23 +36,56 @@ public class LegacySubtitleUtil {
|
||||
* and the last event is an empty cue list.
|
||||
*/
|
||||
public static void toCuesWithTiming(
|
||||
Subtitle subtitle,
|
||||
SubtitleParser.OutputOptions outputOptions,
|
||||
Consumer<CuesWithTiming> output) {
|
||||
int startIndex =
|
||||
outputOptions.startTimeUs != C.TIME_UNSET
|
||||
? max(subtitle.getNextEventTimeIndex(outputOptions.startTimeUs) - 1, 0)
|
||||
: 0;
|
||||
Subtitle subtitle, OutputOptions outputOptions, Consumer<CuesWithTiming> output) {
|
||||
int startIndex = getStartIndex(subtitle, outputOptions);
|
||||
boolean startedInMiddleOfCue = false;
|
||||
if (outputOptions.startTimeUs != C.TIME_UNSET) {
|
||||
List<Cue> cuesAtStartTime = subtitle.getCues(outputOptions.startTimeUs);
|
||||
long firstEventTimeUs = subtitle.getEventTime(startIndex);
|
||||
if (!cuesAtStartTime.isEmpty()
|
||||
&& startIndex < subtitle.getEventTimeCount()
|
||||
&& outputOptions.startTimeUs < firstEventTimeUs) {
|
||||
output.accept(
|
||||
new CuesWithTiming(
|
||||
cuesAtStartTime,
|
||||
outputOptions.startTimeUs,
|
||||
firstEventTimeUs - outputOptions.startTimeUs));
|
||||
startedInMiddleOfCue = true;
|
||||
}
|
||||
}
|
||||
for (int i = startIndex; i < subtitle.getEventTimeCount(); i++) {
|
||||
outputSubtitleEvent(subtitle, i, output);
|
||||
}
|
||||
if (outputOptions.outputAllCues) {
|
||||
for (int i = 0; i < startIndex; i++) {
|
||||
int endIndex = startedInMiddleOfCue ? startIndex - 1 : startIndex;
|
||||
for (int i = 0; i < endIndex; i++) {
|
||||
outputSubtitleEvent(subtitle, i, output);
|
||||
}
|
||||
if (startedInMiddleOfCue) {
|
||||
output.accept(
|
||||
new CuesWithTiming(
|
||||
subtitle.getCues(outputOptions.startTimeUs),
|
||||
subtitle.getEventTime(endIndex),
|
||||
outputOptions.startTimeUs - subtitle.getEventTime(endIndex)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int getStartIndex(Subtitle subtitle, OutputOptions outputOptions) {
|
||||
if (outputOptions.startTimeUs == C.TIME_UNSET) {
|
||||
return 0;
|
||||
}
|
||||
int nextEventTimeIndex = subtitle.getNextEventTimeIndex(outputOptions.startTimeUs);
|
||||
if (nextEventTimeIndex == C.INDEX_UNSET) {
|
||||
return subtitle.getEventTimeCount();
|
||||
}
|
||||
if (nextEventTimeIndex > 0
|
||||
&& subtitle.getEventTime(nextEventTimeIndex - 1) == outputOptions.startTimeUs) {
|
||||
nextEventTimeIndex--;
|
||||
}
|
||||
return nextEventTimeIndex;
|
||||
}
|
||||
|
||||
private static void outputSubtitleEvent(
|
||||
Subtitle subtitle, int eventIndex, Consumer<CuesWithTiming> output) {
|
||||
long startTimeUs = subtitle.getEventTime(eventIndex);
|
||||
|
@ -81,7 +81,8 @@ public interface SubtitleParser {
|
||||
|
||||
/**
|
||||
* Cues after this time (inclusive) will be emitted first. Cues before this time might be
|
||||
* emitted later, depending on {@link #outputAllCues}.
|
||||
* emitted later, depending on {@link #outputAllCues}. Can be {@link C#TIME_UNSET} to emit all
|
||||
* cues.
|
||||
*/
|
||||
public final long startTimeUs;
|
||||
|
||||
|
@ -26,11 +26,16 @@ import androidx.media3.common.text.TextEmphasisSpan;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.common.util.ColorParser;
|
||||
import androidx.media3.extractor.text.CuesWithTiming;
|
||||
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 com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@ -38,6 +43,8 @@ import org.junit.runner.RunWith;
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public final class TtmlParserTest {
|
||||
|
||||
private static final String SIMPLE_TTML_FILE = "media/ttml/simple.xml";
|
||||
private static final String OVERLAPPING_TIMES_TTML_FILE = "media/ttml/overlapping_times.xml";
|
||||
private static final String INLINE_ATTRIBUTES_TTML_FILE =
|
||||
"media/ttml/inline_style_attributes.xml";
|
||||
private static final String INHERIT_STYLE_TTML_FILE = "media/ttml/inherit_style.xml";
|
||||
@ -69,6 +76,177 @@ public final class TtmlParserTest {
|
||||
private static final String TEXT_EMPHASIS_FILE = "media/ttml/text_emphasis.xml";
|
||||
private static final String SHEAR_FILE = "media/ttml/shear.xml";
|
||||
|
||||
@Test
|
||||
public void simple_allCues() throws Exception {
|
||||
ImmutableList<CuesWithTiming> allCues = getAllCues(SIMPLE_TTML_FILE);
|
||||
|
||||
assertThat(allCues).hasSize(3);
|
||||
|
||||
CuesWithTiming firstCue = allCues.get(0);
|
||||
assertThat(firstCue.startTimeUs).isEqualTo(10_000_000);
|
||||
assertThat(firstCue.durationUs).isEqualTo(8_000_000);
|
||||
assertThat(firstCue.endTimeUs).isEqualTo(18_000_000);
|
||||
assertThat(Lists.transform(firstCue.cues, c -> c.text.toString())).containsExactly("cue 1");
|
||||
|
||||
CuesWithTiming secondCue = allCues.get(1);
|
||||
assertThat(secondCue.startTimeUs).isEqualTo(20_000_000);
|
||||
assertThat(secondCue.durationUs).isEqualTo(8_000_000);
|
||||
assertThat(secondCue.endTimeUs).isEqualTo(28_000_000);
|
||||
assertThat(Lists.transform(secondCue.cues, c -> c.text.toString())).containsExactly("cue 2");
|
||||
|
||||
CuesWithTiming thirdCue = allCues.get(2);
|
||||
assertThat(thirdCue.startTimeUs).isEqualTo(30_000_000);
|
||||
assertThat(thirdCue.durationUs).isEqualTo(8_000_000);
|
||||
assertThat(thirdCue.endTimeUs).isEqualTo(38_000_000);
|
||||
assertThat(Lists.transform(thirdCue.cues, c -> c.text.toString())).containsExactly("cue 3");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simple_onlyCuesAfterTime() throws Exception {
|
||||
TtmlParser parser = new TtmlParser();
|
||||
byte[] bytes =
|
||||
TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), SIMPLE_TTML_FILE);
|
||||
|
||||
List<CuesWithTiming> cues = new ArrayList<>();
|
||||
parser.parse(bytes, OutputOptions.onlyCuesAfter(/* startTimeUs= */ 11_000_000), cues::add);
|
||||
|
||||
assertThat(cues).hasSize(3);
|
||||
|
||||
CuesWithTiming firstCue = cues.get(0);
|
||||
// First cue is truncated to OutputOptions.startTimeUs
|
||||
assertThat(firstCue.startTimeUs).isEqualTo(11_000_000);
|
||||
assertThat(Lists.transform(firstCue.cues, c -> c.text.toString())).containsExactly("cue 1");
|
||||
assertThat(getOnlyCueTextAtIndex(cues, 1).toString()).isEqualTo("cue 2");
|
||||
assertThat(getOnlyCueTextAtIndex(cues, 2).toString()).isEqualTo("cue 3");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simple_cuesAfterTimeThenCuesBefore() throws Exception {
|
||||
TtmlParser parser = new TtmlParser();
|
||||
byte[] bytes =
|
||||
TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), SIMPLE_TTML_FILE);
|
||||
|
||||
List<CuesWithTiming> cues = new ArrayList<>();
|
||||
parser.parse(bytes, OutputOptions.cuesAfterThenRemainingCuesBefore(11_000_000), cues::add);
|
||||
|
||||
assertThat(cues).hasSize(4);
|
||||
|
||||
CuesWithTiming firstCue = cues.get(0);
|
||||
// First cue is truncated to OutputOptions.startTimeUs
|
||||
assertThat(firstCue.startTimeUs).isEqualTo(11_000_000);
|
||||
assertThat(Lists.transform(firstCue.cues, c -> c.text.toString())).containsExactly("cue 1");
|
||||
|
||||
assertThat(getOnlyCueTextAtIndex(cues, 1).toString()).isEqualTo("cue 2");
|
||||
assertThat(getOnlyCueTextAtIndex(cues, 2).toString()).isEqualTo("cue 3");
|
||||
|
||||
CuesWithTiming fourthCue = cues.get(3);
|
||||
// Last cue is the part of firstCue before OutputOptions.startTimeUs
|
||||
assertThat(fourthCue.startTimeUs).isEqualTo(10_000_000);
|
||||
assertThat(fourthCue.endTimeUs).isEqualTo(11_000_000);
|
||||
assertThat(Lists.transform(fourthCue.cues, c -> c.text.toString())).containsExactly("cue 1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void overlappingTimes_allCues() throws Exception {
|
||||
ImmutableList<CuesWithTiming> allCues = getAllCues(OVERLAPPING_TIMES_TTML_FILE);
|
||||
|
||||
assertThat(allCues).hasSize(5);
|
||||
|
||||
CuesWithTiming firstCue = allCues.get(0);
|
||||
assertThat(firstCue.startTimeUs).isEqualTo(10_000_000);
|
||||
assertThat(firstCue.durationUs).isEqualTo(5_000_000);
|
||||
assertThat(firstCue.endTimeUs).isEqualTo(15_000_000);
|
||||
assertThat(Lists.transform(firstCue.cues, c -> c.text.toString())).containsExactly("cue 1");
|
||||
|
||||
CuesWithTiming secondCue = allCues.get(1);
|
||||
assertThat(secondCue.startTimeUs).isEqualTo(15_000_000);
|
||||
assertThat(secondCue.durationUs).isEqualTo(1_000_000);
|
||||
assertThat(secondCue.endTimeUs).isEqualTo(16_000_000);
|
||||
assertThat(Lists.transform(secondCue.cues, c -> c.text.toString()))
|
||||
.containsExactly("cue 1\ncue 2: nested inside cue 1");
|
||||
|
||||
CuesWithTiming thirdCue = allCues.get(2);
|
||||
assertThat(thirdCue.startTimeUs).isEqualTo(16_000_000);
|
||||
assertThat(thirdCue.durationUs).isEqualTo(4_000_000);
|
||||
assertThat(thirdCue.endTimeUs).isEqualTo(20_000_000);
|
||||
assertThat(Lists.transform(thirdCue.cues, c -> c.text.toString()))
|
||||
.containsExactly("cue 1\ncue 2: nested inside cue 1\ncue 3: overlaps with cue 2");
|
||||
|
||||
CuesWithTiming fourthCue = allCues.get(3);
|
||||
assertThat(fourthCue.startTimeUs).isEqualTo(20_000_000);
|
||||
assertThat(fourthCue.durationUs).isEqualTo(5_000_000);
|
||||
assertThat(fourthCue.endTimeUs).isEqualTo(25_000_000);
|
||||
assertThat(Lists.transform(fourthCue.cues, c -> c.text.toString()))
|
||||
.containsExactly("cue 1\ncue 3: overlaps with cue 2");
|
||||
|
||||
CuesWithTiming fifthCue = allCues.get(4);
|
||||
assertThat(fifthCue.startTimeUs).isEqualTo(25_000_000);
|
||||
assertThat(fifthCue.durationUs).isEqualTo(3_000_000);
|
||||
assertThat(fifthCue.endTimeUs).isEqualTo(28_000_000);
|
||||
assertThat(Lists.transform(fifthCue.cues, c -> c.text.toString()))
|
||||
.containsExactly("cue 3: overlaps with cue 2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void overlappingTimes_onlyCuesAfterTime() throws Exception {
|
||||
TtmlParser parser = new TtmlParser();
|
||||
byte[] bytes =
|
||||
TestUtil.getByteArray(
|
||||
ApplicationProvider.getApplicationContext(), OVERLAPPING_TIMES_TTML_FILE);
|
||||
|
||||
List<CuesWithTiming> cues = new ArrayList<>();
|
||||
parser.parse(bytes, OutputOptions.onlyCuesAfter(/* startTimeUs= */ 11_000_000), cues::add);
|
||||
|
||||
assertThat(cues).hasSize(5);
|
||||
|
||||
CuesWithTiming firstCue = cues.get(0);
|
||||
// First cue is truncated to OutputOptions.startTimeUs
|
||||
assertThat(firstCue.startTimeUs).isEqualTo(11_000_000);
|
||||
assertThat(Lists.transform(firstCue.cues, c -> c.text.toString())).containsExactly("cue 1");
|
||||
assertThat(getOnlyCueTextAtIndex(cues, 1).toString())
|
||||
.isEqualTo("cue 1\ncue 2: nested inside cue 1");
|
||||
assertThat(getOnlyCueTextAtIndex(cues, 2).toString())
|
||||
.isEqualTo("cue 1\ncue 2: nested inside cue 1\ncue 3: overlaps with cue 2");
|
||||
assertThat(getOnlyCueTextAtIndex(cues, 3).toString())
|
||||
.isEqualTo("cue 1\ncue 3: overlaps with cue 2");
|
||||
assertThat(getOnlyCueTextAtIndex(cues, 4).toString()).isEqualTo("cue 3: overlaps with cue 2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void overlappingTimes_cuesAfterTimeThenCuesBefore() throws Exception {
|
||||
TtmlParser parser = new TtmlParser();
|
||||
byte[] bytes =
|
||||
TestUtil.getByteArray(
|
||||
ApplicationProvider.getApplicationContext(), OVERLAPPING_TIMES_TTML_FILE);
|
||||
|
||||
List<CuesWithTiming> cues = new ArrayList<>();
|
||||
parser.parse(
|
||||
bytes,
|
||||
OutputOptions.cuesAfterThenRemainingCuesBefore(/* startTimeUs= */ 11_000_000),
|
||||
cues::add);
|
||||
|
||||
assertThat(cues).hasSize(6);
|
||||
|
||||
CuesWithTiming firstCue = cues.get(0);
|
||||
// First cue is truncated to OutputOptions.startTimeUs
|
||||
assertThat(firstCue.startTimeUs).isEqualTo(11_000_000);
|
||||
assertThat(Lists.transform(firstCue.cues, c -> c.text.toString())).containsExactly("cue 1");
|
||||
|
||||
assertThat(getOnlyCueTextAtIndex(cues, 1).toString())
|
||||
.isEqualTo("cue 1\ncue 2: nested inside cue 1");
|
||||
assertThat(getOnlyCueTextAtIndex(cues, 2).toString())
|
||||
.isEqualTo("cue 1\ncue 2: nested inside cue 1\ncue 3: overlaps with cue 2");
|
||||
assertThat(getOnlyCueTextAtIndex(cues, 3).toString())
|
||||
.isEqualTo("cue 1\ncue 3: overlaps with cue 2");
|
||||
assertThat(getOnlyCueTextAtIndex(cues, 4).toString()).isEqualTo("cue 3: overlaps with cue 2");
|
||||
|
||||
CuesWithTiming sixthCue = cues.get(5);
|
||||
// Last cue is truncated to end at OutputOptions.startTimeUs
|
||||
assertThat(sixthCue.startTimeUs).isEqualTo(10_000_000);
|
||||
assertThat(sixthCue.endTimeUs).isEqualTo(11_000_000);
|
||||
assertThat(Lists.transform(sixthCue.cues, c -> c.text.toString())).containsExactly("cue 1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inlineAttributes() throws Exception {
|
||||
ImmutableList<CuesWithTiming> allCues = getAllCues(INLINE_ATTRIBUTES_TTML_FILE);
|
||||
@ -904,21 +1082,23 @@ public final class TtmlParserTest {
|
||||
assertThat(eighthCue.shearDegrees).isWithin(0.01f).of(90f);
|
||||
}
|
||||
|
||||
private static Spanned getOnlyCueTextAtIndex(ImmutableList<CuesWithTiming> allCues, int index) {
|
||||
private static Spanned getOnlyCueTextAtIndex(List<CuesWithTiming> allCues, int index) {
|
||||
Cue cue = getOnlyCueAtIndex(allCues, index);
|
||||
assertThat(cue.text).isInstanceOf(Spanned.class);
|
||||
return (Spanned) Assertions.checkNotNull(cue.text);
|
||||
}
|
||||
|
||||
private static Cue getOnlyCueAtIndex(ImmutableList<CuesWithTiming> allCues, int index) {
|
||||
private static Cue getOnlyCueAtIndex(List<CuesWithTiming> allCues, int index) {
|
||||
ImmutableList<Cue> cues = allCues.get(index).cues;
|
||||
assertThat(cues).hasSize(1);
|
||||
return cues.get(0);
|
||||
}
|
||||
|
||||
private static ImmutableList<CuesWithTiming> getAllCues(String file) throws Exception {
|
||||
private static ImmutableList<CuesWithTiming> getAllCues(String file) throws IOException {
|
||||
TtmlParser ttmlParser = new TtmlParser();
|
||||
byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), file);
|
||||
return ttmlParser.parse(bytes, 0, bytes.length);
|
||||
ImmutableList.Builder<CuesWithTiming> allCues = ImmutableList.builder();
|
||||
ttmlParser.parse(bytes, OutputOptions.allCues(), allCues::add);
|
||||
return allCues.build();
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ public class LegacySubtitleUtilWebvttTest {
|
||||
private static final String FIRST_SUBTITLE_STRING = "This is the first subtitle.";
|
||||
private static final String SECOND_SUBTITLE_STRING = "This is the second subtitle.";
|
||||
|
||||
private static final WebvttSubtitle simpleSubtitle =
|
||||
private static final WebvttSubtitle SIMPLE_SUBTITLE =
|
||||
new WebvttSubtitle(
|
||||
Arrays.asList(
|
||||
new WebvttCueInfo(
|
||||
@ -52,7 +52,7 @@ public class LegacySubtitleUtilWebvttTest {
|
||||
/* startTimeUs= */ 3_000_000,
|
||||
/* endTimeUs= */ 4_000_000)));
|
||||
|
||||
private static final WebvttSubtitle overlappingSubtitle =
|
||||
private static final WebvttSubtitle OVERLAPPING_SUBTITLE =
|
||||
new WebvttSubtitle(
|
||||
Arrays.asList(
|
||||
new WebvttCueInfo(
|
||||
@ -67,7 +67,7 @@ public class LegacySubtitleUtilWebvttTest {
|
||||
@Test
|
||||
public void toCuesWithTiming_allCues_simpleSubtitle() {
|
||||
ImmutableList<CuesWithTiming> cuesWithTimingsList =
|
||||
toCuesWithTimingList(simpleSubtitle, SubtitleParser.OutputOptions.allCues());
|
||||
toCuesWithTimingList(SIMPLE_SUBTITLE, SubtitleParser.OutputOptions.allCues());
|
||||
|
||||
assertThat(cuesWithTimingsList).hasSize(2);
|
||||
assertThat(cuesWithTimingsList.get(0).startTimeUs).isEqualTo(1_000_000);
|
||||
@ -85,7 +85,7 @@ public class LegacySubtitleUtilWebvttTest {
|
||||
@Test
|
||||
public void toCuesWithTiming_allCues_overlappingSubtitle() {
|
||||
ImmutableList<CuesWithTiming> cuesWithTimingsList =
|
||||
toCuesWithTimingList(overlappingSubtitle, SubtitleParser.OutputOptions.allCues());
|
||||
toCuesWithTimingList(OVERLAPPING_SUBTITLE, SubtitleParser.OutputOptions.allCues());
|
||||
|
||||
assertThat(cuesWithTimingsList).hasSize(3);
|
||||
assertThat(cuesWithTimingsList.get(0).startTimeUs).isEqualTo(1_000_000);
|
||||
@ -109,7 +109,8 @@ public class LegacySubtitleUtilWebvttTest {
|
||||
@Test
|
||||
public void toCuesWithTiming_onlyEmitCuesAfterStartTime_startBetweenCues_simpleSubtitle() {
|
||||
ImmutableList<CuesWithTiming> cuesWithTimingsList =
|
||||
toCuesWithTimingList(simpleSubtitle, SubtitleParser.OutputOptions.onlyCuesAfter(2_500_000));
|
||||
toCuesWithTimingList(
|
||||
SIMPLE_SUBTITLE, SubtitleParser.OutputOptions.onlyCuesAfter(2_500_000));
|
||||
|
||||
assertThat(cuesWithTimingsList).hasSize(1);
|
||||
assertThat(cuesWithTimingsList.get(0).startTimeUs).isEqualTo(3_000_000);
|
||||
@ -122,7 +123,8 @@ public class LegacySubtitleUtilWebvttTest {
|
||||
@Test
|
||||
public void toCuesWithTiming_onlyEmitCuesAfterStartTime_startAtCueEnd_simpleSubtitle() {
|
||||
ImmutableList<CuesWithTiming> cuesWithTimingsList =
|
||||
toCuesWithTimingList(simpleSubtitle, SubtitleParser.OutputOptions.onlyCuesAfter(2_000_000));
|
||||
toCuesWithTimingList(
|
||||
SIMPLE_SUBTITLE, SubtitleParser.OutputOptions.onlyCuesAfter(2_000_000));
|
||||
|
||||
assertThat(cuesWithTimingsList).hasSize(1);
|
||||
assertThat(cuesWithTimingsList.get(0).startTimeUs).isEqualTo(3_000_000);
|
||||
@ -135,7 +137,8 @@ public class LegacySubtitleUtilWebvttTest {
|
||||
@Test
|
||||
public void toCuesWithTiming_onlyEmitCuesAfterStartTime_startAtCueStart_simpleSubtitle() {
|
||||
ImmutableList<CuesWithTiming> cuesWithTimingsList =
|
||||
toCuesWithTimingList(simpleSubtitle, SubtitleParser.OutputOptions.onlyCuesAfter(3_000_000));
|
||||
toCuesWithTimingList(
|
||||
SIMPLE_SUBTITLE, SubtitleParser.OutputOptions.onlyCuesAfter(3_000_000));
|
||||
|
||||
assertThat(cuesWithTimingsList).hasSize(1);
|
||||
assertThat(cuesWithTimingsList.get(0).startTimeUs).isEqualTo(3_000_000);
|
||||
@ -148,11 +151,13 @@ public class LegacySubtitleUtilWebvttTest {
|
||||
@Test
|
||||
public void toCuesWithTiming_onlyEmitCuesAfterStartTime_startInMiddleOfCue_simpleSubtitle() {
|
||||
ImmutableList<CuesWithTiming> cuesWithTimingsList =
|
||||
toCuesWithTimingList(simpleSubtitle, SubtitleParser.OutputOptions.onlyCuesAfter(1_500_000));
|
||||
toCuesWithTimingList(
|
||||
SIMPLE_SUBTITLE, SubtitleParser.OutputOptions.onlyCuesAfter(1_500_000));
|
||||
|
||||
assertThat(cuesWithTimingsList).hasSize(2);
|
||||
assertThat(cuesWithTimingsList.get(0).startTimeUs).isEqualTo(1_000_000);
|
||||
assertThat(cuesWithTimingsList.get(0).durationUs).isEqualTo(1_000_000);
|
||||
// First cue is truncated to start at OutputOptions.startTimeUs
|
||||
assertThat(cuesWithTimingsList.get(0).startTimeUs).isEqualTo(1_500_000);
|
||||
assertThat(cuesWithTimingsList.get(0).durationUs).isEqualTo(500_000);
|
||||
assertThat(cuesWithTimingsList.get(0).endTimeUs).isEqualTo(2_000_000);
|
||||
assertThat(Lists.transform(cuesWithTimingsList.get(0).cues, c -> c.text))
|
||||
.containsExactly(FIRST_SUBTITLE_STRING);
|
||||
@ -167,11 +172,12 @@ public class LegacySubtitleUtilWebvttTest {
|
||||
public void toCuesWithTiming_onlyEmitCuesAfterStartTime_overlappingSubtitle() {
|
||||
ImmutableList<CuesWithTiming> cuesWithTimingsList =
|
||||
toCuesWithTimingList(
|
||||
overlappingSubtitle, SubtitleParser.OutputOptions.onlyCuesAfter(2_500_000));
|
||||
OVERLAPPING_SUBTITLE, SubtitleParser.OutputOptions.onlyCuesAfter(2_500_000));
|
||||
|
||||
assertThat(cuesWithTimingsList).hasSize(2);
|
||||
assertThat(cuesWithTimingsList.get(0).startTimeUs).isEqualTo(2_000_000);
|
||||
assertThat(cuesWithTimingsList.get(0).durationUs).isEqualTo(1_000_000);
|
||||
// First event is truncated to start at OutputOptions.startTimeUs.
|
||||
assertThat(cuesWithTimingsList.get(0).startTimeUs).isEqualTo(2_500_000);
|
||||
assertThat(cuesWithTimingsList.get(0).durationUs).isEqualTo(500_000);
|
||||
assertThat(cuesWithTimingsList.get(0).endTimeUs).isEqualTo(3_000_000);
|
||||
assertThat(Lists.transform(cuesWithTimingsList.get(0).cues, c -> c.text))
|
||||
.containsExactly(FIRST_SUBTITLE_STRING, SECOND_SUBTITLE_STRING)
|
||||
@ -184,11 +190,61 @@ public class LegacySubtitleUtilWebvttTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toCuesWithTiming_emitCuesAfterStartTimeThenThoseBefore_simpleSubtitle() {
|
||||
public void
|
||||
toCuesWithTiming_emitCuesAfterStartTimeThenThoseBefore_startAtStartOfCue_simpleSubtitle() {
|
||||
ImmutableList<CuesWithTiming> cuesWithTimingsList =
|
||||
toCuesWithTimingList(
|
||||
simpleSubtitle,
|
||||
SubtitleParser.OutputOptions.cuesAfterThenRemainingCuesBefore(2_500_000));
|
||||
SIMPLE_SUBTITLE,
|
||||
SubtitleParser.OutputOptions.cuesAfterThenRemainingCuesBefore(3_000_000));
|
||||
|
||||
assertThat(cuesWithTimingsList).hasSize(2);
|
||||
assertThat(cuesWithTimingsList.get(0).startTimeUs).isEqualTo(3_000_000);
|
||||
assertThat(cuesWithTimingsList.get(0).durationUs).isEqualTo(1_000_000);
|
||||
assertThat(cuesWithTimingsList.get(0).endTimeUs).isEqualTo(4_000_000);
|
||||
assertThat(Lists.transform(cuesWithTimingsList.get(0).cues, c -> c.text))
|
||||
.containsExactly(SECOND_SUBTITLE_STRING);
|
||||
assertThat(cuesWithTimingsList.get(1).startTimeUs).isEqualTo(1_000_000);
|
||||
assertThat(cuesWithTimingsList.get(1).durationUs).isEqualTo(1_000_000);
|
||||
assertThat(cuesWithTimingsList.get(1).endTimeUs).isEqualTo(2_000_000);
|
||||
assertThat(Lists.transform(cuesWithTimingsList.get(1).cues, c -> c.text))
|
||||
.containsExactly(FIRST_SUBTITLE_STRING);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
toCuesWithTiming_emitCuesAfterStartTimeThenThoseBefore_startInMiddleOfCue_simpleSubtitle() {
|
||||
ImmutableList<CuesWithTiming> cuesWithTimingsList =
|
||||
toCuesWithTimingList(
|
||||
SIMPLE_SUBTITLE,
|
||||
SubtitleParser.OutputOptions.cuesAfterThenRemainingCuesBefore(1_500_000));
|
||||
|
||||
assertThat(cuesWithTimingsList).hasSize(3);
|
||||
// First event is truncated to start at OutputOptions.startTimeUs.
|
||||
assertThat(cuesWithTimingsList.get(0).startTimeUs).isEqualTo(1_500_000);
|
||||
assertThat(cuesWithTimingsList.get(0).durationUs).isEqualTo(500_000);
|
||||
assertThat(cuesWithTimingsList.get(0).endTimeUs).isEqualTo(2_000_000);
|
||||
assertThat(Lists.transform(cuesWithTimingsList.get(0).cues, c -> c.text))
|
||||
.containsExactly(FIRST_SUBTITLE_STRING);
|
||||
assertThat(cuesWithTimingsList.get(1).startTimeUs).isEqualTo(3_000_000);
|
||||
assertThat(cuesWithTimingsList.get(1).durationUs).isEqualTo(1_000_000);
|
||||
assertThat(cuesWithTimingsList.get(1).endTimeUs).isEqualTo(4_000_000);
|
||||
assertThat(Lists.transform(cuesWithTimingsList.get(1).cues, c -> c.text))
|
||||
.containsExactly(SECOND_SUBTITLE_STRING);
|
||||
// Final event is the part of the 'first event' that is before OutputOptions.startTimeUs
|
||||
assertThat(cuesWithTimingsList.get(2).startTimeUs).isEqualTo(1_000_000);
|
||||
assertThat(cuesWithTimingsList.get(2).durationUs).isEqualTo(500_000);
|
||||
assertThat(cuesWithTimingsList.get(2).endTimeUs).isEqualTo(1_500_000);
|
||||
assertThat(Lists.transform(cuesWithTimingsList.get(2).cues, c -> c.text))
|
||||
.containsExactly(FIRST_SUBTITLE_STRING);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
toCuesWithTiming_emitCuesAfterStartTimeThenThoseBefore_startAtEndOfCue_simpleSubtitle() {
|
||||
ImmutableList<CuesWithTiming> cuesWithTimingsList =
|
||||
toCuesWithTimingList(
|
||||
SIMPLE_SUBTITLE,
|
||||
SubtitleParser.OutputOptions.cuesAfterThenRemainingCuesBefore(2_000_000));
|
||||
|
||||
assertThat(cuesWithTimingsList).hasSize(2);
|
||||
assertThat(cuesWithTimingsList.get(0).startTimeUs).isEqualTo(3_000_000);
|
||||
@ -207,12 +263,13 @@ public class LegacySubtitleUtilWebvttTest {
|
||||
public void toCuesWithTiming_emitCuesAfterStartTimeThenThoseBefore_overlappingSubtitle() {
|
||||
ImmutableList<CuesWithTiming> cuesWithTimingsList =
|
||||
toCuesWithTimingList(
|
||||
overlappingSubtitle,
|
||||
OVERLAPPING_SUBTITLE,
|
||||
SubtitleParser.OutputOptions.cuesAfterThenRemainingCuesBefore(2_500_000));
|
||||
|
||||
assertThat(cuesWithTimingsList).hasSize(3);
|
||||
assertThat(cuesWithTimingsList.get(0).startTimeUs).isEqualTo(2_000_000);
|
||||
assertThat(cuesWithTimingsList.get(0).durationUs).isEqualTo(1_000_000);
|
||||
assertThat(cuesWithTimingsList).hasSize(4);
|
||||
// First event is truncated to start at OutputOptions.startTimeUs.
|
||||
assertThat(cuesWithTimingsList.get(0).startTimeUs).isEqualTo(2_500_000);
|
||||
assertThat(cuesWithTimingsList.get(0).durationUs).isEqualTo(500_000);
|
||||
assertThat(cuesWithTimingsList.get(0).endTimeUs).isEqualTo(3_000_000);
|
||||
assertThat(Lists.transform(cuesWithTimingsList.get(0).cues, c -> c.text))
|
||||
.containsExactly(FIRST_SUBTITLE_STRING, SECOND_SUBTITLE_STRING)
|
||||
@ -227,6 +284,13 @@ public class LegacySubtitleUtilWebvttTest {
|
||||
assertThat(cuesWithTimingsList.get(2).endTimeUs).isEqualTo(2_000_000);
|
||||
assertThat(Lists.transform(cuesWithTimingsList.get(2).cues, c -> c.text))
|
||||
.containsExactly(FIRST_SUBTITLE_STRING);
|
||||
// Final event is the part of the 'first event' that is before OutputOptions.startTimeUs
|
||||
assertThat(cuesWithTimingsList.get(3).startTimeUs).isEqualTo(2_000_000);
|
||||
assertThat(cuesWithTimingsList.get(3).durationUs).isEqualTo(500_000);
|
||||
assertThat(cuesWithTimingsList.get(3).endTimeUs).isEqualTo(2_500_000);
|
||||
assertThat(Lists.transform(cuesWithTimingsList.get(3).cues, c -> c.text))
|
||||
.containsExactly(FIRST_SUBTITLE_STRING, SECOND_SUBTITLE_STRING)
|
||||
.inOrder();
|
||||
}
|
||||
|
||||
private static ImmutableList<CuesWithTiming> toCuesWithTimingList(
|
||||
|
@ -0,0 +1,31 @@
|
||||
<!--
|
||||
~ Copyright 2023 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<tt xmlns:ttm="http://www.w3.org/2006/10/ttaf1#metadata"
|
||||
xmlns:ttp="http://www.w3.org/2006/10/ttaf1#parameter"
|
||||
xmlns:tts="http://www.w3.org/2006/10/ttaf1#style"
|
||||
xmlns="http://www.w3.org/ns/ttml">
|
||||
<body>
|
||||
<div>
|
||||
<p begin="10s" end="25s">cue 1</p>
|
||||
</div>
|
||||
<div>
|
||||
<p begin="15s" end="20s">cue 2: nested inside cue 1</p>
|
||||
</div>
|
||||
<div>
|
||||
<p begin="16s" end="28s">cue 3: overlaps with cue 2</p>
|
||||
</div>
|
||||
</body>
|
||||
</tt>
|
31
libraries/test_data/src/test/assets/media/ttml/simple.xml
Normal file
31
libraries/test_data/src/test/assets/media/ttml/simple.xml
Normal file
@ -0,0 +1,31 @@
|
||||
<!--
|
||||
~ Copyright 2023 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<tt xmlns:ttm="http://www.w3.org/2006/10/ttaf1#metadata"
|
||||
xmlns:ttp="http://www.w3.org/2006/10/ttaf1#parameter"
|
||||
xmlns:tts="http://www.w3.org/2006/10/ttaf1#style"
|
||||
xmlns="http://www.w3.org/ns/ttml">
|
||||
<body>
|
||||
<div>
|
||||
<p begin="10s" end="18s">cue 1</p>
|
||||
</div>
|
||||
<div>
|
||||
<p begin="20s" end="28s">cue 2</p>
|
||||
</div>
|
||||
<div>
|
||||
<p begin="30s" end="38s">cue 3</p>
|
||||
</div>
|
||||
</body>
|
||||
</tt>
|
Loading…
x
Reference in New Issue
Block a user