Fix TTML positioning
Issue: #2824 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=156781252
This commit is contained in:
parent
78c4cb74ea
commit
e892e3a5c7
@ -157,39 +157,39 @@ public final class TtmlDecoderTest extends InstrumentationTestCase {
|
|||||||
assertEquals(2, output.size());
|
assertEquals(2, output.size());
|
||||||
Cue ttmlCue = output.get(0);
|
Cue ttmlCue = output.get(0);
|
||||||
assertEquals("lorem", ttmlCue.text.toString());
|
assertEquals("lorem", ttmlCue.text.toString());
|
||||||
assertEquals(10.f / 100.f, ttmlCue.position);
|
assertEquals(10f / 100f, ttmlCue.position);
|
||||||
assertEquals(10.f / 100.f, ttmlCue.line);
|
assertEquals(10f / 100f, ttmlCue.line);
|
||||||
assertEquals(20.f / 100.f, ttmlCue.size);
|
assertEquals(20f / 100f, ttmlCue.size);
|
||||||
|
|
||||||
ttmlCue = output.get(1);
|
ttmlCue = output.get(1);
|
||||||
assertEquals("amet", ttmlCue.text.toString());
|
assertEquals("amet", ttmlCue.text.toString());
|
||||||
assertEquals(60.f / 100.f, ttmlCue.position);
|
assertEquals(60f / 100f, ttmlCue.position);
|
||||||
assertEquals(10.f / 100.f, ttmlCue.line);
|
assertEquals(10f / 100f, ttmlCue.line);
|
||||||
assertEquals(20.f / 100.f, ttmlCue.size);
|
assertEquals(20f / 100f, ttmlCue.size);
|
||||||
|
|
||||||
output = subtitle.getCues(5000000);
|
output = subtitle.getCues(5000000);
|
||||||
assertEquals(1, output.size());
|
assertEquals(1, output.size());
|
||||||
ttmlCue = output.get(0);
|
ttmlCue = output.get(0);
|
||||||
assertEquals("ipsum", ttmlCue.text.toString());
|
assertEquals("ipsum", ttmlCue.text.toString());
|
||||||
assertEquals(40.f / 100.f, ttmlCue.position);
|
assertEquals(40f / 100f, ttmlCue.position);
|
||||||
assertEquals(40.f / 100.f, ttmlCue.line);
|
assertEquals(40f / 100f, ttmlCue.line);
|
||||||
assertEquals(20.f / 100.f, ttmlCue.size);
|
assertEquals(20f / 100f, ttmlCue.size);
|
||||||
|
|
||||||
output = subtitle.getCues(9000000);
|
output = subtitle.getCues(9000000);
|
||||||
assertEquals(1, output.size());
|
assertEquals(1, output.size());
|
||||||
ttmlCue = output.get(0);
|
ttmlCue = output.get(0);
|
||||||
assertEquals("dolor", ttmlCue.text.toString());
|
assertEquals("dolor", ttmlCue.text.toString());
|
||||||
assertEquals(10.f / 100.f, ttmlCue.position);
|
assertEquals(10f / 100f, ttmlCue.position);
|
||||||
assertEquals(80.f / 100.f, ttmlCue.line);
|
assertEquals(80f / 100f, ttmlCue.line);
|
||||||
assertEquals(Cue.DIMEN_UNSET, ttmlCue.size);
|
assertEquals(1f, ttmlCue.size);
|
||||||
|
|
||||||
output = subtitle.getCues(21000000);
|
output = subtitle.getCues(21000000);
|
||||||
assertEquals(1, output.size());
|
assertEquals(1, output.size());
|
||||||
ttmlCue = output.get(0);
|
ttmlCue = output.get(0);
|
||||||
assertEquals("She first said this", ttmlCue.text.toString());
|
assertEquals("She first said this", ttmlCue.text.toString());
|
||||||
assertEquals(45.f / 100.f, ttmlCue.position);
|
assertEquals(45f / 100f, ttmlCue.position);
|
||||||
assertEquals(45.f / 100.f, ttmlCue.line);
|
assertEquals(45f / 100f, ttmlCue.line);
|
||||||
assertEquals(35.f / 100.f, ttmlCue.size);
|
assertEquals(35f / 100f, ttmlCue.size);
|
||||||
output = subtitle.getCues(25000000);
|
output = subtitle.getCues(25000000);
|
||||||
ttmlCue = output.get(0);
|
ttmlCue = output.get(0);
|
||||||
assertEquals("She first said this\nThen this", ttmlCue.text.toString());
|
assertEquals("She first said this\nThen this", ttmlCue.text.toString());
|
||||||
@ -197,8 +197,8 @@ public final class TtmlDecoderTest extends InstrumentationTestCase {
|
|||||||
assertEquals(1, output.size());
|
assertEquals(1, output.size());
|
||||||
ttmlCue = output.get(0);
|
ttmlCue = output.get(0);
|
||||||
assertEquals("She first said this\nThen this\nFinally this", ttmlCue.text.toString());
|
assertEquals("She first said this\nThen this\nFinally this", ttmlCue.text.toString());
|
||||||
assertEquals(45.f / 100.f, ttmlCue.position);
|
assertEquals(45f / 100f, ttmlCue.position);
|
||||||
assertEquals(45.f / 100.f, ttmlCue.line);
|
assertEquals(45f / 100f, ttmlCue.line);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testEmptyStyleAttribute() throws IOException, SubtitleDecoderException {
|
public void testEmptyStyleAttribute() throws IOException, SubtitleDecoderException {
|
||||||
|
@ -17,7 +17,6 @@ package com.google.android.exoplayer2.text.ttml;
|
|||||||
|
|
||||||
import android.text.Layout;
|
import android.text.Layout;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.text.Cue;
|
import com.google.android.exoplayer2.text.Cue;
|
||||||
import com.google.android.exoplayer2.text.SimpleSubtitleDecoder;
|
import com.google.android.exoplayer2.text.SimpleSubtitleDecoder;
|
||||||
@ -100,7 +99,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
|||||||
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<>();
|
||||||
regionMap.put(TtmlNode.ANONYMOUS_REGION_ID, new TtmlRegion());
|
regionMap.put(TtmlNode.ANONYMOUS_REGION_ID, new TtmlRegion(null));
|
||||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes, 0, length);
|
ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes, 0, length);
|
||||||
xmlParser.setInput(inputStream, null);
|
xmlParser.setInput(inputStream, null);
|
||||||
TtmlSubtitle ttmlSubtitle = null;
|
TtmlSubtitle ttmlSubtitle = null;
|
||||||
@ -211,9 +210,9 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
|||||||
globalStyles.put(style.getId(), style);
|
globalStyles.put(style.getId(), style);
|
||||||
}
|
}
|
||||||
} else if (XmlPullParserUtil.isStartTag(xmlParser, TtmlNode.TAG_REGION)) {
|
} else if (XmlPullParserUtil.isStartTag(xmlParser, TtmlNode.TAG_REGION)) {
|
||||||
Pair<String, TtmlRegion> ttmlRegionInfo = parseRegionAttributes(xmlParser);
|
TtmlRegion ttmlRegion = parseRegionAttributes(xmlParser);
|
||||||
if (ttmlRegionInfo != null) {
|
if (ttmlRegion != null) {
|
||||||
globalRegions.put(ttmlRegionInfo.first, ttmlRegionInfo.second);
|
globalRegions.put(ttmlRegion.id, ttmlRegion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while (!XmlPullParserUtil.isEndTag(xmlParser, TtmlNode.TAG_HEAD));
|
} while (!XmlPullParserUtil.isEndTag(xmlParser, TtmlNode.TAG_HEAD));
|
||||||
@ -221,41 +220,84 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a region declaration. Supports origin and extent definition but only when defined in
|
* Parses a region declaration.
|
||||||
* terms of percentage of the viewport. Regions that do not correctly declare origin are ignored.
|
* <p>
|
||||||
|
* If the region defines an origin and/or extent, it is required that they're defined as
|
||||||
|
* percentages of the viewport. Region declarations that define origin and/or extent in other
|
||||||
|
* formats are unsupported, and null is returned.
|
||||||
*/
|
*/
|
||||||
private Pair<String, TtmlRegion> parseRegionAttributes(XmlPullParser xmlParser) {
|
private TtmlRegion parseRegionAttributes(XmlPullParser xmlParser) {
|
||||||
String regionId = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_ID);
|
String regionId = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_ID);
|
||||||
String regionOrigin = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_ORIGIN);
|
if (regionId == null) {
|
||||||
String regionExtent = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_EXTENT);
|
|
||||||
if (regionOrigin == null || regionId == null) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
float position = Cue.DIMEN_UNSET;
|
|
||||||
float line = Cue.DIMEN_UNSET;
|
float position;
|
||||||
Matcher originMatcher = PERCENTAGE_COORDINATES.matcher(regionOrigin);
|
float line;
|
||||||
if (originMatcher.matches()) {
|
String regionOrigin = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_ORIGIN);
|
||||||
try {
|
if (regionOrigin != null) {
|
||||||
position = Float.parseFloat(originMatcher.group(1)) / 100.f;
|
Matcher originMatcher = PERCENTAGE_COORDINATES.matcher(regionOrigin);
|
||||||
line = Float.parseFloat(originMatcher.group(2)) / 100.f;
|
if (originMatcher.matches()) {
|
||||||
} catch (NumberFormatException e) {
|
try {
|
||||||
Log.w(TAG, "Ignoring region with malformed origin: '" + regionOrigin + "'", e);
|
position = Float.parseFloat(originMatcher.group(1)) / 100f;
|
||||||
position = Cue.DIMEN_UNSET;
|
line = Float.parseFloat(originMatcher.group(2)) / 100f;
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
Log.w(TAG, "Ignoring region with malformed origin: " + regionOrigin);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Ignoring region with unsupported origin: " + regionOrigin);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Origin is omitted. Default to top left.
|
||||||
|
position = 0;
|
||||||
|
line = 0;
|
||||||
}
|
}
|
||||||
float width = Cue.DIMEN_UNSET;
|
|
||||||
|
float width;
|
||||||
|
float height;
|
||||||
|
String regionExtent = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_EXTENT);
|
||||||
if (regionExtent != null) {
|
if (regionExtent != null) {
|
||||||
Matcher extentMatcher = PERCENTAGE_COORDINATES.matcher(regionExtent);
|
Matcher extentMatcher = PERCENTAGE_COORDINATES.matcher(regionExtent);
|
||||||
if (extentMatcher.matches()) {
|
if (extentMatcher.matches()) {
|
||||||
try {
|
try {
|
||||||
width = Float.parseFloat(extentMatcher.group(1)) / 100.f;
|
width = Float.parseFloat(extentMatcher.group(1)) / 100f;
|
||||||
|
height = Float.parseFloat(extentMatcher.group(2)) / 100f;
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
Log.w(TAG, "Ignoring malformed region extent: '" + regionExtent + "'", e);
|
Log.w(TAG, "Ignoring region with malformed extent: " + regionOrigin);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Ignoring region with unsupported extent: " + regionOrigin);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Extent is omitted. Default to extent of parent.
|
||||||
|
width = 1;
|
||||||
|
height = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Cue.AnchorType int lineAnchor = Cue.ANCHOR_TYPE_START;
|
||||||
|
String displayAlign = XmlPullParserUtil.getAttributeValue(xmlParser,
|
||||||
|
TtmlNode.ATTR_TTS_DISPLAY_ALIGN);
|
||||||
|
if (displayAlign != null) {
|
||||||
|
switch (displayAlign.toLowerCase()) {
|
||||||
|
case "center":
|
||||||
|
lineAnchor = Cue.ANCHOR_TYPE_MIDDLE;
|
||||||
|
line += height / 2;
|
||||||
|
break;
|
||||||
|
case "after":
|
||||||
|
lineAnchor = Cue.ANCHOR_TYPE_END;
|
||||||
|
line += height;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Default "before" case. Do nothing.
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return position != Cue.DIMEN_UNSET ? new Pair<>(regionId, new TtmlRegion(position, line,
|
|
||||||
Cue.LINE_TYPE_FRACTION, width)) : null;
|
return new TtmlRegion(regionId, position, line, Cue.LINE_TYPE_FRACTION, lineAnchor, width);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String[] parseStyleIds(String parentStyleIds) {
|
private String[] parseStyleIds(String parentStyleIds) {
|
||||||
@ -277,7 +319,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
|||||||
try {
|
try {
|
||||||
style.setBackgroundColor(ColorParser.parseTtmlColor(attributeValue));
|
style.setBackgroundColor(ColorParser.parseTtmlColor(attributeValue));
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
Log.w(TAG, "failed parsing background value: '" + attributeValue + "'");
|
Log.w(TAG, "Failed parsing background value: " + attributeValue);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TtmlNode.ATTR_TTS_COLOR:
|
case TtmlNode.ATTR_TTS_COLOR:
|
||||||
@ -285,7 +327,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
|||||||
try {
|
try {
|
||||||
style.setFontColor(ColorParser.parseTtmlColor(attributeValue));
|
style.setFontColor(ColorParser.parseTtmlColor(attributeValue));
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
Log.w(TAG, "failed parsing color value: '" + attributeValue + "'");
|
Log.w(TAG, "Failed parsing color value: " + attributeValue);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TtmlNode.ATTR_TTS_FONT_FAMILY:
|
case TtmlNode.ATTR_TTS_FONT_FAMILY:
|
||||||
@ -296,7 +338,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
|||||||
style = createIfNull(style);
|
style = createIfNull(style);
|
||||||
parseFontSize(attributeValue, style);
|
parseFontSize(attributeValue, style);
|
||||||
} catch (SubtitleDecoderException e) {
|
} catch (SubtitleDecoderException e) {
|
||||||
Log.w(TAG, "failed parsing fontSize value: '" + attributeValue + "'");
|
Log.w(TAG, "Failed parsing fontSize value: " + attributeValue);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TtmlNode.ATTR_TTS_FONT_WEIGHT:
|
case TtmlNode.ATTR_TTS_FONT_WEIGHT:
|
||||||
|
@ -50,14 +50,15 @@ import java.util.TreeSet;
|
|||||||
|
|
||||||
public static final String ANONYMOUS_REGION_ID = "";
|
public static final String ANONYMOUS_REGION_ID = "";
|
||||||
public static final String ATTR_ID = "id";
|
public static final String ATTR_ID = "id";
|
||||||
public static final String ATTR_TTS_BACKGROUND_COLOR = "backgroundColor";
|
public static final String ATTR_TTS_ORIGIN = "origin";
|
||||||
public static final String ATTR_TTS_EXTENT = "extent";
|
public static final String ATTR_TTS_EXTENT = "extent";
|
||||||
|
public static final String ATTR_TTS_DISPLAY_ALIGN = "displayAlign";
|
||||||
|
public static final String ATTR_TTS_BACKGROUND_COLOR = "backgroundColor";
|
||||||
public static final String ATTR_TTS_FONT_STYLE = "fontStyle";
|
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_SIZE = "fontSize";
|
||||||
public static final String ATTR_TTS_FONT_FAMILY = "fontFamily";
|
public static final String ATTR_TTS_FONT_FAMILY = "fontFamily";
|
||||||
public static final String ATTR_TTS_FONT_WEIGHT = "fontWeight";
|
public static final String ATTR_TTS_FONT_WEIGHT = "fontWeight";
|
||||||
public static final String ATTR_TTS_COLOR = "color";
|
public static final String ATTR_TTS_COLOR = "color";
|
||||||
public static final String ATTR_TTS_ORIGIN = "origin";
|
|
||||||
public static final String ATTR_TTS_TEXT_DECORATION = "textDecoration";
|
public static final String ATTR_TTS_TEXT_DECORATION = "textDecoration";
|
||||||
public static final String ATTR_TTS_TEXT_ALIGN = "textAlign";
|
public static final String ATTR_TTS_TEXT_ALIGN = "textAlign";
|
||||||
|
|
||||||
@ -179,7 +180,7 @@ import java.util.TreeSet;
|
|||||||
for (Entry<String, SpannableStringBuilder> entry : regionOutputs.entrySet()) {
|
for (Entry<String, SpannableStringBuilder> entry : regionOutputs.entrySet()) {
|
||||||
TtmlRegion region = regionMap.get(entry.getKey());
|
TtmlRegion region = regionMap.get(entry.getKey());
|
||||||
cues.add(new Cue(cleanUpText(entry.getValue()), null, region.line, region.lineType,
|
cues.add(new Cue(cleanUpText(entry.getValue()), null, region.line, region.lineType,
|
||||||
Cue.TYPE_UNSET, region.position, Cue.TYPE_UNSET, region.width));
|
region.lineAnchor, region.position, Cue.TYPE_UNSET, region.width));
|
||||||
}
|
}
|
||||||
return cues;
|
return cues;
|
||||||
}
|
}
|
||||||
|
@ -22,20 +22,24 @@ import com.google.android.exoplayer2.text.Cue;
|
|||||||
*/
|
*/
|
||||||
/* package */ final class TtmlRegion {
|
/* package */ final class TtmlRegion {
|
||||||
|
|
||||||
|
public final String id;
|
||||||
public final float position;
|
public final float position;
|
||||||
public final float line;
|
public final float line;
|
||||||
@Cue.LineType
|
@Cue.LineType public final int lineType;
|
||||||
public final int lineType;
|
@Cue.AnchorType public final int lineAnchor;
|
||||||
public final float width;
|
public final float width;
|
||||||
|
|
||||||
public TtmlRegion() {
|
public TtmlRegion(String id) {
|
||||||
this(Cue.DIMEN_UNSET, Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET);
|
this(id, Cue.DIMEN_UNSET, Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TtmlRegion(float position, float line, @Cue.LineType int lineType, float width) {
|
public TtmlRegion(String id, float position, float line, @Cue.LineType int lineType,
|
||||||
|
@Cue.AnchorType int lineAnchor, float width) {
|
||||||
|
this.id = id;
|
||||||
this.position = position;
|
this.position = position;
|
||||||
this.line = line;
|
this.line = line;
|
||||||
this.lineType = lineType;
|
this.lineType = lineType;
|
||||||
|
this.lineAnchor = lineAnchor;
|
||||||
this.width = width;
|
this.width = width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user