mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Remove TTML package from null-checking blacklist
PiperOrigin-RevId: 290629644
This commit is contained in:
parent
8a2a527129
commit
37908dd4df
4 changed files with 123 additions and 84 deletions
|
|
@ -16,11 +16,13 @@
|
||||||
package com.google.android.exoplayer2.text.ttml;
|
package com.google.android.exoplayer2.text.ttml;
|
||||||
|
|
||||||
import android.text.Layout;
|
import android.text.Layout;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
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;
|
||||||
import com.google.android.exoplayer2.text.Subtitle;
|
import com.google.android.exoplayer2.text.Subtitle;
|
||||||
import com.google.android.exoplayer2.text.SubtitleDecoderException;
|
import com.google.android.exoplayer2.text.SubtitleDecoderException;
|
||||||
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.ColorParser;
|
import com.google.android.exoplayer2.util.ColorParser;
|
||||||
import com.google.android.exoplayer2.util.Log;
|
import com.google.android.exoplayer2.util.Log;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
|
@ -32,6 +34,7 @@ import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import org.checkerframework.checker.nullness.qual.PolyNull;
|
||||||
import org.xmlpull.v1.XmlPullParser;
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
import org.xmlpull.v1.XmlPullParserFactory;
|
import org.xmlpull.v1.XmlPullParserFactory;
|
||||||
|
|
@ -110,18 +113,18 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
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(null));
|
regionMap.put(TtmlNode.ANONYMOUS_REGION_ID, new TtmlRegion(TtmlNode.ANONYMOUS_REGION_ID));
|
||||||
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;
|
@Nullable TtmlSubtitle ttmlSubtitle = null;
|
||||||
ArrayDeque<TtmlNode> nodeStack = new ArrayDeque<>();
|
ArrayDeque<TtmlNode> nodeStack = new ArrayDeque<>();
|
||||||
int unsupportedNodeDepth = 0;
|
int unsupportedNodeDepth = 0;
|
||||||
int eventType = xmlParser.getEventType();
|
int eventType = xmlParser.getEventType();
|
||||||
FrameAndTickRate frameAndTickRate = DEFAULT_FRAME_AND_TICK_RATE;
|
FrameAndTickRate frameAndTickRate = DEFAULT_FRAME_AND_TICK_RATE;
|
||||||
CellResolution cellResolution = DEFAULT_CELL_RESOLUTION;
|
CellResolution cellResolution = DEFAULT_CELL_RESOLUTION;
|
||||||
TtsExtent ttsExtent = null;
|
@Nullable TtsExtent ttsExtent = null;
|
||||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||||
TtmlNode parent = nodeStack.peek();
|
@Nullable TtmlNode parent = nodeStack.peek();
|
||||||
if (unsupportedNodeDepth == 0) {
|
if (unsupportedNodeDepth == 0) {
|
||||||
String name = xmlParser.getName();
|
String name = xmlParser.getName();
|
||||||
if (eventType == XmlPullParser.START_TAG) {
|
if (eventType == XmlPullParser.START_TAG) {
|
||||||
|
|
@ -149,10 +152,12 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (eventType == XmlPullParser.TEXT) {
|
} else if (eventType == XmlPullParser.TEXT) {
|
||||||
parent.addChild(TtmlNode.buildTextNode(xmlParser.getText()));
|
Assertions.checkNotNull(parent).addChild(TtmlNode.buildTextNode(xmlParser.getText()));
|
||||||
} else if (eventType == XmlPullParser.END_TAG) {
|
} else if (eventType == XmlPullParser.END_TAG) {
|
||||||
if (xmlParser.getName().equals(TtmlNode.TAG_TT)) {
|
if (xmlParser.getName().equals(TtmlNode.TAG_TT)) {
|
||||||
ttmlSubtitle = new TtmlSubtitle(nodeStack.peek(), globalStyles, regionMap, imageMap);
|
ttmlSubtitle =
|
||||||
|
new TtmlSubtitle(
|
||||||
|
Assertions.checkNotNull(nodeStack.peek()), globalStyles, regionMap, imageMap);
|
||||||
}
|
}
|
||||||
nodeStack.pop();
|
nodeStack.pop();
|
||||||
}
|
}
|
||||||
|
|
@ -166,7 +171,11 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
xmlParser.next();
|
xmlParser.next();
|
||||||
eventType = xmlParser.getEventType();
|
eventType = xmlParser.getEventType();
|
||||||
}
|
}
|
||||||
return ttmlSubtitle;
|
if (ttmlSubtitle != null) {
|
||||||
|
return ttmlSubtitle;
|
||||||
|
} else {
|
||||||
|
throw new SubtitleDecoderException("No TTML subtitles found");
|
||||||
|
}
|
||||||
} catch (XmlPullParserException xppe) {
|
} catch (XmlPullParserException xppe) {
|
||||||
throw new SubtitleDecoderException("Unable to decode source", xppe);
|
throw new SubtitleDecoderException("Unable to decode source", xppe);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|
@ -221,8 +230,8 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
int columns = Integer.parseInt(cellResolutionMatcher.group(1));
|
int columns = Integer.parseInt(Assertions.checkNotNull(cellResolutionMatcher.group(1)));
|
||||||
int rows = Integer.parseInt(cellResolutionMatcher.group(2));
|
int rows = Integer.parseInt(Assertions.checkNotNull(cellResolutionMatcher.group(2)));
|
||||||
if (columns == 0 || rows == 0) {
|
if (columns == 0 || rows == 0) {
|
||||||
throw new SubtitleDecoderException("Invalid cell resolution " + columns + " " + rows);
|
throw new SubtitleDecoderException("Invalid cell resolution " + columns + " " + rows);
|
||||||
}
|
}
|
||||||
|
|
@ -233,7 +242,9 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
private TtsExtent parseTtsExtent(XmlPullParser xmlParser) {
|
private TtsExtent parseTtsExtent(XmlPullParser xmlParser) {
|
||||||
|
@Nullable
|
||||||
String ttsExtent = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_EXTENT);
|
String ttsExtent = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_EXTENT);
|
||||||
if (ttsExtent == null) {
|
if (ttsExtent == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -245,8 +256,8 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
int width = Integer.parseInt(extentMatcher.group(1));
|
int width = Integer.parseInt(Assertions.checkNotNull(extentMatcher.group(1)));
|
||||||
int height = Integer.parseInt(extentMatcher.group(2));
|
int height = Integer.parseInt(Assertions.checkNotNull(extentMatcher.group(2)));
|
||||||
return new TtsExtent(width, height);
|
return new TtsExtent(width, height);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
Log.w(TAG, "Ignoring malformed tts extent: " + ttsExtent);
|
Log.w(TAG, "Ignoring malformed tts extent: " + ttsExtent);
|
||||||
|
|
@ -258,24 +269,26 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
XmlPullParser xmlParser,
|
XmlPullParser xmlParser,
|
||||||
Map<String, TtmlStyle> globalStyles,
|
Map<String, TtmlStyle> globalStyles,
|
||||||
CellResolution cellResolution,
|
CellResolution cellResolution,
|
||||||
TtsExtent ttsExtent,
|
@Nullable TtsExtent ttsExtent,
|
||||||
Map<String, TtmlRegion> globalRegions,
|
Map<String, TtmlRegion> globalRegions,
|
||||||
Map<String, String> imageMap)
|
Map<String, String> imageMap)
|
||||||
throws IOException, XmlPullParserException {
|
throws IOException, XmlPullParserException {
|
||||||
do {
|
do {
|
||||||
xmlParser.next();
|
xmlParser.next();
|
||||||
if (XmlPullParserUtil.isStartTag(xmlParser, TtmlNode.TAG_STYLE)) {
|
if (XmlPullParserUtil.isStartTag(xmlParser, TtmlNode.TAG_STYLE)) {
|
||||||
String parentStyleId = XmlPullParserUtil.getAttributeValue(xmlParser, ATTR_STYLE);
|
@Nullable String parentStyleId = XmlPullParserUtil.getAttributeValue(xmlParser, ATTR_STYLE);
|
||||||
TtmlStyle style = parseStyleAttributes(xmlParser, new TtmlStyle());
|
TtmlStyle style = parseStyleAttributes(xmlParser, new TtmlStyle());
|
||||||
if (parentStyleId != null) {
|
if (parentStyleId != null) {
|
||||||
for (String id : parseStyleIds(parentStyleId)) {
|
for (String id : parseStyleIds(parentStyleId)) {
|
||||||
style.chain(globalStyles.get(id));
|
style.chain(globalStyles.get(id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (style.getId() != null) {
|
String styleId = style.getId();
|
||||||
globalStyles.put(style.getId(), style);
|
if (styleId != null) {
|
||||||
|
globalStyles.put(styleId, style);
|
||||||
}
|
}
|
||||||
} else if (XmlPullParserUtil.isStartTag(xmlParser, TtmlNode.TAG_REGION)) {
|
} else if (XmlPullParserUtil.isStartTag(xmlParser, TtmlNode.TAG_REGION)) {
|
||||||
|
@Nullable
|
||||||
TtmlRegion ttmlRegion = parseRegionAttributes(xmlParser, cellResolution, ttsExtent);
|
TtmlRegion ttmlRegion = parseRegionAttributes(xmlParser, cellResolution, ttsExtent);
|
||||||
if (ttmlRegion != null) {
|
if (ttmlRegion != null) {
|
||||||
globalRegions.put(ttmlRegion.id, ttmlRegion);
|
globalRegions.put(ttmlRegion.id, ttmlRegion);
|
||||||
|
|
@ -292,7 +305,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
do {
|
do {
|
||||||
xmlParser.next();
|
xmlParser.next();
|
||||||
if (XmlPullParserUtil.isStartTag(xmlParser, TtmlNode.TAG_IMAGE)) {
|
if (XmlPullParserUtil.isStartTag(xmlParser, TtmlNode.TAG_IMAGE)) {
|
||||||
String id = XmlPullParserUtil.getAttributeValue(xmlParser, "id");
|
@Nullable String id = XmlPullParserUtil.getAttributeValue(xmlParser, "id");
|
||||||
if (id != null) {
|
if (id != null) {
|
||||||
String encodedBitmapData = xmlParser.nextText();
|
String encodedBitmapData = xmlParser.nextText();
|
||||||
imageMap.put(id, encodedBitmapData);
|
imageMap.put(id, encodedBitmapData);
|
||||||
|
|
@ -309,9 +322,10 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
* fractions. In case of missing tts:extent the pixel defined regions can't be parsed, and null is
|
* fractions. In case of missing tts:extent the pixel defined regions can't be parsed, and null is
|
||||||
* returned.
|
* returned.
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
private TtmlRegion parseRegionAttributes(
|
private TtmlRegion parseRegionAttributes(
|
||||||
XmlPullParser xmlParser, CellResolution cellResolution, TtsExtent ttsExtent) {
|
XmlPullParser xmlParser, CellResolution cellResolution, @Nullable TtsExtent ttsExtent) {
|
||||||
String regionId = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_ID);
|
@Nullable String regionId = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_ID);
|
||||||
if (regionId == null) {
|
if (regionId == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -319,14 +333,16 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
float position;
|
float position;
|
||||||
float line;
|
float line;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
String regionOrigin = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_ORIGIN);
|
String regionOrigin = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_ORIGIN);
|
||||||
if (regionOrigin != null) {
|
if (regionOrigin != null) {
|
||||||
Matcher originPercentageMatcher = PERCENTAGE_COORDINATES.matcher(regionOrigin);
|
Matcher originPercentageMatcher = PERCENTAGE_COORDINATES.matcher(regionOrigin);
|
||||||
Matcher originPixelMatcher = PIXEL_COORDINATES.matcher(regionOrigin);
|
Matcher originPixelMatcher = PIXEL_COORDINATES.matcher(regionOrigin);
|
||||||
if (originPercentageMatcher.matches()) {
|
if (originPercentageMatcher.matches()) {
|
||||||
try {
|
try {
|
||||||
position = Float.parseFloat(originPercentageMatcher.group(1)) / 100f;
|
position =
|
||||||
line = Float.parseFloat(originPercentageMatcher.group(2)) / 100f;
|
Float.parseFloat(Assertions.checkNotNull(originPercentageMatcher.group(1))) / 100f;
|
||||||
|
line = Float.parseFloat(Assertions.checkNotNull(originPercentageMatcher.group(2))) / 100f;
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
Log.w(TAG, "Ignoring region with malformed origin: " + regionOrigin);
|
Log.w(TAG, "Ignoring region with malformed origin: " + regionOrigin);
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -337,8 +353,8 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
int width = Integer.parseInt(originPixelMatcher.group(1));
|
int width = Integer.parseInt(Assertions.checkNotNull(originPixelMatcher.group(1)));
|
||||||
int height = Integer.parseInt(originPixelMatcher.group(2));
|
int height = Integer.parseInt(Assertions.checkNotNull(originPixelMatcher.group(2)));
|
||||||
// Convert pixel values to fractions.
|
// Convert pixel values to fractions.
|
||||||
position = width / (float) ttsExtent.width;
|
position = width / (float) ttsExtent.width;
|
||||||
line = height / (float) ttsExtent.height;
|
line = height / (float) ttsExtent.height;
|
||||||
|
|
@ -362,14 +378,17 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
|
|
||||||
float width;
|
float width;
|
||||||
float height;
|
float height;
|
||||||
|
@Nullable
|
||||||
String regionExtent = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_EXTENT);
|
String regionExtent = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_EXTENT);
|
||||||
if (regionExtent != null) {
|
if (regionExtent != null) {
|
||||||
Matcher extentPercentageMatcher = PERCENTAGE_COORDINATES.matcher(regionExtent);
|
Matcher extentPercentageMatcher = PERCENTAGE_COORDINATES.matcher(regionExtent);
|
||||||
Matcher extentPixelMatcher = PIXEL_COORDINATES.matcher(regionExtent);
|
Matcher extentPixelMatcher = PIXEL_COORDINATES.matcher(regionExtent);
|
||||||
if (extentPercentageMatcher.matches()) {
|
if (extentPercentageMatcher.matches()) {
|
||||||
try {
|
try {
|
||||||
width = Float.parseFloat(extentPercentageMatcher.group(1)) / 100f;
|
width =
|
||||||
height = Float.parseFloat(extentPercentageMatcher.group(2)) / 100f;
|
Float.parseFloat(Assertions.checkNotNull(extentPercentageMatcher.group(1))) / 100f;
|
||||||
|
height =
|
||||||
|
Float.parseFloat(Assertions.checkNotNull(extentPercentageMatcher.group(2))) / 100f;
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
Log.w(TAG, "Ignoring region with malformed extent: " + regionOrigin);
|
Log.w(TAG, "Ignoring region with malformed extent: " + regionOrigin);
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -380,8 +399,8 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
int extentWidth = Integer.parseInt(extentPixelMatcher.group(1));
|
int extentWidth = Integer.parseInt(Assertions.checkNotNull(extentPixelMatcher.group(1)));
|
||||||
int extentHeight = Integer.parseInt(extentPixelMatcher.group(2));
|
int extentHeight = Integer.parseInt(Assertions.checkNotNull(extentPixelMatcher.group(2)));
|
||||||
// Convert pixel values to fractions.
|
// Convert pixel values to fractions.
|
||||||
width = extentWidth / (float) ttsExtent.width;
|
width = extentWidth / (float) ttsExtent.width;
|
||||||
height = extentHeight / (float) ttsExtent.height;
|
height = extentHeight / (float) ttsExtent.height;
|
||||||
|
|
@ -404,8 +423,9 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Cue.AnchorType int lineAnchor = Cue.ANCHOR_TYPE_START;
|
@Cue.AnchorType int lineAnchor = Cue.ANCHOR_TYPE_START;
|
||||||
String displayAlign = XmlPullParserUtil.getAttributeValue(xmlParser,
|
@Nullable
|
||||||
TtmlNode.ATTR_TTS_DISPLAY_ALIGN);
|
String displayAlign =
|
||||||
|
XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_DISPLAY_ALIGN);
|
||||||
if (displayAlign != null) {
|
if (displayAlign != null) {
|
||||||
switch (Util.toLowerInvariant(displayAlign)) {
|
switch (Util.toLowerInvariant(displayAlign)) {
|
||||||
case "center":
|
case "center":
|
||||||
|
|
@ -440,7 +460,8 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
return parentStyleIds.isEmpty() ? new String[0] : Util.split(parentStyleIds, "\\s+");
|
return parentStyleIds.isEmpty() ? new String[0] : Util.split(parentStyleIds, "\\s+");
|
||||||
}
|
}
|
||||||
|
|
||||||
private TtmlStyle parseStyleAttributes(XmlPullParser parser, TtmlStyle style) {
|
@PolyNull
|
||||||
|
private TtmlStyle parseStyleAttributes(XmlPullParser parser, @PolyNull TtmlStyle style) {
|
||||||
int attributeCount = parser.getAttributeCount();
|
int attributeCount = parser.getAttributeCount();
|
||||||
for (int i = 0; i < attributeCount; i++) {
|
for (int i = 0; i < attributeCount; i++) {
|
||||||
String attributeValue = parser.getAttributeValue(i);
|
String attributeValue = parser.getAttributeValue(i);
|
||||||
|
|
@ -527,21 +548,24 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
return style;
|
return style;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TtmlStyle createIfNull(TtmlStyle style) {
|
private TtmlStyle createIfNull(@Nullable TtmlStyle style) {
|
||||||
return style == null ? new TtmlStyle() : style;
|
return style == null ? new TtmlStyle() : style;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TtmlNode parseNode(XmlPullParser parser, TtmlNode parent,
|
private TtmlNode parseNode(
|
||||||
Map<String, TtmlRegion> regionMap, FrameAndTickRate frameAndTickRate)
|
XmlPullParser parser,
|
||||||
|
@Nullable TtmlNode parent,
|
||||||
|
Map<String, TtmlRegion> regionMap,
|
||||||
|
FrameAndTickRate frameAndTickRate)
|
||||||
throws SubtitleDecoderException {
|
throws SubtitleDecoderException {
|
||||||
long duration = C.TIME_UNSET;
|
long duration = C.TIME_UNSET;
|
||||||
long startTime = C.TIME_UNSET;
|
long startTime = C.TIME_UNSET;
|
||||||
long endTime = C.TIME_UNSET;
|
long endTime = C.TIME_UNSET;
|
||||||
String regionId = TtmlNode.ANONYMOUS_REGION_ID;
|
String regionId = TtmlNode.ANONYMOUS_REGION_ID;
|
||||||
String imageId = null;
|
@Nullable String imageId = null;
|
||||||
String[] styleIds = null;
|
@Nullable String[] styleIds = null;
|
||||||
int attributeCount = parser.getAttributeCount();
|
int attributeCount = parser.getAttributeCount();
|
||||||
TtmlStyle style = parseStyleAttributes(parser, null);
|
@Nullable TtmlStyle style = parseStyleAttributes(parser, null);
|
||||||
for (int i = 0; i < attributeCount; i++) {
|
for (int i = 0; i < attributeCount; i++) {
|
||||||
String attr = parser.getAttributeName(i);
|
String attr = parser.getAttributeName(i);
|
||||||
String value = parser.getAttributeValue(i);
|
String value = parser.getAttributeValue(i);
|
||||||
|
|
@ -636,7 +660,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matcher.matches()) {
|
if (matcher.matches()) {
|
||||||
String unit = matcher.group(3);
|
String unit = Assertions.checkNotNull(matcher.group(3));
|
||||||
switch (unit) {
|
switch (unit) {
|
||||||
case "px":
|
case "px":
|
||||||
out.setFontSizeUnit(TtmlStyle.FONT_SIZE_UNIT_PIXEL);
|
out.setFontSizeUnit(TtmlStyle.FONT_SIZE_UNIT_PIXEL);
|
||||||
|
|
@ -650,7 +674,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
default:
|
default:
|
||||||
throw new SubtitleDecoderException("Invalid unit for fontSize: '" + unit + "'.");
|
throw new SubtitleDecoderException("Invalid unit for fontSize: '" + unit + "'.");
|
||||||
}
|
}
|
||||||
out.setFontSize(Float.valueOf(matcher.group(1)));
|
out.setFontSize(Float.parseFloat(Assertions.checkNotNull(matcher.group(1))));
|
||||||
} else {
|
} else {
|
||||||
throw new SubtitleDecoderException("Invalid expression for fontSize: '" + expression + "'.");
|
throw new SubtitleDecoderException("Invalid expression for fontSize: '" + expression + "'.");
|
||||||
}
|
}
|
||||||
|
|
@ -671,18 +695,18 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
throws SubtitleDecoderException {
|
throws SubtitleDecoderException {
|
||||||
Matcher matcher = CLOCK_TIME.matcher(time);
|
Matcher matcher = CLOCK_TIME.matcher(time);
|
||||||
if (matcher.matches()) {
|
if (matcher.matches()) {
|
||||||
String hours = matcher.group(1);
|
String hours = Assertions.checkNotNull(matcher.group(1));
|
||||||
double durationSeconds = Long.parseLong(hours) * 3600;
|
double durationSeconds = Long.parseLong(hours) * 3600;
|
||||||
String minutes = matcher.group(2);
|
String minutes = Assertions.checkNotNull(matcher.group(2));
|
||||||
durationSeconds += Long.parseLong(minutes) * 60;
|
durationSeconds += Long.parseLong(minutes) * 60;
|
||||||
String seconds = matcher.group(3);
|
String seconds = Assertions.checkNotNull(matcher.group(3));
|
||||||
durationSeconds += Long.parseLong(seconds);
|
durationSeconds += Long.parseLong(seconds);
|
||||||
String fraction = matcher.group(4);
|
@Nullable String fraction = matcher.group(4);
|
||||||
durationSeconds += (fraction != null) ? Double.parseDouble(fraction) : 0;
|
durationSeconds += (fraction != null) ? Double.parseDouble(fraction) : 0;
|
||||||
String frames = matcher.group(5);
|
@Nullable String frames = matcher.group(5);
|
||||||
durationSeconds += (frames != null)
|
durationSeconds += (frames != null)
|
||||||
? Long.parseLong(frames) / frameAndTickRate.effectiveFrameRate : 0;
|
? Long.parseLong(frames) / frameAndTickRate.effectiveFrameRate : 0;
|
||||||
String subframes = matcher.group(6);
|
@Nullable String subframes = matcher.group(6);
|
||||||
durationSeconds += (subframes != null)
|
durationSeconds += (subframes != null)
|
||||||
? ((double) Long.parseLong(subframes)) / frameAndTickRate.subFrameRate
|
? ((double) Long.parseLong(subframes)) / frameAndTickRate.subFrameRate
|
||||||
/ frameAndTickRate.effectiveFrameRate
|
/ frameAndTickRate.effectiveFrameRate
|
||||||
|
|
@ -691,9 +715,9 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
}
|
}
|
||||||
matcher = OFFSET_TIME.matcher(time);
|
matcher = OFFSET_TIME.matcher(time);
|
||||||
if (matcher.matches()) {
|
if (matcher.matches()) {
|
||||||
String timeValue = matcher.group(1);
|
String timeValue = Assertions.checkNotNull(matcher.group(1));
|
||||||
double offsetSeconds = Double.parseDouble(timeValue);
|
double offsetSeconds = Double.parseDouble(timeValue);
|
||||||
String unit = matcher.group(2);
|
String unit = Assertions.checkNotNull(matcher.group(2));
|
||||||
switch (unit) {
|
switch (unit) {
|
||||||
case "h":
|
case "h":
|
||||||
offsetSeconds *= 3600;
|
offsetSeconds *= 3600;
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A package internal representation of TTML node.
|
* A package internal representation of TTML node.
|
||||||
|
|
@ -93,7 +94,7 @@ import java.util.TreeSet;
|
||||||
private final HashMap<String, Integer> nodeStartsByRegion;
|
private final HashMap<String, Integer> nodeStartsByRegion;
|
||||||
private final HashMap<String, Integer> nodeEndsByRegion;
|
private final HashMap<String, Integer> nodeEndsByRegion;
|
||||||
|
|
||||||
private List<TtmlNode> children;
|
@MonotonicNonNull private List<TtmlNode> children;
|
||||||
|
|
||||||
public static TtmlNode buildTextNode(String text) {
|
public static TtmlNode buildTextNode(String text) {
|
||||||
return new TtmlNode(
|
return new TtmlNode(
|
||||||
|
|
@ -196,6 +197,7 @@ import java.util.TreeSet;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
public String[] getStyleIds() {
|
public String[] getStyleIds() {
|
||||||
return styleIds;
|
return styleIds;
|
||||||
}
|
}
|
||||||
|
|
@ -217,7 +219,7 @@ import java.util.TreeSet;
|
||||||
|
|
||||||
// Create image based cues.
|
// Create image based cues.
|
||||||
for (Pair<String, String> regionImagePair : regionImageOutputs) {
|
for (Pair<String, String> regionImagePair : regionImageOutputs) {
|
||||||
String encodedBitmapData = imageMap.get(regionImagePair.second);
|
@Nullable String encodedBitmapData = imageMap.get(regionImagePair.second);
|
||||||
if (encodedBitmapData == null) {
|
if (encodedBitmapData == null) {
|
||||||
// Image reference points to an invalid image. Do nothing.
|
// Image reference points to an invalid image. Do nothing.
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -225,7 +227,7 @@ import java.util.TreeSet;
|
||||||
|
|
||||||
byte[] bitmapData = Base64.decode(encodedBitmapData, Base64.DEFAULT);
|
byte[] bitmapData = Base64.decode(encodedBitmapData, Base64.DEFAULT);
|
||||||
Bitmap bitmap = BitmapFactory.decodeByteArray(bitmapData, /* offset= */ 0, bitmapData.length);
|
Bitmap bitmap = BitmapFactory.decodeByteArray(bitmapData, /* offset= */ 0, bitmapData.length);
|
||||||
TtmlRegion region = regionMap.get(regionImagePair.first);
|
TtmlRegion region = Assertions.checkNotNull(regionMap.get(regionImagePair.first));
|
||||||
|
|
||||||
cues.add(
|
cues.add(
|
||||||
new Cue.Builder()
|
new Cue.Builder()
|
||||||
|
|
@ -241,7 +243,7 @@ import java.util.TreeSet;
|
||||||
|
|
||||||
// Create text based cues.
|
// Create text based cues.
|
||||||
for (Entry<String, SpannableStringBuilder> entry : regionTextOutputs.entrySet()) {
|
for (Entry<String, SpannableStringBuilder> entry : regionTextOutputs.entrySet()) {
|
||||||
TtmlRegion region = regionMap.get(entry.getKey());
|
TtmlRegion region = Assertions.checkNotNull(regionMap.get(entry.getKey()));
|
||||||
cues.add(
|
cues.add(
|
||||||
new Cue(
|
new Cue(
|
||||||
cleanUpText(entry.getValue()),
|
cleanUpText(entry.getValue()),
|
||||||
|
|
@ -286,7 +288,7 @@ import java.util.TreeSet;
|
||||||
String resolvedRegionId = ANONYMOUS_REGION_ID.equals(regionId) ? inheritedRegion : regionId;
|
String resolvedRegionId = ANONYMOUS_REGION_ID.equals(regionId) ? inheritedRegion : regionId;
|
||||||
|
|
||||||
if (isTextNode && descendsPNode) {
|
if (isTextNode && descendsPNode) {
|
||||||
getRegionOutput(resolvedRegionId, regionOutputs).append(text);
|
getRegionOutput(resolvedRegionId, regionOutputs).append(Assertions.checkNotNull(text));
|
||||||
} else if (TAG_BR.equals(tag) && descendsPNode) {
|
} else if (TAG_BR.equals(tag) && descendsPNode) {
|
||||||
getRegionOutput(resolvedRegionId, regionOutputs).append('\n');
|
getRegionOutput(resolvedRegionId, regionOutputs).append('\n');
|
||||||
} else if (isActive(timeUs)) {
|
} else if (isActive(timeUs)) {
|
||||||
|
|
@ -330,7 +332,7 @@ import java.util.TreeSet;
|
||||||
int start = nodeStartsByRegion.containsKey(regionId) ? nodeStartsByRegion.get(regionId) : 0;
|
int start = nodeStartsByRegion.containsKey(regionId) ? nodeStartsByRegion.get(regionId) : 0;
|
||||||
int end = entry.getValue();
|
int end = entry.getValue();
|
||||||
if (start != end) {
|
if (start != end) {
|
||||||
SpannableStringBuilder regionOutput = regionOutputs.get(regionId);
|
SpannableStringBuilder regionOutput = Assertions.checkNotNull(regionOutputs.get(regionId));
|
||||||
applyStyleToOutput(globalStyles, regionOutput, start, end);
|
applyStyleToOutput(globalStyles, regionOutput, start, end);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -344,7 +346,7 @@ import java.util.TreeSet;
|
||||||
SpannableStringBuilder regionOutput,
|
SpannableStringBuilder regionOutput,
|
||||||
int start,
|
int start,
|
||||||
int end) {
|
int end) {
|
||||||
TtmlStyle resolvedStyle = TtmlRenderUtil.resolveStyle(style, styleIds, globalStyles);
|
@Nullable TtmlStyle resolvedStyle = TtmlRenderUtil.resolveStyle(style, styleIds, globalStyles);
|
||||||
if (resolvedStyle != null) {
|
if (resolvedStyle != null) {
|
||||||
TtmlRenderUtil.applyStylesToSpan(regionOutput, start, end, resolvedStyle);
|
TtmlRenderUtil.applyStylesToSpan(regionOutput, start, end, resolvedStyle);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.text.ttml;
|
package com.google.android.exoplayer2.text.ttml;
|
||||||
|
|
||||||
|
import android.text.Layout.Alignment;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.style.AbsoluteSizeSpan;
|
import android.text.style.AbsoluteSizeSpan;
|
||||||
|
|
@ -26,6 +27,7 @@ import android.text.style.StrikethroughSpan;
|
||||||
import android.text.style.StyleSpan;
|
import android.text.style.StyleSpan;
|
||||||
import android.text.style.TypefaceSpan;
|
import android.text.style.TypefaceSpan;
|
||||||
import android.text.style.UnderlineSpan;
|
import android.text.style.UnderlineSpan;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.text.SpanUtil;
|
import com.google.android.exoplayer2.text.SpanUtil;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
@ -34,30 +36,35 @@ import java.util.Map;
|
||||||
*/
|
*/
|
||||||
/* package */ final class TtmlRenderUtil {
|
/* package */ final class TtmlRenderUtil {
|
||||||
|
|
||||||
public static TtmlStyle resolveStyle(TtmlStyle style, String[] styleIds,
|
@Nullable
|
||||||
Map<String, TtmlStyle> globalStyles) {
|
public static TtmlStyle resolveStyle(
|
||||||
if (style == null && styleIds == null) {
|
@Nullable TtmlStyle style, @Nullable String[] styleIds, Map<String, TtmlStyle> globalStyles) {
|
||||||
// No styles at all.
|
if (style == null) {
|
||||||
return null;
|
if (styleIds == null) {
|
||||||
} else if (style == null && styleIds.length == 1) {
|
// No styles at all.
|
||||||
// Only one single referential style present.
|
return null;
|
||||||
return globalStyles.get(styleIds[0]);
|
} else if (styleIds.length == 1) {
|
||||||
} else if (style == null && styleIds.length > 1) {
|
// Only one single referential style present.
|
||||||
// Only multiple referential styles present.
|
return globalStyles.get(styleIds[0]);
|
||||||
TtmlStyle chainedStyle = new TtmlStyle();
|
} else if (styleIds.length > 1) {
|
||||||
for (String id : styleIds) {
|
// Only multiple referential styles present.
|
||||||
chainedStyle.chain(globalStyles.get(id));
|
TtmlStyle chainedStyle = new TtmlStyle();
|
||||||
|
for (String id : styleIds) {
|
||||||
|
chainedStyle.chain(globalStyles.get(id));
|
||||||
|
}
|
||||||
|
return chainedStyle;
|
||||||
}
|
}
|
||||||
return chainedStyle;
|
} else /* style != null */ {
|
||||||
} else if (style != null && styleIds != null && styleIds.length == 1) {
|
if (styleIds != null && styleIds.length == 1) {
|
||||||
// Merge a single referential style into inline style.
|
// Merge a single referential style into inline style.
|
||||||
return style.chain(globalStyles.get(styleIds[0]));
|
return style.chain(globalStyles.get(styleIds[0]));
|
||||||
} else if (style != null && styleIds != null && styleIds.length > 1) {
|
} else if (styleIds != null && styleIds.length > 1) {
|
||||||
// Merge multiple referential styles into inline style.
|
// Merge multiple referential styles into inline style.
|
||||||
for (String id : styleIds) {
|
for (String id : styleIds) {
|
||||||
style.chain(globalStyles.get(id));
|
style.chain(globalStyles.get(id));
|
||||||
|
}
|
||||||
|
return style;
|
||||||
}
|
}
|
||||||
return style;
|
|
||||||
}
|
}
|
||||||
// Only inline styles available.
|
// Only inline styles available.
|
||||||
return style;
|
return style;
|
||||||
|
|
@ -100,10 +107,11 @@ import java.util.Map;
|
||||||
end,
|
end,
|
||||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
}
|
}
|
||||||
if (style.getTextAlign() != null) {
|
@Nullable Alignment textAlign = style.getTextAlign();
|
||||||
|
if (textAlign != null) {
|
||||||
SpanUtil.addOrReplaceSpan(
|
SpanUtil.addOrReplaceSpan(
|
||||||
builder,
|
builder,
|
||||||
new AlignmentSpan.Standard(style.getTextAlign()),
|
new AlignmentSpan.Standard(textAlign),
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,11 @@ package com.google.android.exoplayer2.text.ttml;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.text.Layout;
|
import android.text.Layout;
|
||||||
import androidx.annotation.IntDef;
|
import androidx.annotation.IntDef;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Style object of a <code>TtmlNode</code>
|
* Style object of a <code>TtmlNode</code>
|
||||||
|
|
@ -58,7 +60,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
private static final int OFF = 0;
|
private static final int OFF = 0;
|
||||||
private static final int ON = 1;
|
private static final int ON = 1;
|
||||||
|
|
||||||
private String fontFamily;
|
private @MonotonicNonNull String fontFamily;
|
||||||
private int fontColor;
|
private int fontColor;
|
||||||
private boolean hasFontColor;
|
private boolean hasFontColor;
|
||||||
private int backgroundColor;
|
private int backgroundColor;
|
||||||
|
|
@ -69,8 +71,8 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
@OptionalBoolean private int italic;
|
@OptionalBoolean private int italic;
|
||||||
@FontSizeUnit private int fontSizeUnit;
|
@FontSizeUnit private int fontSizeUnit;
|
||||||
private float fontSize;
|
private float fontSize;
|
||||||
private String id;
|
private @MonotonicNonNull String id;
|
||||||
private Layout.Alignment textAlign;
|
private Layout.@MonotonicNonNull Alignment textAlign;
|
||||||
|
|
||||||
public TtmlStyle() {
|
public TtmlStyle() {
|
||||||
linethrough = UNSPECIFIED;
|
linethrough = UNSPECIFIED;
|
||||||
|
|
@ -122,6 +124,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
public String getFontFamily() {
|
public String getFontFamily() {
|
||||||
return fontFamily;
|
return fontFamily;
|
||||||
}
|
}
|
||||||
|
|
@ -171,7 +174,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
*
|
*
|
||||||
* @param ancestor the referential style to inherit from
|
* @param ancestor the referential style to inherit from
|
||||||
*/
|
*/
|
||||||
public TtmlStyle chain(TtmlStyle ancestor) {
|
public TtmlStyle chain(@Nullable TtmlStyle ancestor) {
|
||||||
return inherit(ancestor, true);
|
return inherit(ancestor, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -182,11 +185,11 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
*
|
*
|
||||||
* @param ancestor the ancestor style to inherit from
|
* @param ancestor the ancestor style to inherit from
|
||||||
*/
|
*/
|
||||||
public TtmlStyle inherit(TtmlStyle ancestor) {
|
public TtmlStyle inherit(@Nullable TtmlStyle ancestor) {
|
||||||
return inherit(ancestor, false);
|
return inherit(ancestor, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TtmlStyle inherit(TtmlStyle ancestor, boolean chaining) {
|
private TtmlStyle inherit(@Nullable TtmlStyle ancestor, boolean chaining) {
|
||||||
if (ancestor != null) {
|
if (ancestor != null) {
|
||||||
if (!hasFontColor && ancestor.hasFontColor) {
|
if (!hasFontColor && ancestor.hasFontColor) {
|
||||||
setFontColor(ancestor.fontColor);
|
setFontColor(ancestor.fontColor);
|
||||||
|
|
@ -197,7 +200,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
if (italic == UNSPECIFIED) {
|
if (italic == UNSPECIFIED) {
|
||||||
italic = ancestor.italic;
|
italic = ancestor.italic;
|
||||||
}
|
}
|
||||||
if (fontFamily == null) {
|
if (fontFamily == null && ancestor.fontFamily != null) {
|
||||||
fontFamily = ancestor.fontFamily;
|
fontFamily = ancestor.fontFamily;
|
||||||
}
|
}
|
||||||
if (linethrough == UNSPECIFIED) {
|
if (linethrough == UNSPECIFIED) {
|
||||||
|
|
@ -206,7 +209,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
if (underline == UNSPECIFIED) {
|
if (underline == UNSPECIFIED) {
|
||||||
underline = ancestor.underline;
|
underline = ancestor.underline;
|
||||||
}
|
}
|
||||||
if (textAlign == null) {
|
if (textAlign == null && ancestor.textAlign != null) {
|
||||||
textAlign = ancestor.textAlign;
|
textAlign = ancestor.textAlign;
|
||||||
}
|
}
|
||||||
if (fontSizeUnit == UNSPECIFIED) {
|
if (fontSizeUnit == UNSPECIFIED) {
|
||||||
|
|
@ -226,10 +229,12 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
public Layout.Alignment getTextAlign() {
|
public Layout.Alignment getTextAlign() {
|
||||||
return textAlign;
|
return textAlign;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue