diff --git a/library/src/androidTest/assets/ttml/multiple_regions.xml b/library/src/androidTest/assets/ttml/multiple_regions.xml index edc704d8b1..3bde2c99b5 100644 --- a/library/src/androidTest/assets/ttml/multiple_regions.xml +++ b/library/src/androidTest/assets/ttml/multiple_regions.xml @@ -8,7 +8,7 @@ - + diff --git a/library/src/androidTest/java/com/google/android/exoplayer/text/ttml/TtmlParserTest.java b/library/src/androidTest/java/com/google/android/exoplayer/text/ttml/TtmlParserTest.java index ce2282ab86..04686b959c 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer/text/ttml/TtmlParserTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer/text/ttml/TtmlParserTest.java @@ -156,10 +156,13 @@ public final class TtmlParserTest extends InstrumentationTestCase { assertEquals("lorem", ttmlCue.text.toString()); assertEquals(10.f / 100.f, ttmlCue.position); assertEquals(10.f / 100.f, ttmlCue.line); + assertEquals(20.f / 100.f, ttmlCue.size); + ttmlCue = output.get(1); assertEquals("amet", ttmlCue.text.toString()); assertEquals(60.f / 100.f, ttmlCue.position); assertEquals(10.f / 100.f, ttmlCue.line); + assertEquals(20.f / 100.f, ttmlCue.size); output = subtitle.getCues(5000000); assertEquals(1, output.size()); @@ -167,6 +170,7 @@ public final class TtmlParserTest extends InstrumentationTestCase { assertEquals("ipsum", ttmlCue.text.toString()); assertEquals(40.f / 100.f, ttmlCue.position); assertEquals(40.f / 100.f, ttmlCue.line); + assertEquals(20.f / 100.f, ttmlCue.size); output = subtitle.getCues(9000000); assertEquals(1, output.size()); @@ -174,6 +178,7 @@ public final class TtmlParserTest extends InstrumentationTestCase { assertEquals("dolor", ttmlCue.text.toString()); assertEquals(10.f / 100.f, ttmlCue.position); assertEquals(80.f / 100.f, ttmlCue.line); + assertEquals(Cue.DIMEN_UNSET, ttmlCue.size); output = subtitle.getCues(21000000); assertEquals(1, output.size()); @@ -181,6 +186,7 @@ public final class TtmlParserTest extends InstrumentationTestCase { assertEquals("She first said this", ttmlCue.text.toString()); assertEquals(45.f / 100.f, ttmlCue.position); assertEquals(45.f / 100.f, ttmlCue.line); + assertEquals(35.f / 100.f, ttmlCue.size); output = subtitle.getCues(25000000); ttmlCue = output.get(0); assertEquals("She first said this\nThen this", ttmlCue.text.toString()); diff --git a/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlNode.java b/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlNode.java index 4dd33407c1..abf9a4643c 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlNode.java +++ b/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlNode.java @@ -53,6 +53,7 @@ import java.util.TreeSet; public static final String ANONYMOUS_REGION_ID = ""; public static final String ATTR_ID = "id"; public static final String ATTR_TTS_BACKGROUND_COLOR = "backgroundColor"; + public static final String ATTR_TTS_EXTENT = "extent"; public static final String ATTR_TTS_FONT_STYLE = "fontStyle"; public static final String ATTR_TTS_FONT_SIZE = "fontSize"; public static final String ATTR_TTS_FONT_FAMILY = "fontFamily"; @@ -180,7 +181,7 @@ import java.util.TreeSet; for (Entry entry : regionOutputs.entrySet()) { TtmlRegion region = regionMap.get(entry.getKey()); cues.add(new Cue(cleanUpText(entry.getValue()), null, region.line, Cue.TYPE_UNSET, - Cue.TYPE_UNSET, region.position, Cue.TYPE_UNSET, Cue.DIMEN_UNSET)); + Cue.TYPE_UNSET, region.position, Cue.TYPE_UNSET, region.width)); } return cues; } diff --git a/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlParser.java b/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlParser.java index 984bc6d160..e0907d0364 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlParser.java +++ b/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlParser.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer.text.ttml; import com.google.android.exoplayer.C; import com.google.android.exoplayer.ParserException; +import com.google.android.exoplayer.text.Cue; import com.google.android.exoplayer.text.SimpleSubtitleParser; import com.google.android.exoplayer.util.ColorParser; import com.google.android.exoplayer.util.ParserUtil; @@ -74,9 +75,8 @@ public final class TtmlParser extends SimpleSubtitleParser { + "(?:(\\.[0-9]+)|:([0-9][0-9])(?:\\.([0-9]+))?)?$"); private static final Pattern OFFSET_TIME = Pattern.compile("^([0-9]+(?:\\.[0-9]+)?)(h|m|s|ms|f|t)$"); - private static final Pattern FONT_SIZE = - Pattern.compile("^(([0-9]*.)?[0-9]+)(px|em|%)$"); - private static final Pattern ORIGIN_COORDINATES = + private static final Pattern FONT_SIZE = Pattern.compile("^(([0-9]*.)?[0-9]+)(px|em|%)$"); + private static final Pattern PERCENTAGE_COORDINATES = Pattern.compile("^(\\d+\\.?\\d*?)% (\\d+\\.?\\d*?)%$"); // TODO: read and apply the following attributes if specified. @@ -183,23 +183,42 @@ public final class TtmlParser extends SimpleSubtitleParser { return globalStyles; } + /** + * Parses a region declaration. Supports origin and extent definition but only when defined in + * terms of percentage of the viewport. Regions that do not correctly declare origin are ignored. + */ private Pair parseRegionAttributes(XmlPullParser xmlParser) { String regionId = ParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_ID); String regionOrigin = ParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_ORIGIN); + String regionExtent = ParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_EXTENT); if (regionOrigin == null || regionId == null) { return null; } - Matcher originMatcher = ORIGIN_COORDINATES.matcher(regionOrigin); + float position = Cue.DIMEN_UNSET; + float line = Cue.DIMEN_UNSET; + Matcher originMatcher = PERCENTAGE_COORDINATES.matcher(regionOrigin); if (originMatcher.matches()) { try { - float position = Float.parseFloat(originMatcher.group(1)) / 100.f; - float line = Float.parseFloat(originMatcher.group(2)) / 100.f; - return new Pair<>(regionId, new TtmlRegion(position, line)); + position = Float.parseFloat(originMatcher.group(1)) / 100.f; + line = Float.parseFloat(originMatcher.group(2)) / 100.f; } catch (NumberFormatException e) { - Log.w(TAG, "Ignoring malformed region declaration: '" + regionOrigin + "'", e); + Log.w(TAG, "Ignoring region with malformed origin: '" + regionOrigin + "'", e); + position = Cue.DIMEN_UNSET; } } - return null; + float width = Cue.DIMEN_UNSET; + if (regionExtent != null) { + Matcher extentMatcher = PERCENTAGE_COORDINATES.matcher(regionExtent); + if (extentMatcher.matches()) { + try { + width = Float.parseFloat(extentMatcher.group(1)) / 100.f; + } catch (NumberFormatException e) { + Log.w(TAG, "Ignoring malformed region extent: '" + regionExtent + "'", e); + } + } + } + return position != Cue.DIMEN_UNSET ? new Pair<>(regionId, new TtmlRegion(position, line, width)) + : null; } private String[] parseStyleIds(String parentStyleIds) { @@ -387,10 +406,11 @@ public final class TtmlParser extends SimpleSubtitleParser { matcher = FONT_SIZE.matcher(expression); } else if (expressions.length == 2){ matcher = FONT_SIZE.matcher(expressions[1]); - Log.w(TAG, "multiple values in fontSize attribute. Picking the second " - + "value for vertical font size and ignoring the first."); + Log.w(TAG, "Multiple values in fontSize attribute. Picking the second value for vertical font" + + " size and ignoring the first."); } else { - throw new ParserException(); + throw new ParserException("Invalid number of entries for fontSize: " + expressions.length + + "."); } if (matcher.matches()) { @@ -406,11 +426,11 @@ public final class TtmlParser extends SimpleSubtitleParser { out.setFontSizeUnit(TtmlStyle.FONT_SIZE_UNIT_PERCENT); break; default: - throw new ParserException(); + throw new ParserException("Invalid unit for fontSize: '" + unit + "'."); } out.setFontSize(Float.valueOf(matcher.group(1))); } else { - throw new ParserException(); + throw new ParserException("Invalid expression for fontSize: '" + expression + "'."); } } diff --git a/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlRegion.java b/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlRegion.java index 757d134ad7..135018a861 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlRegion.java +++ b/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlRegion.java @@ -24,14 +24,16 @@ import com.google.android.exoplayer.text.Cue; public final float position; public final float line; + public final float width; public TtmlRegion() { - this(Cue.DIMEN_UNSET, Cue.DIMEN_UNSET); + this(Cue.DIMEN_UNSET, Cue.DIMEN_UNSET, Cue.DIMEN_UNSET); } - public TtmlRegion(float position, float line) { + public TtmlRegion(float position, float line, float width) { this.position = position; this.line = line; + this.width = width; } }