Update WebvttParser
to implement new partial-output method
This requires adapting the 'to `CuesWithTiming` list' logic to work with
the new partial-output API, and that needs a private method so it's no
longer a good fit for a default method on `Subtitle` - hence moving it
to a new utility class.
Also update the implementation to never return `UNSET` duration (this is
an equivalent change to the `SsaParser` change in 9631923440
).
PiperOrigin-RevId: 566598094
This commit is contained in:
parent
24e700c216
commit
92a3f3a8cd
@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
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 java.util.List;
|
||||||
|
|
||||||
|
/** Utility methods for working with legacy {@link Subtitle} objects. */
|
||||||
|
@UnstableApi
|
||||||
|
public class LegacySubtitleUtil {
|
||||||
|
|
||||||
|
private LegacySubtitleUtil() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a {@link Subtitle} to a list of {@link CuesWithTiming} representing it, emitted to
|
||||||
|
* {@code output}.
|
||||||
|
*
|
||||||
|
* <p>This may only be called with {@link Subtitle} instances where the first event is non-empty
|
||||||
|
* 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;
|
||||||
|
for (int i = startIndex; i < subtitle.getEventTimeCount(); i++) {
|
||||||
|
outputSubtitleEvent(subtitle, i, output);
|
||||||
|
}
|
||||||
|
if (outputOptions.outputAllCues) {
|
||||||
|
for (int i = 0; i < startIndex; i++) {
|
||||||
|
outputSubtitleEvent(subtitle, i, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void outputSubtitleEvent(
|
||||||
|
Subtitle subtitle, int eventIndex, Consumer<CuesWithTiming> output) {
|
||||||
|
long startTimeUs = subtitle.getEventTime(eventIndex);
|
||||||
|
List<Cue> cuesForThisStartTime = subtitle.getCues(startTimeUs);
|
||||||
|
if (cuesForThisStartTime.isEmpty()) {
|
||||||
|
// An empty cue list has already been implicitly encoded in the duration of the previous
|
||||||
|
// sample.
|
||||||
|
return;
|
||||||
|
} else if (eventIndex == subtitle.getEventTimeCount() - 1) {
|
||||||
|
// The last cue list must be empty
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
// It's safe to inspect element i+1, because we already exited the loop above if
|
||||||
|
// i == getEventTimeCount() - 1.
|
||||||
|
long durationUs = subtitle.getEventTime(eventIndex + 1) - subtitle.getEventTime(eventIndex);
|
||||||
|
output.accept(new CuesWithTiming(cuesForThisStartTime, startTimeUs, durationUs));
|
||||||
|
}
|
||||||
|
}
|
@ -18,7 +18,6 @@ package androidx.media3.extractor.text;
|
|||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.text.Cue;
|
import androidx.media3.common.text.Cue;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/** A subtitle consisting of timed {@link Cue}s. */
|
/** A subtitle consisting of timed {@link Cue}s. */
|
||||||
@ -57,24 +56,4 @@ public interface Subtitle {
|
|||||||
* @return A list of cues that should be displayed, possibly empty.
|
* @return A list of cues that should be displayed, possibly empty.
|
||||||
*/
|
*/
|
||||||
List<Cue> getCues(long timeUs);
|
List<Cue> getCues(long timeUs);
|
||||||
|
|
||||||
/** Converts the current instance to a list of {@link CuesWithTiming} representing it. */
|
|
||||||
// TODO(b/181312195): Remove this when TtmlDecoder has been migrated to TtmlParser (and in-line it
|
|
||||||
// in DelegatingSubtitleDecoderTtmlParserTest).
|
|
||||||
default ImmutableList<CuesWithTiming> toCuesWithTimingList() {
|
|
||||||
ImmutableList.Builder<CuesWithTiming> allCues = ImmutableList.builder();
|
|
||||||
for (int i = 0; i < getEventTimeCount(); i++) {
|
|
||||||
long startTimeUs = getEventTime(i);
|
|
||||||
List<Cue> cuesForThisStartTime = getCues(startTimeUs);
|
|
||||||
if (cuesForThisStartTime.isEmpty() && i != 0) {
|
|
||||||
// An empty cue list has already been implicitly encoded in the duration of the previous
|
|
||||||
// sample (unless there was no previous sample).
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
long durationUs =
|
|
||||||
i < getEventTimeCount() - 1 ? getEventTime(i + 1) - getEventTime(i) : C.TIME_UNSET;
|
|
||||||
allCues.add(new CuesWithTiming(cuesForThisStartTime, startTimeUs, durationUs));
|
|
||||||
}
|
|
||||||
return allCues.build();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -18,9 +18,11 @@ package androidx.media3.extractor.text.webvtt;
|
|||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.ParserException;
|
import androidx.media3.common.ParserException;
|
||||||
|
import androidx.media3.common.util.Consumer;
|
||||||
import androidx.media3.common.util.ParsableByteArray;
|
import androidx.media3.common.util.ParsableByteArray;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.extractor.text.CuesWithTiming;
|
import androidx.media3.extractor.text.CuesWithTiming;
|
||||||
|
import androidx.media3.extractor.text.LegacySubtitleUtil;
|
||||||
import androidx.media3.extractor.text.SubtitleParser;
|
import androidx.media3.extractor.text.SubtitleParser;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -53,6 +55,18 @@ public final class WebvttParser implements SubtitleParser {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ImmutableList<CuesWithTiming> parse(byte[] data, int offset, int length) {
|
public ImmutableList<CuesWithTiming> parse(byte[] data, int offset, int length) {
|
||||||
|
ImmutableList.Builder<CuesWithTiming> result = ImmutableList.builder();
|
||||||
|
parse(data, offset, length, OutputOptions.allCues(), result::add);
|
||||||
|
return result.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void parse(
|
||||||
|
byte[] data,
|
||||||
|
int offset,
|
||||||
|
int length,
|
||||||
|
OutputOptions outputOptions,
|
||||||
|
Consumer<CuesWithTiming> output) {
|
||||||
parsableWebvttData.reset(data, /* limit= */ offset + length);
|
parsableWebvttData.reset(data, /* limit= */ offset + length);
|
||||||
parsableWebvttData.setPosition(offset);
|
parsableWebvttData.setPosition(offset);
|
||||||
List<WebvttCssStyle> definedStyles = new ArrayList<>();
|
List<WebvttCssStyle> definedStyles = new ArrayList<>();
|
||||||
@ -85,7 +99,7 @@ public final class WebvttParser implements SubtitleParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
WebvttSubtitle subtitle = new WebvttSubtitle(cueInfos);
|
WebvttSubtitle subtitle = new WebvttSubtitle(cueInfos);
|
||||||
return subtitle.toCuesWithTimingList();
|
LegacySubtitleUtil.toCuesWithTiming(subtitle, outputOptions, output);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -25,13 +25,11 @@ import androidx.media3.common.text.TextAnnotation;
|
|||||||
import androidx.media3.common.text.TextEmphasisSpan;
|
import androidx.media3.common.text.TextEmphasisSpan;
|
||||||
import androidx.media3.common.util.Assertions;
|
import androidx.media3.common.util.Assertions;
|
||||||
import androidx.media3.common.util.ColorParser;
|
import androidx.media3.common.util.ColorParser;
|
||||||
import androidx.media3.extractor.text.CuesWithTiming;
|
|
||||||
import androidx.media3.extractor.text.Subtitle;
|
import androidx.media3.extractor.text.Subtitle;
|
||||||
import androidx.media3.extractor.text.SubtitleDecoderException;
|
import androidx.media3.extractor.text.SubtitleDecoderException;
|
||||||
import androidx.media3.test.utils.TestUtil;
|
import androidx.media3.test.utils.TestUtil;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -877,39 +875,6 @@ public final class TtmlDecoderTest {
|
|||||||
assertThat(eighthCue.shearDegrees).isWithin(0.01f).of(90f);
|
assertThat(eighthCue.shearDegrees).isWithin(0.01f).of(90f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void toCuesWithTimingConversion() throws IOException, SubtitleDecoderException {
|
|
||||||
TtmlSubtitle subtitle = getSubtitle(INLINE_ATTRIBUTES_TTML_FILE);
|
|
||||||
ImmutableList<CuesWithTiming> cuesWithTimingsList = subtitle.toCuesWithTimingList();
|
|
||||||
|
|
||||||
assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
|
|
||||||
assertThat(subtitle.getCues(subtitle.getEventTime(1))).isEmpty();
|
|
||||||
assertThat(subtitle.getCues(subtitle.getEventTime(3))).isEmpty();
|
|
||||||
// cuesWithTimingsList has 2 fewer the events because it skips the empty cues
|
|
||||||
assertThat(cuesWithTimingsList).hasSize(2);
|
|
||||||
|
|
||||||
subtitle = getSubtitle(INHERIT_STYLE_TTML_FILE);
|
|
||||||
cuesWithTimingsList = subtitle.toCuesWithTimingList();
|
|
||||||
|
|
||||||
assertThat(subtitle.getEventTimeCount()).isEqualTo(2);
|
|
||||||
assertThat(subtitle.getCues(subtitle.getEventTime(1))).isEmpty();
|
|
||||||
// cuesWithTimingsList has 1 fewer events because it skips the empty cues
|
|
||||||
assertThat(cuesWithTimingsList).hasSize(1);
|
|
||||||
|
|
||||||
subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE);
|
|
||||||
cuesWithTimingsList = subtitle.toCuesWithTimingList();
|
|
||||||
|
|
||||||
assertThat(subtitle.getEventTimeCount()).isEqualTo(12);
|
|
||||||
assertThat(subtitle.getCues(subtitle.getEventTime(1))).isEmpty();
|
|
||||||
assertThat(subtitle.getCues(subtitle.getEventTime(3))).isEmpty();
|
|
||||||
assertThat(subtitle.getCues(subtitle.getEventTime(5))).isEmpty();
|
|
||||||
assertThat(subtitle.getCues(subtitle.getEventTime(7))).isEmpty();
|
|
||||||
assertThat(subtitle.getCues(subtitle.getEventTime(9))).isEmpty();
|
|
||||||
assertThat(subtitle.getCues(subtitle.getEventTime(11))).isEmpty();
|
|
||||||
// cuesWithTimingsList has 6 fewer events because it skips the empty cues
|
|
||||||
assertThat(cuesWithTimingsList).hasSize(6);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Spanned getOnlyCueTextAtTimeUs(Subtitle subtitle, long timeUs) {
|
private static Spanned getOnlyCueTextAtTimeUs(Subtitle subtitle, long timeUs) {
|
||||||
Cue cue = getOnlyCueAtTimeUs(subtitle, timeUs);
|
Cue cue = getOnlyCueAtTimeUs(subtitle, timeUs);
|
||||||
assertThat(cue.text).isInstanceOf(Spanned.class);
|
assertThat(cue.text).isInstanceOf(Spanned.class);
|
||||||
|
@ -0,0 +1,221 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
package androidx.media3.extractor.text.webvtt;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import androidx.media3.extractor.text.CuesWithTiming;
|
||||||
|
import androidx.media3.extractor.text.LegacySubtitleUtil;
|
||||||
|
import androidx.media3.extractor.text.Subtitle;
|
||||||
|
import androidx.media3.extractor.text.SubtitleParser;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link LegacySubtitleUtil} using {@link WebvttSubtitle}.
|
||||||
|
*
|
||||||
|
* <p>This is in the webvtt package so we don't need to increase the visibility of {@link
|
||||||
|
* WebvttSubtitle}.
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
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 =
|
||||||
|
new WebvttSubtitle(
|
||||||
|
Arrays.asList(
|
||||||
|
new WebvttCueInfo(
|
||||||
|
WebvttCueParser.newCueForText(FIRST_SUBTITLE_STRING),
|
||||||
|
/* startTimeUs= */ 1_000_000,
|
||||||
|
/* endTimeUs= */ 2_000_000),
|
||||||
|
new WebvttCueInfo(
|
||||||
|
WebvttCueParser.newCueForText(SECOND_SUBTITLE_STRING),
|
||||||
|
/* startTimeUs= */ 3_000_000,
|
||||||
|
/* endTimeUs= */ 4_000_000)));
|
||||||
|
|
||||||
|
private static final WebvttSubtitle overlappingSubtitle =
|
||||||
|
new WebvttSubtitle(
|
||||||
|
Arrays.asList(
|
||||||
|
new WebvttCueInfo(
|
||||||
|
WebvttCueParser.newCueForText(FIRST_SUBTITLE_STRING),
|
||||||
|
/* startTimeUs= */ 1_000_000,
|
||||||
|
/* endTimeUs= */ 3_000_000),
|
||||||
|
new WebvttCueInfo(
|
||||||
|
WebvttCueParser.newCueForText(SECOND_SUBTITLE_STRING),
|
||||||
|
/* startTimeUs= */ 2_000_000,
|
||||||
|
/* endTimeUs= */ 4_000_000)));
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void toCuesWithTiming_allCues_simpleSubtitle() {
|
||||||
|
ImmutableList<CuesWithTiming> cuesWithTimingsList =
|
||||||
|
toCuesWithTimingList(simpleSubtitle, SubtitleParser.OutputOptions.allCues());
|
||||||
|
|
||||||
|
assertThat(cuesWithTimingsList).hasSize(2);
|
||||||
|
assertThat(cuesWithTimingsList.get(0).startTimeUs).isEqualTo(1_000_000);
|
||||||
|
assertThat(cuesWithTimingsList.get(0).durationUs).isEqualTo(1_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(Lists.transform(cuesWithTimingsList.get(1).cues, c -> c.text))
|
||||||
|
.containsExactly(SECOND_SUBTITLE_STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void toCuesWithTiming_allCues_overlappingSubtitle() {
|
||||||
|
ImmutableList<CuesWithTiming> cuesWithTimingsList =
|
||||||
|
toCuesWithTimingList(overlappingSubtitle, SubtitleParser.OutputOptions.allCues());
|
||||||
|
|
||||||
|
assertThat(cuesWithTimingsList).hasSize(3);
|
||||||
|
assertThat(cuesWithTimingsList.get(0).startTimeUs).isEqualTo(1_000_000);
|
||||||
|
assertThat(cuesWithTimingsList.get(0).durationUs).isEqualTo(1_000_000);
|
||||||
|
assertThat(Lists.transform(cuesWithTimingsList.get(0).cues, c -> c.text))
|
||||||
|
.containsExactly(FIRST_SUBTITLE_STRING);
|
||||||
|
assertThat(cuesWithTimingsList.get(1).startTimeUs).isEqualTo(2_000_000);
|
||||||
|
assertThat(cuesWithTimingsList.get(1).durationUs).isEqualTo(1_000_000);
|
||||||
|
assertThat(Lists.transform(cuesWithTimingsList.get(1).cues, c -> c.text))
|
||||||
|
.containsExactly(FIRST_SUBTITLE_STRING, SECOND_SUBTITLE_STRING)
|
||||||
|
.inOrder();
|
||||||
|
assertThat(cuesWithTimingsList.get(2).startTimeUs).isEqualTo(3_000_000);
|
||||||
|
assertThat(cuesWithTimingsList.get(2).durationUs).isEqualTo(1_000_000);
|
||||||
|
assertThat(Lists.transform(cuesWithTimingsList.get(2).cues, c -> c.text))
|
||||||
|
.containsExactly(SECOND_SUBTITLE_STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void toCuesWithTiming_onlyEmitCuesAfterStartTime_startBetweenCues_simpleSubtitle() {
|
||||||
|
ImmutableList<CuesWithTiming> cuesWithTimingsList =
|
||||||
|
toCuesWithTimingList(simpleSubtitle, SubtitleParser.OutputOptions.onlyCuesAfter(2_500_000));
|
||||||
|
|
||||||
|
assertThat(cuesWithTimingsList).hasSize(1);
|
||||||
|
assertThat(cuesWithTimingsList.get(0).startTimeUs).isEqualTo(3_000_000);
|
||||||
|
assertThat(cuesWithTimingsList.get(0).durationUs).isEqualTo(1_000_000);
|
||||||
|
assertThat(Lists.transform(cuesWithTimingsList.get(0).cues, c -> c.text))
|
||||||
|
.containsExactly(SECOND_SUBTITLE_STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void toCuesWithTiming_onlyEmitCuesAfterStartTime_startAtCueEnd_simpleSubtitle() {
|
||||||
|
ImmutableList<CuesWithTiming> cuesWithTimingsList =
|
||||||
|
toCuesWithTimingList(simpleSubtitle, SubtitleParser.OutputOptions.onlyCuesAfter(2_000_000));
|
||||||
|
|
||||||
|
assertThat(cuesWithTimingsList).hasSize(1);
|
||||||
|
assertThat(cuesWithTimingsList.get(0).startTimeUs).isEqualTo(3_000_000);
|
||||||
|
assertThat(cuesWithTimingsList.get(0).durationUs).isEqualTo(1_000_000);
|
||||||
|
assertThat(Lists.transform(cuesWithTimingsList.get(0).cues, c -> c.text))
|
||||||
|
.containsExactly(SECOND_SUBTITLE_STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void toCuesWithTiming_onlyEmitCuesAfterStartTime_startAtCueStart_simpleSubtitle() {
|
||||||
|
ImmutableList<CuesWithTiming> cuesWithTimingsList =
|
||||||
|
toCuesWithTimingList(simpleSubtitle, SubtitleParser.OutputOptions.onlyCuesAfter(3_000_000));
|
||||||
|
|
||||||
|
assertThat(cuesWithTimingsList).hasSize(1);
|
||||||
|
assertThat(cuesWithTimingsList.get(0).startTimeUs).isEqualTo(3_000_000);
|
||||||
|
assertThat(cuesWithTimingsList.get(0).durationUs).isEqualTo(1_000_000);
|
||||||
|
assertThat(Lists.transform(cuesWithTimingsList.get(0).cues, c -> c.text))
|
||||||
|
.containsExactly(SECOND_SUBTITLE_STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void toCuesWithTiming_onlyEmitCuesAfterStartTime_startInMiddleOfCue_simpleSubtitle() {
|
||||||
|
ImmutableList<CuesWithTiming> cuesWithTimingsList =
|
||||||
|
toCuesWithTimingList(simpleSubtitle, 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);
|
||||||
|
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(Lists.transform(cuesWithTimingsList.get(1).cues, c -> c.text))
|
||||||
|
.containsExactly(SECOND_SUBTITLE_STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void toCuesWithTiming_onlyEmitCuesAfterStartTime_overlappingSubtitle() {
|
||||||
|
ImmutableList<CuesWithTiming> cuesWithTimingsList =
|
||||||
|
toCuesWithTimingList(
|
||||||
|
overlappingSubtitle, 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);
|
||||||
|
assertThat(Lists.transform(cuesWithTimingsList.get(0).cues, c -> c.text))
|
||||||
|
.containsExactly(FIRST_SUBTITLE_STRING, SECOND_SUBTITLE_STRING)
|
||||||
|
.inOrder();
|
||||||
|
assertThat(cuesWithTimingsList.get(1).startTimeUs).isEqualTo(3_000_000);
|
||||||
|
assertThat(cuesWithTimingsList.get(1).durationUs).isEqualTo(1_000_000);
|
||||||
|
assertThat(Lists.transform(cuesWithTimingsList.get(1).cues, c -> c.text))
|
||||||
|
.containsExactly(SECOND_SUBTITLE_STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void toCuesWithTiming_emitCuesAfterStartTimeThenThoseBefore_simpleSubtitle() {
|
||||||
|
ImmutableList<CuesWithTiming> cuesWithTimingsList =
|
||||||
|
toCuesWithTimingList(
|
||||||
|
simpleSubtitle,
|
||||||
|
SubtitleParser.OutputOptions.cuesAfterThenRemainingCuesBefore(2_500_000));
|
||||||
|
|
||||||
|
assertThat(cuesWithTimingsList).hasSize(2);
|
||||||
|
assertThat(cuesWithTimingsList.get(0).startTimeUs).isEqualTo(3_000_000);
|
||||||
|
assertThat(cuesWithTimingsList.get(0).durationUs).isEqualTo(1_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(Lists.transform(cuesWithTimingsList.get(1).cues, c -> c.text))
|
||||||
|
.containsExactly(FIRST_SUBTITLE_STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void toCuesWithTiming_emitCuesAfterStartTimeThenThoseBefore_overlappingSubtitle() {
|
||||||
|
ImmutableList<CuesWithTiming> cuesWithTimingsList =
|
||||||
|
toCuesWithTimingList(
|
||||||
|
overlappingSubtitle,
|
||||||
|
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(Lists.transform(cuesWithTimingsList.get(0).cues, c -> c.text))
|
||||||
|
.containsExactly(FIRST_SUBTITLE_STRING, SECOND_SUBTITLE_STRING)
|
||||||
|
.inOrder();
|
||||||
|
assertThat(cuesWithTimingsList.get(1).startTimeUs).isEqualTo(3_000_000);
|
||||||
|
assertThat(cuesWithTimingsList.get(1).durationUs).isEqualTo(1_000_000);
|
||||||
|
assertThat(Lists.transform(cuesWithTimingsList.get(1).cues, c -> c.text))
|
||||||
|
.containsExactly(SECOND_SUBTITLE_STRING);
|
||||||
|
assertThat(cuesWithTimingsList.get(2).startTimeUs).isEqualTo(1_000_000);
|
||||||
|
assertThat(cuesWithTimingsList.get(2).durationUs).isEqualTo(1_000_000);
|
||||||
|
assertThat(Lists.transform(cuesWithTimingsList.get(2).cues, c -> c.text))
|
||||||
|
.containsExactly(FIRST_SUBTITLE_STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ImmutableList<CuesWithTiming> toCuesWithTimingList(
|
||||||
|
Subtitle subtitle, SubtitleParser.OutputOptions outputOptions) {
|
||||||
|
ImmutableList.Builder<CuesWithTiming> result = ImmutableList.builder();
|
||||||
|
LegacySubtitleUtil.toCuesWithTiming(subtitle, outputOptions, result::add);
|
||||||
|
return result.build();
|
||||||
|
}
|
||||||
|
}
|
@ -20,9 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
|
|||||||
import static java.lang.Long.MAX_VALUE;
|
import static java.lang.Long.MAX_VALUE;
|
||||||
|
|
||||||
import androidx.media3.common.text.Cue;
|
import androidx.media3.common.text.Cue;
|
||||||
import androidx.media3.extractor.text.CuesWithTiming;
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -299,31 +297,6 @@ public class WebvttSubtitleTest {
|
|||||||
assertThat(nestedSubtitle.getCues(Long.MAX_VALUE)).isEmpty();
|
assertThat(nestedSubtitle.getCues(Long.MAX_VALUE)).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void toCuesWithTimingConversion() {
|
|
||||||
ImmutableList<CuesWithTiming> cuesWithTimingsList = simpleSubtitle.toCuesWithTimingList();
|
|
||||||
|
|
||||||
assertThat(simpleSubtitle.getEventTimeCount()).isEqualTo(4);
|
|
||||||
assertThat(simpleSubtitle.getCues(simpleSubtitle.getEventTime(1))).isEmpty();
|
|
||||||
assertThat(simpleSubtitle.getCues(simpleSubtitle.getEventTime(3))).isEmpty();
|
|
||||||
// cuesWithTimingsList has half the events because it skips the empty cues
|
|
||||||
assertThat(cuesWithTimingsList).hasSize(2);
|
|
||||||
|
|
||||||
cuesWithTimingsList = overlappingSubtitle.toCuesWithTimingList();
|
|
||||||
|
|
||||||
assertThat(overlappingSubtitle.getEventTimeCount()).isEqualTo(4);
|
|
||||||
assertThat(overlappingSubtitle.getCues(overlappingSubtitle.getEventTime(3))).isEmpty();
|
|
||||||
// cuesWithTimingsList has one fewer events because it skips the empty cues
|
|
||||||
assertThat(cuesWithTimingsList).hasSize(3);
|
|
||||||
|
|
||||||
cuesWithTimingsList = nestedSubtitle.toCuesWithTimingList();
|
|
||||||
|
|
||||||
assertThat(nestedSubtitle.getEventTimeCount()).isEqualTo(4);
|
|
||||||
assertThat(nestedSubtitle.getCues(nestedSubtitle.getEventTime(3))).isEmpty();
|
|
||||||
// cuesWithTimingsList has one fewer events because it skips the empty cues
|
|
||||||
assertThat(cuesWithTimingsList).hasSize(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<String> getCueTexts(List<Cue> cues) {
|
private static List<String> getCueTexts(List<Cue> cues) {
|
||||||
List<String> cueTexts = new ArrayList<>();
|
List<String> cueTexts = new ArrayList<>();
|
||||||
for (int i = 0; i < cues.size(); i++) {
|
for (int i = 0; i < cues.size(); i++) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user