mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Add support for TTML regions.
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=121564959
This commit is contained in:
parent
d978398faf
commit
1fea3fe3c0
18 changed files with 381 additions and 503 deletions
|
|
@ -1,7 +1,8 @@
|
||||||
<tt xmlns:ttm="http://www.w3.org/2006/10/ttaf1#metadata" xmlns:ttp="http://www.w3.org/2006/10/ttaf1#parameter"
|
<tt xmlns:ttm="http://www.w3.org/2006/10/ttaf1#metadata"
|
||||||
xmlns:tts="http://www.w3.org/2006/10/ttaf1#style"
|
xmlns:ttp="http://www.w3.org/2006/10/ttaf1#parameter"
|
||||||
xmlns="http://www.w3.org/ns/ttml"
|
xmlns:tts="http://www.w3.org/2006/10/ttaf1#style"
|
||||||
xmlns="http://www.w3.org/2006/10/ttaf1">
|
xmlns="http://www.w3.org/ns/ttml"
|
||||||
|
xmlns="http://www.w3.org/2006/10/ttaf1">
|
||||||
<head>
|
<head>
|
||||||
<styling>
|
<styling>
|
||||||
<style id="s0"
|
<style id="s0"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
<tt xmlns:ttm="http://www.w3.org/2006/10/ttaf1#metadata" xmlns:ttp="http://www.w3.org/2006/10/ttaf1#parameter"
|
<tt xmlns:ttm="http://www.w3.org/2006/10/ttaf1#metadata"
|
||||||
xmlns:tts="http://www.w3.org/2006/10/ttaf1#style"
|
xmlns:ttp="http://www.w3.org/2006/10/ttaf1#parameter"
|
||||||
xmlns="http://www.w3.org/ns/ttml"
|
xmlns:tts="http://www.w3.org/2006/10/ttaf1#style"
|
||||||
xmlns="http://www.w3.org/2006/10/ttaf1">
|
xmlns="http://www.w3.org/ns/ttml"
|
||||||
|
xmlns="http://www.w3.org/2006/10/ttaf1">
|
||||||
<head>
|
<head>
|
||||||
<styling>
|
<styling>
|
||||||
<style id="s0"
|
<style id="s0"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
<tt xmlns:ttm="http://www.w3.org/2006/10/ttaf1#metadata" xmlns:ttp="http://www.w3.org/2006/10/ttaf1#parameter"
|
<tt xmlns:ttm="http://www.w3.org/2006/10/ttaf1#metadata"
|
||||||
xmlns:tts="http://www.w3.org/2006/10/ttaf1#style"
|
xmlns:ttp="http://www.w3.org/2006/10/ttaf1#parameter"
|
||||||
xmlns="http://www.w3.org/ns/ttml"
|
xmlns:tts="http://www.w3.org/2006/10/ttaf1#style"
|
||||||
xmlns="http://www.w3.org/2006/10/ttaf1">
|
xmlns="http://www.w3.org/ns/ttml"
|
||||||
|
xmlns="http://www.w3.org/2006/10/ttaf1">
|
||||||
<head>
|
<head>
|
||||||
<styling>
|
<styling>
|
||||||
<style id="s0"
|
<style id="s0"
|
||||||
|
|
|
||||||
29
library/src/androidTest/assets/ttml/multiple_regions.xml
Normal file
29
library/src/androidTest/assets/ttml/multiple_regions.xml
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
<tt xmlns="http://www.w3.org/ns/ttml"
|
||||||
|
xmlns="http://www.w3.org/2006/10/ttaf1"
|
||||||
|
xmlns:id="http://www.w3.org/XML/1998/namespace"
|
||||||
|
xmlns:ttp="http://www.w3.org/ns/ttml#parameter"
|
||||||
|
xmlns:tts="http://www.w3.org/ns/ttml#styling"
|
||||||
|
xmlns:ttm="http://www.w3.org/ns/ttml#metadata">
|
||||||
|
<head>
|
||||||
|
<layout>
|
||||||
|
<region xml:id="region1" ttm:origin="10% 10%" extent="20% 20%"/>
|
||||||
|
<region xml:id="region2" ttm:origin="40% 40%" extent="20% 20%"/>
|
||||||
|
<region xml:id="region3" ttm:origin="10% 80%" extent="10% 10%"/>
|
||||||
|
<region xml:id="region4" ttm:origin="60% 10%" extent="20% 20%"/>
|
||||||
|
<region xml:id="ultimate" ttm:origin="45% 45%" extent="35% 35%"/>
|
||||||
|
</layout>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<p begin="1s" end="4s" region="region1">lorem</p>
|
||||||
|
<p begin="5s" end="8s" region="region2">ipsum</p>
|
||||||
|
<p begin="9s" end="18s" region="region3">dolor</p>
|
||||||
|
<p begin="1s" end="4s" region="region4">amet</p>
|
||||||
|
</div>
|
||||||
|
<div region="ultimate">
|
||||||
|
<p begin="21s" end="34s">She first said this</p>
|
||||||
|
<p begin="25s" end="34s">Then this</p>
|
||||||
|
<p begin="29s" end="34s">Finally this</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</tt>
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
<tt xmlns:ttm="http://www.w3.org/2006/10/ttaf1#metadata"
|
|
||||||
xmlns:ttp="http://www.w3.org/2006/10/ttaf1#parameter"
|
|
||||||
xmlns:tts="http://www.w3.org/2006/10/ttaf1#style"
|
|
||||||
xmlns="http://www.w3.org/ns/ttml"
|
|
||||||
xmlns="http://www.w3.org/2006/10/ttaf1">
|
|
||||||
<body>
|
|
||||||
<div>
|
|
||||||
<p begin="10s" end="18s"
|
|
||||||
tts:backgroundColor="black"
|
|
||||||
abc:fontFamily="sansSerif"
|
|
||||||
def:fontStyle="italic"
|
|
||||||
ghi:textDecoration="lineThrough"
|
|
||||||
jkl:color="yellow">text 1</p>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</tt>
|
|
||||||
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
<tt>
|
|
||||||
<body>
|
|
||||||
<div>
|
|
||||||
<p begin="10s" end="18s"
|
|
||||||
tts:backgroundColor="black"
|
|
||||||
abc:fontFamily="sansSerif"
|
|
||||||
def:fontStyle="italic"
|
|
||||||
ghi:textDecoration="lineThrough"
|
|
||||||
jkl:color="yellow">text 1</p>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</tt>
|
|
||||||
|
|
||||||
|
|
@ -1,115 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2014 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.google.android.exoplayer.text.ttml;
|
|
||||||
|
|
||||||
import android.graphics.Color;
|
|
||||||
import android.test.InstrumentationTestCase;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unit test for <code>TtmlColorParser</code>.
|
|
||||||
*/
|
|
||||||
public class TtmlColorParserTest extends InstrumentationTestCase {
|
|
||||||
|
|
||||||
public void testHexCodeParsing() {
|
|
||||||
assertEquals(Color.WHITE, TtmlColorParser.parseColor("#FFFFFF"));
|
|
||||||
assertEquals(Color.WHITE, TtmlColorParser.parseColor("#FFFFFFFF"));
|
|
||||||
assertEquals(Color.parseColor("#FF123456"), TtmlColorParser.parseColor("#123456"));
|
|
||||||
// Hex colors in TTML are RGBA, where-as {@link Color#parseColor} takes ARGB.
|
|
||||||
assertEquals(Color.parseColor("#00FFFFFF"), TtmlColorParser.parseColor("#FFFFFF00"));
|
|
||||||
assertEquals(Color.parseColor("#78123456"), TtmlColorParser.parseColor("#12345678"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testColorNameParsing() {
|
|
||||||
assertEquals(TtmlColorParser.TRANSPARENT, TtmlColorParser.parseColor("transparent"));
|
|
||||||
assertEquals(TtmlColorParser.BLACK, TtmlColorParser.parseColor("black"));
|
|
||||||
assertEquals(TtmlColorParser.GRAY, TtmlColorParser.parseColor("gray"));
|
|
||||||
assertEquals(TtmlColorParser.SILVER, TtmlColorParser.parseColor("silver"));
|
|
||||||
assertEquals(TtmlColorParser.WHITE, TtmlColorParser.parseColor("white"));
|
|
||||||
assertEquals(TtmlColorParser.MAROON, TtmlColorParser.parseColor("maroon"));
|
|
||||||
assertEquals(TtmlColorParser.RED, TtmlColorParser.parseColor("red"));
|
|
||||||
assertEquals(TtmlColorParser.PURPLE, TtmlColorParser.parseColor("purple"));
|
|
||||||
assertEquals(TtmlColorParser.FUCHSIA, TtmlColorParser.parseColor("fuchsia"));
|
|
||||||
assertEquals(TtmlColorParser.MAGENTA, TtmlColorParser.parseColor("magenta"));
|
|
||||||
assertEquals(TtmlColorParser.GREEN, TtmlColorParser.parseColor("green"));
|
|
||||||
assertEquals(TtmlColorParser.LIME, TtmlColorParser.parseColor("lime"));
|
|
||||||
assertEquals(TtmlColorParser.OLIVE, TtmlColorParser.parseColor("olive"));
|
|
||||||
assertEquals(TtmlColorParser.YELLOW, TtmlColorParser.parseColor("yellow"));
|
|
||||||
assertEquals(TtmlColorParser.NAVY, TtmlColorParser.parseColor("navy"));
|
|
||||||
assertEquals(TtmlColorParser.BLUE, TtmlColorParser.parseColor("blue"));
|
|
||||||
assertEquals(TtmlColorParser.TEAL, TtmlColorParser.parseColor("teal"));
|
|
||||||
assertEquals(TtmlColorParser.AQUA, TtmlColorParser.parseColor("aqua"));
|
|
||||||
assertEquals(TtmlColorParser.CYAN, TtmlColorParser.parseColor("cyan"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testParseUnknownColor() {
|
|
||||||
try {
|
|
||||||
TtmlColorParser.parseColor("colorOfAnElectron");
|
|
||||||
fail();
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
// expected
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testParseNull() {
|
|
||||||
try {
|
|
||||||
TtmlColorParser.parseColor(null);
|
|
||||||
fail();
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
// expected
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testParseEmpty() {
|
|
||||||
try {
|
|
||||||
TtmlColorParser.parseColor("");
|
|
||||||
fail();
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
// expected
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testRgbColorParsing() {
|
|
||||||
assertEquals(Color.WHITE, TtmlColorParser.parseColor("rgb(255,255,255)"));
|
|
||||||
// spaces do not matter
|
|
||||||
assertEquals(Color.WHITE, TtmlColorParser.parseColor(" rgb ( 255, 255, 255)"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testRgbColorParsing_rgbValuesOutOfBounds() {
|
|
||||||
int outOfBounds = TtmlColorParser.parseColor("rgb(999, 999, 999)");
|
|
||||||
int color = Color.rgb(999, 999, 999);
|
|
||||||
// behave like framework Color behaves
|
|
||||||
assertEquals(color, outOfBounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testRgbColorParsing_rgbValuesNegative() {
|
|
||||||
try {
|
|
||||||
TtmlColorParser.parseColor("rgb(-4, 55, 209)");
|
|
||||||
fail();
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
// expected
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testRgbaColorParsing() {
|
|
||||||
assertEquals(Color.WHITE, TtmlColorParser.parseColor("rgba(255,255,255,0)"));
|
|
||||||
assertEquals(Color.argb(0, 255, 255, 255), TtmlColorParser.parseColor("rgba(255,255,255,255)"));
|
|
||||||
assertEquals(Color.BLACK, TtmlColorParser.parseColor("rgba(0, 0, 0, 0)"));
|
|
||||||
assertEquals(Color.argb(0, 0, 0, 0), TtmlColorParser.parseColor("rgba(0, 0, 0, 255)"));
|
|
||||||
assertEquals(Color.RED, TtmlColorParser.parseColor("rgba(255, 0, 0, 0)"));
|
|
||||||
assertEquals(Color.argb(0, 255, 0, 0), TtmlColorParser.parseColor("rgba(255, 0, 0, 255)"));
|
|
||||||
assertEquals(Color.argb(205, 255, 0, 0), TtmlColorParser.parseColor("rgba(255, 0, 0, 50)"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -51,10 +51,9 @@ public final class TtmlParserTest extends InstrumentationTestCase {
|
||||||
private static final String INHERIT_MULTIPLE_STYLES_TTML_FILE =
|
private static final String INHERIT_MULTIPLE_STYLES_TTML_FILE =
|
||||||
"ttml/inherit_multiple_styles.xml";
|
"ttml/inherit_multiple_styles.xml";
|
||||||
private static final String CHAIN_MULTIPLE_STYLES_TTML_FILE = "ttml/chain_multiple_styles.xml";
|
private static final String CHAIN_MULTIPLE_STYLES_TTML_FILE = "ttml/chain_multiple_styles.xml";
|
||||||
|
private static final String MULTIPLE_REGIONS_TTML_FILE = "ttml/multiple_regions.xml";
|
||||||
private static final String NO_UNDERLINE_LINETHROUGH_TTML_FILE =
|
private static final String NO_UNDERLINE_LINETHROUGH_TTML_FILE =
|
||||||
"ttml/no_underline_linethrough.xml";
|
"ttml/no_underline_linethrough.xml";
|
||||||
private static final String NAMESPACE_CONFUSION_TTML_FILE = "ttml/namespace_confusion.xml";
|
|
||||||
private static final String NAMESPACE_NOT_DECLARED_TTML_FILE = "ttml/namespace_not_declared.xml";
|
|
||||||
private static final String FONT_SIZE_TTML_FILE = "ttml/font_size.xml";
|
private static final String FONT_SIZE_TTML_FILE = "ttml/font_size.xml";
|
||||||
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";
|
||||||
|
|
@ -69,7 +68,7 @@ public final class TtmlParserTest extends InstrumentationTestCase {
|
||||||
TtmlNode body = queryChildrenForTag(root, TtmlNode.TAG_BODY, 0);
|
TtmlNode body = queryChildrenForTag(root, TtmlNode.TAG_BODY, 0);
|
||||||
TtmlNode firstDiv = queryChildrenForTag(body, TtmlNode.TAG_DIV, 0);
|
TtmlNode firstDiv = queryChildrenForTag(body, TtmlNode.TAG_DIV, 0);
|
||||||
TtmlStyle firstPStyle = queryChildrenForTag(firstDiv, TtmlNode.TAG_P, 0).style;
|
TtmlStyle firstPStyle = queryChildrenForTag(firstDiv, TtmlNode.TAG_P, 0).style;
|
||||||
assertEquals(ColorParser.parseTtmlColor("yellow"), firstPStyle.getColor());
|
assertEquals(ColorParser.parseTtmlColor("yellow"), firstPStyle.getFontColor());
|
||||||
assertEquals(ColorParser.parseTtmlColor("blue"), firstPStyle.getBackgroundColor());
|
assertEquals(ColorParser.parseTtmlColor("blue"), firstPStyle.getBackgroundColor());
|
||||||
assertEquals("serif", firstPStyle.getFontFamily());
|
assertEquals("serif", firstPStyle.getFontFamily());
|
||||||
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, firstPStyle.getStyle());
|
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, firstPStyle.getStyle());
|
||||||
|
|
@ -84,15 +83,14 @@ public final class TtmlParserTest extends InstrumentationTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* regression test for devices on JellyBean where some named colors are not correctly defined
|
* Regression test for devices on JellyBean where some named colors are not correctly defined
|
||||||
* on framework level. Tests that <i>lime</i> resolves to <code>#FF00FF00</code> not
|
* on framework level. Tests that <i>lime</i> resolves to <code>#FF00FF00</code> not
|
||||||
* <code>#00FF00</code>.
|
* <code>#00FF00</code>.
|
||||||
*
|
*
|
||||||
* See: https://github.com/android/platform_frameworks_base/blob/jb-mr2-release/
|
* @see <a href="https://github.com/android/platform_frameworks_base/blob/jb-mr2-release/graphics/java/android/graphics/Color.java#L414">
|
||||||
* graphics/java/android/graphics/Color.java#L414
|
* JellyBean Color</a>
|
||||||
* https://github.com/android/platform_frameworks_base/blob/kitkat-mr2.2-release/
|
* <a href="https://github.com/android/platform_frameworks_base/blob/kitkat-mr2.2-release/graphics/java/android/graphics/Color.java#L414">
|
||||||
* graphics/java/android/graphics/Color.java#L414
|
* Kitkat Color</a>
|
||||||
*
|
|
||||||
* @throws IOException thrown if reading subtitle file fails.
|
* @throws IOException thrown if reading subtitle file fails.
|
||||||
*/
|
*/
|
||||||
public void testLime() throws IOException {
|
public void testLime() throws IOException {
|
||||||
|
|
@ -132,7 +130,6 @@ public final class TtmlParserTest extends InstrumentationTestCase {
|
||||||
public void testInheritMultipleStyles() throws IOException {
|
public void testInheritMultipleStyles() throws IOException {
|
||||||
TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE);
|
TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE);
|
||||||
assertEquals(12, subtitle.getEventTimeCount());
|
assertEquals(12, subtitle.getEventTimeCount());
|
||||||
|
|
||||||
assertSpans(subtitle, 10, "text 1", "sansSerif", TtmlStyle.STYLE_BOLD_ITALIC, 0xFF0000FF,
|
assertSpans(subtitle, 10, "text 1", "sansSerif", TtmlStyle.STYLE_BOLD_ITALIC, 0xFF0000FF,
|
||||||
0xFFFFFF00, false, true, null);
|
0xFFFFFF00, false, true, null);
|
||||||
}
|
}
|
||||||
|
|
@ -140,7 +137,6 @@ public final class TtmlParserTest extends InstrumentationTestCase {
|
||||||
public void testInheritMultipleStylesWithoutLocalAttributes() throws IOException {
|
public void testInheritMultipleStylesWithoutLocalAttributes() throws IOException {
|
||||||
TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE);
|
TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE);
|
||||||
assertEquals(12, subtitle.getEventTimeCount());
|
assertEquals(12, subtitle.getEventTimeCount());
|
||||||
|
|
||||||
assertSpans(subtitle, 20, "text 2", "sansSerif", TtmlStyle.STYLE_BOLD_ITALIC, 0xFF0000FF,
|
assertSpans(subtitle, 20, "text 2", "sansSerif", TtmlStyle.STYLE_BOLD_ITALIC, 0xFF0000FF,
|
||||||
0xFF000000, false, true, null);
|
0xFF000000, false, true, null);
|
||||||
}
|
}
|
||||||
|
|
@ -148,11 +144,54 @@ public final class TtmlParserTest extends InstrumentationTestCase {
|
||||||
public void testMergeMultipleStylesWithParentStyle() throws IOException {
|
public void testMergeMultipleStylesWithParentStyle() throws IOException {
|
||||||
TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE);
|
TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE);
|
||||||
assertEquals(12, subtitle.getEventTimeCount());
|
assertEquals(12, subtitle.getEventTimeCount());
|
||||||
|
|
||||||
assertSpans(subtitle, 30, "text 2.5", "sansSerifInline", TtmlStyle.STYLE_ITALIC, 0xFFFF0000,
|
assertSpans(subtitle, 30, "text 2.5", "sansSerifInline", TtmlStyle.STYLE_ITALIC, 0xFFFF0000,
|
||||||
0xFFFFFF00, true, true, null);
|
0xFFFFFF00, true, true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testMultipleRegions() throws IOException {
|
||||||
|
TtmlSubtitle subtitle = getSubtitle(MULTIPLE_REGIONS_TTML_FILE);
|
||||||
|
List<Cue> output = subtitle.getCues(1000000);
|
||||||
|
assertEquals(2, output.size());
|
||||||
|
Cue ttmlCue = output.get(0);
|
||||||
|
assertEquals("lorem", ttmlCue.text.toString());
|
||||||
|
assertEquals(10.f / 100.f, ttmlCue.position);
|
||||||
|
assertEquals(10.f / 100.f, ttmlCue.line);
|
||||||
|
ttmlCue = output.get(1);
|
||||||
|
assertEquals("amet", ttmlCue.text.toString());
|
||||||
|
assertEquals(60.f / 100.f, ttmlCue.position);
|
||||||
|
assertEquals(10.f / 100.f, ttmlCue.line);
|
||||||
|
|
||||||
|
output = subtitle.getCues(5000000);
|
||||||
|
assertEquals(1, output.size());
|
||||||
|
ttmlCue = output.get(0);
|
||||||
|
assertEquals("ipsum", ttmlCue.text.toString());
|
||||||
|
assertEquals(40.f / 100.f, ttmlCue.position);
|
||||||
|
assertEquals(40.f / 100.f, ttmlCue.line);
|
||||||
|
|
||||||
|
output = subtitle.getCues(9000000);
|
||||||
|
assertEquals(1, output.size());
|
||||||
|
ttmlCue = output.get(0);
|
||||||
|
assertEquals("dolor", ttmlCue.text.toString());
|
||||||
|
assertEquals(10.f / 100.f, ttmlCue.position);
|
||||||
|
assertEquals(80.f / 100.f, ttmlCue.line);
|
||||||
|
|
||||||
|
output = subtitle.getCues(21000000);
|
||||||
|
assertEquals(1, output.size());
|
||||||
|
ttmlCue = output.get(0);
|
||||||
|
assertEquals("She first said this", ttmlCue.text.toString());
|
||||||
|
assertEquals(45.f / 100.f, ttmlCue.position);
|
||||||
|
assertEquals(45.f / 100.f, ttmlCue.line);
|
||||||
|
output = subtitle.getCues(25000000);
|
||||||
|
ttmlCue = output.get(0);
|
||||||
|
assertEquals("She first said this\nThen this", ttmlCue.text.toString());
|
||||||
|
output = subtitle.getCues(29000000);
|
||||||
|
assertEquals(1, output.size());
|
||||||
|
ttmlCue = output.get(0);
|
||||||
|
assertEquals("She first said this\nThen this\nFinally this", ttmlCue.text.toString());
|
||||||
|
assertEquals(45.f / 100.f, ttmlCue.position);
|
||||||
|
assertEquals(45.f / 100.f, ttmlCue.line);
|
||||||
|
}
|
||||||
|
|
||||||
public void testEmptyStyleAttribute() throws IOException {
|
public void testEmptyStyleAttribute() throws IOException {
|
||||||
TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE);
|
TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE);
|
||||||
assertEquals(12, subtitle.getEventTimeCount());
|
assertEquals(12, subtitle.getEventTimeCount());
|
||||||
|
|
@ -196,13 +235,13 @@ public final class TtmlParserTest extends InstrumentationTestCase {
|
||||||
TtmlStyle style = globalStyles.get("s2");
|
TtmlStyle style = globalStyles.get("s2");
|
||||||
assertEquals("serif", style.getFontFamily());
|
assertEquals("serif", style.getFontFamily());
|
||||||
assertEquals(0xFFFF0000, style.getBackgroundColor());
|
assertEquals(0xFFFF0000, style.getBackgroundColor());
|
||||||
assertEquals(0xFF000000, style.getColor());
|
assertEquals(0xFF000000, style.getFontColor());
|
||||||
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, style.getStyle());
|
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, style.getStyle());
|
||||||
assertTrue(style.isLinethrough());
|
assertTrue(style.isLinethrough());
|
||||||
|
|
||||||
style = globalStyles.get("s3");
|
style = globalStyles.get("s3");
|
||||||
// only difference: color must be RED
|
// only difference: color must be RED
|
||||||
assertEquals(0xFFFF0000, style.getColor());
|
assertEquals(0xFFFF0000, style.getFontColor());
|
||||||
assertEquals("serif", style.getFontFamily());
|
assertEquals("serif", style.getFontFamily());
|
||||||
assertEquals(0xFFFF0000, style.getBackgroundColor());
|
assertEquals(0xFFFF0000, style.getBackgroundColor());
|
||||||
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, style.getStyle());
|
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, style.getStyle());
|
||||||
|
|
@ -234,43 +273,6 @@ public final class TtmlParserTest extends InstrumentationTestCase {
|
||||||
style.isLinethrough());
|
style.isLinethrough());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testNamspaceConfusionDoesNotHurt() throws IOException {
|
|
||||||
TtmlSubtitle subtitle = getSubtitle(NAMESPACE_CONFUSION_TTML_FILE);
|
|
||||||
assertEquals(2, subtitle.getEventTimeCount());
|
|
||||||
|
|
||||||
TtmlNode root = subtitle.getRoot();
|
|
||||||
TtmlNode body = queryChildrenForTag(root, TtmlNode.TAG_BODY, 0);
|
|
||||||
TtmlNode div = queryChildrenForTag(body, TtmlNode.TAG_DIV, 0);
|
|
||||||
TtmlStyle style = queryChildrenForTag(div, TtmlNode.TAG_P, 0).style;
|
|
||||||
|
|
||||||
assertNotNull(style);
|
|
||||||
assertEquals(0xFF000000, style.getBackgroundColor());
|
|
||||||
assertEquals(0xFFFFFF00, style.getColor());
|
|
||||||
assertEquals(TtmlStyle.STYLE_ITALIC, style.getStyle());
|
|
||||||
assertEquals("sansSerif", style.getFontFamily());
|
|
||||||
assertFalse(style.isUnderline());
|
|
||||||
assertTrue(style.isLinethrough());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testNamespaceNotDeclared() throws IOException {
|
|
||||||
TtmlSubtitle subtitle = getSubtitle(NAMESPACE_NOT_DECLARED_TTML_FILE);
|
|
||||||
assertEquals(2, subtitle.getEventTimeCount());
|
|
||||||
|
|
||||||
TtmlNode root = subtitle.getRoot();
|
|
||||||
TtmlNode body = queryChildrenForTag(root, TtmlNode.TAG_BODY, 0);
|
|
||||||
TtmlNode div = queryChildrenForTag(body, TtmlNode.TAG_DIV, 0);
|
|
||||||
TtmlStyle style = queryChildrenForTag(div, TtmlNode.TAG_P, 0).style;
|
|
||||||
|
|
||||||
assertNotNull(style);
|
|
||||||
assertEquals(0xFF000000, style.getBackgroundColor());
|
|
||||||
assertEquals(0xFFFFFF00, style.getColor());
|
|
||||||
assertEquals(TtmlStyle.STYLE_ITALIC, style.getStyle());
|
|
||||||
assertEquals("sansSerif", style.getFontFamily());
|
|
||||||
assertFalse(style.isUnderline());
|
|
||||||
assertTrue(style.isLinethrough());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testFontSizeSpans() throws IOException {
|
public void testFontSizeSpans() throws IOException {
|
||||||
TtmlSubtitle subtitle = getSubtitle(FONT_SIZE_TTML_FILE);
|
TtmlSubtitle subtitle = getSubtitle(FONT_SIZE_TTML_FILE);
|
||||||
assertEquals(10, subtitle.getEventTimeCount());
|
assertEquals(10, subtitle.getEventTimeCount());
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ public class TtmlRenderUtilTest extends InstrumentationTestCase {
|
||||||
// inherited from s0
|
// inherited from s0
|
||||||
assertEquals(Color.BLACK, resolved.getBackgroundColor());
|
assertEquals(Color.BLACK, resolved.getBackgroundColor());
|
||||||
// inherited from s1
|
// inherited from s1
|
||||||
assertEquals(Color.RED, resolved.getColor());
|
assertEquals(Color.RED, resolved.getFontColor());
|
||||||
// merged from s0 and s1
|
// merged from s0 and s1
|
||||||
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, resolved.getStyle());
|
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, resolved.getStyle());
|
||||||
}
|
}
|
||||||
|
|
@ -101,7 +101,7 @@ public class TtmlRenderUtilTest extends InstrumentationTestCase {
|
||||||
TtmlStyle s1 = new TtmlStyle();
|
TtmlStyle s1 = new TtmlStyle();
|
||||||
s1.setId("s1");
|
s1.setId("s1");
|
||||||
s1.setBackgroundColor(Color.RED);
|
s1.setBackgroundColor(Color.RED);
|
||||||
s1.setColor(Color.RED);
|
s1.setFontColor(Color.RED);
|
||||||
s1.setItalic(true);
|
s1.setItalic(true);
|
||||||
globalStyles.put(s1.getId(), s1);
|
globalStyles.put(s1.getId(), s1);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,8 @@ public final class TtmlStyleTest extends InstrumentationTestCase {
|
||||||
assertTrue(style.isLinethrough());
|
assertTrue(style.isLinethrough());
|
||||||
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, style.getStyle());
|
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, style.getStyle());
|
||||||
assertEquals(FONT_FAMILY, style.getFontFamily());
|
assertEquals(FONT_FAMILY, style.getFontFamily());
|
||||||
assertEquals(Color.WHITE, style.getColor());
|
assertEquals(Color.WHITE, style.getFontColor());
|
||||||
assertFalse("do not inherit backgroundColor", style.hasBackgroundColorSpecified());
|
assertFalse("do not inherit backgroundColor", style.hasBackgroundColor());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testChainStyle() {
|
public void testChainStyle() {
|
||||||
|
|
@ -53,7 +53,7 @@ public final class TtmlStyleTest extends InstrumentationTestCase {
|
||||||
assertTrue(style.isLinethrough());
|
assertTrue(style.isLinethrough());
|
||||||
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, style.getStyle());
|
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, style.getStyle());
|
||||||
assertEquals(FONT_FAMILY, style.getFontFamily());
|
assertEquals(FONT_FAMILY, style.getFontFamily());
|
||||||
assertEquals(FOREGROUND_COLOR, style.getColor());
|
assertEquals(FOREGROUND_COLOR, style.getFontColor());
|
||||||
// do inherit backgroundColor when chaining
|
// do inherit backgroundColor when chaining
|
||||||
assertEquals("do not inherit backgroundColor when chaining",
|
assertEquals("do not inherit backgroundColor when chaining",
|
||||||
BACKGROUND_COLOR, style.getBackgroundColor());
|
BACKGROUND_COLOR, style.getBackgroundColor());
|
||||||
|
|
@ -65,7 +65,7 @@ public final class TtmlStyleTest extends InstrumentationTestCase {
|
||||||
ancestor.setItalic(true);
|
ancestor.setItalic(true);
|
||||||
ancestor.setBold(true);
|
ancestor.setBold(true);
|
||||||
ancestor.setBackgroundColor(BACKGROUND_COLOR);
|
ancestor.setBackgroundColor(BACKGROUND_COLOR);
|
||||||
ancestor.setColor(FOREGROUND_COLOR);
|
ancestor.setFontColor(FOREGROUND_COLOR);
|
||||||
ancestor.setLinethrough(true);
|
ancestor.setLinethrough(true);
|
||||||
ancestor.setUnderline(true);
|
ancestor.setUnderline(true);
|
||||||
ancestor.setFontFamily(FONT_FAMILY);
|
ancestor.setFontFamily(FONT_FAMILY);
|
||||||
|
|
@ -109,17 +109,17 @@ public final class TtmlStyleTest extends InstrumentationTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testColor() {
|
public void testColor() {
|
||||||
assertFalse(style.hasColorSpecified());
|
assertFalse(style.hasFontColor());
|
||||||
style.setColor(Color.BLACK);
|
style.setFontColor(Color.BLACK);
|
||||||
assertEquals(Color.BLACK, style.getColor());
|
assertEquals(Color.BLACK, style.getFontColor());
|
||||||
assertTrue(style.hasColorSpecified());
|
assertTrue(style.hasFontColor());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testBackgroundColor() {
|
public void testBackgroundColor() {
|
||||||
assertFalse(style.hasBackgroundColorSpecified());
|
assertFalse(style.hasBackgroundColor());
|
||||||
style.setBackgroundColor(Color.BLACK);
|
style.setBackgroundColor(Color.BLACK);
|
||||||
assertEquals(Color.BLACK, style.getBackgroundColor());
|
assertEquals(Color.BLACK, style.getBackgroundColor());
|
||||||
assertTrue(style.hasBackgroundColorSpecified());
|
assertTrue(style.hasBackgroundColor());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testId() {
|
public void testId() {
|
||||||
|
|
|
||||||
|
|
@ -1,140 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2014 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.google.android.exoplayer.text.ttml;
|
|
||||||
|
|
||||||
import com.google.android.exoplayer.util.Assertions;
|
|
||||||
import com.google.android.exoplayer.util.Util;
|
|
||||||
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parser to parse ttml color value expression
|
|
||||||
* (http://www.w3.org/TR/ttml1/#style-value-color)
|
|
||||||
*/
|
|
||||||
/*package*/ final class TtmlColorParser {
|
|
||||||
|
|
||||||
private static final String RGB = "rgb";
|
|
||||||
private static final String RGBA = "rgba";
|
|
||||||
|
|
||||||
private static final Pattern RGB_PATTERN = Pattern.compile(
|
|
||||||
"^rgb\\((\\d{1,3}),(\\d{1,3}),(\\d{1,3})\\)$");
|
|
||||||
|
|
||||||
private static final Pattern RGBA_PATTERN = Pattern.compile(
|
|
||||||
"^rgba\\((\\d{1,3}),(\\d{1,3}),(\\d{1,3}),(\\d{1,3})\\)$");
|
|
||||||
|
|
||||||
static final int TRANSPARENT = 0x00000000;
|
|
||||||
static final int BLACK = 0xFF000000;
|
|
||||||
static final int SILVER = 0xFFC0C0C0;
|
|
||||||
static final int GRAY = 0xFF808080;
|
|
||||||
static final int WHITE = 0xFFFFFFFF;
|
|
||||||
static final int MAROON = 0xFF800000;
|
|
||||||
static final int RED = 0xFFFF0000;
|
|
||||||
static final int PURPLE = 0xFF800080;
|
|
||||||
static final int FUCHSIA = 0xFFFF00FF;
|
|
||||||
static final int MAGENTA = FUCHSIA;
|
|
||||||
static final int GREEN = 0xFF008000;
|
|
||||||
static final int LIME = 0xFF00FF00;
|
|
||||||
static final int OLIVE = 0xFF808000;
|
|
||||||
static final int YELLOW = 0xFFFFFF00;
|
|
||||||
static final int NAVY = 0xFF000080;
|
|
||||||
static final int BLUE = 0xFF0000FF;
|
|
||||||
static final int TEAL = 0xFF008080;
|
|
||||||
static final int AQUA = 0x00FFFFFF;
|
|
||||||
static final int CYAN = 0xFF00FFFF;
|
|
||||||
|
|
||||||
private static final Map<String, Integer> COLOR_NAME_MAP;
|
|
||||||
static {
|
|
||||||
COLOR_NAME_MAP = new HashMap<>();
|
|
||||||
COLOR_NAME_MAP.put("transparent", TRANSPARENT);
|
|
||||||
COLOR_NAME_MAP.put("black", BLACK);
|
|
||||||
COLOR_NAME_MAP.put("silver", SILVER);
|
|
||||||
COLOR_NAME_MAP.put("gray", GRAY);
|
|
||||||
COLOR_NAME_MAP.put("white", WHITE);
|
|
||||||
COLOR_NAME_MAP.put("maroon", MAROON);
|
|
||||||
COLOR_NAME_MAP.put("red", RED);
|
|
||||||
COLOR_NAME_MAP.put("purple", PURPLE);
|
|
||||||
COLOR_NAME_MAP.put("fuchsia", FUCHSIA);
|
|
||||||
COLOR_NAME_MAP.put("magenta", MAGENTA);
|
|
||||||
COLOR_NAME_MAP.put("green", GREEN);
|
|
||||||
COLOR_NAME_MAP.put("lime", LIME);
|
|
||||||
COLOR_NAME_MAP.put("olive", OLIVE);
|
|
||||||
COLOR_NAME_MAP.put("yellow", YELLOW);
|
|
||||||
COLOR_NAME_MAP.put("navy", NAVY);
|
|
||||||
COLOR_NAME_MAP.put("blue", BLUE);
|
|
||||||
COLOR_NAME_MAP.put("teal", TEAL);
|
|
||||||
COLOR_NAME_MAP.put("aqua", AQUA);
|
|
||||||
COLOR_NAME_MAP.put("cyan", CYAN);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int parseColor(String colorExpression) {
|
|
||||||
Assertions.checkArgument(!TextUtils.isEmpty(colorExpression));
|
|
||||||
colorExpression = colorExpression.replace(" ", "");
|
|
||||||
if (colorExpression.charAt(0) == '#') {
|
|
||||||
// Parse using Long to avoid failure when colorExpression is greater than #7FFFFFFF.
|
|
||||||
int color = (int) Long.parseLong(colorExpression.substring(1), 16);
|
|
||||||
if (colorExpression.length() == 7) {
|
|
||||||
// Set the alpha value
|
|
||||||
color |= 0xFF000000;
|
|
||||||
} else if (colorExpression.length() == 9) {
|
|
||||||
// We have #RRGGBBAA, but we need #AARRGGBB
|
|
||||||
color = ((color & 0xFF) << 24) | (color >>> 8);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
}
|
|
||||||
return color;
|
|
||||||
} else if (colorExpression.startsWith(RGBA)) {
|
|
||||||
Matcher matcher = RGBA_PATTERN.matcher(colorExpression);
|
|
||||||
if (matcher.matches()) {
|
|
||||||
return argb(
|
|
||||||
255 - Integer.parseInt(matcher.group(4), 10),
|
|
||||||
Integer.parseInt(matcher.group(1), 10),
|
|
||||||
Integer.parseInt(matcher.group(2), 10),
|
|
||||||
Integer.parseInt(matcher.group(3), 10)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (colorExpression.startsWith(RGB)) {
|
|
||||||
Matcher matcher = RGB_PATTERN.matcher(colorExpression);
|
|
||||||
if (matcher.matches()) {
|
|
||||||
return rgb(
|
|
||||||
Integer.parseInt(matcher.group(1), 10),
|
|
||||||
Integer.parseInt(matcher.group(2), 10),
|
|
||||||
Integer.parseInt(matcher.group(3), 10)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// we use our own color map
|
|
||||||
Integer color = COLOR_NAME_MAP.get(Util.toLowerInvariant(colorExpression));
|
|
||||||
if (color != null) {
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int argb(int alpha, int red, int green, int blue) {
|
|
||||||
return (alpha << 24) | (red << 16) | (green << 8) | blue;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int rgb(int red, int green, int blue) {
|
|
||||||
return argb(0xFF, red, green, blue);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -15,12 +15,17 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.text.ttml;
|
package com.google.android.exoplayer.text.ttml;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.text.Cue;
|
||||||
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
|
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.TreeMap;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -45,6 +50,7 @@ import java.util.TreeSet;
|
||||||
public static final String TAG_SMPTE_DATA = "smpte:data";
|
public static final String TAG_SMPTE_DATA = "smpte:data";
|
||||||
public static final String TAG_SMPTE_INFORMATION = "smpte:information";
|
public static final String TAG_SMPTE_INFORMATION = "smpte:information";
|
||||||
|
|
||||||
|
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_BACKGROUND_COLOR = "backgroundColor";
|
||||||
public static final String ATTR_TTS_FONT_STYLE = "fontStyle";
|
public static final String ATTR_TTS_FONT_STYLE = "fontStyle";
|
||||||
|
|
@ -52,6 +58,7 @@ import java.util.TreeSet;
|
||||||
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";
|
||||||
|
|
||||||
|
|
@ -74,24 +81,26 @@ import java.util.TreeSet;
|
||||||
public final long startTimeUs;
|
public final long startTimeUs;
|
||||||
public final long endTimeUs;
|
public final long endTimeUs;
|
||||||
public final TtmlStyle style;
|
public final TtmlStyle style;
|
||||||
|
public final String regionId;
|
||||||
|
|
||||||
private final String[] styleIds;
|
private final String[] styleIds;
|
||||||
|
private final HashMap<String, Integer> nodeStartsByRegion;
|
||||||
|
private final HashMap<String, Integer> nodeEndsByRegion;
|
||||||
|
|
||||||
private List<TtmlNode> children;
|
private List<TtmlNode> children;
|
||||||
private int start;
|
|
||||||
private int end;
|
|
||||||
|
|
||||||
public static TtmlNode buildTextNode(String text) {
|
public static TtmlNode buildTextNode(String text) {
|
||||||
return new TtmlNode(null, TtmlRenderUtil.applyTextElementSpacePolicy(text), UNDEFINED_TIME,
|
return new TtmlNode(null, TtmlRenderUtil.applyTextElementSpacePolicy(text), UNDEFINED_TIME,
|
||||||
UNDEFINED_TIME, null, null);
|
UNDEFINED_TIME, null, null, ANONYMOUS_REGION_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TtmlNode buildNode(String tag, long startTimeUs, long endTimeUs,
|
public static TtmlNode buildNode(String tag, long startTimeUs, long endTimeUs,
|
||||||
TtmlStyle style, String[] styleIds) {
|
TtmlStyle style, String[] styleIds, String regionId) {
|
||||||
return new TtmlNode(tag, null, startTimeUs, endTimeUs, style, styleIds);
|
return new TtmlNode(tag, null, startTimeUs, endTimeUs, style, styleIds, regionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TtmlNode(String tag, String text, long startTimeUs, long endTimeUs,
|
private TtmlNode(String tag, String text, long startTimeUs, long endTimeUs,
|
||||||
TtmlStyle style, String[] styleIds) {
|
TtmlStyle style, String[] styleIds, String regionId) {
|
||||||
this.tag = tag;
|
this.tag = tag;
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.style = style;
|
this.style = style;
|
||||||
|
|
@ -99,6 +108,9 @@ import java.util.TreeSet;
|
||||||
this.isTextNode = text != null;
|
this.isTextNode = text != null;
|
||||||
this.startTimeUs = startTimeUs;
|
this.startTimeUs = startTimeUs;
|
||||||
this.endTimeUs = endTimeUs;
|
this.endTimeUs = endTimeUs;
|
||||||
|
this.regionId = Assertions.checkNotNull(regionId);
|
||||||
|
nodeStartsByRegion = new HashMap<>();
|
||||||
|
nodeEndsByRegion = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isActive(long timeUs) {
|
public boolean isActive(long timeUs) {
|
||||||
|
|
@ -130,10 +142,8 @@ import java.util.TreeSet;
|
||||||
TreeSet<Long> eventTimeSet = new TreeSet<>();
|
TreeSet<Long> eventTimeSet = new TreeSet<>();
|
||||||
getEventTimes(eventTimeSet, false);
|
getEventTimes(eventTimeSet, false);
|
||||||
long[] eventTimes = new long[eventTimeSet.size()];
|
long[] eventTimes = new long[eventTimeSet.size()];
|
||||||
Iterator<Long> eventTimeIterator = eventTimeSet.iterator();
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
while (eventTimeIterator.hasNext()) {
|
for (long eventTimeUs : eventTimeSet) {
|
||||||
long eventTimeUs = eventTimeIterator.next();
|
|
||||||
eventTimes[i++] = eventTimeUs;
|
eventTimes[i++] = eventTimeUs;
|
||||||
}
|
}
|
||||||
return eventTimes;
|
return eventTimes;
|
||||||
|
|
@ -161,10 +171,83 @@ import java.util.TreeSet;
|
||||||
return styleIds;
|
return styleIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CharSequence getText(long timeUs, Map<String, TtmlStyle> globalStyles) {
|
public List<Cue> getCues(long timeUs, Map<String, TtmlStyle> globalStyles,
|
||||||
SpannableStringBuilder builder = new SpannableStringBuilder();
|
Map<String, TtmlRegion> regionMap) {
|
||||||
traverseForText(timeUs, builder, false);
|
TreeMap<String, SpannableStringBuilder> regionOutputs = new TreeMap<>();
|
||||||
traverseForStyle(builder, globalStyles);
|
traverseForText(timeUs, false, regionId, regionOutputs);
|
||||||
|
traverseForStyle(globalStyles, regionOutputs);
|
||||||
|
List<Cue> cues = new ArrayList<>();
|
||||||
|
for (Entry<String, SpannableStringBuilder> 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));
|
||||||
|
}
|
||||||
|
return cues;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void traverseForText(long timeUs, boolean descendsPNode,
|
||||||
|
String inheritedRegion, Map<String, SpannableStringBuilder> regionOutputs) {
|
||||||
|
nodeStartsByRegion.clear();
|
||||||
|
nodeEndsByRegion.clear();
|
||||||
|
String resolvedRegionId = regionId;
|
||||||
|
if (ANONYMOUS_REGION_ID.equals(resolvedRegionId)) {
|
||||||
|
resolvedRegionId = inheritedRegion;
|
||||||
|
}
|
||||||
|
if (isTextNode && descendsPNode) {
|
||||||
|
getRegionOutput(resolvedRegionId, regionOutputs).append(text);
|
||||||
|
} else if (TAG_BR.equals(tag) && descendsPNode) {
|
||||||
|
getRegionOutput(resolvedRegionId, regionOutputs).append('\n');
|
||||||
|
} else if (TAG_METADATA.equals(tag)) {
|
||||||
|
// Do nothing.
|
||||||
|
} else if (isActive(timeUs)) {
|
||||||
|
boolean isPNode = TAG_P.equals(tag);
|
||||||
|
for (Entry<String, SpannableStringBuilder> entry : regionOutputs.entrySet()) {
|
||||||
|
nodeStartsByRegion.put(entry.getKey(), entry.getValue().length());
|
||||||
|
}
|
||||||
|
for (int i = 0; i < getChildCount(); i++) {
|
||||||
|
getChild(i).traverseForText(timeUs, descendsPNode || isPNode, resolvedRegionId,
|
||||||
|
regionOutputs);
|
||||||
|
}
|
||||||
|
if (isPNode) {
|
||||||
|
TtmlRenderUtil.endParagraph(getRegionOutput(resolvedRegionId, regionOutputs));
|
||||||
|
}
|
||||||
|
for (Entry<String, SpannableStringBuilder> entry : regionOutputs.entrySet()) {
|
||||||
|
nodeEndsByRegion.put(entry.getKey(), entry.getValue().length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SpannableStringBuilder getRegionOutput(String resolvedRegionId,
|
||||||
|
Map<String, SpannableStringBuilder> regionOutputs) {
|
||||||
|
if (!regionOutputs.containsKey(resolvedRegionId)) {
|
||||||
|
regionOutputs.put(resolvedRegionId, new SpannableStringBuilder());
|
||||||
|
}
|
||||||
|
return regionOutputs.get(resolvedRegionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void traverseForStyle(Map<String, TtmlStyle> globalStyles,
|
||||||
|
Map<String, SpannableStringBuilder> regionOutputs) {
|
||||||
|
for (Entry<String, Integer> entry : nodeEndsByRegion.entrySet()) {
|
||||||
|
String regionId = entry.getKey();
|
||||||
|
int start = nodeStartsByRegion.containsKey(regionId) ? nodeStartsByRegion.get(regionId) : 0;
|
||||||
|
applyStyleToOutput(globalStyles, regionOutputs.get(regionId), start, entry.getValue());
|
||||||
|
for (int i = 0; i < getChildCount(); ++i) {
|
||||||
|
getChild(i).traverseForStyle(globalStyles, regionOutputs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyStyleToOutput(Map<String, TtmlStyle> globalStyles,
|
||||||
|
SpannableStringBuilder regionOutput, int start, int end) {
|
||||||
|
if (start != end) {
|
||||||
|
TtmlStyle resolvedStyle = TtmlRenderUtil.resolveStyle(style, styleIds, globalStyles);
|
||||||
|
if (resolvedStyle != null) {
|
||||||
|
TtmlRenderUtil.applyStylesToSpan(regionOutput, start, end, resolvedStyle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SpannableStringBuilder cleanUpText(SpannableStringBuilder builder) {
|
||||||
// Having joined the text elements, we need to do some final cleanup on the result.
|
// Having joined the text elements, we need to do some final cleanup on the result.
|
||||||
// 1. Collapse multiple consecutive spaces into a single space.
|
// 1. Collapse multiple consecutive spaces into a single space.
|
||||||
int builderLength = builder.length();
|
int builderLength = builder.length();
|
||||||
|
|
@ -208,44 +291,7 @@ import java.util.TreeSet;
|
||||||
builder.delete(builderLength - 1, builderLength);
|
builder.delete(builderLength - 1, builderLength);
|
||||||
/*builderLength--;*/
|
/*builderLength--;*/
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SpannableStringBuilder traverseForText(long timeUs, SpannableStringBuilder builder,
|
|
||||||
boolean descendsPNode) {
|
|
||||||
start = builder.length();
|
|
||||||
end = start;
|
|
||||||
if (isTextNode && descendsPNode) {
|
|
||||||
builder.append(text);
|
|
||||||
} else if (TAG_BR.equals(tag) && descendsPNode) {
|
|
||||||
builder.append('\n');
|
|
||||||
} else if (TAG_METADATA.equals(tag)) {
|
|
||||||
// Do nothing.
|
|
||||||
} else if (isActive(timeUs)) {
|
|
||||||
boolean isPNode = TAG_P.equals(tag);
|
|
||||||
for (int i = 0; i < getChildCount(); ++i) {
|
|
||||||
getChild(i).traverseForText(timeUs, builder, descendsPNode || isPNode);
|
|
||||||
}
|
|
||||||
if (isPNode) {
|
|
||||||
TtmlRenderUtil.endParagraph(builder);
|
|
||||||
}
|
|
||||||
end = builder.length();
|
|
||||||
}
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void traverseForStyle(SpannableStringBuilder builder,
|
|
||||||
Map<String, TtmlStyle> globalStyles) {
|
|
||||||
if (start != end) {
|
|
||||||
TtmlStyle resolvedStyle = TtmlRenderUtil.resolveStyle(style, styleIds, globalStyles);
|
|
||||||
if (resolvedStyle != null) {
|
|
||||||
TtmlRenderUtil.applyStylesToSpan(builder, start, end, resolvedStyle);
|
|
||||||
}
|
|
||||||
for (int i = 0; i < getChildCount(); ++i) {
|
|
||||||
getChild(i).traverseForStyle(builder, globalStyles);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import com.google.android.exoplayer.util.Util;
|
||||||
|
|
||||||
import android.text.Layout;
|
import android.text.Layout;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
import org.xmlpull.v1.XmlPullParser;
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
@ -66,6 +67,7 @@ public final class TtmlParser extends SimpleSubtitleParser {
|
||||||
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";
|
||||||
private static final String ATTR_STYLE = "style";
|
private static final String ATTR_STYLE = "style";
|
||||||
|
private static final String ATTR_REGION = "region";
|
||||||
|
|
||||||
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])"
|
||||||
|
|
@ -74,6 +76,8 @@ public final class TtmlParser extends SimpleSubtitleParser {
|
||||||
Pattern.compile("^([0-9]+(?:\\.[0-9]+)?)(h|m|s|ms|f|t)$");
|
Pattern.compile("^([0-9]+(?:\\.[0-9]+)?)(h|m|s|ms|f|t)$");
|
||||||
private static final Pattern FONT_SIZE =
|
private static final Pattern FONT_SIZE =
|
||||||
Pattern.compile("^(([0-9]*.)?[0-9]+)(px|em|%)$");
|
Pattern.compile("^(([0-9]*.)?[0-9]+)(px|em|%)$");
|
||||||
|
private static final Pattern ORIGIN_COORDINATES =
|
||||||
|
Pattern.compile("^(\\d+\\.?\\d*?)% (\\d+\\.?\\d*?)%$");
|
||||||
|
|
||||||
// TODO: read and apply the following attributes if specified.
|
// TODO: read and apply the following attributes if specified.
|
||||||
private static final int DEFAULT_FRAMERATE = 30;
|
private static final int DEFAULT_FRAMERATE = 30;
|
||||||
|
|
@ -85,6 +89,7 @@ public final class TtmlParser extends SimpleSubtitleParser {
|
||||||
public TtmlParser() {
|
public TtmlParser() {
|
||||||
try {
|
try {
|
||||||
xmlParserFactory = XmlPullParserFactory.newInstance();
|
xmlParserFactory = XmlPullParserFactory.newInstance();
|
||||||
|
xmlParserFactory.setNamespaceAware(true);
|
||||||
} catch (XmlPullParserException e) {
|
} catch (XmlPullParserException e) {
|
||||||
throw new RuntimeException("Couldn't create XmlPullParserFactory instance", e);
|
throw new RuntimeException("Couldn't create XmlPullParserFactory instance", e);
|
||||||
}
|
}
|
||||||
|
|
@ -95,6 +100,8 @@ public final class TtmlParser extends SimpleSubtitleParser {
|
||||||
try {
|
try {
|
||||||
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<>();
|
||||||
|
regionMap.put(TtmlNode.ANONYMOUS_REGION_ID, new TtmlRegion());
|
||||||
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;
|
||||||
|
|
@ -110,10 +117,10 @@ public final class TtmlParser extends SimpleSubtitleParser {
|
||||||
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);
|
parseHeader(xmlParser, globalStyles, regionMap);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
TtmlNode node = parseNode(xmlParser, parent);
|
TtmlNode node = parseNode(xmlParser, parent, regionMap);
|
||||||
nodeStack.addLast(node);
|
nodeStack.addLast(node);
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
parent.addChild(node);
|
parent.addChild(node);
|
||||||
|
|
@ -128,7 +135,7 @@ public final class TtmlParser extends SimpleSubtitleParser {
|
||||||
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.getLast(), globalStyles);
|
ttmlSubtitle = new TtmlSubtitle(nodeStack.getLast(), globalStyles, regionMap);
|
||||||
}
|
}
|
||||||
nodeStack.removeLast();
|
nodeStack.removeLast();
|
||||||
}
|
}
|
||||||
|
|
@ -151,28 +158,50 @@ public final class TtmlParser extends SimpleSubtitleParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, TtmlStyle> parseHeader(XmlPullParser xmlParser,
|
private Map<String, TtmlStyle> parseHeader(XmlPullParser xmlParser,
|
||||||
Map<String, TtmlStyle> globalStyles)
|
Map<String, TtmlStyle> globalStyles, Map<String, TtmlRegion> globalRegions)
|
||||||
throws IOException, XmlPullParserException {
|
throws IOException, XmlPullParserException {
|
||||||
|
|
||||||
do {
|
do {
|
||||||
xmlParser.next();
|
xmlParser.next();
|
||||||
if (ParserUtil.isStartTag(xmlParser, TtmlNode.TAG_STYLE)) {
|
if (ParserUtil.isStartTag(xmlParser, TtmlNode.TAG_STYLE)) {
|
||||||
String parentStyleId = xmlParser.getAttributeValue(null, ATTR_STYLE);
|
String parentStyleId = ParserUtil.getAttributeValue(xmlParser, ATTR_STYLE);
|
||||||
TtmlStyle style = parseStyleAttributes(xmlParser, new TtmlStyle());
|
TtmlStyle style = parseStyleAttributes(xmlParser, new TtmlStyle());
|
||||||
if (parentStyleId != null) {
|
if (parentStyleId != null) {
|
||||||
String[] ids = parseStyleIds(parentStyleId);
|
for (String id : parseStyleIds(parentStyleId)) {
|
||||||
for (String id : ids) {
|
|
||||||
style.chain(globalStyles.get(id));
|
style.chain(globalStyles.get(id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (style.getId() != null) {
|
if (style.getId() != null) {
|
||||||
globalStyles.put(style.getId(), style);
|
globalStyles.put(style.getId(), style);
|
||||||
}
|
}
|
||||||
|
} else if (ParserUtil.isStartTag(xmlParser, TtmlNode.TAG_REGION)) {
|
||||||
|
Pair<String, TtmlRegion> ttmlRegionInfo = parseRegionAttributes(xmlParser);
|
||||||
|
if (ttmlRegionInfo != null) {
|
||||||
|
globalRegions.put(ttmlRegionInfo.first, ttmlRegionInfo.second);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} while (!ParserUtil.isEndTag(xmlParser, TtmlNode.TAG_HEAD));
|
} while (!ParserUtil.isEndTag(xmlParser, TtmlNode.TAG_HEAD));
|
||||||
return globalStyles;
|
return globalStyles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Pair<String, TtmlRegion> parseRegionAttributes(XmlPullParser xmlParser) {
|
||||||
|
String regionId = ParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_ID);
|
||||||
|
String regionOrigin = ParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_ORIGIN);
|
||||||
|
if (regionOrigin == null || regionId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Matcher originMatcher = ORIGIN_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));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
Log.w(TAG, "Ignoring malformed region declaration: '" + regionOrigin + "'", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private String[] parseStyleIds(String parentStyleIds) {
|
private String[] parseStyleIds(String parentStyleIds) {
|
||||||
return parentStyleIds.split("\\s+");
|
return parentStyleIds.split("\\s+");
|
||||||
}
|
}
|
||||||
|
|
@ -180,9 +209,8 @@ public final class TtmlParser extends SimpleSubtitleParser {
|
||||||
private TtmlStyle parseStyleAttributes(XmlPullParser parser, TtmlStyle style) {
|
private TtmlStyle parseStyleAttributes(XmlPullParser parser, 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 attributeName = parser.getAttributeName(i);
|
|
||||||
String attributeValue = parser.getAttributeValue(i);
|
String attributeValue = parser.getAttributeValue(i);
|
||||||
switch (ParserUtil.removeNamespacePrefix(attributeName)) {
|
switch (parser.getAttributeName(i)) {
|
||||||
case TtmlNode.ATTR_ID:
|
case TtmlNode.ATTR_ID:
|
||||||
if (TtmlNode.TAG_STYLE.equals(parser.getName())) {
|
if (TtmlNode.TAG_STYLE.equals(parser.getName())) {
|
||||||
style = createIfNull(style).setId(attributeValue);
|
style = createIfNull(style).setId(attributeValue);
|
||||||
|
|
@ -199,7 +227,7 @@ public final class TtmlParser extends SimpleSubtitleParser {
|
||||||
case TtmlNode.ATTR_TTS_COLOR:
|
case TtmlNode.ATTR_TTS_COLOR:
|
||||||
style = createIfNull(style);
|
style = createIfNull(style);
|
||||||
try {
|
try {
|
||||||
style.setColor(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 + "'");
|
||||||
}
|
}
|
||||||
|
|
@ -270,15 +298,17 @@ public final class TtmlParser extends SimpleSubtitleParser {
|
||||||
return style == null ? new TtmlStyle() : style;
|
return style == null ? new TtmlStyle() : style;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TtmlNode parseNode(XmlPullParser parser, TtmlNode parent) throws ParserException {
|
private TtmlNode parseNode(XmlPullParser parser, TtmlNode parent,
|
||||||
|
Map<String, TtmlRegion> regionMap) 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;
|
||||||
|
String regionId = TtmlNode.ANONYMOUS_REGION_ID;
|
||||||
String[] styleIds = null;
|
String[] styleIds = null;
|
||||||
int attributeCount = parser.getAttributeCount();
|
int attributeCount = parser.getAttributeCount();
|
||||||
TtmlStyle style = parseStyleAttributes(parser, null);
|
TtmlStyle style = parseStyleAttributes(parser, null);
|
||||||
for (int i = 0; i < attributeCount; i++) {
|
for (int i = 0; i < attributeCount; i++) {
|
||||||
String attr = ParserUtil.removeNamespacePrefix(parser.getAttributeName(i));
|
String attr = parser.getAttributeName(i);
|
||||||
String value = parser.getAttributeValue(i);
|
String value = parser.getAttributeValue(i);
|
||||||
switch (attr) {
|
switch (attr) {
|
||||||
case ATTR_BEGIN:
|
case ATTR_BEGIN:
|
||||||
|
|
@ -300,6 +330,13 @@ public final class TtmlParser extends SimpleSubtitleParser {
|
||||||
styleIds = ids;
|
styleIds = ids;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case ATTR_REGION:
|
||||||
|
if (regionMap.containsKey(value)) {
|
||||||
|
// If the region has not been correctly declared or does not define a position, we use
|
||||||
|
// the anonymous region.
|
||||||
|
regionId = value;
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
break;
|
break;
|
||||||
|
|
@ -322,7 +359,7 @@ public final class TtmlParser extends SimpleSubtitleParser {
|
||||||
endTime = parent.endTimeUs;
|
endTime = parent.endTimeUs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return TtmlNode.buildNode(parser.getName(), startTime, endTime, style, styleIds);
|
return TtmlNode.buildNode(parser.getName(), startTime, endTime, style, styleIds, regionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isSupportedTag(String tag) {
|
private static boolean isSupportedTag(String tag) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.exoplayer.text.ttml;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.text.Cue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a TTML Region.
|
||||||
|
*/
|
||||||
|
/* package */ final class TtmlRegion {
|
||||||
|
|
||||||
|
public final float position;
|
||||||
|
public final float line;
|
||||||
|
|
||||||
|
public TtmlRegion() {
|
||||||
|
this(Cue.DIMEN_UNSET, Cue.DIMEN_UNSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TtmlRegion(float position, float line) {
|
||||||
|
this.position = position;
|
||||||
|
this.line = line;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -38,29 +38,29 @@ import java.util.Map;
|
||||||
public static TtmlStyle resolveStyle(TtmlStyle style, String[] styleIds,
|
public static TtmlStyle resolveStyle(TtmlStyle style, String[] styleIds,
|
||||||
Map<String, TtmlStyle> globalStyles) {
|
Map<String, TtmlStyle> globalStyles) {
|
||||||
if (style == null && styleIds == null) {
|
if (style == null && styleIds == null) {
|
||||||
// no styles at all
|
// No styles at all.
|
||||||
return null;
|
return null;
|
||||||
} else if (style == null && styleIds.length == 1) {
|
} else if (style == null && styleIds.length == 1) {
|
||||||
// only one single referential style present
|
// Only one single referential style present.
|
||||||
return globalStyles.get(styleIds[0]);
|
return globalStyles.get(styleIds[0]);
|
||||||
} else if (style == null && styleIds.length > 1) {
|
} else if (style == null && styleIds.length > 1) {
|
||||||
// only multiple referential styles present
|
// Only multiple referential styles present.
|
||||||
TtmlStyle chainedStyle = new TtmlStyle();
|
TtmlStyle chainedStyle = new TtmlStyle();
|
||||||
for (String styleId : styleIds) {
|
for (String id : styleIds) {
|
||||||
chainedStyle.chain(globalStyles.get(styleId));
|
chainedStyle.chain(globalStyles.get(id));
|
||||||
}
|
}
|
||||||
return chainedStyle;
|
return chainedStyle;
|
||||||
} else if (style != null && styleIds != null && styleIds.length == 1) {
|
} else if (style != null && 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 (style != null && styleIds != null && styleIds.length > 1) {
|
||||||
// merge multiple referential styles into inline style
|
// Merge multiple referential styles into inline style.
|
||||||
for (String styleId : styleIds) {
|
for (String id : styleIds) {
|
||||||
style.chain(globalStyles.get(styleId));
|
style.chain(globalStyles.get(id));
|
||||||
}
|
}
|
||||||
return style;
|
return style;
|
||||||
}
|
}
|
||||||
// only inline styles available
|
// Only inline styles available.
|
||||||
return style;
|
return style;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,11 +77,11 @@ import java.util.Map;
|
||||||
if (style.isUnderline()) {
|
if (style.isUnderline()) {
|
||||||
builder.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
builder.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
}
|
}
|
||||||
if (style.hasColorSpecified()) {
|
if (style.hasFontColor()) {
|
||||||
builder.setSpan(new ForegroundColorSpan(style.getColor()), start, end,
|
builder.setSpan(new ForegroundColorSpan(style.getFontColor()), start, end,
|
||||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
}
|
}
|
||||||
if (style.hasBackgroundColorSpecified()) {
|
if (style.hasBackgroundColor()) {
|
||||||
builder.setSpan(new BackgroundColorSpan(style.getBackgroundColor()), start, end,
|
builder.setSpan(new BackgroundColorSpan(style.getBackgroundColor()), start, end,
|
||||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,53 +25,55 @@ import android.text.Layout;
|
||||||
*/
|
*/
|
||||||
/* package */ final class TtmlStyle {
|
/* package */ final class TtmlStyle {
|
||||||
|
|
||||||
public static final short UNSPECIFIED = -1;
|
public static final int UNSPECIFIED = -1;
|
||||||
|
|
||||||
public static final short STYLE_NORMAL = Typeface.NORMAL;
|
public static final int STYLE_NORMAL = Typeface.NORMAL;
|
||||||
public static final short STYLE_BOLD = Typeface.BOLD;
|
public static final int STYLE_BOLD = Typeface.BOLD;
|
||||||
public static final short STYLE_ITALIC = Typeface.ITALIC;
|
public static final int STYLE_ITALIC = Typeface.ITALIC;
|
||||||
public static final short STYLE_BOLD_ITALIC = Typeface.BOLD_ITALIC;
|
public static final int STYLE_BOLD_ITALIC = Typeface.BOLD_ITALIC;
|
||||||
|
|
||||||
public static final short FONT_SIZE_UNIT_PIXEL = 1;
|
public static final int FONT_SIZE_UNIT_PIXEL = 1;
|
||||||
public static final short FONT_SIZE_UNIT_EM = 2;
|
public static final int FONT_SIZE_UNIT_EM = 2;
|
||||||
public static final short FONT_SIZE_UNIT_PERCENT = 3;
|
public static final int FONT_SIZE_UNIT_PERCENT = 3;
|
||||||
|
|
||||||
private static final short OFF = 0;
|
private static final int OFF = 0;
|
||||||
private static final short ON = 1;
|
private static final int ON = 1;
|
||||||
|
|
||||||
private String fontFamily;
|
private String fontFamily;
|
||||||
private int color;
|
private int fontColor;
|
||||||
private boolean colorSpecified;
|
private boolean hasFontColor;
|
||||||
private int backgroundColor;
|
private int backgroundColor;
|
||||||
private boolean backgroundColorSpecified;
|
private boolean hasBackgroundColor;
|
||||||
private short linethrough = UNSPECIFIED;
|
private int linethrough;
|
||||||
private short underline = UNSPECIFIED;
|
private int underline;
|
||||||
private short bold = UNSPECIFIED;
|
private int bold;
|
||||||
private short italic = UNSPECIFIED;
|
private int italic;
|
||||||
private short fontSizeUnit = UNSPECIFIED;
|
private int fontSizeUnit;
|
||||||
private float fontSize;
|
private float fontSize;
|
||||||
private String id;
|
private String id;
|
||||||
private TtmlStyle inheritableStyle;
|
private TtmlStyle inheritableStyle;
|
||||||
private Layout.Alignment textAlign;
|
private Layout.Alignment textAlign;
|
||||||
|
|
||||||
|
public TtmlStyle() {
|
||||||
|
linethrough = UNSPECIFIED;
|
||||||
|
underline = UNSPECIFIED;
|
||||||
|
bold = UNSPECIFIED;
|
||||||
|
italic = UNSPECIFIED;
|
||||||
|
fontSizeUnit = UNSPECIFIED;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the style or <code>UNSPECIFIED</code> when no style information is given.
|
* Returns the style or {@link #UNSPECIFIED} when no style information is given.
|
||||||
*
|
*
|
||||||
* @return UNSPECIFIED, STYLE_NORMAL, STYLE_BOLD, STYLE_BOLD or STYLE_BOLD_ITALIC
|
* @return {@link #UNSPECIFIED}, {@link #STYLE_NORMAL}, {@link #STYLE_BOLD}, {@link #STYLE_BOLD}
|
||||||
|
* or {@link #STYLE_BOLD_ITALIC}.
|
||||||
*/
|
*/
|
||||||
public short getStyle() {
|
public int getStyle() {
|
||||||
if (bold == UNSPECIFIED && italic == UNSPECIFIED) {
|
if (bold == UNSPECIFIED && italic == UNSPECIFIED) {
|
||||||
return UNSPECIFIED;
|
return UNSPECIFIED;
|
||||||
}
|
}
|
||||||
|
return (bold != UNSPECIFIED ? bold : STYLE_NORMAL)
|
||||||
short style = STYLE_NORMAL;
|
| (italic != UNSPECIFIED ? italic : STYLE_NORMAL);
|
||||||
if (bold != UNSPECIFIED) {
|
|
||||||
style += bold;
|
|
||||||
}
|
|
||||||
if (italic != UNSPECIFIED){
|
|
||||||
style += italic;
|
|
||||||
}
|
|
||||||
return style;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isLinethrough() {
|
public boolean isLinethrough() {
|
||||||
|
|
@ -104,33 +106,39 @@ import android.text.Layout;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getColor() {
|
public int getFontColor() {
|
||||||
return color;
|
if (!hasFontColor) {
|
||||||
|
throw new IllegalStateException("Font color has not been defined.");
|
||||||
|
}
|
||||||
|
return fontColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TtmlStyle setColor(int color) {
|
public TtmlStyle setFontColor(int fontColor) {
|
||||||
Assertions.checkState(inheritableStyle == null);
|
Assertions.checkState(inheritableStyle == null);
|
||||||
this.color = color;
|
this.fontColor = fontColor;
|
||||||
colorSpecified = true;
|
hasFontColor = true;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasColorSpecified() {
|
public boolean hasFontColor() {
|
||||||
return colorSpecified;
|
return hasFontColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getBackgroundColor() {
|
public int getBackgroundColor() {
|
||||||
|
if (!hasBackgroundColor) {
|
||||||
|
throw new IllegalStateException("Background color has not been defined.");
|
||||||
|
}
|
||||||
return backgroundColor;
|
return backgroundColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TtmlStyle setBackgroundColor(int backgroundColor) {
|
public TtmlStyle setBackgroundColor(int backgroundColor) {
|
||||||
this.backgroundColor = backgroundColor;
|
this.backgroundColor = backgroundColor;
|
||||||
backgroundColorSpecified = true;
|
hasBackgroundColor = true;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasBackgroundColorSpecified() {
|
public boolean hasBackgroundColor() {
|
||||||
return backgroundColorSpecified;
|
return hasBackgroundColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TtmlStyle setBold(boolean isBold) {
|
public TtmlStyle setBold(boolean isBold) {
|
||||||
|
|
@ -168,8 +176,8 @@ import android.text.Layout;
|
||||||
|
|
||||||
private TtmlStyle inherit(TtmlStyle ancestor, boolean chaining) {
|
private TtmlStyle inherit(TtmlStyle ancestor, boolean chaining) {
|
||||||
if (ancestor != null) {
|
if (ancestor != null) {
|
||||||
if (!colorSpecified && ancestor.colorSpecified) {
|
if (!hasFontColor && ancestor.hasFontColor) {
|
||||||
setColor(ancestor.color);
|
setFontColor(ancestor.fontColor);
|
||||||
}
|
}
|
||||||
if (bold == UNSPECIFIED) {
|
if (bold == UNSPECIFIED) {
|
||||||
bold = ancestor.bold;
|
bold = ancestor.bold;
|
||||||
|
|
@ -194,7 +202,7 @@ import android.text.Layout;
|
||||||
fontSize = ancestor.fontSize;
|
fontSize = ancestor.fontSize;
|
||||||
}
|
}
|
||||||
// attributes not inherited as of http://www.w3.org/TR/ttml1/
|
// attributes not inherited as of http://www.w3.org/TR/ttml1/
|
||||||
if (chaining && !backgroundColorSpecified && ancestor.backgroundColorSpecified) {
|
if (chaining && !hasBackgroundColor && ancestor.hasBackgroundColor) {
|
||||||
setBackgroundColor(ancestor.backgroundColor);
|
setBackgroundColor(ancestor.backgroundColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -224,12 +232,12 @@ import android.text.Layout;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TtmlStyle setFontSizeUnit(short unit) {
|
public TtmlStyle setFontSizeUnit(int fontSizeUnit) {
|
||||||
this.fontSizeUnit = unit;
|
this.fontSizeUnit = fontSizeUnit;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public short getFontSizeUnit() {
|
public int getFontSizeUnit() {
|
||||||
return fontSizeUnit;
|
return fontSizeUnit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,14 +26,17 @@ import java.util.Map;
|
||||||
/**
|
/**
|
||||||
* A representation of a TTML subtitle.
|
* A representation of a TTML subtitle.
|
||||||
*/
|
*/
|
||||||
public final class TtmlSubtitle implements Subtitle {
|
/* package */ final class TtmlSubtitle implements Subtitle {
|
||||||
|
|
||||||
private final TtmlNode root;
|
private final TtmlNode root;
|
||||||
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;
|
||||||
|
|
||||||
public TtmlSubtitle(TtmlNode root, Map<String, TtmlStyle> globalStyles) {
|
public TtmlSubtitle(TtmlNode root, Map<String, TtmlStyle> globalStyles,
|
||||||
|
Map<String, TtmlRegion> regionMap) {
|
||||||
this.root = root;
|
this.root = root;
|
||||||
|
this.regionMap = regionMap;
|
||||||
this.globalStyles = globalStyles != null
|
this.globalStyles = globalStyles != null
|
||||||
? Collections.unmodifiableMap(globalStyles) : Collections.<String, TtmlStyle>emptyMap();
|
? Collections.unmodifiableMap(globalStyles) : Collections.<String, TtmlStyle>emptyMap();
|
||||||
this.eventTimesUs = root.getEventTimesUs();
|
this.eventTimesUs = root.getEventTimesUs();
|
||||||
|
|
@ -62,13 +65,7 @@ public final class TtmlSubtitle implements Subtitle {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Cue> getCues(long timeUs) {
|
public List<Cue> getCues(long timeUs) {
|
||||||
CharSequence cueText = root.getText(timeUs, globalStyles);
|
return root.getCues(timeUs, globalStyles, regionMap);
|
||||||
if (cueText == null) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
} else {
|
|
||||||
Cue cue = new Cue(cueText);
|
|
||||||
return Collections.singletonList(cue);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* @VisibleForTesting */
|
/* @VisibleForTesting */
|
||||||
|
|
|
||||||
|
|
@ -26,26 +26,30 @@ public final class ParserUtil {
|
||||||
private ParserUtil() {}
|
private ParserUtil() {}
|
||||||
|
|
||||||
public static boolean isEndTag(XmlPullParser xpp, String name) throws XmlPullParserException {
|
public static boolean isEndTag(XmlPullParser xpp, String name) throws XmlPullParserException {
|
||||||
return xpp.getEventType() == XmlPullParser.END_TAG && name.equals(xpp.getName());
|
return isEndTag(xpp) && xpp.getName().equals(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isEndTag(XmlPullParser xpp) throws XmlPullParserException {
|
||||||
|
return xpp.getEventType() == XmlPullParser.END_TAG;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isStartTag(XmlPullParser xpp, String name)
|
public static boolean isStartTag(XmlPullParser xpp, String name)
|
||||||
throws XmlPullParserException {
|
throws XmlPullParserException {
|
||||||
return xpp.getEventType() == XmlPullParser.START_TAG && name.equals(xpp.getName());
|
return isStartTag(xpp) && xpp.getName().equals(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isStartTag(XmlPullParser xpp) throws XmlPullParserException {
|
public static boolean isStartTag(XmlPullParser xpp) throws XmlPullParserException {
|
||||||
return xpp.getEventType() == XmlPullParser.START_TAG;
|
return xpp.getEventType() == XmlPullParser.START_TAG;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static String getAttributeValue(XmlPullParser xpp, String attributeName) {
|
||||||
* Removes the namespace part ('^.*:') of the attributeName.
|
int attributeCount = xpp.getAttributeCount();
|
||||||
*
|
for (int i = 0; i < attributeCount; i++) {
|
||||||
* @param attributeName the string to remove the namespace prefix from
|
if (attributeName.equals(xpp.getAttributeName(i))) {
|
||||||
* @return the name of the attribute without the prefix
|
return xpp.getAttributeValue(i);
|
||||||
*/
|
}
|
||||||
public static String removeNamespacePrefix(String attributeName) {
|
}
|
||||||
return attributeName.replaceFirst("^.*:", "");
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue