Commit 85741ff2 by olly Committed by Oliver Woodman

TTML: Parse and use frameRate/Multiplier + tickRate

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=126792535
parent 2426907f
<tt xmlns="http://www.w3.org/ns/ttml"
xmlns:ttp="http://www.w3.org/ns/ttml#parameter"
ttp:frameRate="50"
ttp:frameRateMultiplier="1000 1001"
ttp:tickRate="100">
<head>
<styling>
</styling>
</head>
<body>
<div>
<p begin="100t" end="101t">text 1</p>
</div>
<div>
<p begin="50000f" end="100000f">text 2</p>
</div>
</body>
</tt>
...@@ -58,6 +58,7 @@ public final class TtmlParserTest extends InstrumentationTestCase { ...@@ -58,6 +58,7 @@ public final class TtmlParserTest extends InstrumentationTestCase {
private static final String FONT_SIZE_MISSING_UNIT_TTML_FILE = "ttml/font_size_no_unit.xml"; private static final String FONT_SIZE_MISSING_UNIT_TTML_FILE = "ttml/font_size_no_unit.xml";
private static final String FONT_SIZE_INVALID_TTML_FILE = "ttml/font_size_invalid.xml"; private static final String FONT_SIZE_INVALID_TTML_FILE = "ttml/font_size_invalid.xml";
private static final String FONT_SIZE_EMPTY_TTML_FILE = "ttml/font_size_empty.xml"; private static final String FONT_SIZE_EMPTY_TTML_FILE = "ttml/font_size_empty.xml";
private static final String FRAME_RATE_TTML_FILE = "ttml/frame_rate.xml";
public void testInlineAttributes() throws IOException { public void testInlineAttributes() throws IOException {
TtmlSubtitle subtitle = getSubtitle(INLINE_ATTRIBUTES_TTML_FILE); TtmlSubtitle subtitle = getSubtitle(INLINE_ATTRIBUTES_TTML_FILE);
...@@ -364,6 +365,15 @@ public final class TtmlParserTest extends InstrumentationTestCase { ...@@ -364,6 +365,15 @@ public final class TtmlParserTest extends InstrumentationTestCase {
assertEquals(0, spannable.getSpans(0, spannable.length(), AbsoluteSizeSpan.class).length); assertEquals(0, spannable.getSpans(0, spannable.length(), AbsoluteSizeSpan.class).length);
} }
public void testFrameRate() throws IOException {
TtmlSubtitle subtitle = getSubtitle(FRAME_RATE_TTML_FILE);
assertEquals(4, subtitle.getEventTimeCount());
assertEquals(1_000_000, subtitle.getEventTime(0));
assertEquals(1_010_000, subtitle.getEventTime(1));
assertEquals(1_001_000_000, subtitle.getEventTime(2), 1000);
assertEquals(2_002_000_000, subtitle.getEventTime(3), 2000);
}
private void assertSpans(TtmlSubtitle subtitle, int second, private void assertSpans(TtmlSubtitle subtitle, int second,
String text, String font, int fontStyle, String text, String font, int fontStyle,
int backgroundColor, int color, boolean isUnderline, int backgroundColor, int color, boolean isUnderline,
......
...@@ -64,6 +64,8 @@ public final class TtmlParser extends SimpleSubtitleParser { ...@@ -64,6 +64,8 @@ public final class TtmlParser extends SimpleSubtitleParser {
private static final String TAG = "TtmlParser"; private static final String TAG = "TtmlParser";
private static final String TTP = "http://www.w3.org/ns/ttml#parameter";
private static final String ATTR_BEGIN = "begin"; private static final String ATTR_BEGIN = "begin";
private static final String ATTR_DURATION = "dur"; private static final String ATTR_DURATION = "dur";
private static final String ATTR_END = "end"; private static final String ATTR_END = "end";
...@@ -79,10 +81,10 @@ public final class TtmlParser extends SimpleSubtitleParser { ...@@ -79,10 +81,10 @@ public final class TtmlParser extends SimpleSubtitleParser {
private static final Pattern PERCENTAGE_COORDINATES = private static final Pattern PERCENTAGE_COORDINATES =
Pattern.compile("^(\\d+\\.?\\d*?)% (\\d+\\.?\\d*?)%$"); Pattern.compile("^(\\d+\\.?\\d*?)% (\\d+\\.?\\d*?)%$");
// TODO: read and apply the following attributes if specified. private static final int DEFAULT_FRAME_RATE = 30;
private static final int DEFAULT_FRAMERATE = 30;
private static final int DEFAULT_SUBFRAMERATE = 1; private static final FrameAndTickRate DEFAULT_FRAME_AND_TICK_RATE =
private static final int DEFAULT_TICKRATE = 1; new FrameAndTickRate(DEFAULT_FRAME_RATE, 1, 1);
private final XmlPullParserFactory xmlParserFactory; private final XmlPullParserFactory xmlParserFactory;
...@@ -109,11 +111,15 @@ public final class TtmlParser extends SimpleSubtitleParser { ...@@ -109,11 +111,15 @@ public final class TtmlParser extends SimpleSubtitleParser {
LinkedList<TtmlNode> nodeStack = new LinkedList<>(); LinkedList<TtmlNode> nodeStack = new LinkedList<>();
int unsupportedNodeDepth = 0; int unsupportedNodeDepth = 0;
int eventType = xmlParser.getEventType(); int eventType = xmlParser.getEventType();
FrameAndTickRate frameAndTickRate = DEFAULT_FRAME_AND_TICK_RATE;
while (eventType != XmlPullParser.END_DOCUMENT) { while (eventType != XmlPullParser.END_DOCUMENT) {
TtmlNode parent = nodeStack.peekLast(); TtmlNode parent = nodeStack.peekLast();
if (unsupportedNodeDepth == 0) { if (unsupportedNodeDepth == 0) {
String name = xmlParser.getName(); String name = xmlParser.getName();
if (eventType == XmlPullParser.START_TAG) { if (eventType == XmlPullParser.START_TAG) {
if (TtmlNode.TAG_TT.equals(name)) {
frameAndTickRate = parseFrameAndTickRates(xmlParser);
}
if (!isSupportedTag(name)) { if (!isSupportedTag(name)) {
Log.i(TAG, "Ignoring unsupported tag: " + xmlParser.getName()); Log.i(TAG, "Ignoring unsupported tag: " + xmlParser.getName());
unsupportedNodeDepth++; unsupportedNodeDepth++;
...@@ -121,7 +127,7 @@ public final class TtmlParser extends SimpleSubtitleParser { ...@@ -121,7 +127,7 @@ public final class TtmlParser extends SimpleSubtitleParser {
parseHeader(xmlParser, globalStyles, regionMap); parseHeader(xmlParser, globalStyles, regionMap);
} else { } else {
try { try {
TtmlNode node = parseNode(xmlParser, parent, regionMap); TtmlNode node = parseNode(xmlParser, parent, regionMap, frameAndTickRate);
nodeStack.addLast(node); nodeStack.addLast(node);
if (parent != null) { if (parent != null) {
parent.addChild(node); parent.addChild(node);
...@@ -158,6 +164,39 @@ public final class TtmlParser extends SimpleSubtitleParser { ...@@ -158,6 +164,39 @@ public final class TtmlParser extends SimpleSubtitleParser {
} }
} }
private FrameAndTickRate parseFrameAndTickRates(XmlPullParser xmlParser) throws ParserException {
int frameRate = DEFAULT_FRAME_RATE;
String frameRateStr = xmlParser.getAttributeValue(TTP, "frameRate");
if (frameRateStr != null) {
frameRate = Integer.parseInt(frameRateStr);
}
float frameRateMultiplier = 1;
String frameRateMultiplierStr = xmlParser.getAttributeValue(TTP, "frameRateMultiplier");
if (frameRateMultiplierStr != null) {
String[] parts = frameRateMultiplierStr.split(" ");
if (parts.length != 2) {
throw new ParserException("frameRateMultiplier doesn't have 2 parts");
}
float numerator = Integer.parseInt(parts[0]);
float denominator = Integer.parseInt(parts[1]);
frameRateMultiplier = numerator / denominator;
}
int subFrameRate = DEFAULT_FRAME_AND_TICK_RATE.subFrameRate;
String subFrameRateStr = xmlParser.getAttributeValue(TTP, "subFrameRate");
if (subFrameRateStr != null) {
subFrameRate = Integer.parseInt(subFrameRateStr);
}
int tickRate = DEFAULT_FRAME_AND_TICK_RATE.tickRate;
String tickRateStr = xmlParser.getAttributeValue(TTP, "tickRate");
if (tickRateStr != null) {
tickRate = Integer.parseInt(tickRateStr);
}
return new FrameAndTickRate(frameRate * frameRateMultiplier, subFrameRate, tickRate);
}
private Map<String, TtmlStyle> parseHeader(XmlPullParser xmlParser, private Map<String, TtmlStyle> parseHeader(XmlPullParser xmlParser,
Map<String, TtmlStyle> globalStyles, Map<String, TtmlRegion> globalRegions) Map<String, TtmlStyle> globalStyles, Map<String, TtmlRegion> globalRegions)
throws IOException, XmlPullParserException { throws IOException, XmlPullParserException {
...@@ -319,7 +358,7 @@ public final class TtmlParser extends SimpleSubtitleParser { ...@@ -319,7 +358,7 @@ public final class TtmlParser extends SimpleSubtitleParser {
} }
private TtmlNode parseNode(XmlPullParser parser, TtmlNode parent, private TtmlNode parseNode(XmlPullParser parser, TtmlNode parent,
Map<String, TtmlRegion> regionMap) throws ParserException { Map<String, TtmlRegion> regionMap, FrameAndTickRate frameAndTickRate) throws ParserException {
long duration = 0; long duration = 0;
long startTime = TtmlNode.UNDEFINED_TIME; long startTime = TtmlNode.UNDEFINED_TIME;
long endTime = TtmlNode.UNDEFINED_TIME; long endTime = TtmlNode.UNDEFINED_TIME;
...@@ -332,16 +371,13 @@ public final class TtmlParser extends SimpleSubtitleParser { ...@@ -332,16 +371,13 @@ public final class TtmlParser extends SimpleSubtitleParser {
String value = parser.getAttributeValue(i); String value = parser.getAttributeValue(i);
switch (attr) { switch (attr) {
case ATTR_BEGIN: case ATTR_BEGIN:
startTime = parseTimeExpression(value, startTime = parseTimeExpression(value, frameAndTickRate);
DEFAULT_FRAMERATE, DEFAULT_SUBFRAMERATE, DEFAULT_TICKRATE);
break; break;
case ATTR_END: case ATTR_END:
endTime = parseTimeExpression(value, endTime = parseTimeExpression(value, frameAndTickRate);
DEFAULT_FRAMERATE, DEFAULT_SUBFRAMERATE, DEFAULT_TICKRATE);
break; break;
case ATTR_DURATION: case ATTR_DURATION:
duration = parseTimeExpression(value, duration = parseTimeExpression(value, frameAndTickRate);
DEFAULT_FRAMERATE, DEFAULT_SUBFRAMERATE, DEFAULT_TICKRATE);
break; break;
case ATTR_STYLE: case ATTR_STYLE:
// IDREFS: potentially multiple space delimited ids // IDREFS: potentially multiple space delimited ids
...@@ -442,14 +478,12 @@ public final class TtmlParser extends SimpleSubtitleParser { ...@@ -442,14 +478,12 @@ public final class TtmlParser extends SimpleSubtitleParser {
* <a href="http://www.w3.org/TR/ttaf1-dfxp/#timing-value-timeExpression">timeExpression</a> * <a href="http://www.w3.org/TR/ttaf1-dfxp/#timing-value-timeExpression">timeExpression</a>
* *
* @param time A string that includes the time expression. * @param time A string that includes the time expression.
* @param frameRate The frame rate of the stream. * @param frameAndTickRate The effective frame and tick rates of the stream.
* @param subframeRate The sub-frame rate of the stream
* @param tickRate The tick rate of the stream.
* @return The parsed timestamp in microseconds. * @return The parsed timestamp in microseconds.
* @throws ParserException If the given string does not contain a valid time expression. * @throws ParserException If the given string does not contain a valid time expression.
*/ */
private static long parseTimeExpression(String time, int frameRate, int subframeRate, private static long parseTimeExpression(String time, FrameAndTickRate frameAndTickRate)
int tickRate) throws ParserException { throws ParserException {
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 = matcher.group(1);
...@@ -461,10 +495,13 @@ public final class TtmlParser extends SimpleSubtitleParser { ...@@ -461,10 +495,13 @@ public final class TtmlParser extends SimpleSubtitleParser {
String fraction = matcher.group(4); String fraction = matcher.group(4);
durationSeconds += (fraction != null) ? Double.parseDouble(fraction) : 0; durationSeconds += (fraction != null) ? Double.parseDouble(fraction) : 0;
String frames = matcher.group(5); String frames = matcher.group(5);
durationSeconds += (frames != null) ? ((double) Long.parseLong(frames)) / frameRate : 0; durationSeconds += (frames != null)
? Long.parseLong(frames) / frameAndTickRate.effectiveFrameRate : 0;
String subframes = matcher.group(6); String subframes = matcher.group(6);
durationSeconds += (subframes != null) ? durationSeconds += (subframes != null)
((double) Long.parseLong(subframes)) / subframeRate / frameRate : 0; ? ((double) Long.parseLong(subframes)) / frameAndTickRate.subFrameRate
/ frameAndTickRate.effectiveFrameRate
: 0;
return (long) (durationSeconds * C.MICROS_PER_SECOND); return (long) (durationSeconds * C.MICROS_PER_SECOND);
} }
matcher = OFFSET_TIME.matcher(time); matcher = OFFSET_TIME.matcher(time);
...@@ -486,10 +523,10 @@ public final class TtmlParser extends SimpleSubtitleParser { ...@@ -486,10 +523,10 @@ public final class TtmlParser extends SimpleSubtitleParser {
offsetSeconds /= 1000; offsetSeconds /= 1000;
break; break;
case "f": case "f":
offsetSeconds /= frameRate; offsetSeconds /= frameAndTickRate.effectiveFrameRate;
break; break;
case "t": case "t":
offsetSeconds /= tickRate; offsetSeconds /= frameAndTickRate.tickRate;
break; break;
} }
return (long) (offsetSeconds * C.MICROS_PER_SECOND); return (long) (offsetSeconds * C.MICROS_PER_SECOND);
...@@ -497,4 +534,15 @@ public final class TtmlParser extends SimpleSubtitleParser { ...@@ -497,4 +534,15 @@ public final class TtmlParser extends SimpleSubtitleParser {
throw new ParserException("Malformed time expression: " + time); throw new ParserException("Malformed time expression: " + time);
} }
private static final class FrameAndTickRate {
final float effectiveFrameRate;
final int subFrameRate;
final int tickRate;
FrameAndTickRate(float effectiveFrameRate, int subFrameRate, int tickRate) {
this.effectiveFrameRate = effectiveFrameRate;
this.subFrameRate = subFrameRate;
this.tickRate = tickRate;
}
}
} }
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