Commit 254a586e by Arnold Szabo

#1583 - Parsing base64 encoded image data from metadata

parent 34797651
...@@ -3,6 +3,11 @@ ...@@ -3,6 +3,11 @@
"name": "YouTube DASH", "name": "YouTube DASH",
"samples": [ "samples": [
{ {
"name": "DVB Image sub",
"uri": "https://livesim.dashif.org/dash/vod/testpic_2s/img_subs.mpd",
"extension": "mpd"
},
{
"name": "Google Glass (MP4,H264)", "name": "Google Glass (MP4,H264)",
"uri": "https://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=51AF5F39AB0CEC3E5497CD9C900EBFEAECCCB5C7.8506521BFC350652163895D4C26DEE124209AA9E&key=ik0", "uri": "https://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=51AF5F39AB0CEC3E5497CD9C900EBFEAECCCB5C7.8506521BFC350652163895D4C26DEE124209AA9E&key=ik0",
"extension": "mpd" "extension": "mpd"
......
...@@ -68,6 +68,8 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { ...@@ -68,6 +68,8 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
private static final String ATTR_END = "end"; private static final String ATTR_END = "end";
private static final String ATTR_STYLE = "style"; private static final String ATTR_STYLE = "style";
private static final String ATTR_REGION = "region"; private static final String ATTR_REGION = "region";
private static final String ATTR_IMAGE = "backgroundImage";
private static final Pattern CLOCK_TIME = private static final Pattern CLOCK_TIME =
Pattern.compile("^([0-9][0-9]+):([0-9][0-9]):([0-9][0-9])" Pattern.compile("^([0-9][0-9]+):([0-9][0-9]):([0-9][0-9])"
...@@ -105,6 +107,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { ...@@ -105,6 +107,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<>();
Map<String, String> imageMap = new HashMap<>();
regionMap.put(TtmlNode.ANONYMOUS_REGION_ID, new TtmlRegion(null)); 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);
...@@ -127,7 +130,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { ...@@ -127,7 +130,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
Log.i(TAG, "Ignoring unsupported tag: " + xmlParser.getName()); Log.i(TAG, "Ignoring unsupported tag: " + xmlParser.getName());
unsupportedNodeDepth++; unsupportedNodeDepth++;
} else if (TtmlNode.TAG_HEAD.equals(name)) { } else if (TtmlNode.TAG_HEAD.equals(name)) {
parseHeader(xmlParser, globalStyles, regionMap, cellResolution); parseHeader(xmlParser, globalStyles, regionMap, cellResolution, imageMap);
} else { } else {
try { try {
TtmlNode node = parseNode(xmlParser, parent, regionMap, frameAndTickRate); TtmlNode node = parseNode(xmlParser, parent, regionMap, frameAndTickRate);
...@@ -145,7 +148,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { ...@@ -145,7 +148,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
parent.addChild(TtmlNode.buildTextNode(xmlParser.getText())); 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); ttmlSubtitle = new TtmlSubtitle(nodeStack.peek(), globalStyles, regionMap, imageMap);
} }
nodeStack.pop(); nodeStack.pop();
} }
...@@ -230,7 +233,8 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { ...@@ -230,7 +233,8 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
XmlPullParser xmlParser, XmlPullParser xmlParser,
Map<String, TtmlStyle> globalStyles, Map<String, TtmlStyle> globalStyles,
Map<String, TtmlRegion> globalRegions, Map<String, TtmlRegion> globalRegions,
CellResolution cellResolution) CellResolution cellResolution,
Map<String, String> imageMap)
throws IOException, XmlPullParserException { throws IOException, XmlPullParserException {
do { do {
xmlParser.next(); xmlParser.next();
...@@ -250,11 +254,29 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { ...@@ -250,11 +254,29 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
if (ttmlRegion != null) { if (ttmlRegion != null) {
globalRegions.put(ttmlRegion.id, ttmlRegion); globalRegions.put(ttmlRegion.id, ttmlRegion);
} }
} else if(XmlPullParserUtil.isStartTag(xmlParser, TtmlNode.TAG_METADATA)){
parseMetaData(xmlParser, imageMap);
} }
} while (!XmlPullParserUtil.isEndTag(xmlParser, TtmlNode.TAG_HEAD)); } while (!XmlPullParserUtil.isEndTag(xmlParser, TtmlNode.TAG_HEAD));
return globalStyles; return globalStyles;
} }
public void parseMetaData(XmlPullParser xmlParser, Map<String, String> imageMap) throws IOException, XmlPullParserException {
do {
xmlParser.next();
if (XmlPullParserUtil.isStartTag(xmlParser, TtmlNode.TAG_SMPTE_IMAGE)) {
for (int i = 0; i < xmlParser.getAttributeCount(); i++) {
String id = XmlPullParserUtil.getAttributeValue(xmlParser, "id");
if(id != null){
String base64 = xmlParser.nextText();
imageMap.put(id, base64);
}
}
}
} while (!XmlPullParserUtil.isEndTag(xmlParser, TtmlNode.TAG_METADATA));
}
/** /**
* Parses a region declaration. * Parses a region declaration.
* *
...@@ -457,6 +479,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { ...@@ -457,6 +479,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
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 = "";
String[] styleIds = null; String[] styleIds = null;
int attributeCount = parser.getAttributeCount(); int attributeCount = parser.getAttributeCount();
TtmlStyle style = parseStyleAttributes(parser, null); TtmlStyle style = parseStyleAttributes(parser, null);
...@@ -487,6 +510,9 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { ...@@ -487,6 +510,9 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
regionId = value; regionId = value;
} }
break; break;
case ATTR_IMAGE:
imageId = value.substring(1);
break;
default: default:
// Do nothing. // Do nothing.
break; break;
...@@ -509,7 +535,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { ...@@ -509,7 +535,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
endTime = parent.endTimeUs; endTime = parent.endTimeUs;
} }
} }
return TtmlNode.buildNode(parser.getName(), startTime, endTime, style, styleIds, regionId); return TtmlNode.buildNode(parser.getName(), startTime, endTime, style, styleIds, regionId, imageId);
} }
private static boolean isSupportedTag(String tag) { private static boolean isSupportedTag(String tag) {
......
...@@ -15,10 +15,16 @@ ...@@ -15,10 +15,16 @@
*/ */
package com.google.android.exoplayer2.text.ttml; package com.google.android.exoplayer2.text.ttml;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.util.Base64;
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.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
...@@ -44,9 +50,9 @@ import java.util.TreeSet; ...@@ -44,9 +50,9 @@ import java.util.TreeSet;
public static final String TAG_LAYOUT = "layout"; public static final String TAG_LAYOUT = "layout";
public static final String TAG_REGION = "region"; public static final String TAG_REGION = "region";
public static final String TAG_METADATA = "metadata"; public static final String TAG_METADATA = "metadata";
public static final String TAG_SMPTE_IMAGE = "smpte:image"; public static final String TAG_SMPTE_IMAGE = "image";
public static final String TAG_SMPTE_DATA = "smpte:data"; public static final String TAG_SMPTE_DATA = "data";
public static final String TAG_SMPTE_INFORMATION = "smpte:information"; public static final String TAG_SMPTE_INFORMATION = "information";
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";
...@@ -82,6 +88,7 @@ import java.util.TreeSet; ...@@ -82,6 +88,7 @@ import java.util.TreeSet;
public final long endTimeUs; public final long endTimeUs;
public final TtmlStyle style; public final TtmlStyle style;
public final String regionId; public final String regionId;
public final String imageId;
private final String[] styleIds; private final String[] styleIds;
private final HashMap<String, Integer> nodeStartsByRegion; private final HashMap<String, Integer> nodeStartsByRegion;
...@@ -91,18 +98,19 @@ import java.util.TreeSet; ...@@ -91,18 +98,19 @@ import java.util.TreeSet;
public static TtmlNode buildTextNode(String text) { public static TtmlNode buildTextNode(String text) {
return new TtmlNode(null, TtmlRenderUtil.applyTextElementSpacePolicy(text), C.TIME_UNSET, return new TtmlNode(null, TtmlRenderUtil.applyTextElementSpacePolicy(text), C.TIME_UNSET,
C.TIME_UNSET, null, null, ANONYMOUS_REGION_ID); C.TIME_UNSET, null, null, ANONYMOUS_REGION_ID, null);
} }
public static TtmlNode buildNode(String tag, long startTimeUs, long endTimeUs, public static TtmlNode buildNode(String tag, long startTimeUs, long endTimeUs,
TtmlStyle style, String[] styleIds, String regionId) { TtmlStyle style, String[] styleIds, String regionId, String imageId) {
return new TtmlNode(tag, null, startTimeUs, endTimeUs, style, styleIds, regionId); return new TtmlNode(tag, null, startTimeUs, endTimeUs, style, styleIds, regionId, imageId);
} }
private TtmlNode(String tag, String text, long startTimeUs, long endTimeUs, private TtmlNode(String tag, String text, long startTimeUs, long endTimeUs,
TtmlStyle style, String[] styleIds, String regionId) { TtmlStyle style, String[] styleIds, String regionId, String imageId) {
this.tag = tag; this.tag = tag;
this.text = text; this.text = text;
this.imageId = imageId;
this.style = style; this.style = style;
this.styleIds = styleIds; this.styleIds = styleIds;
this.isTextNode = text != null; this.isTextNode = text != null;
...@@ -172,11 +180,37 @@ import java.util.TreeSet; ...@@ -172,11 +180,37 @@ import java.util.TreeSet;
} }
public List<Cue> getCues(long timeUs, Map<String, TtmlStyle> globalStyles, public List<Cue> getCues(long timeUs, Map<String, TtmlStyle> globalStyles,
Map<String, TtmlRegion> regionMap) { Map<String, TtmlRegion> regionMap, Map<String, String> imageMap) {
TreeMap<String, SpannableStringBuilder> regionOutputs = new TreeMap<>(); TreeMap<String, SpannableStringBuilder> regionOutputs = new TreeMap<>();
List<Pair<String, String>> regionImageList = new ArrayList<>();
traverseForText(timeUs, false, regionId, regionOutputs); traverseForText(timeUs, false, regionId, regionOutputs);
traverseForStyle(timeUs, globalStyles, regionOutputs); traverseForStyle(timeUs, globalStyles, regionOutputs);
traverseForImage(timeUs, regionId, regionImageList);
List<Cue> cues = new ArrayList<>(); List<Cue> cues = new ArrayList<>();
// Create text based cues
for (Pair<String, String> regionImagePair : regionImageList) {
String base64 = imageMap.get(regionImagePair.second);
byte[] decodedString = Base64.decode(base64, Base64.DEFAULT);
Bitmap decodedByte = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length);
TtmlRegion region = regionMap.get(regionImagePair.first);
cues.add(
new Cue(decodedByte,
region.position,
Cue.TYPE_UNSET,
region.line,
region.lineAnchor,
region.width,
Cue.DIMEN_UNSET
)
);
}
// Create image based cues
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( cues.add(
...@@ -195,6 +229,19 @@ import java.util.TreeSet; ...@@ -195,6 +229,19 @@ import java.util.TreeSet;
return cues; return cues;
} }
private void traverseForImage(long timeUs, String inheritedRegion, List<Pair<String, String>> regionImageList) {
// TODO isActive needed?
String resolvedRegionId = ANONYMOUS_REGION_ID.equals(regionId) ? inheritedRegion : regionId;
if (TAG_DIV.equals(tag) && imageId != null) {
regionImageList.add(new Pair<>(resolvedRegionId, imageId));
}
for (int i = 0; i < getChildCount(); ++i) {
getChild(i).traverseForImage(timeUs, resolvedRegionId, regionImageList);
}
}
private void traverseForText( private void traverseForText(
long timeUs, long timeUs,
boolean descendsPNode, boolean descendsPNode,
......
...@@ -32,11 +32,14 @@ import java.util.Map; ...@@ -32,11 +32,14 @@ import java.util.Map;
private final long[] eventTimesUs; private final long[] eventTimesUs;
private final Map<String, TtmlStyle> globalStyles; private final Map<String, TtmlStyle> globalStyles;
private final Map<String, TtmlRegion> regionMap; private final Map<String, TtmlRegion> regionMap;
private final Map<String, String> imageMap;
public TtmlSubtitle(TtmlNode root, Map<String, TtmlStyle> globalStyles, public TtmlSubtitle(TtmlNode root, Map<String, TtmlStyle> globalStyles,
Map<String, TtmlRegion> regionMap) { Map<String, TtmlRegion> regionMap, Map<String, String> imageMap) {
this.root = root; this.root = root;
this.regionMap = regionMap; this.regionMap = regionMap;
this.imageMap = imageMap;
this.globalStyles = this.globalStyles =
globalStyles != null ? Collections.unmodifiableMap(globalStyles) : Collections.emptyMap(); globalStyles != null ? Collections.unmodifiableMap(globalStyles) : Collections.emptyMap();
this.eventTimesUs = root.getEventTimesUs(); this.eventTimesUs = root.getEventTimesUs();
...@@ -65,7 +68,7 @@ import java.util.Map; ...@@ -65,7 +68,7 @@ import java.util.Map;
@Override @Override
public List<Cue> getCues(long timeUs) { public List<Cue> getCues(long timeUs) {
return root.getCues(timeUs, globalStyles, regionMap); return root.getCues(timeUs, globalStyles, regionMap, imageMap);
} }
/* @VisibleForTesting */ /* @VisibleForTesting */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment