PiperOrigin-RevId: 561352515
This commit is contained in:
ibaker 2023-08-30 08:47:38 -07:00 committed by Copybara-Service
parent b37e37aa3c
commit 64792dfda2
5 changed files with 105 additions and 1018 deletions

View File

@ -24,6 +24,7 @@ import androidx.media3.extractor.text.SubtitleDecoder;
import androidx.media3.extractor.text.SubtitleParser; 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 java.util.Objects; import java.util.Objects;
/** A factory for {@link SubtitleDecoder} instances. */ /** A factory for {@link SubtitleDecoder} instances. */
@ -54,6 +55,7 @@ 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>TTML ({@link TtmlDecoder})
* <li>Cea608 ({@link Cea608Decoder}) * <li>Cea608 ({@link Cea608Decoder})
* <li>Cea708 ({@link Cea708Decoder}) * <li>Cea708 ({@link Cea708Decoder})
* <li>Exoplayer Cues ({@link ExoplayerCuesDecoder}) * <li>Exoplayer Cues ({@link ExoplayerCuesDecoder})
@ -68,6 +70,7 @@ 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.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)
|| Objects.equals(mimeType, MimeTypes.APPLICATION_CEA708) || Objects.equals(mimeType, MimeTypes.APPLICATION_CEA708)
@ -84,6 +87,8 @@ 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.APPLICATION_TTML:
return new TtmlDecoder();
case MimeTypes.APPLICATION_CEA608: case MimeTypes.APPLICATION_CEA608:
case MimeTypes.APPLICATION_MP4CEA608: case MimeTypes.APPLICATION_MP4CEA608:
return new Cea608Decoder( return new Cea608Decoder(

View File

@ -23,7 +23,6 @@ import androidx.media3.extractor.text.dvb.DvbParser;
import androidx.media3.extractor.text.pgs.PgsParser; import androidx.media3.extractor.text.pgs.PgsParser;
import androidx.media3.extractor.text.ssa.SsaParser; 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.ttml.TtmlParser;
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 androidx.media3.extractor.text.webvtt.WebvttParser;
@ -42,7 +41,6 @@ import java.util.Objects;
* <li>TX3G ({@link Tx3gParser}) * <li>TX3G ({@link Tx3gParser})
* <li>PGS ({@link PgsParser}) * <li>PGS ({@link PgsParser})
* <li>DVB ({@link DvbParser}) * <li>DVB ({@link DvbParser})
* <li>TTML ({@link TtmlParser})
* </ul> * </ul>
*/ */
@UnstableApi @UnstableApi
@ -57,8 +55,7 @@ public final class DefaultSubtitleParserFactory implements SubtitleParser.Factor
|| Objects.equals(mimeType, MimeTypes.APPLICATION_SUBRIP) || Objects.equals(mimeType, MimeTypes.APPLICATION_SUBRIP)
|| Objects.equals(mimeType, MimeTypes.APPLICATION_TX3G) || Objects.equals(mimeType, MimeTypes.APPLICATION_TX3G)
|| Objects.equals(mimeType, MimeTypes.APPLICATION_PGS) || Objects.equals(mimeType, MimeTypes.APPLICATION_PGS)
|| Objects.equals(mimeType, MimeTypes.APPLICATION_DVBSUBS) || Objects.equals(mimeType, MimeTypes.APPLICATION_DVBSUBS);
|| Objects.equals(mimeType, MimeTypes.APPLICATION_TTML);
} }
@Override @Override
@ -80,8 +77,6 @@ public final class DefaultSubtitleParserFactory implements SubtitleParser.Factor
return new PgsParser(); return new PgsParser();
case MimeTypes.APPLICATION_DVBSUBS: case MimeTypes.APPLICATION_DVBSUBS:
return new DvbParser(format.initializationData); return new DvbParser(format.initializationData);
case MimeTypes.APPLICATION_TTML:
return new TtmlParser();
default: default:
break; break;
} }

View File

@ -15,8 +15,6 @@
*/ */
package androidx.media3.extractor.text.ttml; package androidx.media3.extractor.text.ttml;
import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static java.lang.Math.max; import static java.lang.Math.max;
import static java.lang.Math.min; import static java.lang.Math.min;
@ -31,12 +29,10 @@ import androidx.media3.common.util.Log;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.common.util.XmlPullParserUtil; import androidx.media3.common.util.XmlPullParserUtil;
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.SubtitleDecoderException; import androidx.media3.extractor.text.SubtitleDecoderException;
import androidx.media3.extractor.text.SubtitleParser;
import com.google.common.base.Ascii; import com.google.common.base.Ascii;
import com.google.common.collect.ImmutableList;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayDeque; import java.util.ArrayDeque;
@ -72,9 +68,9 @@ import org.xmlpull.v1.XmlPullParserFactory;
* <p>See the <a href="http://www.w3.org/TR/ttaf1-dfxp/">TTML specification</a> * <p>See the <a href="http://www.w3.org/TR/ttaf1-dfxp/">TTML specification</a>
*/ */
@UnstableApi @UnstableApi
public final class TtmlParser implements SubtitleParser { public final class TtmlDecoder extends SimpleSubtitleDecoder {
private static final String TAG = "TtmlParser"; private static final String TAG = "TtmlDecoder";
private static final String TTP = "http://www.w3.org/ns/ttml#parameter"; private static final String TTP = "http://www.w3.org/ns/ttml#parameter";
@ -108,7 +104,8 @@ public final class TtmlParser implements SubtitleParser {
private final XmlPullParserFactory xmlParserFactory; private final XmlPullParserFactory xmlParserFactory;
public TtmlParser() { public TtmlDecoder() {
super("TtmlDecoder");
try { try {
xmlParserFactory = XmlPullParserFactory.newInstance(); xmlParserFactory = XmlPullParserFactory.newInstance();
xmlParserFactory.setNamespaceAware(true); xmlParserFactory.setNamespaceAware(true);
@ -117,16 +114,16 @@ public final class TtmlParser implements SubtitleParser {
} }
} }
@Nullable
@Override @Override
public ImmutableList<CuesWithTiming> parse(byte[] data, int offset, int length) { protected Subtitle decode(byte[] data, int length, boolean reset)
throws SubtitleDecoderException {
try { try {
XmlPullParser xmlParser = xmlParserFactory.newPullParser(); XmlPullParser xmlParser = xmlParserFactory.newPullParser();
Map<String, TtmlStyle> globalStyles = new HashMap<>(); Map<String, TtmlStyle> globalStyles = new HashMap<>();
Map<String, TtmlRegion> regionMap = new HashMap<>(); Map<String, TtmlRegion> regionMap = new HashMap<>();
Map<String, String> imageMap = new HashMap<>(); Map<String, String> imageMap = new HashMap<>();
regionMap.put(TtmlNode.ANONYMOUS_REGION_ID, new TtmlRegion(TtmlNode.ANONYMOUS_REGION_ID)); regionMap.put(TtmlNode.ANONYMOUS_REGION_ID, new TtmlRegion(TtmlNode.ANONYMOUS_REGION_ID));
ByteArrayInputStream inputStream = new ByteArrayInputStream(data, offset, length); ByteArrayInputStream inputStream = new ByteArrayInputStream(data, 0, length);
xmlParser.setInput(inputStream, null); xmlParser.setInput(inputStream, null);
@Nullable TtmlSubtitle ttmlSubtitle = null; @Nullable TtmlSubtitle ttmlSubtitle = null;
ArrayDeque<TtmlNode> nodeStack = new ArrayDeque<>(); ArrayDeque<TtmlNode> nodeStack = new ArrayDeque<>();
@ -183,15 +180,20 @@ public final class TtmlParser implements SubtitleParser {
xmlParser.next(); xmlParser.next();
eventType = xmlParser.getEventType(); eventType = xmlParser.getEventType();
} }
return checkNotNull(ttmlSubtitle).toCuesWithTimingList(); if (ttmlSubtitle != null) {
return ttmlSubtitle;
} else {
throw new SubtitleDecoderException("No TTML subtitles found");
}
} catch (XmlPullParserException xppe) { } catch (XmlPullParserException xppe) {
throw new IllegalStateException("Unable to decode source", xppe); throw new SubtitleDecoderException("Unable to decode source", xppe);
} catch (IOException e) { } catch (IOException e) {
throw new IllegalStateException("Unexpected error when reading input.", e); throw new IllegalStateException("Unexpected error when reading input.", e);
} }
} }
private static FrameAndTickRate parseFrameAndTickRates(XmlPullParser xmlParser) { private static FrameAndTickRate parseFrameAndTickRates(XmlPullParser xmlParser)
throws SubtitleDecoderException {
int frameRate = DEFAULT_FRAME_RATE; int frameRate = DEFAULT_FRAME_RATE;
String frameRateString = xmlParser.getAttributeValue(TTP, "frameRate"); String frameRateString = xmlParser.getAttributeValue(TTP, "frameRate");
if (frameRateString != null) { if (frameRateString != null) {
@ -202,7 +204,9 @@ public final class TtmlParser implements SubtitleParser {
String frameRateMultiplierString = xmlParser.getAttributeValue(TTP, "frameRateMultiplier"); String frameRateMultiplierString = xmlParser.getAttributeValue(TTP, "frameRateMultiplier");
if (frameRateMultiplierString != null) { if (frameRateMultiplierString != null) {
String[] parts = Util.split(frameRateMultiplierString, " "); String[] parts = Util.split(frameRateMultiplierString, " ");
checkArgument(parts.length == 2, "frameRateMultiplier doesn't have 2 parts"); if (parts.length != 2) {
throw new SubtitleDecoderException("frameRateMultiplier doesn't have 2 parts");
}
float numerator = Integer.parseInt(parts[0]); float numerator = Integer.parseInt(parts[0]);
float denominator = Integer.parseInt(parts[1]); float denominator = Integer.parseInt(parts[1]);
frameRateMultiplier = numerator / denominator; frameRateMultiplier = numerator / denominator;
@ -223,7 +227,7 @@ public final class TtmlParser implements SubtitleParser {
} }
private static CellResolution parseCellResolution( private static CellResolution parseCellResolution(
XmlPullParser xmlParser, CellResolution defaultValue) { XmlPullParser xmlParser, CellResolution defaultValue) throws SubtitleDecoderException {
String cellResolution = xmlParser.getAttributeValue(TTP, "cellResolution"); String cellResolution = xmlParser.getAttributeValue(TTP, "cellResolution");
if (cellResolution == null) { if (cellResolution == null) {
return defaultValue; return defaultValue;
@ -237,7 +241,9 @@ public final class TtmlParser implements SubtitleParser {
try { try {
int columns = Integer.parseInt(Assertions.checkNotNull(cellResolutionMatcher.group(1))); int columns = Integer.parseInt(Assertions.checkNotNull(cellResolutionMatcher.group(1)));
int rows = Integer.parseInt(Assertions.checkNotNull(cellResolutionMatcher.group(2))); int rows = Integer.parseInt(Assertions.checkNotNull(cellResolutionMatcher.group(2)));
checkArgument(columns != 0 && rows != 0, "Invalid cell resolution " + columns + " " + rows); if (columns == 0 || rows == 0) {
throw new SubtitleDecoderException("Invalid cell resolution " + columns + " " + rows);
}
return new CellResolution(columns, rows); return new CellResolution(columns, rows);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
Log.w(TAG, "Ignoring malformed cell resolution: " + cellResolution); Log.w(TAG, "Ignoring malformed cell resolution: " + cellResolution);

View File

@ -13,7 +13,7 @@
* 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.exoplayer.text; package androidx.media3.extractor.text.ttml;
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;
@ -27,7 +27,7 @@ import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.ColorParser; import androidx.media3.common.util.ColorParser;
import androidx.media3.extractor.text.CuesWithTiming; import androidx.media3.extractor.text.CuesWithTiming;
import androidx.media3.extractor.text.Subtitle; import androidx.media3.extractor.text.Subtitle;
import androidx.media3.extractor.text.ttml.TtmlParser; import androidx.media3.extractor.text.SubtitleDecoderException;
import androidx.media3.test.utils.TestUtil; import androidx.media3.test.utils.TestUtil;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
@ -37,9 +37,9 @@ import java.util.List;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
/** Unit test for {@link TtmlParser}. */ /** Unit test for {@link TtmlDecoder}. */
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public final class DelegatingSubtitleDecoderTtmlParserTest { public final class TtmlDecoderTest {
private static final String INLINE_ATTRIBUTES_TTML_FILE = private static final String INLINE_ATTRIBUTES_TTML_FILE =
"media/ttml/inline_style_attributes.xml"; "media/ttml/inline_style_attributes.xml";
@ -73,8 +73,8 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
private static final String SHEAR_FILE = "media/ttml/shear.xml"; private static final String SHEAR_FILE = "media/ttml/shear.xml";
@Test @Test
public void inlineAttributes() throws IOException { public void inlineAttributes() throws IOException, SubtitleDecoderException {
Subtitle subtitle = getSubtitle(INLINE_ATTRIBUTES_TTML_FILE); TtmlSubtitle subtitle = getSubtitle(INLINE_ATTRIBUTES_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(4); assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
@ -92,8 +92,8 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
} }
@Test @Test
public void inheritInlineAttributes() throws IOException { public void inheritInlineAttributes() throws IOException, SubtitleDecoderException {
Subtitle subtitle = getSubtitle(INLINE_ATTRIBUTES_TTML_FILE); TtmlSubtitle subtitle = getSubtitle(INLINE_ATTRIBUTES_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(4); assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
@ -118,8 +118,8 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
// KitKat Color: // KitKat Color:
// https://github.com/android/platform_frameworks_base/blob/kitkat-mr2.2-release/graphics/java/android/graphics/Color.java#L414 // https://github.com/android/platform_frameworks_base/blob/kitkat-mr2.2-release/graphics/java/android/graphics/Color.java#L414
@Test @Test
public void lime() throws IOException { public void lime() throws IOException, SubtitleDecoderException {
Subtitle subtitle = getSubtitle(INLINE_ATTRIBUTES_TTML_FILE); TtmlSubtitle subtitle = getSubtitle(INLINE_ATTRIBUTES_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(4); assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
@ -133,8 +133,8 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
} }
@Test @Test
public void inheritGlobalStyle() throws IOException { public void inheritGlobalStyle() throws IOException, SubtitleDecoderException {
Subtitle subtitle = getSubtitle(INHERIT_STYLE_TTML_FILE); TtmlSubtitle subtitle = getSubtitle(INHERIT_STYLE_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(2); assertThat(subtitle.getEventTimeCount()).isEqualTo(2);
@ -148,8 +148,9 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
} }
@Test @Test
public void inheritGlobalStyleOverriddenByInlineAttributes() throws IOException { public void inheritGlobalStyleOverriddenByInlineAttributes()
Subtitle subtitle = getSubtitle(INHERIT_STYLE_OVERRIDE_TTML_FILE); throws IOException, SubtitleDecoderException {
TtmlSubtitle subtitle = getSubtitle(INHERIT_STYLE_OVERRIDE_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(4); assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
@ -181,8 +182,8 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
} }
@Test @Test
public void inheritGlobalAndParent() throws IOException { public void inheritGlobalAndParent() throws IOException, SubtitleDecoderException {
Subtitle subtitle = getSubtitle(INHERIT_GLOBAL_AND_PARENT_TTML_FILE); TtmlSubtitle subtitle = getSubtitle(INHERIT_GLOBAL_AND_PARENT_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(4); assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
@ -214,8 +215,8 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
} }
@Test @Test
public void inheritMultipleStyles() throws IOException { public void inheritMultipleStyles() throws IOException, SubtitleDecoderException {
Subtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE); TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(12); assertThat(subtitle.getEventTimeCount()).isEqualTo(12);
@ -229,8 +230,9 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
} }
@Test @Test
public void inheritMultipleStylesWithoutLocalAttributes() throws IOException { public void inheritMultipleStylesWithoutLocalAttributes()
Subtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE); throws IOException, SubtitleDecoderException {
TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(12); assertThat(subtitle.getEventTimeCount()).isEqualTo(12);
@ -250,8 +252,8 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
} }
@Test @Test
public void mergeMultipleStylesWithParentStyle() throws IOException { public void mergeMultipleStylesWithParentStyle() throws IOException, SubtitleDecoderException {
Subtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE); TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(12); assertThat(subtitle.getEventTimeCount()).isEqualTo(12);
@ -272,8 +274,8 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
} }
@Test @Test
public void multipleRegions() throws IOException { public void multipleRegions() throws IOException, SubtitleDecoderException {
Subtitle subtitle = getSubtitle(MULTIPLE_REGIONS_TTML_FILE); TtmlSubtitle subtitle = getSubtitle(MULTIPLE_REGIONS_TTML_FILE);
List<Cue> cues = subtitle.getCues(1_000_000); List<Cue> cues = subtitle.getCues(1_000_000);
assertThat(cues).hasSize(2); assertThat(cues).hasSize(2);
@ -321,8 +323,8 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
} }
@Test @Test
public void emptyStyleAttribute() throws IOException { public void emptyStyleAttribute() throws IOException, SubtitleDecoderException {
Subtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE); TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(12); assertThat(subtitle.getEventTimeCount()).isEqualTo(12);
@ -333,8 +335,8 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
} }
@Test @Test
public void nonexistingStyleId() throws IOException { public void nonexistingStyleId() throws IOException, SubtitleDecoderException {
Subtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE); TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(12); assertThat(subtitle.getEventTimeCount()).isEqualTo(12);
@ -345,8 +347,9 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
} }
@Test @Test
public void nonExistingAndExistingStyleIdWithRedundantSpaces() throws IOException { public void nonExistingAndExistingStyleIdWithRedundantSpaces()
Subtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE); throws IOException, SubtitleDecoderException {
TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(12); assertThat(subtitle.getEventTimeCount()).isEqualTo(12);
@ -356,8 +359,8 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
} }
@Test @Test
public void multipleChaining() throws IOException { public void multipleChaining() throws IOException, SubtitleDecoderException {
Subtitle subtitle = getSubtitle(CHAIN_MULTIPLE_STYLES_TTML_FILE); TtmlSubtitle subtitle = getSubtitle(CHAIN_MULTIPLE_STYLES_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(4); assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
@ -380,8 +383,8 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
} }
@Test @Test
public void noUnderline() throws IOException { public void noUnderline() throws IOException, SubtitleDecoderException {
Subtitle subtitle = getSubtitle(NO_UNDERLINE_LINETHROUGH_TTML_FILE); TtmlSubtitle subtitle = getSubtitle(NO_UNDERLINE_LINETHROUGH_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(4); assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
@ -392,8 +395,8 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
} }
@Test @Test
public void noLinethrough() throws IOException { public void noLinethrough() throws IOException, SubtitleDecoderException {
Subtitle subtitle = getSubtitle(NO_UNDERLINE_LINETHROUGH_TTML_FILE); TtmlSubtitle subtitle = getSubtitle(NO_UNDERLINE_LINETHROUGH_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(4); assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
@ -404,8 +407,8 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
} }
@Test @Test
public void fontSizeSpans() throws IOException { public void fontSizeSpans() throws IOException, SubtitleDecoderException {
Subtitle subtitle = getSubtitle(FONT_SIZE_TTML_FILE); TtmlSubtitle subtitle = getSubtitle(FONT_SIZE_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(10); assertThat(subtitle.getEventTimeCount()).isEqualTo(10);
@ -431,8 +434,8 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
} }
@Test @Test
public void fontSizeWithMissingUnitIsIgnored() throws IOException { public void fontSizeWithMissingUnitIsIgnored() throws IOException, SubtitleDecoderException {
Subtitle subtitle = getSubtitle(FONT_SIZE_MISSING_UNIT_TTML_FILE); TtmlSubtitle subtitle = getSubtitle(FONT_SIZE_MISSING_UNIT_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(2); assertThat(subtitle.getEventTimeCount()).isEqualTo(2);
@ -443,8 +446,8 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
} }
@Test @Test
public void fontSizeWithInvalidValueIsIgnored() throws IOException { public void fontSizeWithInvalidValueIsIgnored() throws IOException, SubtitleDecoderException {
Subtitle subtitle = getSubtitle(FONT_SIZE_INVALID_TTML_FILE); TtmlSubtitle subtitle = getSubtitle(FONT_SIZE_INVALID_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(6); assertThat(subtitle.getEventTimeCount()).isEqualTo(6);
@ -465,8 +468,8 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
} }
@Test @Test
public void fontSizeWithEmptyValueIsIgnored() throws IOException { public void fontSizeWithEmptyValueIsIgnored() throws IOException, SubtitleDecoderException {
Subtitle subtitle = getSubtitle(FONT_SIZE_EMPTY_TTML_FILE); TtmlSubtitle subtitle = getSubtitle(FONT_SIZE_EMPTY_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(2); assertThat(subtitle.getEventTimeCount()).isEqualTo(2);
@ -477,8 +480,8 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
} }
@Test @Test
public void frameRate() throws IOException { public void frameRate() throws IOException, SubtitleDecoderException {
Subtitle subtitle = getSubtitle(FRAME_RATE_TTML_FILE); TtmlSubtitle subtitle = getSubtitle(FRAME_RATE_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(4); assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
assertThat(subtitle.getEventTime(0)).isEqualTo(1_000_000); assertThat(subtitle.getEventTime(0)).isEqualTo(1_000_000);
@ -488,8 +491,8 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
} }
@Test @Test
public void bitmapPercentageRegion() throws IOException { public void bitmapPercentageRegion() throws IOException, SubtitleDecoderException {
Subtitle subtitle = getSubtitle(BITMAP_REGION_FILE); TtmlSubtitle subtitle = getSubtitle(BITMAP_REGION_FILE);
Cue cue = getOnlyCueAtTimeUs(subtitle, 1_000_000); Cue cue = getOnlyCueAtTimeUs(subtitle, 1_000_000);
assertThat(cue.text).isNull(); assertThat(cue.text).isNull();
@ -517,8 +520,8 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
} }
@Test @Test
public void bitmapPixelRegion() throws IOException { public void bitmapPixelRegion() throws IOException, SubtitleDecoderException {
Subtitle subtitle = getSubtitle(BITMAP_PIXEL_REGION_FILE); TtmlSubtitle subtitle = getSubtitle(BITMAP_PIXEL_REGION_FILE);
Cue cue = getOnlyCueAtTimeUs(subtitle, 1_000_000); Cue cue = getOnlyCueAtTimeUs(subtitle, 1_000_000);
assertThat(cue.text).isNull(); assertThat(cue.text).isNull();
@ -538,8 +541,8 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
} }
@Test @Test
public void bitmapUnsupportedRegion() throws IOException { public void bitmapUnsupportedRegion() throws IOException, SubtitleDecoderException {
Subtitle subtitle = getSubtitle(BITMAP_UNSUPPORTED_REGION_FILE); TtmlSubtitle subtitle = getSubtitle(BITMAP_UNSUPPORTED_REGION_FILE);
Cue cue = getOnlyCueAtTimeUs(subtitle, 1_000_000); Cue cue = getOnlyCueAtTimeUs(subtitle, 1_000_000);
assertThat(cue.text).isNull(); assertThat(cue.text).isNull();
@ -559,8 +562,8 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
} }
@Test @Test
public void textAlign() throws IOException { public void textAlign() throws IOException, SubtitleDecoderException {
Subtitle subtitle = getSubtitle(TEXT_ALIGN_FILE); TtmlSubtitle subtitle = getSubtitle(TEXT_ALIGN_FILE);
Cue firstCue = getOnlyCueAtTimeUs(subtitle, 10_000_000); Cue firstCue = getOnlyCueAtTimeUs(subtitle, 10_000_000);
assertThat(firstCue.text.toString()).isEqualTo("Start alignment"); assertThat(firstCue.text.toString()).isEqualTo("Start alignment");
@ -600,8 +603,8 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
} }
@Test @Test
public void multiRowAlign() throws IOException { public void multiRowAlign() throws IOException, SubtitleDecoderException {
Subtitle subtitle = getSubtitle(MULTI_ROW_ALIGN_FILE); TtmlSubtitle subtitle = getSubtitle(MULTI_ROW_ALIGN_FILE);
Cue firstCue = getOnlyCueAtTimeUs(subtitle, 10_000_000); Cue firstCue = getOnlyCueAtTimeUs(subtitle, 10_000_000);
assertThat(firstCue.multiRowAlignment).isEqualTo(Layout.Alignment.ALIGN_NORMAL); assertThat(firstCue.multiRowAlignment).isEqualTo(Layout.Alignment.ALIGN_NORMAL);
@ -626,8 +629,8 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
} }
@Test @Test
public void verticalText() throws IOException { public void verticalText() throws IOException, SubtitleDecoderException {
Subtitle subtitle = getSubtitle(VERTICAL_TEXT_FILE); TtmlSubtitle subtitle = getSubtitle(VERTICAL_TEXT_FILE);
Cue firstCue = getOnlyCueAtTimeUs(subtitle, 10_000_000); Cue firstCue = getOnlyCueAtTimeUs(subtitle, 10_000_000);
assertThat(firstCue.verticalType).isEqualTo(Cue.VERTICAL_TYPE_RL); assertThat(firstCue.verticalType).isEqualTo(Cue.VERTICAL_TYPE_RL);
@ -640,8 +643,8 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
} }
@Test @Test
public void textCombine() throws IOException { public void textCombine() throws IOException, SubtitleDecoderException {
Subtitle subtitle = getSubtitle(TEXT_COMBINE_FILE); TtmlSubtitle subtitle = getSubtitle(TEXT_COMBINE_FILE);
Spanned firstCue = getOnlyCueTextAtTimeUs(subtitle, 10_000_000); Spanned firstCue = getOnlyCueTextAtTimeUs(subtitle, 10_000_000);
assertThat(firstCue) assertThat(firstCue)
@ -658,8 +661,8 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
} }
@Test @Test
public void rubies() throws IOException { public void rubies() throws IOException, SubtitleDecoderException {
Subtitle subtitle = getSubtitle(RUBIES_FILE); TtmlSubtitle subtitle = getSubtitle(RUBIES_FILE);
Spanned firstCue = getOnlyCueTextAtTimeUs(subtitle, 10_000_000); Spanned firstCue = getOnlyCueTextAtTimeUs(subtitle, 10_000_000);
assertThat(firstCue.toString()).isEqualTo("Cue with annotated text."); assertThat(firstCue.toString()).isEqualTo("Cue with annotated text.");
@ -678,9 +681,7 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
Spanned thirdCue = getOnlyCueTextAtTimeUs(subtitle, 30_000_000); Spanned thirdCue = getOnlyCueTextAtTimeUs(subtitle, 30_000_000);
assertThat(thirdCue.toString()).isEqualTo("Cue with annotated text."); assertThat(thirdCue.toString()).isEqualTo("Cue with annotated text.");
assertThat(thirdCue) assertThat(thirdCue).hasRubySpanBetween("Cue with ".length(), "Cue with annotated".length());
.hasRubySpanBetween("Cue with ".length(), "Cue with annotated".length())
.withTextAndPosition("rubies", TextAnnotation.POSITION_UNKNOWN);
Spanned fourthCue = getOnlyCueTextAtTimeUs(subtitle, 40_000_000); Spanned fourthCue = getOnlyCueTextAtTimeUs(subtitle, 40_000_000);
assertThat(fourthCue.toString()).isEqualTo("Cue with annotated text."); assertThat(fourthCue.toString()).isEqualTo("Cue with annotated text.");
@ -708,8 +709,8 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
} }
@Test @Test
public void textEmphasis() throws IOException { public void textEmphasis() throws IOException, SubtitleDecoderException {
Subtitle subtitle = getSubtitle(TEXT_EMPHASIS_FILE); TtmlSubtitle subtitle = getSubtitle(TEXT_EMPHASIS_FILE);
Spanned firstCue = getOnlyCueTextAtTimeUs(subtitle, 10_000_000); Spanned firstCue = getOnlyCueTextAtTimeUs(subtitle, 10_000_000);
assertThat(firstCue) assertThat(firstCue)
@ -848,8 +849,8 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
} }
@Test @Test
public void shear() throws IOException { public void shear() throws IOException, SubtitleDecoderException {
Subtitle subtitle = getSubtitle(SHEAR_FILE); TtmlSubtitle subtitle = getSubtitle(SHEAR_FILE);
Cue firstCue = getOnlyCueAtTimeUs(subtitle, 10_000_000); Cue firstCue = getOnlyCueAtTimeUs(subtitle, 10_000_000);
assertThat(firstCue.shearDegrees).isZero(); assertThat(firstCue.shearDegrees).isZero();
@ -877,8 +878,8 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
} }
@Test @Test
public void toCuesWithTimingConversion() throws IOException { public void toCuesWithTimingConversion() throws IOException, SubtitleDecoderException {
Subtitle subtitle = getSubtitle(INLINE_ATTRIBUTES_TTML_FILE); TtmlSubtitle subtitle = getSubtitle(INLINE_ATTRIBUTES_TTML_FILE);
ImmutableList<CuesWithTiming> cuesWithTimingsList = subtitle.toCuesWithTimingList(); ImmutableList<CuesWithTiming> cuesWithTimingsList = subtitle.toCuesWithTimingList();
assertThat(subtitle.getEventTimeCount()).isEqualTo(4); assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
@ -921,10 +922,10 @@ public final class DelegatingSubtitleDecoderTtmlParserTest {
return cues.get(0); return cues.get(0);
} }
private static Subtitle getSubtitle(String file) throws IOException { private static TtmlSubtitle getSubtitle(String file)
DelegatingSubtitleDecoder ttmlDecoder = throws IOException, SubtitleDecoderException {
new DelegatingSubtitleDecoder("TtmlParserDecoder", new TtmlParser()); TtmlDecoder ttmlDecoder = new TtmlDecoder();
byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), file); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), file);
return ttmlDecoder.decode(bytes, bytes.length, false); return (TtmlSubtitle) ttmlDecoder.decode(bytes, bytes.length, false);
} }
} }

View File

@ -1,920 +0,0 @@
/*
* 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.ttml;
import static androidx.media3.test.utils.truth.SpannedSubject.assertThat;
import static com.google.common.truth.Truth.assertThat;
import android.text.Layout;
import android.text.Spanned;
import androidx.media3.common.text.Cue;
import androidx.media3.common.text.TextAnnotation;
import androidx.media3.common.text.TextEmphasisSpan;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.ColorParser;
import androidx.media3.extractor.text.CuesWithTiming;
import androidx.media3.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 org.junit.Test;
import org.junit.runner.RunWith;
/** Unit test for {@link TtmlParser}. */
@RunWith(AndroidJUnit4.class)
public final class TtmlParserTest {
private static final String INLINE_ATTRIBUTES_TTML_FILE =
"media/ttml/inline_style_attributes.xml";
private static final String INHERIT_STYLE_TTML_FILE = "media/ttml/inherit_style.xml";
private static final String INHERIT_STYLE_OVERRIDE_TTML_FILE =
"media/ttml/inherit_and_override_style.xml";
private static final String INHERIT_GLOBAL_AND_PARENT_TTML_FILE =
"media/ttml/inherit_global_and_parent.xml";
private static final String INHERIT_MULTIPLE_STYLES_TTML_FILE =
"media/ttml/inherit_multiple_styles.xml";
private static final String CHAIN_MULTIPLE_STYLES_TTML_FILE =
"media/ttml/chain_multiple_styles.xml";
private static final String MULTIPLE_REGIONS_TTML_FILE = "media/ttml/multiple_regions.xml";
private static final String NO_UNDERLINE_LINETHROUGH_TTML_FILE =
"media/ttml/no_underline_linethrough.xml";
private static final String FONT_SIZE_TTML_FILE = "media/ttml/font_size.xml";
private static final String FONT_SIZE_MISSING_UNIT_TTML_FILE = "media/ttml/font_size_no_unit.xml";
private static final String FONT_SIZE_INVALID_TTML_FILE = "media/ttml/font_size_invalid.xml";
private static final String FONT_SIZE_EMPTY_TTML_FILE = "media/ttml/font_size_empty.xml";
private static final String FRAME_RATE_TTML_FILE = "media/ttml/frame_rate.xml";
private static final String BITMAP_REGION_FILE = "media/ttml/bitmap_percentage_region.xml";
private static final String BITMAP_PIXEL_REGION_FILE = "media/ttml/bitmap_pixel_region.xml";
private static final String BITMAP_UNSUPPORTED_REGION_FILE =
"media/ttml/bitmap_unsupported_region.xml";
private static final String TEXT_ALIGN_FILE = "media/ttml/text_align.xml";
private static final String MULTI_ROW_ALIGN_FILE = "media/ttml/multi_row_align.xml";
private static final String VERTICAL_TEXT_FILE = "media/ttml/vertical_text.xml";
private static final String TEXT_COMBINE_FILE = "media/ttml/text_combine.xml";
private static final String RUBIES_FILE = "media/ttml/rubies.xml";
private static final String TEXT_EMPHASIS_FILE = "media/ttml/text_emphasis.xml";
private static final String SHEAR_FILE = "media/ttml/shear.xml";
@Test
public void inlineAttributes() throws Exception {
ImmutableList<CuesWithTiming> allCues = getAllCues(INLINE_ATTRIBUTES_TTML_FILE);
assertThat(allCues).hasSize(2);
Spanned spanned = getOnlyCueTextAtIndex(allCues, 0);
assertThat(spanned.toString()).isEqualTo("text 1");
assertThat(spanned).hasTypefaceSpanBetween(0, spanned.length()).withFamily("serif");
assertThat(spanned).hasBoldItalicSpanBetween(0, spanned.length());
assertThat(spanned).hasUnderlineSpanBetween(0, spanned.length());
assertThat(spanned)
.hasBackgroundColorSpanBetween(0, spanned.length())
.withColor(ColorParser.parseTtmlColor("blue"));
assertThat(spanned)
.hasForegroundColorSpanBetween(0, spanned.length())
.withColor(ColorParser.parseTtmlColor("yellow"));
}
@Test
public void inheritInlineAttributes() throws Exception {
ImmutableList<CuesWithTiming> allCues = getAllCues(INLINE_ATTRIBUTES_TTML_FILE);
assertThat(allCues).hasSize(2);
Spanned spanned = getOnlyCueTextAtIndex(allCues, 1);
assertThat(spanned.toString()).isEqualTo("text 2");
assertThat(spanned).hasTypefaceSpanBetween(0, spanned.length()).withFamily("sansSerif");
assertThat(spanned).hasItalicSpanBetween(0, spanned.length());
assertThat(spanned).hasStrikethroughSpanBetween(0, spanned.length());
assertThat(spanned).hasBackgroundColorSpanBetween(0, spanned.length()).withColor(0xFF00FFFF);
assertThat(spanned)
.hasForegroundColorSpanBetween(0, spanned.length())
.withColor(ColorParser.parseTtmlColor("lime"));
}
/**
* Regression test for devices on JellyBean where some named colors are not correctly defined on
* framework level. Tests that <i>lime</i> resolves to <code>#FF00FF00</code> not <code>#00FF00
* </code>.
*/
// JellyBean Color:
// https://github.com/android/platform_frameworks_base/blob/jb-mr2-release/graphics/java/android/graphics/Color.java#L414
// KitKat Color:
// https://github.com/android/platform_frameworks_base/blob/kitkat-mr2.2-release/graphics/java/android/graphics/Color.java#L414
@Test
public void lime() throws Exception {
ImmutableList<CuesWithTiming> allCues = getAllCues(INLINE_ATTRIBUTES_TTML_FILE);
assertThat(allCues).hasSize(2);
Spanned spanned = getOnlyCueTextAtIndex(allCues, 1);
assertThat(spanned.toString()).isEqualTo("text 2");
assertThat(spanned).hasTypefaceSpanBetween(0, spanned.length()).withFamily("sansSerif");
assertThat(spanned).hasItalicSpanBetween(0, spanned.length());
assertThat(spanned).hasStrikethroughSpanBetween(0, spanned.length());
assertThat(spanned).hasBackgroundColorSpanBetween(0, spanned.length()).withColor(0xFF00FFFF);
assertThat(spanned).hasForegroundColorSpanBetween(0, spanned.length()).withColor(0xFF00FF00);
}
@Test
public void inheritGlobalStyle() throws Exception {
ImmutableList<CuesWithTiming> allCues = getAllCues(INHERIT_STYLE_TTML_FILE);
assertThat(allCues).hasSize(1);
Spanned spanned = getOnlyCueTextAtIndex(allCues, 0);
assertThat(spanned.toString()).isEqualTo("text 1");
assertThat(spanned).hasTypefaceSpanBetween(0, spanned.length()).withFamily("serif");
assertThat(spanned).hasBoldItalicSpanBetween(0, spanned.length());
assertThat(spanned).hasUnderlineSpanBetween(0, spanned.length());
assertThat(spanned).hasBackgroundColorSpanBetween(0, spanned.length()).withColor(0xFF0000FF);
assertThat(spanned).hasForegroundColorSpanBetween(0, spanned.length()).withColor(0xFFFFFF00);
}
@Test
public void inheritGlobalStyleOverriddenByInlineAttributes() throws Exception {
ImmutableList<CuesWithTiming> allCues = getAllCues(INHERIT_STYLE_OVERRIDE_TTML_FILE);
assertThat(allCues).hasSize(2);
Spanned firstCueText = getOnlyCueTextAtIndex(allCues, 0);
assertThat(firstCueText.toString()).isEqualTo("text 1");
assertThat(firstCueText).hasTypefaceSpanBetween(0, firstCueText.length()).withFamily("serif");
assertThat(firstCueText).hasBoldItalicSpanBetween(0, firstCueText.length());
assertThat(firstCueText).hasUnderlineSpanBetween(0, firstCueText.length());
assertThat(firstCueText)
.hasBackgroundColorSpanBetween(0, firstCueText.length())
.withColor(0xFF0000FF);
assertThat(firstCueText)
.hasForegroundColorSpanBetween(0, firstCueText.length())
.withColor(0xFFFFFF00);
Spanned secondCueText = getOnlyCueTextAtIndex(allCues, 1);
assertThat(secondCueText.toString()).isEqualTo("text 2");
assertThat(secondCueText)
.hasTypefaceSpanBetween(0, secondCueText.length())
.withFamily("sansSerif");
assertThat(secondCueText).hasItalicSpanBetween(0, secondCueText.length());
assertThat(secondCueText).hasUnderlineSpanBetween(0, secondCueText.length());
assertThat(secondCueText)
.hasBackgroundColorSpanBetween(0, secondCueText.length())
.withColor(0xFFFF0000);
assertThat(secondCueText)
.hasForegroundColorSpanBetween(0, secondCueText.length())
.withColor(0xFFFFFF00);
}
@Test
public void inheritGlobalAndParent() throws Exception {
ImmutableList<CuesWithTiming> allCues = getAllCues(INHERIT_GLOBAL_AND_PARENT_TTML_FILE);
assertThat(allCues).hasSize(2);
Spanned firstCueText = getOnlyCueTextAtIndex(allCues, 0);
assertThat(firstCueText.toString()).isEqualTo("text 1");
assertThat(firstCueText)
.hasTypefaceSpanBetween(0, firstCueText.length())
.withFamily("sansSerif");
assertThat(firstCueText).hasStrikethroughSpanBetween(0, firstCueText.length());
assertThat(firstCueText)
.hasBackgroundColorSpanBetween(0, firstCueText.length())
.withColor(0xFFFF0000);
assertThat(firstCueText)
.hasForegroundColorSpanBetween(0, firstCueText.length())
.withColor(ColorParser.parseTtmlColor("lime"));
Spanned secondCueText = getOnlyCueTextAtIndex(allCues, 1);
assertThat(secondCueText.toString()).isEqualTo("text 2");
assertThat(secondCueText).hasTypefaceSpanBetween(0, secondCueText.length()).withFamily("serif");
assertThat(secondCueText).hasBoldItalicSpanBetween(0, secondCueText.length());
assertThat(secondCueText).hasUnderlineSpanBetween(0, secondCueText.length());
assertThat(secondCueText).hasStrikethroughSpanBetween(0, secondCueText.length());
assertThat(secondCueText)
.hasBackgroundColorSpanBetween(0, secondCueText.length())
.withColor(0xFF0000FF);
assertThat(secondCueText)
.hasForegroundColorSpanBetween(0, secondCueText.length())
.withColor(0xFFFFFF00);
}
@Test
public void inheritMultipleStyles() throws Exception {
ImmutableList<CuesWithTiming> allCues = getAllCues(INHERIT_MULTIPLE_STYLES_TTML_FILE);
assertThat(allCues).hasSize(6);
Spanned spanned = getOnlyCueTextAtIndex(allCues, 0);
assertThat(spanned.toString()).isEqualTo("text 1");
assertThat(spanned).hasTypefaceSpanBetween(0, spanned.length()).withFamily("sansSerif");
assertThat(spanned).hasBoldItalicSpanBetween(0, spanned.length());
assertThat(spanned).hasStrikethroughSpanBetween(0, spanned.length());
assertThat(spanned).hasBackgroundColorSpanBetween(0, spanned.length()).withColor(0xFF0000FF);
assertThat(spanned).hasForegroundColorSpanBetween(0, spanned.length()).withColor(0xFFFFFF00);
}
@Test
public void inheritMultipleStylesWithoutLocalAttributes() throws Exception {
ImmutableList<CuesWithTiming> allCues = getAllCues(INHERIT_MULTIPLE_STYLES_TTML_FILE);
assertThat(allCues).hasSize(6);
Spanned secondCueText = getOnlyCueTextAtIndex(allCues, 1);
assertThat(secondCueText.toString()).isEqualTo("text 2");
assertThat(secondCueText)
.hasTypefaceSpanBetween(0, secondCueText.length())
.withFamily("sansSerif");
assertThat(secondCueText).hasBoldItalicSpanBetween(0, secondCueText.length());
assertThat(secondCueText).hasStrikethroughSpanBetween(0, secondCueText.length());
assertThat(secondCueText)
.hasBackgroundColorSpanBetween(0, secondCueText.length())
.withColor(0xFF0000FF);
assertThat(secondCueText)
.hasForegroundColorSpanBetween(0, secondCueText.length())
.withColor(0xFF000000);
}
@Test
public void mergeMultipleStylesWithParentStyle() throws Exception {
ImmutableList<CuesWithTiming> allCues = getAllCues(INHERIT_MULTIPLE_STYLES_TTML_FILE);
assertThat(allCues).hasSize(6);
Spanned thirdCueText = getOnlyCueTextAtIndex(allCues, 2);
assertThat(thirdCueText.toString()).isEqualTo("text 2.5");
assertThat(thirdCueText)
.hasTypefaceSpanBetween(0, thirdCueText.length())
.withFamily("sansSerifInline");
assertThat(thirdCueText).hasItalicSpanBetween(0, thirdCueText.length());
assertThat(thirdCueText).hasUnderlineSpanBetween(0, thirdCueText.length());
assertThat(thirdCueText).hasStrikethroughSpanBetween(0, thirdCueText.length());
assertThat(thirdCueText)
.hasBackgroundColorSpanBetween(0, thirdCueText.length())
.withColor(0xFFFF0000);
assertThat(thirdCueText)
.hasForegroundColorSpanBetween(0, thirdCueText.length())
.withColor(0xFFFFFF00);
}
@Test
public void multipleRegions() throws Exception {
ImmutableList<CuesWithTiming> allCues = getAllCues(MULTIPLE_REGIONS_TTML_FILE);
assertThat(allCues).hasSize(6);
ImmutableList<Cue> cues = allCues.get(0).cues;
assertThat(cues).hasSize(2);
Cue cue = cues.get(0);
assertThat(cue.text.toString()).isEqualTo("lorem");
assertThat(cue.position).isEqualTo(10f / 100f);
assertThat(cue.line).isEqualTo(10f / 100f);
assertThat(cue.size).isEqualTo(20f / 100f);
cue = cues.get(1);
assertThat(cue.text.toString()).isEqualTo("amet");
assertThat(cue.position).isEqualTo(60f / 100f);
assertThat(cue.line).isEqualTo(10f / 100f);
assertThat(cue.size).isEqualTo(20f / 100f);
cue = Iterables.getOnlyElement(allCues.get(1).cues);
assertThat(cue.text.toString()).isEqualTo("ipsum");
assertThat(cue.position).isEqualTo(40f / 100f);
assertThat(cue.line).isEqualTo(40f / 100f);
assertThat(cue.size).isEqualTo(20f / 100f);
cue = Iterables.getOnlyElement(allCues.get(2).cues);
assertThat(cue.text.toString()).isEqualTo("dolor");
assertThat(cue.position).isEqualTo(Cue.DIMEN_UNSET);
assertThat(cue.line).isEqualTo(Cue.DIMEN_UNSET);
assertThat(cue.size).isEqualTo(Cue.DIMEN_UNSET);
// TODO: Should be as below, once https://github.com/google/ExoPlayer/issues/2953 is fixed.
// assertEquals(10f / 100f, cue.position);
// assertEquals(80f / 100f, cue.line);
// assertEquals(1f, cue.size);
cue = Iterables.getOnlyElement(allCues.get(3).cues);
assertThat(cue.text.toString()).isEqualTo("They first said this");
assertThat(cue.position).isEqualTo(45f / 100f);
assertThat(cue.line).isEqualTo(45f / 100f);
assertThat(cue.size).isEqualTo(35f / 100f);
cue = Iterables.getOnlyElement(allCues.get(4).cues);
assertThat(cue.text.toString()).isEqualTo("They first said this\nThen this");
cue = Iterables.getOnlyElement(allCues.get(5).cues);
assertThat(cue.text.toString()).isEqualTo("They first said this\nThen this\nFinally this");
assertThat(cue.position).isEqualTo(45f / 100f);
assertThat(cue.line).isEqualTo(45f / 100f);
}
@Test
public void emptyStyleAttribute() throws Exception {
ImmutableList<CuesWithTiming> allCues = getAllCues(INHERIT_MULTIPLE_STYLES_TTML_FILE);
assertThat(allCues).hasSize(6);
Spanned spanned = getOnlyCueTextAtIndex(allCues, 3);
assertThat(allCues.get(3).startTimeUs).isEqualTo(40_000_000);
assertThat(spanned.toString()).isEqualTo("text 3");
assertThat(spanned).hasNoSpans();
}
@Test
public void nonexistingStyleId() throws Exception {
ImmutableList<CuesWithTiming> allCues = getAllCues(INHERIT_MULTIPLE_STYLES_TTML_FILE);
assertThat(allCues).hasSize(6);
Spanned spanned = getOnlyCueTextAtIndex(allCues, 4);
assertThat(allCues.get(4).startTimeUs).isEqualTo(50_000_000);
assertThat(spanned.toString()).isEqualTo("text 4");
assertThat(spanned).hasNoSpans();
}
@Test
public void nonExistingAndExistingStyleIdWithRedundantSpaces() throws Exception {
ImmutableList<CuesWithTiming> allCues = getAllCues(INHERIT_MULTIPLE_STYLES_TTML_FILE);
assertThat(allCues).hasSize(6);
Spanned spanned = getOnlyCueTextAtIndex(allCues, 5);
assertThat(allCues.get(5).startTimeUs).isEqualTo(60_000_000);
assertThat(spanned.toString()).isEqualTo("text 5");
assertThat(spanned).hasBackgroundColorSpanBetween(0, spanned.length()).withColor(0xFFFF0000);
}
@Test
public void multipleChaining() throws Exception {
ImmutableList<CuesWithTiming> allCues = getAllCues(CHAIN_MULTIPLE_STYLES_TTML_FILE);
assertThat(allCues).hasSize(2);
Spanned spanned1 = getOnlyCueTextAtIndex(allCues, 0);
assertThat(spanned1.toString()).isEqualTo("text 1");
assertThat(spanned1).hasTypefaceSpanBetween(0, spanned1.length()).withFamily("serif");
assertThat(spanned1).hasBackgroundColorSpanBetween(0, spanned1.length()).withColor(0xFFFF0000);
assertThat(spanned1).hasForegroundColorSpanBetween(0, spanned1.length()).withColor(0xFF000000);
assertThat(spanned1).hasBoldItalicSpanBetween(0, spanned1.length());
assertThat(spanned1).hasStrikethroughSpanBetween(0, spanned1.length());
// only difference: foreground (font) color must be RED
Spanned spanned2 = getOnlyCueTextAtIndex(allCues, 1);
assertThat(spanned2.toString()).isEqualTo("text 2");
assertThat(spanned2).hasTypefaceSpanBetween(0, spanned2.length()).withFamily("serif");
assertThat(spanned2).hasBackgroundColorSpanBetween(0, spanned2.length()).withColor(0xFFFF0000);
assertThat(spanned2).hasForegroundColorSpanBetween(0, spanned2.length()).withColor(0xFFFF0000);
assertThat(spanned2).hasBoldItalicSpanBetween(0, spanned2.length());
assertThat(spanned2).hasStrikethroughSpanBetween(0, spanned2.length());
}
@Test
public void noUnderline() throws Exception {
ImmutableList<CuesWithTiming> allCues = getAllCues(NO_UNDERLINE_LINETHROUGH_TTML_FILE);
assertThat(allCues).hasSize(2);
Spanned spanned = getOnlyCueTextAtIndex(allCues, 0);
assertThat(spanned.toString()).isEqualTo("text 1");
// noUnderline from inline attribute overrides s0 global underline style id
assertThat(spanned).hasNoUnderlineSpanBetween(0, spanned.length());
}
@Test
public void noLinethrough() throws Exception {
ImmutableList<CuesWithTiming> allCues = getAllCues(NO_UNDERLINE_LINETHROUGH_TTML_FILE);
assertThat(allCues).hasSize(2);
Spanned spanned = getOnlyCueTextAtIndex(allCues, 1);
assertThat(spanned.toString()).isEqualTo("text 2");
// noLineThrough from inline attribute overrides s1 global lineThrough style id
assertThat(spanned).hasNoStrikethroughSpanBetween(0, spanned.length());
}
@Test
public void fontSizeSpans() throws Exception {
ImmutableList<CuesWithTiming> allCues = getAllCues(FONT_SIZE_TTML_FILE);
assertThat(allCues).hasSize(5);
Spanned spanned = getOnlyCueTextAtIndex(allCues, 0);
assertThat(String.valueOf(spanned)).isEqualTo("text 1");
assertThat(spanned).hasAbsoluteSizeSpanBetween(0, spanned.length()).withAbsoluteSize(32);
spanned = getOnlyCueTextAtIndex(allCues, 1);
assertThat(spanned.toString()).isEqualTo("text 2");
assertThat(spanned).hasRelativeSizeSpanBetween(0, spanned.length()).withSizeChange(2.2f);
spanned = getOnlyCueTextAtIndex(allCues, 2);
assertThat(spanned.toString()).isEqualTo("text 3");
assertThat(spanned).hasRelativeSizeSpanBetween(0, spanned.length()).withSizeChange(1.5f);
spanned = getOnlyCueTextAtIndex(allCues, 3);
assertThat(spanned.toString()).isEqualTo("two values");
assertThat(spanned).hasAbsoluteSizeSpanBetween(0, spanned.length()).withAbsoluteSize(16);
spanned = getOnlyCueTextAtIndex(allCues, 4);
assertThat(spanned.toString()).isEqualTo("leading dot");
assertThat(spanned).hasRelativeSizeSpanBetween(0, spanned.length()).withSizeChange(0.5f);
}
@Test
public void fontSizeWithMissingUnitIsIgnored() throws Exception {
ImmutableList<CuesWithTiming> allCues = getAllCues(FONT_SIZE_MISSING_UNIT_TTML_FILE);
assertThat(allCues).hasSize(1);
Spanned spanned = getOnlyCueTextAtIndex(allCues, 0);
assertThat(spanned.toString()).isEqualTo("no unit");
assertThat(spanned).hasNoRelativeSizeSpanBetween(0, spanned.length());
assertThat(spanned).hasNoAbsoluteSizeSpanBetween(0, spanned.length());
}
@Test
public void fontSizeWithInvalidValueIsIgnored() throws Exception {
ImmutableList<CuesWithTiming> allCues = getAllCues(FONT_SIZE_INVALID_TTML_FILE);
assertThat(allCues).hasSize(3);
Spanned spanned = getOnlyCueTextAtIndex(allCues, 0);
assertThat(String.valueOf(spanned)).isEqualTo("invalid");
assertThat(spanned).hasNoRelativeSizeSpanBetween(0, spanned.length());
assertThat(spanned).hasNoAbsoluteSizeSpanBetween(0, spanned.length());
spanned = getOnlyCueTextAtIndex(allCues, 1);
assertThat(String.valueOf(spanned)).isEqualTo("invalid");
assertThat(spanned).hasNoRelativeSizeSpanBetween(0, spanned.length());
assertThat(spanned).hasNoAbsoluteSizeSpanBetween(0, spanned.length());
spanned = getOnlyCueTextAtIndex(allCues, 2);
assertThat(String.valueOf(spanned)).isEqualTo("invalid dot");
assertThat(spanned).hasNoRelativeSizeSpanBetween(0, spanned.length());
assertThat(spanned).hasNoAbsoluteSizeSpanBetween(0, spanned.length());
}
@Test
public void fontSizeWithEmptyValueIsIgnored() throws Exception {
ImmutableList<CuesWithTiming> allCues = getAllCues(FONT_SIZE_EMPTY_TTML_FILE);
assertThat(allCues).hasSize(1);
Spanned spanned = getOnlyCueTextAtIndex(allCues, 0);
assertThat(String.valueOf(spanned)).isEqualTo("empty");
assertThat(spanned).hasNoRelativeSizeSpanBetween(0, spanned.length());
assertThat(spanned).hasNoAbsoluteSizeSpanBetween(0, spanned.length());
}
@Test
public void frameRate() throws Exception {
ImmutableList<CuesWithTiming> allCues = getAllCues(FRAME_RATE_TTML_FILE);
assertThat(allCues).hasSize(2);
assertThat(allCues.get(0).startTimeUs).isEqualTo(1_000_000);
assertThat(allCues.get(0).durationUs).isEqualTo(10_000);
assertThat((double) allCues.get(1).startTimeUs).isWithin(1000).of(1_001_000_000);
assertThat((double) allCues.get(1).durationUs).isWithin(2000).of(1_001_000_000);
}
@Test
public void bitmapPercentageRegion() throws Exception {
ImmutableList<CuesWithTiming> allCues = getAllCues(BITMAP_REGION_FILE);
assertThat(allCues).hasSize(3);
assertThat(allCues.get(0).startTimeUs).isEqualTo(200_000);
assertThat(allCues.get(0).durationUs).isEqualTo(2_800_000);
Cue cue = Iterables.getOnlyElement(allCues.get(0).cues);
assertThat(cue.text).isNull();
assertThat(cue.bitmap).isNotNull();
assertThat(cue.position).isEqualTo(24f / 100f);
assertThat(cue.line).isEqualTo(28f / 100f);
assertThat(cue.size).isEqualTo(51f / 100f);
assertThat(cue.bitmapHeight).isEqualTo(12f / 100f);
assertThat(allCues.get(1).startTimeUs).isEqualTo(3_200_000);
assertThat(allCues.get(1).durationUs).isEqualTo(3_737_000);
cue = Iterables.getOnlyElement(allCues.get(1).cues);
assertThat(cue.text).isNull();
assertThat(cue.bitmap).isNotNull();
assertThat(cue.position).isEqualTo(21f / 100f);
assertThat(cue.line).isEqualTo(35f / 100f);
assertThat(cue.size).isEqualTo(57f / 100f);
assertThat(cue.bitmapHeight).isEqualTo(6f / 100f);
assertThat(allCues.get(2).startTimeUs).isEqualTo(7_200_000);
cue = Iterables.getOnlyElement(allCues.get(2).cues);
assertThat(cue.text).isNull();
assertThat(cue.bitmap).isNotNull();
assertThat(cue.position).isEqualTo(24f / 100f);
assertThat(cue.line).isEqualTo(28f / 100f);
assertThat(cue.size).isEqualTo(51f / 100f);
assertThat(cue.bitmapHeight).isEqualTo(12f / 100f);
}
@Test
public void bitmapPixelRegion() throws Exception {
ImmutableList<CuesWithTiming> allCues = getAllCues(BITMAP_PIXEL_REGION_FILE);
assertThat(allCues).hasSize(2);
assertThat(allCues.get(0).startTimeUs).isEqualTo(200_000);
assertThat(allCues.get(0).durationUs).isEqualTo(2_800_000);
Cue cue = Iterables.getOnlyElement(allCues.get(0).cues);
assertThat(cue.text).isNull();
assertThat(cue.bitmap).isNotNull();
assertThat(cue.position).isEqualTo(307f / 1280f);
assertThat(cue.line).isEqualTo(562f / 720f);
assertThat(cue.size).isEqualTo(653f / 1280f);
assertThat(cue.bitmapHeight).isEqualTo(86f / 720f);
assertThat(allCues.get(1).startTimeUs).isEqualTo(3_200_000);
assertThat(allCues.get(1).durationUs).isEqualTo(3_737_000);
cue = Iterables.getOnlyElement(allCues.get(1).cues);
assertThat(cue.text).isNull();
assertThat(cue.bitmap).isNotNull();
assertThat(cue.position).isEqualTo(269f / 1280f);
assertThat(cue.line).isEqualTo(612f / 720f);
assertThat(cue.size).isEqualTo(730f / 1280f);
assertThat(cue.bitmapHeight).isEqualTo(43f / 720f);
}
@Test
public void bitmapUnsupportedRegion() throws Exception {
ImmutableList<CuesWithTiming> allCues = getAllCues(BITMAP_UNSUPPORTED_REGION_FILE);
assertThat(allCues).hasSize(2);
assertThat(allCues.get(0).startTimeUs).isEqualTo(200_000);
assertThat(allCues.get(0).durationUs).isEqualTo(2_800_000);
Cue cue = Iterables.getOnlyElement(allCues.get(0).cues);
assertThat(cue.text).isNull();
assertThat(cue.bitmap).isNotNull();
assertThat(cue.position).isEqualTo(Cue.DIMEN_UNSET);
assertThat(cue.line).isEqualTo(Cue.DIMEN_UNSET);
assertThat(cue.size).isEqualTo(Cue.DIMEN_UNSET);
assertThat(cue.bitmapHeight).isEqualTo(Cue.DIMEN_UNSET);
assertThat(allCues.get(1).startTimeUs).isEqualTo(3_200_000);
assertThat(allCues.get(1).durationUs).isEqualTo(3_737_000);
cue = Iterables.getOnlyElement(allCues.get(1).cues);
assertThat(cue.text).isNull();
assertThat(cue.bitmap).isNotNull();
assertThat(cue.position).isEqualTo(Cue.DIMEN_UNSET);
assertThat(cue.line).isEqualTo(Cue.DIMEN_UNSET);
assertThat(cue.size).isEqualTo(Cue.DIMEN_UNSET);
assertThat(cue.bitmapHeight).isEqualTo(Cue.DIMEN_UNSET);
}
@Test
public void textAlign() throws Exception {
ImmutableList<CuesWithTiming> allCues = getAllCues(TEXT_ALIGN_FILE);
assertThat(allCues).hasSize(9);
Cue firstCue = Iterables.getOnlyElement(allCues.get(0).cues);
assertThat(firstCue.text.toString()).isEqualTo("Start alignment");
assertThat(firstCue.textAlignment).isEqualTo(Layout.Alignment.ALIGN_NORMAL);
Cue secondCue = Iterables.getOnlyElement(allCues.get(1).cues);
assertThat(secondCue.text.toString()).isEqualTo("Left alignment");
assertThat(secondCue.textAlignment).isEqualTo(Layout.Alignment.ALIGN_NORMAL);
Cue thirdCue = Iterables.getOnlyElement(allCues.get(2).cues);
assertThat(thirdCue.text.toString()).isEqualTo("Center alignment");
assertThat(thirdCue.textAlignment).isEqualTo(Layout.Alignment.ALIGN_CENTER);
Cue fourthCue = Iterables.getOnlyElement(allCues.get(3).cues);
assertThat(fourthCue.text.toString()).isEqualTo("Right alignment");
assertThat(fourthCue.textAlignment).isEqualTo(Layout.Alignment.ALIGN_OPPOSITE);
Cue fifthCue = Iterables.getOnlyElement(allCues.get(4).cues);
assertThat(fifthCue.text.toString()).isEqualTo("End alignment");
assertThat(fifthCue.textAlignment).isEqualTo(Layout.Alignment.ALIGN_OPPOSITE);
Cue sixthCue = Iterables.getOnlyElement(allCues.get(5).cues);
assertThat(sixthCue.text.toString()).isEqualTo("Justify alignment (unsupported)");
assertThat(sixthCue.textAlignment).isNull();
Cue seventhCue = Iterables.getOnlyElement(allCues.get(6).cues);
assertThat(seventhCue.text.toString()).isEqualTo("No textAlign property");
assertThat(seventhCue.textAlignment).isNull();
Cue eighthCue = Iterables.getOnlyElement(allCues.get(7).cues);
assertThat(eighthCue.text.toString()).isEqualTo("Ancestor start alignment");
assertThat(eighthCue.textAlignment).isEqualTo(Layout.Alignment.ALIGN_NORMAL);
Cue ninthCue = Iterables.getOnlyElement(allCues.get(8).cues);
assertThat(ninthCue.text.toString()).isEqualTo("Not a P node");
assertThat(ninthCue.textAlignment).isNull();
}
@Test
public void multiRowAlign() throws Exception {
ImmutableList<CuesWithTiming> allCues = getAllCues(MULTI_ROW_ALIGN_FILE);
Cue firstCue = Iterables.getOnlyElement(allCues.get(0).cues);
assertThat(firstCue.multiRowAlignment).isEqualTo(Layout.Alignment.ALIGN_NORMAL);
Cue secondCue = Iterables.getOnlyElement(allCues.get(1).cues);
assertThat(secondCue.multiRowAlignment).isEqualTo(Layout.Alignment.ALIGN_CENTER);
Cue thirdCue = Iterables.getOnlyElement(allCues.get(2).cues);
assertThat(thirdCue.multiRowAlignment).isEqualTo(Layout.Alignment.ALIGN_OPPOSITE);
Cue fourthCue = Iterables.getOnlyElement(allCues.get(3).cues);
assertThat(fourthCue.multiRowAlignment).isEqualTo(Layout.Alignment.ALIGN_NORMAL);
Cue fifthCue = Iterables.getOnlyElement(allCues.get(4).cues);
assertThat(fifthCue.multiRowAlignment).isEqualTo(Layout.Alignment.ALIGN_OPPOSITE);
Cue sixthCue = Iterables.getOnlyElement(allCues.get(5).cues);
assertThat(sixthCue.multiRowAlignment).isNull();
Cue seventhCue = Iterables.getOnlyElement(allCues.get(6).cues);
assertThat(seventhCue.multiRowAlignment).isNull();
}
@Test
public void verticalText() throws Exception {
ImmutableList<CuesWithTiming> allCues = getAllCues(VERTICAL_TEXT_FILE);
Cue firstCue = Iterables.getOnlyElement(allCues.get(0).cues);
assertThat(firstCue.verticalType).isEqualTo(Cue.VERTICAL_TYPE_RL);
Cue secondCue = Iterables.getOnlyElement(allCues.get(1).cues);
assertThat(secondCue.verticalType).isEqualTo(Cue.VERTICAL_TYPE_LR);
Cue thirdCue = Iterables.getOnlyElement(allCues.get(2).cues);
assertThat(thirdCue.verticalType).isEqualTo(Cue.TYPE_UNSET);
}
@Test
public void textCombine() throws Exception {
ImmutableList<CuesWithTiming> allCues = getAllCues(TEXT_COMBINE_FILE);
Spanned firstCue = getOnlyCueTextAtIndex(allCues, 0);
assertThat(firstCue)
.hasHorizontalTextInVerticalContextSpanBetween(
"text with ".length(), "text with combined".length());
Spanned secondCue = getOnlyCueTextAtIndex(allCues, 1);
assertThat(secondCue)
.hasNoHorizontalTextInVerticalContextSpanBetween(
"text with ".length(), "text with un-combined".length());
Spanned thirdCue = getOnlyCueTextAtIndex(allCues, 2);
assertThat(thirdCue).hasNoHorizontalTextInVerticalContextSpanBetween(0, thirdCue.length());
}
@Test
public void rubies() throws Exception {
ImmutableList<CuesWithTiming> allCues = getAllCues(RUBIES_FILE);
Spanned firstCue = getOnlyCueTextAtIndex(allCues, 0);
assertThat(firstCue.toString()).isEqualTo("Cue with annotated text.");
assertThat(firstCue)
.hasRubySpanBetween("Cue with ".length(), "Cue with annotated".length())
.withTextAndPosition("1st rubies", TextAnnotation.POSITION_BEFORE);
assertThat(firstCue)
.hasRubySpanBetween("Cue with annotated ".length(), "Cue with annotated text".length())
.withTextAndPosition("2nd rubies", TextAnnotation.POSITION_UNKNOWN);
Spanned secondCue = getOnlyCueTextAtIndex(allCues, 1);
assertThat(secondCue.toString()).isEqualTo("Cue with annotated text.");
assertThat(secondCue)
.hasRubySpanBetween("Cue with ".length(), "Cue with annotated".length())
.withTextAndPosition("rubies", TextAnnotation.POSITION_UNKNOWN);
Spanned thirdCue = getOnlyCueTextAtIndex(allCues, 2);
assertThat(thirdCue.toString()).isEqualTo("Cue with annotated text.");
assertThat(thirdCue)
.hasRubySpanBetween("Cue with ".length(), "Cue with annotated".length())
.withTextAndPosition("rubies", TextAnnotation.POSITION_UNKNOWN);
Spanned fourthCue = getOnlyCueTextAtIndex(allCues, 3);
assertThat(fourthCue.toString()).isEqualTo("Cue with annotated text.");
assertThat(fourthCue).hasNoRubySpanBetween(0, fourthCue.length());
Spanned fifthCue = getOnlyCueTextAtIndex(allCues, 4);
assertThat(fifthCue.toString()).isEqualTo("Cue with text.");
assertThat(fifthCue).hasNoRubySpanBetween(0, fifthCue.length());
Spanned sixthCue = getOnlyCueTextAtIndex(allCues, 5);
assertThat(sixthCue.toString()).isEqualTo("Cue with annotated text.");
assertThat(sixthCue).hasNoRubySpanBetween(0, sixthCue.length());
Spanned seventhCue = getOnlyCueTextAtIndex(allCues, 6);
assertThat(seventhCue.toString()).isEqualTo("Cue with annotated text.");
assertThat(seventhCue)
.hasRubySpanBetween("Cue with ".length(), "Cue with annotated".length())
.withTextAndPosition("rubies", TextAnnotation.POSITION_BEFORE);
Spanned eighthCue = getOnlyCueTextAtIndex(allCues, 7);
assertThat(eighthCue.toString()).isEqualTo("Cue with annotated text.");
assertThat(eighthCue)
.hasRubySpanBetween("Cue with ".length(), "Cue with annotated".length())
.withTextAndPosition("rubies", TextAnnotation.POSITION_AFTER);
}
@Test
public void textEmphasis() throws Exception {
ImmutableList<CuesWithTiming> allCues = getAllCues(TEXT_EMPHASIS_FILE);
Spanned firstCue = getOnlyCueTextAtIndex(allCues, 0);
assertThat(firstCue)
.hasTextEmphasisSpanBetween("None ".length(), "None おはよ".length())
.withMarkAndPosition(
TextEmphasisSpan.MARK_SHAPE_NONE,
TextEmphasisSpan.MARK_FILL_UNKNOWN,
TextAnnotation.POSITION_BEFORE);
Spanned secondCue = getOnlyCueTextAtIndex(allCues, 1);
assertThat(secondCue)
.hasTextEmphasisSpanBetween("Auto ".length(), "Auto ございます".length())
.withMarkAndPosition(
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
TextEmphasisSpan.MARK_FILL_FILLED,
TextAnnotation.POSITION_BEFORE);
Spanned thirdCue = getOnlyCueTextAtIndex(allCues, 2);
assertThat(thirdCue)
.hasTextEmphasisSpanBetween("Filled circle ".length(), "Filled circle こんばんは".length())
.withMarkAndPosition(
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
TextEmphasisSpan.MARK_FILL_FILLED,
TextAnnotation.POSITION_BEFORE);
Spanned fourthCue = getOnlyCueTextAtIndex(allCues, 3);
assertThat(fourthCue)
.hasTextEmphasisSpanBetween("Filled dot ".length(), "Filled dot ございます".length())
.withMarkAndPosition(
TextEmphasisSpan.MARK_SHAPE_DOT,
TextEmphasisSpan.MARK_FILL_FILLED,
TextAnnotation.POSITION_BEFORE);
Spanned fifthCue = getOnlyCueTextAtIndex(allCues, 4);
assertThat(fifthCue)
.hasTextEmphasisSpanBetween("Filled sesame ".length(), "Filled sesame おはよ".length())
.withMarkAndPosition(
TextEmphasisSpan.MARK_SHAPE_SESAME,
TextEmphasisSpan.MARK_FILL_FILLED,
TextAnnotation.POSITION_BEFORE);
Spanned sixthCue = getOnlyCueTextAtIndex(allCues, 5);
assertThat(sixthCue)
.hasTextEmphasisSpanBetween(
"Open circle before ".length(), "Open circle before ございます".length())
.withMarkAndPosition(
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
TextEmphasisSpan.MARK_FILL_OPEN,
TextAnnotation.POSITION_BEFORE);
Spanned seventhCue = getOnlyCueTextAtIndex(allCues, 6);
assertThat(seventhCue)
.hasTextEmphasisSpanBetween("Open dot after ".length(), "Open dot after おはよ".length())
.withMarkAndPosition(
TextEmphasisSpan.MARK_SHAPE_DOT,
TextEmphasisSpan.MARK_FILL_OPEN,
TextAnnotation.POSITION_AFTER);
Spanned eighthCue = getOnlyCueTextAtIndex(allCues, 7);
assertThat(eighthCue)
.hasTextEmphasisSpanBetween(
"Open sesame outside ".length(), "Open sesame outside ございます".length())
.withMarkAndPosition(
TextEmphasisSpan.MARK_SHAPE_SESAME,
TextEmphasisSpan.MARK_FILL_OPEN,
TextAnnotation.POSITION_BEFORE);
Spanned ninthCue = getOnlyCueTextAtIndex(allCues, 8);
assertThat(ninthCue)
.hasTextEmphasisSpanBetween("Auto outside ".length(), "Auto outside おはよ".length())
.withMarkAndPosition(
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
TextEmphasisSpan.MARK_FILL_FILLED,
TextAnnotation.POSITION_BEFORE);
Spanned tenthCue = getOnlyCueTextAtIndex(allCues, 9);
assertThat(tenthCue)
.hasTextEmphasisSpanBetween("Circle before ".length(), "Circle before ございます".length())
.withMarkAndPosition(
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
TextEmphasisSpan.MARK_FILL_FILLED,
TextAnnotation.POSITION_BEFORE);
Spanned eleventhCue = getOnlyCueTextAtIndex(allCues, 10);
assertThat(eleventhCue)
.hasTextEmphasisSpanBetween("Sesame after ".length(), "Sesame after おはよ".length())
.withMarkAndPosition(
TextEmphasisSpan.MARK_SHAPE_SESAME,
TextEmphasisSpan.MARK_FILL_FILLED,
TextAnnotation.POSITION_AFTER);
Spanned twelfthCue = getOnlyCueTextAtIndex(allCues, 11);
assertThat(twelfthCue)
.hasTextEmphasisSpanBetween("Dot outside ".length(), "Dot outside ございます".length())
.withMarkAndPosition(
TextEmphasisSpan.MARK_SHAPE_DOT,
TextEmphasisSpan.MARK_FILL_FILLED,
TextAnnotation.POSITION_BEFORE);
Spanned thirteenthCue = getOnlyCueTextAtIndex(allCues, 12);
assertThat(thirteenthCue)
.hasNoTextEmphasisSpanBetween(
"No textEmphasis property ".length(), "No textEmphasis property おはよ".length());
Spanned fourteenthCue = getOnlyCueTextAtIndex(allCues, 13);
assertThat(fourteenthCue)
.hasTextEmphasisSpanBetween("Auto (TBLR) ".length(), "Auto (TBLR) ございます".length())
.withMarkAndPosition(
TextEmphasisSpan.MARK_SHAPE_SESAME,
TextEmphasisSpan.MARK_FILL_FILLED,
TextAnnotation.POSITION_BEFORE);
Spanned fifteenthCue = getOnlyCueTextAtIndex(allCues, 14);
assertThat(fifteenthCue)
.hasTextEmphasisSpanBetween("Auto (TBRL) ".length(), "Auto (TBRL) おはよ".length())
.withMarkAndPosition(
TextEmphasisSpan.MARK_SHAPE_SESAME,
TextEmphasisSpan.MARK_FILL_FILLED,
TextAnnotation.POSITION_BEFORE);
Spanned sixteenthCue = getOnlyCueTextAtIndex(allCues, 15);
assertThat(sixteenthCue)
.hasTextEmphasisSpanBetween("Auto (TB) ".length(), "Auto (TB) ございます".length())
.withMarkAndPosition(
TextEmphasisSpan.MARK_SHAPE_SESAME,
TextEmphasisSpan.MARK_FILL_FILLED,
TextAnnotation.POSITION_BEFORE);
Spanned seventeenthCue = getOnlyCueTextAtIndex(allCues, 16);
assertThat(seventeenthCue)
.hasTextEmphasisSpanBetween("Auto (LR) ".length(), "Auto (LR) おはよ".length())
.withMarkAndPosition(
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
TextEmphasisSpan.MARK_FILL_FILLED,
TextAnnotation.POSITION_BEFORE);
}
@Test
public void shear() throws Exception {
ImmutableList<CuesWithTiming> allCues = getAllCues(SHEAR_FILE);
Cue firstCue = Iterables.getOnlyElement(allCues.get(0).cues);
assertThat(firstCue.shearDegrees).isZero();
Cue secondCue = Iterables.getOnlyElement(allCues.get(1).cues);
assertThat(secondCue.shearDegrees).isWithin(0.01f).of(-15f);
Cue thirdCue = Iterables.getOnlyElement(allCues.get(2).cues);
assertThat(thirdCue.shearDegrees).isWithin(0.01f).of(15f);
Cue fourthCue = Iterables.getOnlyElement(allCues.get(3).cues);
assertThat(fourthCue.shearDegrees).isWithin(0.01f).of(-15f);
Cue fifthCue = Iterables.getOnlyElement(allCues.get(4).cues);
assertThat(fifthCue.shearDegrees).isWithin(0.01f).of(-22.5f);
Cue sixthCue = Iterables.getOnlyElement(allCues.get(5).cues);
assertThat(sixthCue.shearDegrees).isWithin(0.01f).of(0f);
Cue seventhCue = Iterables.getOnlyElement(allCues.get(6).cues);
assertThat(seventhCue.shearDegrees).isWithin(0.01f).of(-90f);
Cue eighthCue = Iterables.getOnlyElement(allCues.get(7).cues);
assertThat(eighthCue.shearDegrees).isWithin(0.01f).of(90f);
}
private static Spanned getOnlyCueTextAtIndex(ImmutableList<CuesWithTiming> allCues, int index) {
Cue cue = getOnlyCueAtIndex(allCues, index);
assertThat(cue.text).isInstanceOf(Spanned.class);
return (Spanned) Assertions.checkNotNull(cue.text);
}
private static Cue getOnlyCueAtIndex(ImmutableList<CuesWithTiming> allCues, int index) {
ImmutableList<Cue> cues = allCues.get(index).cues;
assertThat(cues).hasSize(1);
return cues.get(0);
}
private static ImmutableList<CuesWithTiming> getAllCues(String file) throws Exception {
TtmlParser ttmlParser = new TtmlParser();
byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), file);
return ttmlParser.parse(bytes, 0, bytes.length);
}
}