WebvttParser
implementation - moved from WebvttDecoder
`WebvttDecoder` which used to be `SimpleSubtitleDecoder` will now be called `WebvttParser` and implement `SubtitleParser` interface. For backwards compatibility, we will have the same functionality provided by `DelegatingSubtitleDecoder` backed-up by a new `WebvttParser` instance. `WebvttSubtitle` will still be used behind the scenes to handle overlapping `Cues`. PiperOrigin-RevId: 549298733
This commit is contained in:
parent
5d453fcf37
commit
f0f24aa0d4
@ -15,7 +15,10 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.exoplayer.text;
|
package androidx.media3.exoplayer.text;
|
||||||
|
|
||||||
|
import static androidx.annotation.VisibleForTesting.PACKAGE_PRIVATE;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
import androidx.media3.extractor.text.CuesWithTiming;
|
import androidx.media3.extractor.text.CuesWithTiming;
|
||||||
import androidx.media3.extractor.text.SimpleSubtitleDecoder;
|
import androidx.media3.extractor.text.SimpleSubtitleDecoder;
|
||||||
import androidx.media3.extractor.text.Subtitle;
|
import androidx.media3.extractor.text.Subtitle;
|
||||||
@ -43,17 +46,19 @@ import java.util.List;
|
|||||||
* <li>DelegatingSubtitleDecoder("XXX", new XXXParser(initializationData))
|
* <li>DelegatingSubtitleDecoder("XXX", new XXXParser(initializationData))
|
||||||
* <li>XXXDecoder(initializationData)
|
* <li>XXXDecoder(initializationData)
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
|
||||||
* <p>TODO(b/289983417): this will only be used in the old decoding flow (Decoder after SampleQueue)
|
|
||||||
* while we maintain dual architecture. Once we fully migrate to the pre-SampleQueue flow, it can be
|
|
||||||
* deprecated and later deleted.
|
|
||||||
*/
|
*/
|
||||||
/* package */ final class DelegatingSubtitleDecoder extends SimpleSubtitleDecoder {
|
// TODO(b/289983417): this will only be used in the old decoding flow (Decoder after SampleQueue)
|
||||||
|
// while we maintain dual architecture. Once we fully migrate to the pre-SampleQueue flow, it can be
|
||||||
|
// deprecated and later deleted.
|
||||||
|
// TODO: remove VisibleForTesting annotation once SubtitleExtractor uses SubtitleParser (rather than
|
||||||
|
// SubtitleDecoder) and SubtitleExtractorTest is refactored
|
||||||
|
@VisibleForTesting(otherwise = PACKAGE_PRIVATE)
|
||||||
|
public final class DelegatingSubtitleDecoder extends SimpleSubtitleDecoder {
|
||||||
|
|
||||||
private static final Subtitle EMPTY_SUBTITLE = new CuesWithTimingSubtitle(ImmutableList.of());
|
private static final Subtitle EMPTY_SUBTITLE = new CuesWithTimingSubtitle(ImmutableList.of());
|
||||||
private final SubtitleParser subtitleParser;
|
private final SubtitleParser subtitleParser;
|
||||||
|
|
||||||
/* package */ DelegatingSubtitleDecoder(String name, SubtitleParser subtitleParser) {
|
public DelegatingSubtitleDecoder(String name, SubtitleParser subtitleParser) {
|
||||||
super(name);
|
super(name);
|
||||||
this.subtitleParser = subtitleParser;
|
this.subtitleParser = subtitleParser;
|
||||||
}
|
}
|
||||||
@ -63,7 +68,8 @@ import java.util.List;
|
|||||||
if (reset) {
|
if (reset) {
|
||||||
subtitleParser.reset();
|
subtitleParser.reset();
|
||||||
}
|
}
|
||||||
@Nullable List<CuesWithTiming> cuesWithTiming = subtitleParser.parse(data);
|
@Nullable
|
||||||
|
List<CuesWithTiming> cuesWithTiming = subtitleParser.parse(data, /* offset= */ 0, length);
|
||||||
if (cuesWithTiming == null) {
|
if (cuesWithTiming == null) {
|
||||||
return EMPTY_SUBTITLE;
|
return EMPTY_SUBTITLE;
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,6 @@ import androidx.media3.extractor.text.SubtitleParser;
|
|||||||
import androidx.media3.extractor.text.cea.Cea608Decoder;
|
import androidx.media3.extractor.text.cea.Cea608Decoder;
|
||||||
import androidx.media3.extractor.text.cea.Cea708Decoder;
|
import androidx.media3.extractor.text.cea.Cea708Decoder;
|
||||||
import androidx.media3.extractor.text.ttml.TtmlDecoder;
|
import androidx.media3.extractor.text.ttml.TtmlDecoder;
|
||||||
import androidx.media3.extractor.text.webvtt.WebvttDecoder;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/** A factory for {@link SubtitleDecoder} instances. */
|
/** A factory for {@link SubtitleDecoder} instances. */
|
||||||
@ -56,7 +55,6 @@ public interface SubtitleDecoderFactory {
|
|||||||
* <p>Supports formats supported by {@link DefaultSubtitleParserFactory} as well as the following:
|
* <p>Supports formats supported by {@link DefaultSubtitleParserFactory} as well as the following:
|
||||||
*
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>WebVTT ({@link WebvttDecoder})
|
|
||||||
* <li>TTML ({@link TtmlDecoder})
|
* <li>TTML ({@link TtmlDecoder})
|
||||||
* <li>Cea608 ({@link Cea608Decoder})
|
* <li>Cea608 ({@link Cea608Decoder})
|
||||||
* <li>Cea708 ({@link Cea708Decoder})
|
* <li>Cea708 ({@link Cea708Decoder})
|
||||||
@ -72,7 +70,6 @@ public interface SubtitleDecoderFactory {
|
|||||||
public boolean supportsFormat(Format format) {
|
public boolean supportsFormat(Format format) {
|
||||||
@Nullable String mimeType = format.sampleMimeType;
|
@Nullable String mimeType = format.sampleMimeType;
|
||||||
return delegate.supportsFormat(format)
|
return delegate.supportsFormat(format)
|
||||||
|| Objects.equals(mimeType, MimeTypes.TEXT_VTT)
|
|
||||||
|| Objects.equals(mimeType, MimeTypes.APPLICATION_TTML)
|
|| Objects.equals(mimeType, MimeTypes.APPLICATION_TTML)
|
||||||
|| Objects.equals(mimeType, MimeTypes.APPLICATION_CEA608)
|
|| Objects.equals(mimeType, MimeTypes.APPLICATION_CEA608)
|
||||||
|| Objects.equals(mimeType, MimeTypes.APPLICATION_MP4CEA608)
|
|| Objects.equals(mimeType, MimeTypes.APPLICATION_MP4CEA608)
|
||||||
@ -90,8 +87,6 @@ public interface SubtitleDecoderFactory {
|
|||||||
@Nullable String mimeType = format.sampleMimeType;
|
@Nullable String mimeType = format.sampleMimeType;
|
||||||
if (mimeType != null) {
|
if (mimeType != null) {
|
||||||
switch (mimeType) {
|
switch (mimeType) {
|
||||||
case MimeTypes.TEXT_VTT:
|
|
||||||
return new WebvttDecoder();
|
|
||||||
case MimeTypes.APPLICATION_TTML:
|
case MimeTypes.APPLICATION_TTML:
|
||||||
return new TtmlDecoder();
|
return new TtmlDecoder();
|
||||||
case MimeTypes.APPLICATION_CEA608:
|
case MimeTypes.APPLICATION_CEA608:
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2016 The Android Open Source Project
|
* Copyright 2023 The Android Open Source Project
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
@ -13,11 +13,11 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package androidx.media3.extractor.text.webvtt;
|
package androidx.media3.exoplayer.text;
|
||||||
|
|
||||||
import static androidx.media3.test.utils.truth.SpannedSubject.assertThat;
|
import static androidx.media3.test.utils.truth.SpannedSubject.assertThat;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.assertThrows;
|
||||||
|
|
||||||
import android.text.Layout.Alignment;
|
import android.text.Layout.Alignment;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
@ -25,7 +25,8 @@ import androidx.media3.common.text.Cue;
|
|||||||
import androidx.media3.common.text.TextAnnotation;
|
import androidx.media3.common.text.TextAnnotation;
|
||||||
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.SubtitleDecoderException;
|
import androidx.media3.extractor.text.Subtitle;
|
||||||
|
import androidx.media3.extractor.text.webvtt.WebvttParser;
|
||||||
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;
|
||||||
@ -37,10 +38,9 @@ import org.junit.Rule;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
/** Unit test for {@link WebvttDecoder}. */
|
/** Unit test for a {@link DelegatingSubtitleDecoder} backed by {@link WebvttParser}. */
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class WebvttDecoderTest {
|
public final class DelegatingSubtitleDecoderWithWebvttParserTest {
|
||||||
|
|
||||||
private static final String TYPICAL_FILE = "media/webvtt/typical";
|
private static final String TYPICAL_FILE = "media/webvtt/typical";
|
||||||
private static final String TYPICAL_WITH_BAD_TIMESTAMPS =
|
private static final String TYPICAL_WITH_BAD_TIMESTAMPS =
|
||||||
"media/webvtt/typical_with_bad_timestamps";
|
"media/webvtt/typical_with_bad_timestamps";
|
||||||
@ -61,32 +61,27 @@ public class WebvttDecoderTest {
|
|||||||
"media/webvtt/with_css_text_combine_upright";
|
"media/webvtt/with_css_text_combine_upright";
|
||||||
private static final String WITH_BOM = "media/webvtt/with_bom";
|
private static final String WITH_BOM = "media/webvtt/with_bom";
|
||||||
private static final String EMPTY_FILE = "media/webvtt/empty";
|
private static final String EMPTY_FILE = "media/webvtt/empty";
|
||||||
|
|
||||||
@Rule public final Expect expect = Expect.create();
|
@Rule public final Expect expect = Expect.create();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void decodeEmpty() throws IOException {
|
public void decodeEmpty() throws IOException {
|
||||||
WebvttDecoder decoder = new WebvttDecoder();
|
DelegatingSubtitleDecoder decoder =
|
||||||
|
new DelegatingSubtitleDecoder(
|
||||||
|
"DelegatingSubtitleDecoderWithWebvttParser", new WebvttParser());
|
||||||
byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), EMPTY_FILE);
|
byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), EMPTY_FILE);
|
||||||
try {
|
assertThrows(
|
||||||
decoder.decode(bytes, bytes.length, /* reset= */ false);
|
IllegalArgumentException.class,
|
||||||
fail();
|
() -> decoder.decode(bytes, bytes.length, /* reset= */ false));
|
||||||
} catch (SubtitleDecoderException expected) {
|
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void decodeTypical() throws Exception {
|
public void decodeTypical() throws Exception {
|
||||||
WebvttSubtitle subtitle = getSubtitleForTestAsset(TYPICAL_FILE);
|
Subtitle subtitle = getSubtitleForTestAsset(TYPICAL_FILE);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
|
assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(0)).isEqualTo(0L);
|
assertThat(subtitle.getEventTime(0)).isEqualTo(0L);
|
||||||
assertThat(subtitle.getEventTime(1)).isEqualTo(1_234_000L);
|
assertThat(subtitle.getEventTime(1)).isEqualTo(1_234_000L);
|
||||||
Cue firstCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0)));
|
Cue firstCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0)));
|
||||||
assertThat(firstCue.text.toString()).isEqualTo("This is the first subtitle.");
|
assertThat(firstCue.text.toString()).isEqualTo("This is the first subtitle.");
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(2)).isEqualTo(2_345_000L);
|
assertThat(subtitle.getEventTime(2)).isEqualTo(2_345_000L);
|
||||||
assertThat(subtitle.getEventTime(3)).isEqualTo(3_456_000L);
|
assertThat(subtitle.getEventTime(3)).isEqualTo(3_456_000L);
|
||||||
Cue secondCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2)));
|
Cue secondCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2)));
|
||||||
@ -95,15 +90,12 @@ public class WebvttDecoderTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void decodeWithBom() throws Exception {
|
public void decodeWithBom() throws Exception {
|
||||||
WebvttSubtitle subtitle = getSubtitleForTestAsset(WITH_BOM);
|
Subtitle subtitle = getSubtitleForTestAsset(WITH_BOM);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
|
assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(0)).isEqualTo(0L);
|
assertThat(subtitle.getEventTime(0)).isEqualTo(0L);
|
||||||
assertThat(subtitle.getEventTime(1)).isEqualTo(1_234_000L);
|
assertThat(subtitle.getEventTime(1)).isEqualTo(1_234_000L);
|
||||||
Cue firstCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0)));
|
Cue firstCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0)));
|
||||||
assertThat(firstCue.text.toString()).isEqualTo("This is the first subtitle.");
|
assertThat(firstCue.text.toString()).isEqualTo("This is the first subtitle.");
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(2)).isEqualTo(2_345_000L);
|
assertThat(subtitle.getEventTime(2)).isEqualTo(2_345_000L);
|
||||||
assertThat(subtitle.getEventTime(3)).isEqualTo(3_456_000L);
|
assertThat(subtitle.getEventTime(3)).isEqualTo(3_456_000L);
|
||||||
Cue secondCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2)));
|
Cue secondCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2)));
|
||||||
@ -112,15 +104,12 @@ public class WebvttDecoderTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void decodeTypicalWithBadTimestamps() throws Exception {
|
public void decodeTypicalWithBadTimestamps() throws Exception {
|
||||||
WebvttSubtitle subtitle = getSubtitleForTestAsset(TYPICAL_WITH_BAD_TIMESTAMPS);
|
Subtitle subtitle = getSubtitleForTestAsset(TYPICAL_WITH_BAD_TIMESTAMPS);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
|
assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(0)).isEqualTo(0L);
|
assertThat(subtitle.getEventTime(0)).isEqualTo(0L);
|
||||||
assertThat(subtitle.getEventTime(1)).isEqualTo(1_234_000L);
|
assertThat(subtitle.getEventTime(1)).isEqualTo(1_234_000L);
|
||||||
Cue firstCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0)));
|
Cue firstCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0)));
|
||||||
assertThat(firstCue.text.toString()).isEqualTo("This is the first subtitle.");
|
assertThat(firstCue.text.toString()).isEqualTo("This is the first subtitle.");
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(2)).isEqualTo(2_345_000L);
|
assertThat(subtitle.getEventTime(2)).isEqualTo(2_345_000L);
|
||||||
assertThat(subtitle.getEventTime(3)).isEqualTo(3_456_000L);
|
assertThat(subtitle.getEventTime(3)).isEqualTo(3_456_000L);
|
||||||
Cue secondCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2)));
|
Cue secondCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2)));
|
||||||
@ -129,15 +118,12 @@ public class WebvttDecoderTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void decodeTypicalWithIds() throws Exception {
|
public void decodeTypicalWithIds() throws Exception {
|
||||||
WebvttSubtitle subtitle = getSubtitleForTestAsset(TYPICAL_WITH_IDS_FILE);
|
Subtitle subtitle = getSubtitleForTestAsset(TYPICAL_WITH_IDS_FILE);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
|
assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(0)).isEqualTo(0L);
|
assertThat(subtitle.getEventTime(0)).isEqualTo(0L);
|
||||||
assertThat(subtitle.getEventTime(1)).isEqualTo(1_234_000L);
|
assertThat(subtitle.getEventTime(1)).isEqualTo(1_234_000L);
|
||||||
Cue firstCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0)));
|
Cue firstCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0)));
|
||||||
assertThat(firstCue.text.toString()).isEqualTo("This is the first subtitle.");
|
assertThat(firstCue.text.toString()).isEqualTo("This is the first subtitle.");
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(2)).isEqualTo(2_345_000L);
|
assertThat(subtitle.getEventTime(2)).isEqualTo(2_345_000L);
|
||||||
assertThat(subtitle.getEventTime(3)).isEqualTo(3_456_000L);
|
assertThat(subtitle.getEventTime(3)).isEqualTo(3_456_000L);
|
||||||
Cue secondCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2)));
|
Cue secondCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2)));
|
||||||
@ -146,15 +132,12 @@ public class WebvttDecoderTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void decodeTypicalWithComments() throws Exception {
|
public void decodeTypicalWithComments() throws Exception {
|
||||||
WebvttSubtitle subtitle = getSubtitleForTestAsset(TYPICAL_WITH_COMMENTS_FILE);
|
Subtitle subtitle = getSubtitleForTestAsset(TYPICAL_WITH_COMMENTS_FILE);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
|
assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(0)).isEqualTo(0L);
|
assertThat(subtitle.getEventTime(0)).isEqualTo(0L);
|
||||||
assertThat(subtitle.getEventTime(0 + 1)).isEqualTo(1_234_000L);
|
assertThat(subtitle.getEventTime(0 + 1)).isEqualTo(1_234_000L);
|
||||||
Cue firstCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0)));
|
Cue firstCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0)));
|
||||||
assertThat(firstCue.text.toString()).isEqualTo("This is the first subtitle.");
|
assertThat(firstCue.text.toString()).isEqualTo("This is the first subtitle.");
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(2)).isEqualTo(2_345_000L);
|
assertThat(subtitle.getEventTime(2)).isEqualTo(2_345_000L);
|
||||||
assertThat(subtitle.getEventTime(2 + 1)).isEqualTo(3_456_000L);
|
assertThat(subtitle.getEventTime(2 + 1)).isEqualTo(3_456_000L);
|
||||||
Cue secondCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2)));
|
Cue secondCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2)));
|
||||||
@ -163,25 +146,20 @@ public class WebvttDecoderTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void decodeWithTags() throws Exception {
|
public void decodeWithTags() throws Exception {
|
||||||
WebvttSubtitle subtitle = getSubtitleForTestAsset(WITH_TAGS_FILE);
|
Subtitle subtitle = getSubtitleForTestAsset(WITH_TAGS_FILE);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTimeCount()).isEqualTo(8);
|
assertThat(subtitle.getEventTimeCount()).isEqualTo(8);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(0)).isEqualTo(0L);
|
assertThat(subtitle.getEventTime(0)).isEqualTo(0L);
|
||||||
assertThat(subtitle.getEventTime(1)).isEqualTo(1_234_000L);
|
assertThat(subtitle.getEventTime(1)).isEqualTo(1_234_000L);
|
||||||
Cue firstCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0)));
|
Cue firstCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0)));
|
||||||
assertThat(firstCue.text.toString()).isEqualTo("This is the first subtitle.");
|
assertThat(firstCue.text.toString()).isEqualTo("This is the first subtitle.");
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(2)).isEqualTo(2_345_000L);
|
assertThat(subtitle.getEventTime(2)).isEqualTo(2_345_000L);
|
||||||
assertThat(subtitle.getEventTime(3)).isEqualTo(3_456_000L);
|
assertThat(subtitle.getEventTime(3)).isEqualTo(3_456_000L);
|
||||||
Cue secondCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2)));
|
Cue secondCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2)));
|
||||||
assertThat(secondCue.text.toString()).isEqualTo("This is the second subtitle.");
|
assertThat(secondCue.text.toString()).isEqualTo("This is the second subtitle.");
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(4)).isEqualTo(4_000_000L);
|
assertThat(subtitle.getEventTime(4)).isEqualTo(4_000_000L);
|
||||||
assertThat(subtitle.getEventTime(5)).isEqualTo(5_000_000L);
|
assertThat(subtitle.getEventTime(5)).isEqualTo(5_000_000L);
|
||||||
Cue thirdCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(4)));
|
Cue thirdCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(4)));
|
||||||
assertThat(thirdCue.text.toString()).isEqualTo("This is the third subtitle.");
|
assertThat(thirdCue.text.toString()).isEqualTo("This is the third subtitle.");
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(6)).isEqualTo(6_000_000L);
|
assertThat(subtitle.getEventTime(6)).isEqualTo(6_000_000L);
|
||||||
assertThat(subtitle.getEventTime(7)).isEqualTo(7_000_000L);
|
assertThat(subtitle.getEventTime(7)).isEqualTo(7_000_000L);
|
||||||
Cue fourthCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(6)));
|
Cue fourthCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(6)));
|
||||||
@ -190,10 +168,8 @@ public class WebvttDecoderTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void decodeWithPositioning() throws Exception {
|
public void decodeWithPositioning() throws Exception {
|
||||||
WebvttSubtitle subtitle = getSubtitleForTestAsset(WITH_POSITIONING_FILE);
|
Subtitle subtitle = getSubtitleForTestAsset(WITH_POSITIONING_FILE);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTimeCount()).isEqualTo(16);
|
assertThat(subtitle.getEventTimeCount()).isEqualTo(16);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(0)).isEqualTo(0L);
|
assertThat(subtitle.getEventTime(0)).isEqualTo(0L);
|
||||||
assertThat(subtitle.getEventTime(1)).isEqualTo(1_234_000L);
|
assertThat(subtitle.getEventTime(1)).isEqualTo(1_234_000L);
|
||||||
Cue firstCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0)));
|
Cue firstCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0)));
|
||||||
@ -202,12 +178,10 @@ public class WebvttDecoderTest {
|
|||||||
assertThat(firstCue.positionAnchor).isEqualTo(Cue.ANCHOR_TYPE_END);
|
assertThat(firstCue.positionAnchor).isEqualTo(Cue.ANCHOR_TYPE_END);
|
||||||
assertThat(firstCue.textAlignment).isEqualTo(Alignment.ALIGN_NORMAL);
|
assertThat(firstCue.textAlignment).isEqualTo(Alignment.ALIGN_NORMAL);
|
||||||
assertThat(firstCue.size).isEqualTo(0.35f);
|
assertThat(firstCue.size).isEqualTo(0.35f);
|
||||||
|
|
||||||
// Unspecified values should use WebVTT defaults
|
// Unspecified values should use WebVTT defaults
|
||||||
assertThat(firstCue.line).isEqualTo(-1f);
|
assertThat(firstCue.line).isEqualTo(-1f);
|
||||||
assertThat(firstCue.lineType).isEqualTo(Cue.LINE_TYPE_NUMBER);
|
assertThat(firstCue.lineType).isEqualTo(Cue.LINE_TYPE_NUMBER);
|
||||||
assertThat(firstCue.verticalType).isEqualTo(Cue.TYPE_UNSET);
|
assertThat(firstCue.verticalType).isEqualTo(Cue.TYPE_UNSET);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(2)).isEqualTo(2_345_000L);
|
assertThat(subtitle.getEventTime(2)).isEqualTo(2_345_000L);
|
||||||
assertThat(subtitle.getEventTime(3)).isEqualTo(3_456_000L);
|
assertThat(subtitle.getEventTime(3)).isEqualTo(3_456_000L);
|
||||||
Cue secondCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2)));
|
Cue secondCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2)));
|
||||||
@ -215,7 +189,6 @@ public class WebvttDecoderTest {
|
|||||||
// Position is invalid so defaults to 0.5
|
// Position is invalid so defaults to 0.5
|
||||||
assertThat(secondCue.position).isEqualTo(0.5f);
|
assertThat(secondCue.position).isEqualTo(0.5f);
|
||||||
assertThat(secondCue.textAlignment).isEqualTo(Alignment.ALIGN_OPPOSITE);
|
assertThat(secondCue.textAlignment).isEqualTo(Alignment.ALIGN_OPPOSITE);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(4)).isEqualTo(4_000_000L);
|
assertThat(subtitle.getEventTime(4)).isEqualTo(4_000_000L);
|
||||||
assertThat(subtitle.getEventTime(5)).isEqualTo(5_000_000L);
|
assertThat(subtitle.getEventTime(5)).isEqualTo(5_000_000L);
|
||||||
Cue thirdCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(4)));
|
Cue thirdCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(4)));
|
||||||
@ -226,7 +199,6 @@ public class WebvttDecoderTest {
|
|||||||
assertThat(thirdCue.textAlignment).isEqualTo(Alignment.ALIGN_CENTER);
|
assertThat(thirdCue.textAlignment).isEqualTo(Alignment.ALIGN_CENTER);
|
||||||
// Derived from `align:middle`:
|
// Derived from `align:middle`:
|
||||||
assertThat(thirdCue.positionAnchor).isEqualTo(Cue.ANCHOR_TYPE_MIDDLE);
|
assertThat(thirdCue.positionAnchor).isEqualTo(Cue.ANCHOR_TYPE_MIDDLE);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(6)).isEqualTo(6_000_000L);
|
assertThat(subtitle.getEventTime(6)).isEqualTo(6_000_000L);
|
||||||
assertThat(subtitle.getEventTime(7)).isEqualTo(7_000_000L);
|
assertThat(subtitle.getEventTime(7)).isEqualTo(7_000_000L);
|
||||||
Cue fourthCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(6)));
|
Cue fourthCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(6)));
|
||||||
@ -237,7 +209,6 @@ public class WebvttDecoderTest {
|
|||||||
// Derived from `align:middle`:
|
// Derived from `align:middle`:
|
||||||
assertThat(fourthCue.position).isEqualTo(0.5f);
|
assertThat(fourthCue.position).isEqualTo(0.5f);
|
||||||
assertThat(fourthCue.positionAnchor).isEqualTo(Cue.ANCHOR_TYPE_MIDDLE);
|
assertThat(fourthCue.positionAnchor).isEqualTo(Cue.ANCHOR_TYPE_MIDDLE);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(8)).isEqualTo(8_000_000L);
|
assertThat(subtitle.getEventTime(8)).isEqualTo(8_000_000L);
|
||||||
assertThat(subtitle.getEventTime(9)).isEqualTo(9_000_000L);
|
assertThat(subtitle.getEventTime(9)).isEqualTo(9_000_000L);
|
||||||
Cue fifthCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(8)));
|
Cue fifthCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(8)));
|
||||||
@ -246,7 +217,6 @@ public class WebvttDecoderTest {
|
|||||||
// Derived from `align:right`:
|
// Derived from `align:right`:
|
||||||
assertThat(fifthCue.position).isEqualTo(1.0f);
|
assertThat(fifthCue.position).isEqualTo(1.0f);
|
||||||
assertThat(fifthCue.positionAnchor).isEqualTo(Cue.ANCHOR_TYPE_END);
|
assertThat(fifthCue.positionAnchor).isEqualTo(Cue.ANCHOR_TYPE_END);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(10)).isEqualTo(10_000_000L);
|
assertThat(subtitle.getEventTime(10)).isEqualTo(10_000_000L);
|
||||||
assertThat(subtitle.getEventTime(11)).isEqualTo(11_000_000L);
|
assertThat(subtitle.getEventTime(11)).isEqualTo(11_000_000L);
|
||||||
Cue sixthCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(10)));
|
Cue sixthCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(10)));
|
||||||
@ -255,13 +225,11 @@ public class WebvttDecoderTest {
|
|||||||
// Derived from `align:center`:
|
// Derived from `align:center`:
|
||||||
assertThat(sixthCue.position).isEqualTo(0.5f);
|
assertThat(sixthCue.position).isEqualTo(0.5f);
|
||||||
assertThat(sixthCue.positionAnchor).isEqualTo(Cue.ANCHOR_TYPE_MIDDLE);
|
assertThat(sixthCue.positionAnchor).isEqualTo(Cue.ANCHOR_TYPE_MIDDLE);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(12)).isEqualTo(12_000_000L);
|
assertThat(subtitle.getEventTime(12)).isEqualTo(12_000_000L);
|
||||||
assertThat(subtitle.getEventTime(13)).isEqualTo(13_000_000L);
|
assertThat(subtitle.getEventTime(13)).isEqualTo(13_000_000L);
|
||||||
Cue seventhCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(12)));
|
Cue seventhCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(12)));
|
||||||
assertThat(seventhCue.text.toString()).isEqualTo("This is the seventh subtitle.");
|
assertThat(seventhCue.text.toString()).isEqualTo("This is the seventh subtitle.");
|
||||||
assertThat(seventhCue.positionAnchor).isEqualTo(Cue.ANCHOR_TYPE_START);
|
assertThat(seventhCue.positionAnchor).isEqualTo(Cue.ANCHOR_TYPE_START);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(14)).isEqualTo(14_000_000L);
|
assertThat(subtitle.getEventTime(14)).isEqualTo(14_000_000L);
|
||||||
assertThat(subtitle.getEventTime(15)).isEqualTo(15_000_000L);
|
assertThat(subtitle.getEventTime(15)).isEqualTo(15_000_000L);
|
||||||
Cue eighthCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(14)));
|
Cue eighthCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(14)));
|
||||||
@ -271,15 +239,12 @@ public class WebvttDecoderTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void decodeWithOverlappingTimestamps() throws Exception {
|
public void decodeWithOverlappingTimestamps() throws Exception {
|
||||||
WebvttSubtitle subtitle = getSubtitleForTestAsset(WITH_OVERLAPPING_TIMESTAMPS_FILE);
|
Subtitle subtitle = getSubtitleForTestAsset(WITH_OVERLAPPING_TIMESTAMPS_FILE);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTimeCount()).isEqualTo(8);
|
assertThat(subtitle.getEventTimeCount()).isEqualTo(8);
|
||||||
|
|
||||||
Cue firstCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0)));
|
Cue firstCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0)));
|
||||||
assertThat(firstCue.text.toString()).isEqualTo("Displayed at the bottom for 3 seconds.");
|
assertThat(firstCue.text.toString()).isEqualTo("Displayed at the bottom for 3 seconds.");
|
||||||
assertThat(firstCue.line).isEqualTo(-1f);
|
assertThat(firstCue.line).isEqualTo(-1f);
|
||||||
assertThat(firstCue.lineType).isEqualTo(Cue.LINE_TYPE_NUMBER);
|
assertThat(firstCue.lineType).isEqualTo(Cue.LINE_TYPE_NUMBER);
|
||||||
|
|
||||||
List<Cue> firstAndSecondCue = subtitle.getCues(subtitle.getEventTime(1));
|
List<Cue> firstAndSecondCue = subtitle.getCues(subtitle.getEventTime(1));
|
||||||
assertThat(firstAndSecondCue).hasSize(2);
|
assertThat(firstAndSecondCue).hasSize(2);
|
||||||
assertThat(firstAndSecondCue.get(0).text.toString())
|
assertThat(firstAndSecondCue.get(0).text.toString())
|
||||||
@ -290,12 +255,10 @@ public class WebvttDecoderTest {
|
|||||||
.isEqualTo("Appears directly above for 1 second.");
|
.isEqualTo("Appears directly above for 1 second.");
|
||||||
assertThat(firstAndSecondCue.get(1).line).isEqualTo(-2f);
|
assertThat(firstAndSecondCue.get(1).line).isEqualTo(-2f);
|
||||||
assertThat(firstAndSecondCue.get(1).lineType).isEqualTo(Cue.LINE_TYPE_NUMBER);
|
assertThat(firstAndSecondCue.get(1).lineType).isEqualTo(Cue.LINE_TYPE_NUMBER);
|
||||||
|
|
||||||
Cue thirdCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(4)));
|
Cue thirdCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(4)));
|
||||||
assertThat(thirdCue.text.toString()).isEqualTo("Displayed at the bottom for 2 seconds.");
|
assertThat(thirdCue.text.toString()).isEqualTo("Displayed at the bottom for 2 seconds.");
|
||||||
assertThat(thirdCue.line).isEqualTo(-1f);
|
assertThat(thirdCue.line).isEqualTo(-1f);
|
||||||
assertThat(thirdCue.lineType).isEqualTo(Cue.LINE_TYPE_NUMBER);
|
assertThat(thirdCue.lineType).isEqualTo(Cue.LINE_TYPE_NUMBER);
|
||||||
|
|
||||||
List<Cue> thirdAndFourthCue = subtitle.getCues(subtitle.getEventTime(5));
|
List<Cue> thirdAndFourthCue = subtitle.getCues(subtitle.getEventTime(5));
|
||||||
assertThat(thirdAndFourthCue).hasSize(2);
|
assertThat(thirdAndFourthCue).hasSize(2);
|
||||||
assertThat(thirdAndFourthCue.get(0).text.toString())
|
assertThat(thirdAndFourthCue.get(0).text.toString())
|
||||||
@ -306,7 +269,6 @@ public class WebvttDecoderTest {
|
|||||||
.isEqualTo("Appears directly above the previous cue, then replaces it after 1 second.");
|
.isEqualTo("Appears directly above the previous cue, then replaces it after 1 second.");
|
||||||
assertThat(thirdAndFourthCue.get(1).line).isEqualTo(-2f);
|
assertThat(thirdAndFourthCue.get(1).line).isEqualTo(-2f);
|
||||||
assertThat(thirdAndFourthCue.get(1).lineType).isEqualTo(Cue.LINE_TYPE_NUMBER);
|
assertThat(thirdAndFourthCue.get(1).lineType).isEqualTo(Cue.LINE_TYPE_NUMBER);
|
||||||
|
|
||||||
Cue fourthCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(6)));
|
Cue fourthCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(6)));
|
||||||
assertThat(fourthCue.text.toString())
|
assertThat(fourthCue.text.toString())
|
||||||
.isEqualTo("Appears directly above the previous cue, then replaces it after 1 second.");
|
.isEqualTo("Appears directly above the previous cue, then replaces it after 1 second.");
|
||||||
@ -316,22 +278,18 @@ public class WebvttDecoderTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void decodeWithVertical() throws Exception {
|
public void decodeWithVertical() throws Exception {
|
||||||
WebvttSubtitle subtitle = getSubtitleForTestAsset(WITH_VERTICAL_FILE);
|
Subtitle subtitle = getSubtitleForTestAsset(WITH_VERTICAL_FILE);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTimeCount()).isEqualTo(6);
|
assertThat(subtitle.getEventTimeCount()).isEqualTo(6);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(0)).isEqualTo(0L);
|
assertThat(subtitle.getEventTime(0)).isEqualTo(0L);
|
||||||
assertThat(subtitle.getEventTime(1)).isEqualTo(1_234_000L);
|
assertThat(subtitle.getEventTime(1)).isEqualTo(1_234_000L);
|
||||||
Cue firstCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0)));
|
Cue firstCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0)));
|
||||||
assertThat(firstCue.text.toString()).isEqualTo("Vertical right-to-left (e.g. Japanese)");
|
assertThat(firstCue.text.toString()).isEqualTo("Vertical right-to-left (e.g. Japanese)");
|
||||||
assertThat(firstCue.verticalType).isEqualTo(Cue.VERTICAL_TYPE_RL);
|
assertThat(firstCue.verticalType).isEqualTo(Cue.VERTICAL_TYPE_RL);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(2)).isEqualTo(2_345_000L);
|
assertThat(subtitle.getEventTime(2)).isEqualTo(2_345_000L);
|
||||||
assertThat(subtitle.getEventTime(3)).isEqualTo(3_456_000L);
|
assertThat(subtitle.getEventTime(3)).isEqualTo(3_456_000L);
|
||||||
Cue secondCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2)));
|
Cue secondCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2)));
|
||||||
assertThat(secondCue.text.toString()).isEqualTo("Vertical left-to-right (e.g. Mongolian)");
|
assertThat(secondCue.text.toString()).isEqualTo("Vertical left-to-right (e.g. Mongolian)");
|
||||||
assertThat(secondCue.verticalType).isEqualTo(Cue.VERTICAL_TYPE_LR);
|
assertThat(secondCue.verticalType).isEqualTo(Cue.VERTICAL_TYPE_LR);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(4)).isEqualTo(4_000_000L);
|
assertThat(subtitle.getEventTime(4)).isEqualTo(4_000_000L);
|
||||||
assertThat(subtitle.getEventTime(5)).isEqualTo(5_000_000L);
|
assertThat(subtitle.getEventTime(5)).isEqualTo(5_000_000L);
|
||||||
Cue thirdCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(4)));
|
Cue thirdCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(4)));
|
||||||
@ -341,17 +299,14 @@ public class WebvttDecoderTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void decodeWithRubies() throws Exception {
|
public void decodeWithRubies() throws Exception {
|
||||||
WebvttSubtitle subtitle = getSubtitleForTestAsset(WITH_RUBIES_FILE);
|
Subtitle subtitle = getSubtitleForTestAsset(WITH_RUBIES_FILE);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTimeCount()).isEqualTo(8);
|
assertThat(subtitle.getEventTimeCount()).isEqualTo(8);
|
||||||
|
|
||||||
// Check that an explicit `over` position is read from CSS.
|
// Check that an explicit `over` position is read from CSS.
|
||||||
Cue firstCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0)));
|
Cue firstCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0)));
|
||||||
assertThat(firstCue.text.toString()).isEqualTo("Some text with over-ruby.");
|
assertThat(firstCue.text.toString()).isEqualTo("Some text with over-ruby.");
|
||||||
assertThat((Spanned) firstCue.text)
|
assertThat((Spanned) firstCue.text)
|
||||||
.hasRubySpanBetween("Some ".length(), "Some text with over-ruby".length())
|
.hasRubySpanBetween("Some ".length(), "Some text with over-ruby".length())
|
||||||
.withTextAndPosition("over", TextAnnotation.POSITION_BEFORE);
|
.withTextAndPosition("over", TextAnnotation.POSITION_BEFORE);
|
||||||
|
|
||||||
// Check that `under` is read from CSS and unspecified defaults to `over`.
|
// Check that `under` is read from CSS and unspecified defaults to `over`.
|
||||||
Cue secondCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2)));
|
Cue secondCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2)));
|
||||||
assertThat(secondCue.text.toString())
|
assertThat(secondCue.text.toString())
|
||||||
@ -364,7 +319,6 @@ public class WebvttDecoderTest {
|
|||||||
"Some text with under-ruby and ".length(),
|
"Some text with under-ruby and ".length(),
|
||||||
"Some text with under-ruby and over-ruby (default)".length())
|
"Some text with under-ruby and over-ruby (default)".length())
|
||||||
.withTextAndPosition("over", TextAnnotation.POSITION_BEFORE);
|
.withTextAndPosition("over", TextAnnotation.POSITION_BEFORE);
|
||||||
|
|
||||||
// Check many <rt> tags with different positions nested in a single <ruby> span.
|
// Check many <rt> tags with different positions nested in a single <ruby> span.
|
||||||
Cue thirdCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(4)));
|
Cue thirdCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(4)));
|
||||||
assertThat(thirdCue.text.toString()).isEqualTo("base1base2base3.");
|
assertThat(thirdCue.text.toString()).isEqualTo("base1base2base3.");
|
||||||
@ -377,7 +331,6 @@ public class WebvttDecoderTest {
|
|||||||
assertThat((Spanned) thirdCue.text)
|
assertThat((Spanned) thirdCue.text)
|
||||||
.hasRubySpanBetween("base1base2".length(), "base1base2base3".length())
|
.hasRubySpanBetween("base1base2".length(), "base1base2base3".length())
|
||||||
.withTextAndPosition("under3", TextAnnotation.POSITION_AFTER);
|
.withTextAndPosition("under3", TextAnnotation.POSITION_AFTER);
|
||||||
|
|
||||||
// Check a <ruby> span with no <rt> tags.
|
// Check a <ruby> span with no <rt> tags.
|
||||||
Cue fourthCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(6)));
|
Cue fourthCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(6)));
|
||||||
assertThat(fourthCue.text.toString()).isEqualTo("Some text with no ruby text.");
|
assertThat(fourthCue.text.toString()).isEqualTo("Some text with no ruby text.");
|
||||||
@ -386,15 +339,12 @@ public class WebvttDecoderTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void decodeWithBadCueHeader() throws Exception {
|
public void decodeWithBadCueHeader() throws Exception {
|
||||||
WebvttSubtitle subtitle = getSubtitleForTestAsset(WITH_BAD_CUE_HEADER_FILE);
|
Subtitle subtitle = getSubtitleForTestAsset(WITH_BAD_CUE_HEADER_FILE);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
|
assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(0)).isEqualTo(0L);
|
assertThat(subtitle.getEventTime(0)).isEqualTo(0L);
|
||||||
assertThat(subtitle.getEventTime(1)).isEqualTo(1_234_000L);
|
assertThat(subtitle.getEventTime(1)).isEqualTo(1_234_000L);
|
||||||
Cue firstCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0)));
|
Cue firstCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0)));
|
||||||
assertThat(firstCue.text.toString()).isEqualTo("This is the first subtitle.");
|
assertThat(firstCue.text.toString()).isEqualTo("This is the first subtitle.");
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(2)).isEqualTo(4_000_000L);
|
assertThat(subtitle.getEventTime(2)).isEqualTo(4_000_000L);
|
||||||
assertThat(subtitle.getEventTime(3)).isEqualTo(5_000_000L);
|
assertThat(subtitle.getEventTime(3)).isEqualTo(5_000_000L);
|
||||||
Cue secondCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2)));
|
Cue secondCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2)));
|
||||||
@ -403,10 +353,8 @@ public class WebvttDecoderTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void decodeWithCssFontSizeStyle() throws Exception {
|
public void decodeWithCssFontSizeStyle() throws Exception {
|
||||||
WebvttSubtitle subtitle = getSubtitleForTestAsset(WITH_FONT_SIZE);
|
Subtitle subtitle = getSubtitleForTestAsset(WITH_FONT_SIZE);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTimeCount()).isEqualTo(12);
|
assertThat(subtitle.getEventTimeCount()).isEqualTo(12);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(0)).isEqualTo(0L);
|
assertThat(subtitle.getEventTime(0)).isEqualTo(0L);
|
||||||
assertThat(subtitle.getEventTime(1)).isEqualTo(2_000_000L);
|
assertThat(subtitle.getEventTime(1)).isEqualTo(2_000_000L);
|
||||||
Cue firstCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0)));
|
Cue firstCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0)));
|
||||||
@ -414,13 +362,11 @@ public class WebvttDecoderTest {
|
|||||||
assertThat((Spanned) firstCue.text)
|
assertThat((Spanned) firstCue.text)
|
||||||
.hasRelativeSizeSpanBetween(0, "Sentence with font-size set to 4.4em.".length())
|
.hasRelativeSizeSpanBetween(0, "Sentence with font-size set to 4.4em.".length())
|
||||||
.withSizeChange(4.4f);
|
.withSizeChange(4.4f);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(2)).isEqualTo(2_100_000L);
|
assertThat(subtitle.getEventTime(2)).isEqualTo(2_100_000L);
|
||||||
assertThat(subtitle.getEventTime(3)).isEqualTo(2_400_000L);
|
assertThat(subtitle.getEventTime(3)).isEqualTo(2_400_000L);
|
||||||
Cue secondCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2)));
|
Cue secondCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2)));
|
||||||
assertThat(secondCue.text.toString()).isEqualTo("Sentence with bad font-size unit.");
|
assertThat(secondCue.text.toString()).isEqualTo("Sentence with bad font-size unit.");
|
||||||
assertThat((Spanned) secondCue.text).hasNoSpans();
|
assertThat((Spanned) secondCue.text).hasNoSpans();
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(4)).isEqualTo(2_500_000L);
|
assertThat(subtitle.getEventTime(4)).isEqualTo(2_500_000L);
|
||||||
assertThat(subtitle.getEventTime(5)).isEqualTo(4_000_000L);
|
assertThat(subtitle.getEventTime(5)).isEqualTo(4_000_000L);
|
||||||
Cue thirdCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(4)));
|
Cue thirdCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(4)));
|
||||||
@ -428,7 +374,6 @@ public class WebvttDecoderTest {
|
|||||||
assertThat((Spanned) thirdCue.text)
|
assertThat((Spanned) thirdCue.text)
|
||||||
.hasAbsoluteSizeSpanBetween(0, "Absolute font-size expressed in px unit!".length())
|
.hasAbsoluteSizeSpanBetween(0, "Absolute font-size expressed in px unit!".length())
|
||||||
.withAbsoluteSize(2);
|
.withAbsoluteSize(2);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(6)).isEqualTo(4_500_000L);
|
assertThat(subtitle.getEventTime(6)).isEqualTo(4_500_000L);
|
||||||
assertThat(subtitle.getEventTime(7)).isEqualTo(6_000_000L);
|
assertThat(subtitle.getEventTime(7)).isEqualTo(6_000_000L);
|
||||||
Cue fourthCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(6)));
|
Cue fourthCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(6)));
|
||||||
@ -436,13 +381,11 @@ public class WebvttDecoderTest {
|
|||||||
assertThat((Spanned) fourthCue.text)
|
assertThat((Spanned) fourthCue.text)
|
||||||
.hasRelativeSizeSpanBetween(0, "Relative font-size expressed in % unit!".length())
|
.hasRelativeSizeSpanBetween(0, "Relative font-size expressed in % unit!".length())
|
||||||
.withSizeChange(0.035f);
|
.withSizeChange(0.035f);
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(8)).isEqualTo(6_100_000L);
|
assertThat(subtitle.getEventTime(8)).isEqualTo(6_100_000L);
|
||||||
assertThat(subtitle.getEventTime(9)).isEqualTo(6_400_000L);
|
assertThat(subtitle.getEventTime(9)).isEqualTo(6_400_000L);
|
||||||
Cue fifthCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(8)));
|
Cue fifthCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(8)));
|
||||||
assertThat(fifthCue.text.toString()).isEqualTo("Sentence with bad font-size value.");
|
assertThat(fifthCue.text.toString()).isEqualTo("Sentence with bad font-size value.");
|
||||||
assertThat((Spanned) secondCue.text).hasNoSpans();
|
assertThat((Spanned) secondCue.text).hasNoSpans();
|
||||||
|
|
||||||
assertThat(subtitle.getEventTime(10)).isEqualTo(6_500_000L);
|
assertThat(subtitle.getEventTime(10)).isEqualTo(6_500_000L);
|
||||||
assertThat(subtitle.getEventTime(11)).isEqualTo(8_000_000L);
|
assertThat(subtitle.getEventTime(11)).isEqualTo(8_000_000L);
|
||||||
Cue sixthCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(10)));
|
Cue sixthCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(10)));
|
||||||
@ -455,8 +398,7 @@ public class WebvttDecoderTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void webvttWithCssStyle() throws Exception {
|
public void webvttWithCssStyle() throws Exception {
|
||||||
WebvttSubtitle subtitle = getSubtitleForTestAsset(WITH_CSS_STYLES);
|
Subtitle subtitle = getSubtitleForTestAsset(WITH_CSS_STYLES);
|
||||||
|
|
||||||
Spanned firstCueText = getUniqueSpanTextAt(subtitle, 0);
|
Spanned firstCueText = getUniqueSpanTextAt(subtitle, 0);
|
||||||
assertThat(firstCueText.toString()).isEqualTo("This is the first subtitle.");
|
assertThat(firstCueText.toString()).isEqualTo("This is the first subtitle.");
|
||||||
assertThat(firstCueText)
|
assertThat(firstCueText)
|
||||||
@ -465,17 +407,14 @@ public class WebvttDecoderTest {
|
|||||||
assertThat(firstCueText)
|
assertThat(firstCueText)
|
||||||
.hasBackgroundColorSpanBetween(0, firstCueText.length())
|
.hasBackgroundColorSpanBetween(0, firstCueText.length())
|
||||||
.withColor(ColorParser.parseCssColor("green"));
|
.withColor(ColorParser.parseCssColor("green"));
|
||||||
|
|
||||||
Spanned secondCueText = getUniqueSpanTextAt(subtitle, 2_345_000);
|
Spanned secondCueText = getUniqueSpanTextAt(subtitle, 2_345_000);
|
||||||
assertThat(secondCueText.toString()).isEqualTo("This is the second subtitle.");
|
assertThat(secondCueText.toString()).isEqualTo("This is the second subtitle.");
|
||||||
assertThat(secondCueText)
|
assertThat(secondCueText)
|
||||||
.hasForegroundColorSpanBetween(0, secondCueText.length())
|
.hasForegroundColorSpanBetween(0, secondCueText.length())
|
||||||
.withColor(ColorParser.parseCssColor("peachpuff"));
|
.withColor(ColorParser.parseCssColor("peachpuff"));
|
||||||
|
|
||||||
Spanned thirdCueText = getUniqueSpanTextAt(subtitle, 20_000_000);
|
Spanned thirdCueText = getUniqueSpanTextAt(subtitle, 20_000_000);
|
||||||
assertThat(thirdCueText.toString()).isEqualTo("This is a reference by element");
|
assertThat(thirdCueText.toString()).isEqualTo("This is a reference by element");
|
||||||
assertThat(thirdCueText).hasUnderlineSpanBetween("This is a ".length(), thirdCueText.length());
|
assertThat(thirdCueText).hasUnderlineSpanBetween("This is a ".length(), thirdCueText.length());
|
||||||
|
|
||||||
Spanned fourthCueText = getUniqueSpanTextAt(subtitle, 25_000_000);
|
Spanned fourthCueText = getUniqueSpanTextAt(subtitle, 25_000_000);
|
||||||
assertThat(fourthCueText.toString()).isEqualTo("You are an idiot\nYou don't have the guts");
|
assertThat(fourthCueText.toString()).isEqualTo("You are an idiot\nYou don't have the guts");
|
||||||
assertThat(fourthCueText)
|
assertThat(fourthCueText)
|
||||||
@ -487,7 +426,7 @@ public class WebvttDecoderTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void withComplexCssSelectors() throws Exception {
|
public void withComplexCssSelectors() throws Exception {
|
||||||
WebvttSubtitle subtitle = getSubtitleForTestAsset(WITH_CSS_COMPLEX_SELECTORS);
|
Subtitle subtitle = getSubtitleForTestAsset(WITH_CSS_COMPLEX_SELECTORS);
|
||||||
Spanned firstCueText = getUniqueSpanTextAt(subtitle, /* timeUs= */ 0);
|
Spanned firstCueText = getUniqueSpanTextAt(subtitle, /* timeUs= */ 0);
|
||||||
assertThat(firstCueText).hasUnderlineSpanBetween(0, firstCueText.length());
|
assertThat(firstCueText).hasUnderlineSpanBetween(0, firstCueText.length());
|
||||||
assertThat(firstCueText)
|
assertThat(firstCueText)
|
||||||
@ -497,26 +436,22 @@ public class WebvttDecoderTest {
|
|||||||
assertThat(firstCueText)
|
assertThat(firstCueText)
|
||||||
.hasTypefaceSpanBetween("This should be underlined and ".length(), firstCueText.length())
|
.hasTypefaceSpanBetween("This should be underlined and ".length(), firstCueText.length())
|
||||||
.withFamily("courier");
|
.withFamily("courier");
|
||||||
|
|
||||||
Spanned secondCueText = getUniqueSpanTextAt(subtitle, /* timeUs= */ 2_000_000);
|
Spanned secondCueText = getUniqueSpanTextAt(subtitle, /* timeUs= */ 2_000_000);
|
||||||
assertThat(secondCueText)
|
assertThat(secondCueText)
|
||||||
.hasTypefaceSpanBetween("This ".length(), secondCueText.length())
|
.hasTypefaceSpanBetween("This ".length(), secondCueText.length())
|
||||||
.withFamily("courier");
|
.withFamily("courier");
|
||||||
assertThat(secondCueText)
|
assertThat(secondCueText)
|
||||||
.hasNoForegroundColorSpanBetween("This ".length(), secondCueText.length());
|
.hasNoForegroundColorSpanBetween("This ".length(), secondCueText.length());
|
||||||
|
|
||||||
Spanned thirdCueText = getUniqueSpanTextAt(subtitle, /* timeUs= */ 2_500_000);
|
Spanned thirdCueText = getUniqueSpanTextAt(subtitle, /* timeUs= */ 2_500_000);
|
||||||
assertThat(thirdCueText).hasBoldSpanBetween("This ".length(), thirdCueText.length());
|
assertThat(thirdCueText).hasBoldSpanBetween("This ".length(), thirdCueText.length());
|
||||||
assertThat(thirdCueText)
|
assertThat(thirdCueText)
|
||||||
.hasTypefaceSpanBetween("This ".length(), thirdCueText.length())
|
.hasTypefaceSpanBetween("This ".length(), thirdCueText.length())
|
||||||
.withFamily("courier");
|
.withFamily("courier");
|
||||||
|
|
||||||
Spanned fourthCueText = getUniqueSpanTextAt(subtitle, /* timeUs= */ 4_000_000);
|
Spanned fourthCueText = getUniqueSpanTextAt(subtitle, /* timeUs= */ 4_000_000);
|
||||||
assertThat(fourthCueText)
|
assertThat(fourthCueText)
|
||||||
.hasNoStyleSpanBetween("This ".length(), "shouldn't be bold.".length());
|
.hasNoStyleSpanBetween("This ".length(), "shouldn't be bold.".length());
|
||||||
assertThat(fourthCueText)
|
assertThat(fourthCueText)
|
||||||
.hasBoldSpanBetween("This shouldn't be bold.\nThis ".length(), fourthCueText.length());
|
.hasBoldSpanBetween("This shouldn't be bold.\nThis ".length(), fourthCueText.length());
|
||||||
|
|
||||||
Spanned fifthCueText = getUniqueSpanTextAt(subtitle, /* timeUs= */ 5_000_000);
|
Spanned fifthCueText = getUniqueSpanTextAt(subtitle, /* timeUs= */ 5_000_000);
|
||||||
assertThat(fifthCueText)
|
assertThat(fifthCueText)
|
||||||
.hasNoStyleSpanBetween("This is ".length(), "This is specific".length());
|
.hasNoStyleSpanBetween("This is ".length(), "This is specific".length());
|
||||||
@ -526,26 +461,25 @@ public class WebvttDecoderTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void webvttWithCssTextCombineUpright() throws Exception {
|
public void webvttWithCssTextCombineUpright() throws Exception {
|
||||||
WebvttSubtitle subtitle = getSubtitleForTestAsset(WITH_CSS_TEXT_COMBINE_UPRIGHT);
|
Subtitle subtitle = getSubtitleForTestAsset(WITH_CSS_TEXT_COMBINE_UPRIGHT);
|
||||||
|
|
||||||
Spanned firstCueText = getUniqueSpanTextAt(subtitle, 500_000);
|
Spanned firstCueText = getUniqueSpanTextAt(subtitle, 500_000);
|
||||||
assertThat(firstCueText)
|
assertThat(firstCueText)
|
||||||
.hasHorizontalTextInVerticalContextSpanBetween("Combine ".length(), "Combine all".length());
|
.hasHorizontalTextInVerticalContextSpanBetween("Combine ".length(), "Combine all".length());
|
||||||
|
|
||||||
Spanned secondCueText = getUniqueSpanTextAt(subtitle, 3_500_000);
|
Spanned secondCueText = getUniqueSpanTextAt(subtitle, 3_500_000);
|
||||||
assertThat(secondCueText)
|
assertThat(secondCueText)
|
||||||
.hasHorizontalTextInVerticalContextSpanBetween(
|
.hasHorizontalTextInVerticalContextSpanBetween(
|
||||||
"Combine ".length(), "Combine 0004".length());
|
"Combine ".length(), "Combine 0004".length());
|
||||||
}
|
}
|
||||||
|
|
||||||
private WebvttSubtitle getSubtitleForTestAsset(String asset)
|
private Subtitle getSubtitleForTestAsset(String asset) throws IOException {
|
||||||
throws IOException, SubtitleDecoderException {
|
DelegatingSubtitleDecoder decoder =
|
||||||
WebvttDecoder decoder = new WebvttDecoder();
|
new DelegatingSubtitleDecoder(
|
||||||
|
"DelegatingSubtitleDecoderWithWebvttParser", new WebvttParser());
|
||||||
byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), asset);
|
byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), asset);
|
||||||
return (WebvttSubtitle) decoder.decode(bytes, bytes.length, /* reset= */ false);
|
return decoder.decode(bytes, bytes.length, /* reset= */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Spanned getUniqueSpanTextAt(WebvttSubtitle sub, long timeUs) {
|
private Spanned getUniqueSpanTextAt(Subtitle sub, long timeUs) {
|
||||||
return (Spanned) Assertions.checkNotNull(sub.getCues(timeUs).get(0).text);
|
return (Spanned) Assertions.checkNotNull(sub.getCues(timeUs).get(0).text);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -25,6 +25,7 @@ import androidx.media3.extractor.text.ssa.SsaParser;
|
|||||||
import androidx.media3.extractor.text.subrip.SubripParser;
|
import androidx.media3.extractor.text.subrip.SubripParser;
|
||||||
import androidx.media3.extractor.text.tx3g.Tx3gParser;
|
import androidx.media3.extractor.text.tx3g.Tx3gParser;
|
||||||
import androidx.media3.extractor.text.webvtt.Mp4WebvttParser;
|
import androidx.media3.extractor.text.webvtt.Mp4WebvttParser;
|
||||||
|
import androidx.media3.extractor.text.webvtt.WebvttParser;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -33,9 +34,10 @@ import java.util.Objects;
|
|||||||
* <p>The formats supported by this factory are:
|
* <p>The formats supported by this factory are:
|
||||||
*
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
|
* <li>SSA/ASS ({@link SsaParser})
|
||||||
|
* <li>WebVTT ({@link WebvttParser})
|
||||||
* <li>WebVTT (MP4) ({@link Mp4WebvttParser})
|
* <li>WebVTT (MP4) ({@link Mp4WebvttParser})
|
||||||
* <li>SubRip ({@link SubripParser})
|
* <li>SubRip ({@link SubripParser})
|
||||||
* <li>SSA/ASS ({@link SsaParser})
|
|
||||||
* <li>TX3G ({@link Tx3gParser})
|
* <li>TX3G ({@link Tx3gParser})
|
||||||
* <li>PGS ({@link PgsParser})
|
* <li>PGS ({@link PgsParser})
|
||||||
* <li>DVB ({@link DvbParser})
|
* <li>DVB ({@link DvbParser})
|
||||||
@ -48,6 +50,7 @@ public final class DefaultSubtitleParserFactory implements SubtitleParser.Factor
|
|||||||
public boolean supportsFormat(Format format) {
|
public boolean supportsFormat(Format format) {
|
||||||
@Nullable String mimeType = format.sampleMimeType;
|
@Nullable String mimeType = format.sampleMimeType;
|
||||||
return Objects.equals(mimeType, MimeTypes.TEXT_SSA)
|
return Objects.equals(mimeType, MimeTypes.TEXT_SSA)
|
||||||
|
|| Objects.equals(mimeType, MimeTypes.TEXT_VTT)
|
||||||
|| Objects.equals(mimeType, MimeTypes.APPLICATION_MP4VTT)
|
|| Objects.equals(mimeType, MimeTypes.APPLICATION_MP4VTT)
|
||||||
|| Objects.equals(mimeType, MimeTypes.APPLICATION_SUBRIP)
|
|| Objects.equals(mimeType, MimeTypes.APPLICATION_SUBRIP)
|
||||||
|| Objects.equals(mimeType, MimeTypes.APPLICATION_TX3G)
|
|| Objects.equals(mimeType, MimeTypes.APPLICATION_TX3G)
|
||||||
@ -62,6 +65,8 @@ public final class DefaultSubtitleParserFactory implements SubtitleParser.Factor
|
|||||||
switch (mimeType) {
|
switch (mimeType) {
|
||||||
case MimeTypes.TEXT_SSA:
|
case MimeTypes.TEXT_SSA:
|
||||||
return new SsaParser(format.initializationData);
|
return new SsaParser(format.initializationData);
|
||||||
|
case MimeTypes.TEXT_VTT:
|
||||||
|
return new WebvttParser();
|
||||||
case MimeTypes.APPLICATION_MP4VTT:
|
case MimeTypes.APPLICATION_MP4VTT:
|
||||||
return new Mp4WebvttParser();
|
return new Mp4WebvttParser();
|
||||||
case MimeTypes.APPLICATION_SUBRIP:
|
case MimeTypes.APPLICATION_SUBRIP:
|
||||||
|
@ -20,19 +20,19 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.media3.common.ParserException;
|
import androidx.media3.common.ParserException;
|
||||||
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.SimpleSubtitleDecoder;
|
import androidx.media3.extractor.text.CuesWithTiming;
|
||||||
import androidx.media3.extractor.text.Subtitle;
|
import androidx.media3.extractor.text.SubtitleParser;
|
||||||
import androidx.media3.extractor.text.SubtitleDecoderException;
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link SimpleSubtitleDecoder} for WebVTT.
|
* A {@link SubtitleParser} for WebVTT.
|
||||||
*
|
*
|
||||||
* <p>See the <a href="http://dev.w3.org/html5/webvtt">WebVTT specification</a>.
|
* <p>See the <a href="http://dev.w3.org/html5/webvtt">WebVTT specification</a>.
|
||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public final class WebvttDecoder extends SimpleSubtitleDecoder {
|
public final class WebvttParser implements SubtitleParser {
|
||||||
|
|
||||||
private static final int EVENT_NONE = -1;
|
private static final int EVENT_NONE = -1;
|
||||||
private static final int EVENT_END_OF_FILE = 0;
|
private static final int EVENT_END_OF_FILE = 0;
|
||||||
@ -46,23 +46,22 @@ public final class WebvttDecoder extends SimpleSubtitleDecoder {
|
|||||||
private final ParsableByteArray parsableWebvttData;
|
private final ParsableByteArray parsableWebvttData;
|
||||||
private final WebvttCssParser cssParser;
|
private final WebvttCssParser cssParser;
|
||||||
|
|
||||||
public WebvttDecoder() {
|
public WebvttParser() {
|
||||||
super("WebvttDecoder");
|
|
||||||
parsableWebvttData = new ParsableByteArray();
|
parsableWebvttData = new ParsableByteArray();
|
||||||
cssParser = new WebvttCssParser();
|
cssParser = new WebvttCssParser();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Subtitle decode(byte[] data, int length, boolean reset)
|
public ImmutableList<CuesWithTiming> parse(byte[] data, int offset, int length) {
|
||||||
throws SubtitleDecoderException {
|
|
||||||
parsableWebvttData.reset(data, length);
|
parsableWebvttData.reset(data, length);
|
||||||
|
parsableWebvttData.setPosition(offset);
|
||||||
List<WebvttCssStyle> definedStyles = new ArrayList<>();
|
List<WebvttCssStyle> definedStyles = new ArrayList<>();
|
||||||
|
|
||||||
// Validate the first line of the header, and skip the remainder.
|
// Validate the first line of the header, and skip the remainder.
|
||||||
try {
|
try {
|
||||||
WebvttParserUtil.validateWebvttHeaderLine(parsableWebvttData);
|
WebvttParserUtil.validateWebvttHeaderLine(parsableWebvttData);
|
||||||
} catch (ParserException e) {
|
} catch (ParserException e) {
|
||||||
throw new SubtitleDecoderException(e);
|
throw new IllegalArgumentException(e);
|
||||||
}
|
}
|
||||||
while (!TextUtils.isEmpty(parsableWebvttData.readLine())) {}
|
while (!TextUtils.isEmpty(parsableWebvttData.readLine())) {}
|
||||||
|
|
||||||
@ -73,7 +72,7 @@ public final class WebvttDecoder extends SimpleSubtitleDecoder {
|
|||||||
skipComment(parsableWebvttData);
|
skipComment(parsableWebvttData);
|
||||||
} else if (event == EVENT_STYLE_BLOCK) {
|
} else if (event == EVENT_STYLE_BLOCK) {
|
||||||
if (!cueInfos.isEmpty()) {
|
if (!cueInfos.isEmpty()) {
|
||||||
throw new SubtitleDecoderException("A style block was found after the first cue.");
|
throw new IllegalArgumentException("A style block was found after the first cue.");
|
||||||
}
|
}
|
||||||
parsableWebvttData.readLine(); // Consume the "STYLE" header.
|
parsableWebvttData.readLine(); // Consume the "STYLE" header.
|
||||||
definedStyles.addAll(cssParser.parseBlock(parsableWebvttData));
|
definedStyles.addAll(cssParser.parseBlock(parsableWebvttData));
|
||||||
@ -85,9 +84,13 @@ public final class WebvttDecoder extends SimpleSubtitleDecoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new WebvttSubtitle(cueInfos);
|
WebvttSubtitle subtitle = new WebvttSubtitle(cueInfos);
|
||||||
|
return subtitle.toCuesWithTimingList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Positions the input right before the next event, and returns the kind of event found. Does not
|
* Positions the input right before the next event, and returns the kind of event found. Does not
|
||||||
* consume any data from such event, if any.
|
* consume any data from such event, if any.
|
@ -21,8 +21,9 @@ import static org.junit.Assert.assertThrows;
|
|||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.MimeTypes;
|
import androidx.media3.common.MimeTypes;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
|
import androidx.media3.exoplayer.text.DelegatingSubtitleDecoder;
|
||||||
import androidx.media3.extractor.Extractor;
|
import androidx.media3.extractor.Extractor;
|
||||||
import androidx.media3.extractor.text.webvtt.WebvttDecoder;
|
import androidx.media3.extractor.text.webvtt.WebvttParser;
|
||||||
import androidx.media3.test.utils.FakeExtractorInput;
|
import androidx.media3.test.utils.FakeExtractorInput;
|
||||||
import androidx.media3.test.utils.FakeExtractorOutput;
|
import androidx.media3.test.utils.FakeExtractorOutput;
|
||||||
import androidx.media3.test.utils.FakeTrackOutput;
|
import androidx.media3.test.utils.FakeTrackOutput;
|
||||||
@ -64,7 +65,8 @@ public class SubtitleExtractorTest {
|
|||||||
.build();
|
.build();
|
||||||
SubtitleExtractor extractor =
|
SubtitleExtractor extractor =
|
||||||
new SubtitleExtractor(
|
new SubtitleExtractor(
|
||||||
new WebvttDecoder(),
|
new DelegatingSubtitleDecoder(
|
||||||
|
"DelegatingSubtitleDecoderWithWebvttParser", new WebvttParser()),
|
||||||
new Format.Builder().setSampleMimeType(MimeTypes.TEXT_VTT).build());
|
new Format.Builder().setSampleMimeType(MimeTypes.TEXT_VTT).build());
|
||||||
extractor.init(output);
|
extractor.init(output);
|
||||||
|
|
||||||
@ -107,7 +109,8 @@ public class SubtitleExtractorTest {
|
|||||||
.build();
|
.build();
|
||||||
SubtitleExtractor extractor =
|
SubtitleExtractor extractor =
|
||||||
new SubtitleExtractor(
|
new SubtitleExtractor(
|
||||||
new WebvttDecoder(),
|
new DelegatingSubtitleDecoder(
|
||||||
|
"DelegatingSubtitleDecoderWithWebvttParser", new WebvttParser()),
|
||||||
new Format.Builder().setSampleMimeType(MimeTypes.TEXT_VTT).build());
|
new Format.Builder().setSampleMimeType(MimeTypes.TEXT_VTT).build());
|
||||||
extractor.init(output);
|
extractor.init(output);
|
||||||
FakeTrackOutput trackOutput = output.trackOutputs.get(0);
|
FakeTrackOutput trackOutput = output.trackOutputs.get(0);
|
||||||
@ -149,7 +152,8 @@ public class SubtitleExtractorTest {
|
|||||||
.build();
|
.build();
|
||||||
SubtitleExtractor extractor =
|
SubtitleExtractor extractor =
|
||||||
new SubtitleExtractor(
|
new SubtitleExtractor(
|
||||||
new WebvttDecoder(),
|
new DelegatingSubtitleDecoder(
|
||||||
|
"DelegatingSubtitleDecoderWithWebvttParser", new WebvttParser()),
|
||||||
new Format.Builder().setSampleMimeType(MimeTypes.TEXT_VTT).build());
|
new Format.Builder().setSampleMimeType(MimeTypes.TEXT_VTT).build());
|
||||||
extractor.init(output);
|
extractor.init(output);
|
||||||
FakeTrackOutput trackOutput = output.trackOutputs.get(0);
|
FakeTrackOutput trackOutput = output.trackOutputs.get(0);
|
||||||
@ -185,7 +189,10 @@ public class SubtitleExtractorTest {
|
|||||||
public void read_withoutInit_fails() {
|
public void read_withoutInit_fails() {
|
||||||
FakeExtractorInput input = new FakeExtractorInput.Builder().setData(new byte[0]).build();
|
FakeExtractorInput input = new FakeExtractorInput.Builder().setData(new byte[0]).build();
|
||||||
SubtitleExtractor extractor =
|
SubtitleExtractor extractor =
|
||||||
new SubtitleExtractor(new WebvttDecoder(), new Format.Builder().build());
|
new SubtitleExtractor(
|
||||||
|
new DelegatingSubtitleDecoder(
|
||||||
|
"DelegatingSubtitleDecoderWithWebvttParser", new WebvttParser()),
|
||||||
|
new Format.Builder().build());
|
||||||
|
|
||||||
assertThrows(IllegalStateException.class, () -> extractor.read(input, null));
|
assertThrows(IllegalStateException.class, () -> extractor.read(input, null));
|
||||||
}
|
}
|
||||||
@ -194,7 +201,10 @@ public class SubtitleExtractorTest {
|
|||||||
public void read_afterRelease_fails() {
|
public void read_afterRelease_fails() {
|
||||||
FakeExtractorInput input = new FakeExtractorInput.Builder().setData(new byte[0]).build();
|
FakeExtractorInput input = new FakeExtractorInput.Builder().setData(new byte[0]).build();
|
||||||
SubtitleExtractor extractor =
|
SubtitleExtractor extractor =
|
||||||
new SubtitleExtractor(new WebvttDecoder(), new Format.Builder().build());
|
new SubtitleExtractor(
|
||||||
|
new DelegatingSubtitleDecoder(
|
||||||
|
"DelegatingSubtitleDecoderWithWebvttParser", new WebvttParser()),
|
||||||
|
new Format.Builder().build());
|
||||||
FakeExtractorOutput output = new FakeExtractorOutput();
|
FakeExtractorOutput output = new FakeExtractorOutput();
|
||||||
|
|
||||||
extractor.init(output);
|
extractor.init(output);
|
||||||
@ -206,7 +216,10 @@ public class SubtitleExtractorTest {
|
|||||||
@Test
|
@Test
|
||||||
public void seek_withoutInit_fails() {
|
public void seek_withoutInit_fails() {
|
||||||
SubtitleExtractor extractor =
|
SubtitleExtractor extractor =
|
||||||
new SubtitleExtractor(new WebvttDecoder(), new Format.Builder().build());
|
new SubtitleExtractor(
|
||||||
|
new DelegatingSubtitleDecoder(
|
||||||
|
"DelegatingSubtitleDecoderWithWebvttParser", new WebvttParser()),
|
||||||
|
new Format.Builder().build());
|
||||||
|
|
||||||
assertThrows(IllegalStateException.class, () -> extractor.seek(0, 0));
|
assertThrows(IllegalStateException.class, () -> extractor.seek(0, 0));
|
||||||
}
|
}
|
||||||
@ -214,7 +227,10 @@ public class SubtitleExtractorTest {
|
|||||||
@Test
|
@Test
|
||||||
public void seek_afterRelease_fails() {
|
public void seek_afterRelease_fails() {
|
||||||
SubtitleExtractor extractor =
|
SubtitleExtractor extractor =
|
||||||
new SubtitleExtractor(new WebvttDecoder(), new Format.Builder().build());
|
new SubtitleExtractor(
|
||||||
|
new DelegatingSubtitleDecoder(
|
||||||
|
"DelegatingSubtitleDecoderWithWebvttParser", new WebvttParser()),
|
||||||
|
new Format.Builder().build());
|
||||||
FakeExtractorOutput output = new FakeExtractorOutput();
|
FakeExtractorOutput output = new FakeExtractorOutput();
|
||||||
|
|
||||||
extractor.init(output);
|
extractor.init(output);
|
||||||
@ -226,7 +242,10 @@ public class SubtitleExtractorTest {
|
|||||||
@Test
|
@Test
|
||||||
public void released_calledTwice() {
|
public void released_calledTwice() {
|
||||||
SubtitleExtractor extractor =
|
SubtitleExtractor extractor =
|
||||||
new SubtitleExtractor(new WebvttDecoder(), new Format.Builder().build());
|
new SubtitleExtractor(
|
||||||
|
new DelegatingSubtitleDecoder(
|
||||||
|
"DelegatingSubtitleDecoderWithWebvttParser", new WebvttParser()),
|
||||||
|
new Format.Builder().build());
|
||||||
FakeExtractorOutput output = new FakeExtractorOutput();
|
FakeExtractorOutput output = new FakeExtractorOutput();
|
||||||
|
|
||||||
extractor.init(output);
|
extractor.init(output);
|
||||||
|
@ -0,0 +1,563 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 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
|
||||||
|
*
|
||||||
|
* http://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 androidx.media3.test.utils.truth.SpannedSubject.assertThat;
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
|
|
||||||
|
import android.text.Layout.Alignment;
|
||||||
|
import android.text.Spanned;
|
||||||
|
import androidx.media3.common.text.Cue;
|
||||||
|
import androidx.media3.common.text.TextAnnotation;
|
||||||
|
import androidx.media3.common.util.Assertions;
|
||||||
|
import androidx.media3.common.util.ColorParser;
|
||||||
|
import androidx.media3.extractor.text.CuesWithTiming;
|
||||||
|
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.truth.Expect;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
/** Unit test for {@link WebvttParser}. */
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class WebvttParserTest {
|
||||||
|
|
||||||
|
private static final String TYPICAL_FILE = "media/webvtt/typical";
|
||||||
|
private static final String TYPICAL_WITH_BAD_TIMESTAMPS =
|
||||||
|
"media/webvtt/typical_with_bad_timestamps";
|
||||||
|
private static final String TYPICAL_WITH_IDS_FILE = "media/webvtt/typical_with_identifiers";
|
||||||
|
private static final String TYPICAL_WITH_COMMENTS_FILE = "media/webvtt/typical_with_comments";
|
||||||
|
private static final String WITH_POSITIONING_FILE = "media/webvtt/with_positioning";
|
||||||
|
private static final String WITH_OVERLAPPING_TIMESTAMPS_FILE =
|
||||||
|
"media/webvtt/with_overlapping_timestamps";
|
||||||
|
private static final String WITH_VERTICAL_FILE = "media/webvtt/with_vertical";
|
||||||
|
private static final String WITH_RUBIES_FILE = "media/webvtt/with_rubies";
|
||||||
|
private static final String WITH_BAD_CUE_HEADER_FILE = "media/webvtt/with_bad_cue_header";
|
||||||
|
private static final String WITH_TAGS_FILE = "media/webvtt/with_tags";
|
||||||
|
private static final String WITH_CSS_STYLES = "media/webvtt/with_css_styles";
|
||||||
|
private static final String WITH_FONT_SIZE = "media/webvtt/with_font_size";
|
||||||
|
private static final String WITH_CSS_COMPLEX_SELECTORS =
|
||||||
|
"media/webvtt/with_css_complex_selectors";
|
||||||
|
private static final String WITH_CSS_TEXT_COMBINE_UPRIGHT =
|
||||||
|
"media/webvtt/with_css_text_combine_upright";
|
||||||
|
private static final String WITH_BOM = "media/webvtt/with_bom";
|
||||||
|
private static final String EMPTY_FILE = "media/webvtt/empty";
|
||||||
|
|
||||||
|
@Rule public final Expect expect = Expect.create();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parseEmpty() throws IOException {
|
||||||
|
WebvttParser parser = new WebvttParser();
|
||||||
|
byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), EMPTY_FILE);
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> parser.parse(bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parseTypical() throws Exception {
|
||||||
|
List<CuesWithTiming> allCues = getCuesForTestAsset(TYPICAL_FILE);
|
||||||
|
|
||||||
|
assertThat(allCues).hasSize(2);
|
||||||
|
|
||||||
|
assertThat(allCues.get(0).startTimeUs).isEqualTo(0L);
|
||||||
|
assertThat(allCues.get(0).durationUs).isEqualTo(1_234_000L);
|
||||||
|
Cue firstCue = Iterables.getOnlyElement(allCues.get(0).cues);
|
||||||
|
assertThat(firstCue.text.toString()).isEqualTo("This is the first subtitle.");
|
||||||
|
|
||||||
|
assertThat(allCues.get(1).startTimeUs).isEqualTo(2_345_000L);
|
||||||
|
assertThat(allCues.get(1).durationUs).isEqualTo(3_456_000L - 2_345_000L);
|
||||||
|
Cue secondCue = Iterables.getOnlyElement(allCues.get(1).cues);
|
||||||
|
assertThat(secondCue.text.toString()).isEqualTo("This is the second subtitle.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parseWithBom() throws Exception {
|
||||||
|
List<CuesWithTiming> allCues = getCuesForTestAsset(WITH_BOM);
|
||||||
|
|
||||||
|
assertThat(allCues).hasSize(2);
|
||||||
|
|
||||||
|
assertThat(allCues.get(0).startTimeUs).isEqualTo(0L);
|
||||||
|
assertThat(allCues.get(0).durationUs).isEqualTo(1_234_000L);
|
||||||
|
Cue firstCue = Iterables.getOnlyElement(allCues.get(0).cues);
|
||||||
|
assertThat(firstCue.text.toString()).isEqualTo("This is the first subtitle.");
|
||||||
|
|
||||||
|
assertThat(allCues.get(1).startTimeUs).isEqualTo(2_345_000L);
|
||||||
|
assertThat(allCues.get(1).durationUs).isEqualTo(3_456_000L - 2_345_000L);
|
||||||
|
Cue secondCue = Iterables.getOnlyElement(allCues.get(1).cues);
|
||||||
|
assertThat(secondCue.text.toString()).isEqualTo("This is the second subtitle.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parseTypicalWithBadTimestamps() throws Exception {
|
||||||
|
List<CuesWithTiming> allCues = getCuesForTestAsset(TYPICAL_WITH_BAD_TIMESTAMPS);
|
||||||
|
|
||||||
|
assertThat(allCues).hasSize(2);
|
||||||
|
|
||||||
|
assertThat(allCues.get(0).startTimeUs).isEqualTo(0L);
|
||||||
|
assertThat(allCues.get(0).durationUs).isEqualTo(1_234_000L);
|
||||||
|
Cue firstCue = Iterables.getOnlyElement(allCues.get(0).cues);
|
||||||
|
assertThat(firstCue.text.toString()).isEqualTo("This is the first subtitle.");
|
||||||
|
|
||||||
|
assertThat(allCues.get(1).startTimeUs).isEqualTo(2_345_000L);
|
||||||
|
assertThat(allCues.get(1).durationUs).isEqualTo(3_456_000L - 2_345_000L);
|
||||||
|
Cue secondCue = Iterables.getOnlyElement(allCues.get(1).cues);
|
||||||
|
assertThat(secondCue.text.toString()).isEqualTo("This is the second subtitle.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parseTypicalWithIds() throws Exception {
|
||||||
|
List<CuesWithTiming> allCues = getCuesForTestAsset(TYPICAL_WITH_IDS_FILE);
|
||||||
|
|
||||||
|
assertThat(allCues).hasSize(2);
|
||||||
|
|
||||||
|
assertThat(allCues.get(0).startTimeUs).isEqualTo(0L);
|
||||||
|
assertThat(allCues.get(0).durationUs).isEqualTo(1_234_000L);
|
||||||
|
Cue firstCue = Iterables.getOnlyElement(allCues.get(0).cues);
|
||||||
|
assertThat(firstCue.text.toString()).isEqualTo("This is the first subtitle.");
|
||||||
|
|
||||||
|
assertThat(allCues.get(1).startTimeUs).isEqualTo(2_345_000L);
|
||||||
|
assertThat(allCues.get(1).durationUs).isEqualTo(3_456_000L - 2_345_000L);
|
||||||
|
Cue secondCue = Iterables.getOnlyElement(allCues.get(1).cues);
|
||||||
|
assertThat(secondCue.text.toString()).isEqualTo("This is the second subtitle.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parseTypicalWithComments() throws Exception {
|
||||||
|
List<CuesWithTiming> allCues = getCuesForTestAsset(TYPICAL_WITH_COMMENTS_FILE);
|
||||||
|
|
||||||
|
assertThat(allCues).hasSize(2);
|
||||||
|
|
||||||
|
assertThat(allCues.get(0).startTimeUs).isEqualTo(0L);
|
||||||
|
assertThat(allCues.get(0).durationUs).isEqualTo(1_234_000L);
|
||||||
|
Cue firstCue = Iterables.getOnlyElement(allCues.get(0).cues);
|
||||||
|
assertThat(firstCue.text.toString()).isEqualTo("This is the first subtitle.");
|
||||||
|
|
||||||
|
assertThat(allCues.get(1).startTimeUs).isEqualTo(2_345_000L);
|
||||||
|
assertThat(allCues.get(1).durationUs).isEqualTo(3_456_000L - 2_345_000L);
|
||||||
|
Cue secondCue = Iterables.getOnlyElement(allCues.get(1).cues);
|
||||||
|
assertThat(secondCue.text.toString()).isEqualTo("This is the second subtitle.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parseWithTags() throws Exception {
|
||||||
|
List<CuesWithTiming> allCues = getCuesForTestAsset(WITH_TAGS_FILE);
|
||||||
|
|
||||||
|
assertThat(allCues).hasSize(4);
|
||||||
|
|
||||||
|
assertThat(allCues.get(0).startTimeUs).isEqualTo(0L);
|
||||||
|
assertThat(allCues.get(0).durationUs).isEqualTo(1_234_000L);
|
||||||
|
Cue firstCue = Iterables.getOnlyElement(allCues.get(0).cues);
|
||||||
|
assertThat(firstCue.text.toString()).isEqualTo("This is the first subtitle.");
|
||||||
|
|
||||||
|
assertThat(allCues.get(1).startTimeUs).isEqualTo(2_345_000L);
|
||||||
|
assertThat(allCues.get(1).durationUs).isEqualTo(3_456_000L - 2_345_000L);
|
||||||
|
Cue secondCue = Iterables.getOnlyElement(allCues.get(1).cues);
|
||||||
|
assertThat(secondCue.text.toString()).isEqualTo("This is the second subtitle.");
|
||||||
|
|
||||||
|
assertThat(allCues.get(2).startTimeUs).isEqualTo(4_000_000L);
|
||||||
|
assertThat(allCues.get(2).durationUs).isEqualTo(5_000_000L - 4_000_000L);
|
||||||
|
Cue thirdCue = Iterables.getOnlyElement(allCues.get(2).cues);
|
||||||
|
assertThat(thirdCue.text.toString()).isEqualTo("This is the third subtitle.");
|
||||||
|
|
||||||
|
assertThat(allCues.get(3).startTimeUs).isEqualTo(6_000_000L);
|
||||||
|
assertThat(allCues.get(3).durationUs).isEqualTo(7_000_000L - 6_000_000L);
|
||||||
|
Cue fourthCue = Iterables.getOnlyElement(allCues.get(3).cues);
|
||||||
|
assertThat(fourthCue.text.toString()).isEqualTo("This is the <fourth> &subtitle.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parseWithPositioning() throws Exception {
|
||||||
|
List<CuesWithTiming> allCues = getCuesForTestAsset(WITH_POSITIONING_FILE);
|
||||||
|
|
||||||
|
assertThat(allCues).hasSize(8);
|
||||||
|
|
||||||
|
assertThat(allCues.get(0).startTimeUs).isEqualTo(0L);
|
||||||
|
assertThat(allCues.get(0).durationUs).isEqualTo(1_234_000L);
|
||||||
|
Cue firstCue = Iterables.getOnlyElement(allCues.get(0).cues);
|
||||||
|
assertThat(firstCue.text.toString()).isEqualTo("This is the first subtitle.");
|
||||||
|
assertThat(firstCue.position).isEqualTo(0.6f);
|
||||||
|
assertThat(firstCue.positionAnchor).isEqualTo(Cue.ANCHOR_TYPE_END);
|
||||||
|
assertThat(firstCue.textAlignment).isEqualTo(Alignment.ALIGN_NORMAL);
|
||||||
|
assertThat(firstCue.size).isEqualTo(0.35f);
|
||||||
|
|
||||||
|
// Unspecified values should use WebVTT defaults
|
||||||
|
assertThat(firstCue.line).isEqualTo(-1f);
|
||||||
|
assertThat(firstCue.lineType).isEqualTo(Cue.LINE_TYPE_NUMBER);
|
||||||
|
assertThat(firstCue.verticalType).isEqualTo(Cue.TYPE_UNSET);
|
||||||
|
|
||||||
|
assertThat(allCues.get(1).startTimeUs).isEqualTo(2_345_000L);
|
||||||
|
assertThat(allCues.get(1).durationUs).isEqualTo(3_456_000L - 2_345_000L);
|
||||||
|
Cue secondCue = Iterables.getOnlyElement(allCues.get(1).cues);
|
||||||
|
assertThat(secondCue.text.toString()).isEqualTo("This is the second subtitle.");
|
||||||
|
// Position is invalid so defaults to 0.5
|
||||||
|
assertThat(secondCue.position).isEqualTo(0.5f);
|
||||||
|
assertThat(secondCue.textAlignment).isEqualTo(Alignment.ALIGN_OPPOSITE);
|
||||||
|
|
||||||
|
assertThat(allCues.get(2).startTimeUs).isEqualTo(4_000_000L);
|
||||||
|
assertThat(allCues.get(2).durationUs).isEqualTo(5_000_000L - 4_000_000L);
|
||||||
|
Cue thirdCue = Iterables.getOnlyElement(allCues.get(2).cues);
|
||||||
|
assertThat(thirdCue.text.toString()).isEqualTo("This is the third subtitle.");
|
||||||
|
assertThat(thirdCue.line).isEqualTo(0.45f);
|
||||||
|
assertThat(thirdCue.lineType).isEqualTo(Cue.LINE_TYPE_FRACTION);
|
||||||
|
assertThat(thirdCue.lineAnchor).isEqualTo(Cue.ANCHOR_TYPE_END);
|
||||||
|
assertThat(thirdCue.textAlignment).isEqualTo(Alignment.ALIGN_CENTER);
|
||||||
|
// Derived from `align:middle`:
|
||||||
|
assertThat(thirdCue.positionAnchor).isEqualTo(Cue.ANCHOR_TYPE_MIDDLE);
|
||||||
|
|
||||||
|
assertThat(allCues.get(3).startTimeUs).isEqualTo(6_000_000L);
|
||||||
|
assertThat(allCues.get(3).durationUs).isEqualTo(7_000_000L - 6_000_000L);
|
||||||
|
Cue fourthCue = Iterables.getOnlyElement(allCues.get(3).cues);
|
||||||
|
assertThat(fourthCue.text.toString()).isEqualTo("This is the fourth subtitle.");
|
||||||
|
assertThat(fourthCue.line).isEqualTo(-10f);
|
||||||
|
assertThat(fourthCue.lineAnchor).isEqualTo(Cue.ANCHOR_TYPE_START);
|
||||||
|
assertThat(fourthCue.textAlignment).isEqualTo(Alignment.ALIGN_CENTER);
|
||||||
|
// Derived from `align:middle`:
|
||||||
|
assertThat(fourthCue.position).isEqualTo(0.5f);
|
||||||
|
assertThat(fourthCue.positionAnchor).isEqualTo(Cue.ANCHOR_TYPE_MIDDLE);
|
||||||
|
|
||||||
|
assertThat(allCues.get(4).startTimeUs).isEqualTo(8_000_000L);
|
||||||
|
assertThat(allCues.get(4).durationUs).isEqualTo(9_000_000L - 8_000_000L);
|
||||||
|
Cue fifthCue = Iterables.getOnlyElement(allCues.get(4).cues);
|
||||||
|
assertThat(fifthCue.text.toString()).isEqualTo("This is the fifth subtitle.");
|
||||||
|
assertThat(fifthCue.textAlignment).isEqualTo(Alignment.ALIGN_OPPOSITE);
|
||||||
|
// Derived from `align:right`:
|
||||||
|
assertThat(fifthCue.position).isEqualTo(1.0f);
|
||||||
|
assertThat(fifthCue.positionAnchor).isEqualTo(Cue.ANCHOR_TYPE_END);
|
||||||
|
|
||||||
|
assertThat(allCues.get(5).startTimeUs).isEqualTo(10_000_000L);
|
||||||
|
assertThat(allCues.get(5).durationUs).isEqualTo(11_000_000L - 10_000_000L);
|
||||||
|
Cue sixthCue = Iterables.getOnlyElement(allCues.get(5).cues);
|
||||||
|
assertThat(sixthCue.text.toString()).isEqualTo("This is the sixth subtitle.");
|
||||||
|
assertThat(sixthCue.textAlignment).isEqualTo(Alignment.ALIGN_CENTER);
|
||||||
|
// Derived from `align:center`:
|
||||||
|
assertThat(sixthCue.position).isEqualTo(0.5f);
|
||||||
|
assertThat(sixthCue.positionAnchor).isEqualTo(Cue.ANCHOR_TYPE_MIDDLE);
|
||||||
|
|
||||||
|
assertThat(allCues.get(6).startTimeUs).isEqualTo(12_000_000L);
|
||||||
|
assertThat(allCues.get(6).durationUs).isEqualTo(13_000_000L - 12_000_000L);
|
||||||
|
Cue seventhCue = Iterables.getOnlyElement(allCues.get(6).cues);
|
||||||
|
assertThat(seventhCue.text.toString()).isEqualTo("This is the seventh subtitle.");
|
||||||
|
assertThat(seventhCue.positionAnchor).isEqualTo(Cue.ANCHOR_TYPE_START);
|
||||||
|
|
||||||
|
assertThat(allCues.get(7).startTimeUs).isEqualTo(14_000_000L);
|
||||||
|
assertThat(allCues.get(7).durationUs).isEqualTo(15_000_000L - 14_000_000L);
|
||||||
|
Cue eighthCue = Iterables.getOnlyElement(allCues.get(7).cues);
|
||||||
|
assertThat(eighthCue.text.toString()).isEqualTo("This is the eighth subtitle.");
|
||||||
|
assertThat(eighthCue.positionAnchor).isEqualTo(Cue.ANCHOR_TYPE_END);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parseWithOverlappingTimestamps() throws Exception {
|
||||||
|
List<CuesWithTiming> allCues = getCuesForTestAsset(WITH_OVERLAPPING_TIMESTAMPS_FILE);
|
||||||
|
|
||||||
|
assertThat(allCues).hasSize(6);
|
||||||
|
|
||||||
|
assertThat(allCues.get(0).startTimeUs).isEqualTo(0);
|
||||||
|
assertThat(allCues.get(0).durationUs).isEqualTo(1_000_000);
|
||||||
|
Cue firstCue = Iterables.getOnlyElement(allCues.get(0).cues);
|
||||||
|
assertThat(firstCue.text.toString()).isEqualTo("Displayed at the bottom for 3 seconds.");
|
||||||
|
assertThat(firstCue.line).isEqualTo(-1f);
|
||||||
|
assertThat(firstCue.lineType).isEqualTo(Cue.LINE_TYPE_NUMBER);
|
||||||
|
|
||||||
|
assertThat(allCues.get(1).startTimeUs).isEqualTo(1_000_000);
|
||||||
|
assertThat(allCues.get(1).durationUs).isEqualTo(1_000_000);
|
||||||
|
ImmutableList<Cue> firstAndSecondCue = allCues.get(1).cues;
|
||||||
|
assertThat(firstAndSecondCue).hasSize(2);
|
||||||
|
assertThat(firstAndSecondCue.get(0).text.toString())
|
||||||
|
.isEqualTo("Displayed at the bottom for 3 seconds.");
|
||||||
|
assertThat(firstAndSecondCue.get(0).line).isEqualTo(-1f);
|
||||||
|
assertThat(firstAndSecondCue.get(0).lineType).isEqualTo(Cue.LINE_TYPE_NUMBER);
|
||||||
|
assertThat(firstAndSecondCue.get(1).text.toString())
|
||||||
|
.isEqualTo("Appears directly above for 1 second.");
|
||||||
|
assertThat(firstAndSecondCue.get(1).line).isEqualTo(-2f);
|
||||||
|
assertThat(firstAndSecondCue.get(1).lineType).isEqualTo(Cue.LINE_TYPE_NUMBER);
|
||||||
|
|
||||||
|
assertThat(allCues.get(2).startTimeUs).isEqualTo(2_000_000);
|
||||||
|
assertThat(allCues.get(2).durationUs).isEqualTo(1_000_000);
|
||||||
|
Cue thirdCue = Iterables.getOnlyElement(allCues.get(2).cues);
|
||||||
|
assertThat(thirdCue.text.toString()).isEqualTo("Displayed at the bottom for 3 seconds.");
|
||||||
|
assertThat(thirdCue.line).isEqualTo(-1f);
|
||||||
|
assertThat(thirdCue.lineType).isEqualTo(Cue.LINE_TYPE_NUMBER);
|
||||||
|
|
||||||
|
assertThat(allCues.get(3).startTimeUs).isEqualTo(4_000_000);
|
||||||
|
assertThat(allCues.get(3).durationUs).isEqualTo(1_000_000);
|
||||||
|
Cue fourthCue = Iterables.getOnlyElement(allCues.get(3).cues);
|
||||||
|
assertThat(fourthCue.text.toString()).isEqualTo("Displayed at the bottom for 2 seconds.");
|
||||||
|
assertThat(fourthCue.line).isEqualTo(-1f);
|
||||||
|
assertThat(fourthCue.lineType).isEqualTo(Cue.LINE_TYPE_NUMBER);
|
||||||
|
|
||||||
|
assertThat(allCues.get(4).startTimeUs).isEqualTo(5_000_000);
|
||||||
|
assertThat(allCues.get(4).durationUs).isEqualTo(1_000_000);
|
||||||
|
ImmutableList<Cue> fourthAndFifth = allCues.get(4).cues;
|
||||||
|
assertThat(fourthAndFifth).hasSize(2);
|
||||||
|
assertThat(fourthAndFifth.get(0).text.toString())
|
||||||
|
.isEqualTo("Displayed at the bottom for 2 seconds.");
|
||||||
|
assertThat(fourthAndFifth.get(0).line).isEqualTo(-1f);
|
||||||
|
assertThat(fourthAndFifth.get(0).lineType).isEqualTo(Cue.LINE_TYPE_NUMBER);
|
||||||
|
assertThat(fourthAndFifth.get(1).text.toString())
|
||||||
|
.isEqualTo("Appears directly above the previous cue, then replaces it after 1 second.");
|
||||||
|
assertThat(fourthAndFifth.get(1).line).isEqualTo(-2f);
|
||||||
|
assertThat(fourthAndFifth.get(1).lineType).isEqualTo(Cue.LINE_TYPE_NUMBER);
|
||||||
|
|
||||||
|
assertThat(allCues.get(5).startTimeUs).isEqualTo(6_000_000);
|
||||||
|
assertThat(allCues.get(5).durationUs).isEqualTo(1_000_000);
|
||||||
|
Cue sixthCue = Iterables.getOnlyElement(allCues.get(5).cues);
|
||||||
|
assertThat(sixthCue.text.toString())
|
||||||
|
.isEqualTo("Appears directly above the previous cue, then replaces it after 1 second.");
|
||||||
|
assertThat(sixthCue.line).isEqualTo(-1f);
|
||||||
|
assertThat(sixthCue.lineType).isEqualTo(Cue.LINE_TYPE_NUMBER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parseWithVertical() throws Exception {
|
||||||
|
List<CuesWithTiming> allCues = getCuesForTestAsset(WITH_VERTICAL_FILE);
|
||||||
|
|
||||||
|
assertThat(allCues).hasSize(3);
|
||||||
|
|
||||||
|
assertThat(allCues.get(0).startTimeUs).isEqualTo(0L);
|
||||||
|
assertThat(allCues.get(0).durationUs).isEqualTo(1_234_000L);
|
||||||
|
Cue firstCue = Iterables.getOnlyElement(allCues.get(0).cues);
|
||||||
|
assertThat(firstCue.text.toString()).isEqualTo("Vertical right-to-left (e.g. Japanese)");
|
||||||
|
assertThat(firstCue.verticalType).isEqualTo(Cue.VERTICAL_TYPE_RL);
|
||||||
|
|
||||||
|
assertThat(allCues.get(1).startTimeUs).isEqualTo(2_345_000L);
|
||||||
|
assertThat(allCues.get(1).durationUs).isEqualTo(3_456_000L - 2_345_000L);
|
||||||
|
Cue secondCue = Iterables.getOnlyElement(allCues.get(1).cues);
|
||||||
|
assertThat(secondCue.text.toString()).isEqualTo("Vertical left-to-right (e.g. Mongolian)");
|
||||||
|
assertThat(secondCue.verticalType).isEqualTo(Cue.VERTICAL_TYPE_LR);
|
||||||
|
|
||||||
|
assertThat(allCues.get(2).startTimeUs).isEqualTo(4_000_000L);
|
||||||
|
assertThat(allCues.get(2).durationUs).isEqualTo(5_000_000L - 4_000_000L);
|
||||||
|
Cue thirdCue = Iterables.getOnlyElement(allCues.get(2).cues);
|
||||||
|
assertThat(thirdCue.text.toString()).isEqualTo("No vertical setting (i.e. horizontal)");
|
||||||
|
assertThat(thirdCue.verticalType).isEqualTo(Cue.TYPE_UNSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parseWithRubies() throws Exception {
|
||||||
|
List<CuesWithTiming> allCues = getCuesForTestAsset(WITH_RUBIES_FILE);
|
||||||
|
|
||||||
|
assertThat(allCues).hasSize(4);
|
||||||
|
|
||||||
|
// Check that an explicit `over` position is read from CSS.
|
||||||
|
Cue firstCue = Iterables.getOnlyElement(allCues.get(0).cues);
|
||||||
|
assertThat(firstCue.text.toString()).isEqualTo("Some text with over-ruby.");
|
||||||
|
assertThat((Spanned) firstCue.text)
|
||||||
|
.hasRubySpanBetween("Some ".length(), "Some text with over-ruby".length())
|
||||||
|
.withTextAndPosition("over", TextAnnotation.POSITION_BEFORE);
|
||||||
|
|
||||||
|
// Check that `under` is read from CSS and unspecified defaults to `over`.
|
||||||
|
Cue secondCue = Iterables.getOnlyElement(allCues.get(1).cues);
|
||||||
|
assertThat(secondCue.text.toString())
|
||||||
|
.isEqualTo("Some text with under-ruby and over-ruby (default).");
|
||||||
|
assertThat((Spanned) secondCue.text)
|
||||||
|
.hasRubySpanBetween("Some ".length(), "Some text with under-ruby".length())
|
||||||
|
.withTextAndPosition("under", TextAnnotation.POSITION_AFTER);
|
||||||
|
assertThat((Spanned) secondCue.text)
|
||||||
|
.hasRubySpanBetween(
|
||||||
|
"Some text with under-ruby and ".length(),
|
||||||
|
"Some text with under-ruby and over-ruby (default)".length())
|
||||||
|
.withTextAndPosition("over", TextAnnotation.POSITION_BEFORE);
|
||||||
|
|
||||||
|
// Check many <rt> tags with different positions nested in a single <ruby> span.
|
||||||
|
Cue thirdCue = Iterables.getOnlyElement(allCues.get(2).cues);
|
||||||
|
assertThat(thirdCue.text.toString()).isEqualTo("base1base2base3.");
|
||||||
|
assertThat((Spanned) thirdCue.text)
|
||||||
|
.hasRubySpanBetween(/* start= */ 0, "base1".length())
|
||||||
|
.withTextAndPosition("over1", TextAnnotation.POSITION_BEFORE);
|
||||||
|
assertThat((Spanned) thirdCue.text)
|
||||||
|
.hasRubySpanBetween("base1".length(), "base1base2".length())
|
||||||
|
.withTextAndPosition("under2", TextAnnotation.POSITION_AFTER);
|
||||||
|
assertThat((Spanned) thirdCue.text)
|
||||||
|
.hasRubySpanBetween("base1base2".length(), "base1base2base3".length())
|
||||||
|
.withTextAndPosition("under3", TextAnnotation.POSITION_AFTER);
|
||||||
|
|
||||||
|
// Check a <ruby> span with no <rt> tags.
|
||||||
|
Cue fourthCue = Iterables.getOnlyElement(allCues.get(3).cues);
|
||||||
|
assertThat(fourthCue.text.toString()).isEqualTo("Some text with no ruby text.");
|
||||||
|
assertThat((Spanned) fourthCue.text).hasNoSpans();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parseWithBadCueHeader() throws Exception {
|
||||||
|
List<CuesWithTiming> allCues = getCuesForTestAsset(WITH_BAD_CUE_HEADER_FILE);
|
||||||
|
|
||||||
|
assertThat(allCues).hasSize(2);
|
||||||
|
|
||||||
|
assertThat(allCues.get(0).startTimeUs).isEqualTo(0L);
|
||||||
|
assertThat(allCues.get(0).durationUs).isEqualTo(1_234_000L);
|
||||||
|
Cue firstCue = Iterables.getOnlyElement(allCues.get(0).cues);
|
||||||
|
assertThat(firstCue.text.toString()).isEqualTo("This is the first subtitle.");
|
||||||
|
|
||||||
|
assertThat(allCues.get(1).startTimeUs).isEqualTo(4_000_000L);
|
||||||
|
assertThat(allCues.get(1).durationUs).isEqualTo(5_000_000L - 4_000_000L);
|
||||||
|
Cue secondCue = Iterables.getOnlyElement(allCues.get(1).cues);
|
||||||
|
assertThat(secondCue.text.toString()).isEqualTo("This is the third subtitle.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parseWithCssFontSizeStyle() throws Exception {
|
||||||
|
List<CuesWithTiming> allCues = getCuesForTestAsset(WITH_FONT_SIZE);
|
||||||
|
|
||||||
|
assertThat(allCues).hasSize(6);
|
||||||
|
|
||||||
|
assertThat(allCues.get(0).startTimeUs).isEqualTo(0L);
|
||||||
|
assertThat(allCues.get(0).durationUs).isEqualTo(2_000_000L);
|
||||||
|
Cue firstCue = Iterables.getOnlyElement(allCues.get(0).cues);
|
||||||
|
assertThat(firstCue.text.toString()).isEqualTo("Sentence with font-size set to 4.4em.");
|
||||||
|
assertThat((Spanned) firstCue.text)
|
||||||
|
.hasRelativeSizeSpanBetween(0, "Sentence with font-size set to 4.4em.".length())
|
||||||
|
.withSizeChange(4.4f);
|
||||||
|
|
||||||
|
assertThat(allCues.get(1).startTimeUs).isEqualTo(2_100_000L);
|
||||||
|
assertThat(allCues.get(1).durationUs).isEqualTo(2_400_000L - 2_100_000L);
|
||||||
|
Cue secondCue = Iterables.getOnlyElement(allCues.get(1).cues);
|
||||||
|
assertThat(secondCue.text.toString()).isEqualTo("Sentence with bad font-size unit.");
|
||||||
|
assertThat((Spanned) secondCue.text).hasNoSpans();
|
||||||
|
|
||||||
|
assertThat(allCues.get(2).startTimeUs).isEqualTo(2_500_000L);
|
||||||
|
assertThat(allCues.get(2).durationUs).isEqualTo(4_000_000L - 2_500_000L);
|
||||||
|
Cue thirdCue = Iterables.getOnlyElement(allCues.get(2).cues);
|
||||||
|
assertThat(thirdCue.text.toString()).isEqualTo("Absolute font-size expressed in px unit!");
|
||||||
|
assertThat((Spanned) thirdCue.text)
|
||||||
|
.hasAbsoluteSizeSpanBetween(0, "Absolute font-size expressed in px unit!".length())
|
||||||
|
.withAbsoluteSize(2);
|
||||||
|
|
||||||
|
assertThat(allCues.get(3).startTimeUs).isEqualTo(4_500_000L);
|
||||||
|
assertThat(allCues.get(3).durationUs).isEqualTo(6_000_000L - 4_500_000L);
|
||||||
|
Cue fourthCue = Iterables.getOnlyElement(allCues.get(3).cues);
|
||||||
|
assertThat(fourthCue.text.toString()).isEqualTo("Relative font-size expressed in % unit!");
|
||||||
|
assertThat((Spanned) fourthCue.text)
|
||||||
|
.hasRelativeSizeSpanBetween(0, "Relative font-size expressed in % unit!".length())
|
||||||
|
.withSizeChange(0.035f);
|
||||||
|
|
||||||
|
assertThat(allCues.get(4).startTimeUs).isEqualTo(6_100_000L);
|
||||||
|
assertThat(allCues.get(4).durationUs).isEqualTo(6_400_000L - 6_100_000L);
|
||||||
|
Cue fifthCue = Iterables.getOnlyElement(allCues.get(4).cues);
|
||||||
|
assertThat(fifthCue.text.toString()).isEqualTo("Sentence with bad font-size value.");
|
||||||
|
assertThat((Spanned) secondCue.text).hasNoSpans();
|
||||||
|
|
||||||
|
assertThat(allCues.get(5).startTimeUs).isEqualTo(6_500_000L);
|
||||||
|
assertThat(allCues.get(5).durationUs).isEqualTo(8_000_000L - 6_500_000L);
|
||||||
|
Cue sixthCue = Iterables.getOnlyElement(allCues.get(5).cues);
|
||||||
|
assertThat(sixthCue.text.toString())
|
||||||
|
.isEqualTo("Upper and lower case letters in font-size unit.");
|
||||||
|
assertThat((Spanned) sixthCue.text)
|
||||||
|
.hasAbsoluteSizeSpanBetween(0, "Upper and lower case letters in font-size unit.".length())
|
||||||
|
.withAbsoluteSize(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void webvttWithCssStyle() throws Exception {
|
||||||
|
List<CuesWithTiming> allCues = getCuesForTestAsset(WITH_CSS_STYLES);
|
||||||
|
|
||||||
|
Spanned firstCueText = getUniqueSpanTextAt(allCues.get(0));
|
||||||
|
assertThat(firstCueText.toString()).isEqualTo("This is the first subtitle.");
|
||||||
|
assertThat(firstCueText)
|
||||||
|
.hasForegroundColorSpanBetween(0, firstCueText.length())
|
||||||
|
.withColor(ColorParser.parseCssColor("papayawhip"));
|
||||||
|
assertThat(firstCueText)
|
||||||
|
.hasBackgroundColorSpanBetween(0, firstCueText.length())
|
||||||
|
.withColor(ColorParser.parseCssColor("green"));
|
||||||
|
|
||||||
|
Spanned secondCueText = getUniqueSpanTextAt(allCues.get(1));
|
||||||
|
assertThat(secondCueText.toString()).isEqualTo("This is the second subtitle.");
|
||||||
|
assertThat(secondCueText)
|
||||||
|
.hasForegroundColorSpanBetween(0, secondCueText.length())
|
||||||
|
.withColor(ColorParser.parseCssColor("peachpuff"));
|
||||||
|
|
||||||
|
Spanned thirdCueText = getUniqueSpanTextAt(allCues.get(2));
|
||||||
|
assertThat(thirdCueText.toString()).isEqualTo("This is a reference by element");
|
||||||
|
assertThat(thirdCueText).hasUnderlineSpanBetween("This is a ".length(), thirdCueText.length());
|
||||||
|
|
||||||
|
Spanned fourthCueText = getUniqueSpanTextAt(allCues.get(3));
|
||||||
|
assertThat(fourthCueText.toString()).isEqualTo("You are an idiot\nYou don't have the guts");
|
||||||
|
assertThat(fourthCueText)
|
||||||
|
.hasBackgroundColorSpanBetween(0, "You are an idiot".length())
|
||||||
|
.withColor(ColorParser.parseCssColor("lime"));
|
||||||
|
assertThat(fourthCueText)
|
||||||
|
.hasBoldSpanBetween("You are an idiot\n".length(), fourthCueText.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void withComplexCssSelectors() throws Exception {
|
||||||
|
List<CuesWithTiming> allCues = getCuesForTestAsset(WITH_CSS_COMPLEX_SELECTORS);
|
||||||
|
Spanned firstCueText = getUniqueSpanTextAt(allCues.get(0));
|
||||||
|
assertThat(firstCueText).hasUnderlineSpanBetween(0, firstCueText.length());
|
||||||
|
assertThat(firstCueText)
|
||||||
|
.hasForegroundColorSpanBetween(
|
||||||
|
"This should be underlined and ".length(), firstCueText.length())
|
||||||
|
.withColor(ColorParser.parseCssColor("violet"));
|
||||||
|
assertThat(firstCueText)
|
||||||
|
.hasTypefaceSpanBetween("This should be underlined and ".length(), firstCueText.length())
|
||||||
|
.withFamily("courier");
|
||||||
|
|
||||||
|
Spanned secondCueText = getUniqueSpanTextAt(allCues.get(1));
|
||||||
|
assertThat(secondCueText)
|
||||||
|
.hasTypefaceSpanBetween("This ".length(), secondCueText.length())
|
||||||
|
.withFamily("courier");
|
||||||
|
assertThat(secondCueText)
|
||||||
|
.hasNoForegroundColorSpanBetween("This ".length(), secondCueText.length());
|
||||||
|
|
||||||
|
Spanned thirdCueText = getUniqueSpanTextAt(allCues.get(2));
|
||||||
|
assertThat(thirdCueText).hasBoldSpanBetween("This ".length(), thirdCueText.length());
|
||||||
|
assertThat(thirdCueText)
|
||||||
|
.hasTypefaceSpanBetween("This ".length(), thirdCueText.length())
|
||||||
|
.withFamily("courier");
|
||||||
|
|
||||||
|
Spanned fourthCueText = getUniqueSpanTextAt(allCues.get(3));
|
||||||
|
assertThat(fourthCueText)
|
||||||
|
.hasNoStyleSpanBetween("This ".length(), "shouldn't be bold.".length());
|
||||||
|
assertThat(fourthCueText)
|
||||||
|
.hasBoldSpanBetween("This shouldn't be bold.\nThis ".length(), fourthCueText.length());
|
||||||
|
|
||||||
|
Spanned fifthCueText = getUniqueSpanTextAt(allCues.get(4));
|
||||||
|
assertThat(fifthCueText)
|
||||||
|
.hasNoStyleSpanBetween("This is ".length(), "This is specific".length());
|
||||||
|
assertThat(fifthCueText)
|
||||||
|
.hasItalicSpanBetween("This is specific\n".length(), fifthCueText.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void webvttWithCssTextCombineUpright() throws Exception {
|
||||||
|
List<CuesWithTiming> allCues = getCuesForTestAsset(WITH_CSS_TEXT_COMBINE_UPRIGHT);
|
||||||
|
|
||||||
|
Spanned firstCueText = getUniqueSpanTextAt(allCues.get(0));
|
||||||
|
assertThat(firstCueText)
|
||||||
|
.hasHorizontalTextInVerticalContextSpanBetween("Combine ".length(), "Combine all".length());
|
||||||
|
|
||||||
|
Spanned secondCueText = getUniqueSpanTextAt(allCues.get(1));
|
||||||
|
assertThat(secondCueText)
|
||||||
|
.hasHorizontalTextInVerticalContextSpanBetween(
|
||||||
|
"Combine ".length(), "Combine 0004".length());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<CuesWithTiming> getCuesForTestAsset(String asset) throws IOException {
|
||||||
|
WebvttParser parser = new WebvttParser();
|
||||||
|
byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), asset);
|
||||||
|
return parser.parse(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Spanned getUniqueSpanTextAt(CuesWithTiming cuesWithTiming) {
|
||||||
|
return (Spanned) Assertions.checkNotNull(cuesWithTiming.cues.get(0).text);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user