Fix TTML ruby decoding to resolve styles by ID

The current code only works if the tts:ruby attributes are defined
directly on the in-line elements. This changes that so we also consider
tts:ruby attributes on `style` nodes referenced by ID.
PiperOrigin-RevId: 319515177
This commit is contained in:
ibaker 2020-07-03 15:12:42 +01:00 committed by Ian Baker
parent 08478d1163
commit f39b1d0f90
4 changed files with 48 additions and 17 deletions

View File

@ -379,7 +379,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
regionOutput.setText(text); regionOutput.setText(text);
} }
if (resolvedStyle != null) { if (resolvedStyle != null) {
TtmlRenderUtil.applyStylesToSpan(text, start, end, resolvedStyle, parent); TtmlRenderUtil.applyStylesToSpan(text, start, end, resolvedStyle, parent, globalStyles);
regionOutput regionOutput
.setTextAlignment(resolvedStyle.getTextAlign()) .setTextAlignment(resolvedStyle.getTextAlign())
.setVerticalType(resolvedStyle.getVerticalType()); .setVerticalType(resolvedStyle.getVerticalType());

View File

@ -78,7 +78,12 @@ import java.util.Map;
} }
public static void applyStylesToSpan( public static void applyStylesToSpan(
Spannable builder, int start, int end, TtmlStyle style, @Nullable TtmlNode parent) { Spannable builder,
int start,
int end,
TtmlStyle style,
@Nullable TtmlNode parent,
Map<String, TtmlStyle> globalStyles) {
if (style.getStyle() != TtmlStyle.UNSPECIFIED) { if (style.getStyle() != TtmlStyle.UNSPECIFIED) {
builder.setSpan(new StyleSpan(style.getStyle()), start, end, builder.setSpan(new StyleSpan(style.getStyle()), start, end,
@ -117,12 +122,12 @@ import java.util.Map;
switch (style.getRubyType()) { switch (style.getRubyType()) {
case TtmlStyle.RUBY_TYPE_BASE: case TtmlStyle.RUBY_TYPE_BASE:
// look for the sibling RUBY_TEXT and add it as span between start & end. // look for the sibling RUBY_TEXT and add it as span between start & end.
@Nullable TtmlNode containerNode = findRubyContainerNode(parent); @Nullable TtmlNode containerNode = findRubyContainerNode(parent, globalStyles);
if (containerNode == null) { if (containerNode == null) {
// No matching container node // No matching container node
break; break;
} }
@Nullable TtmlNode textNode = findRubyTextNode(containerNode); @Nullable TtmlNode textNode = findRubyTextNode(containerNode, globalStyles);
if (textNode == null) { if (textNode == null) {
// no matching text node // no matching text node
break; break;
@ -200,12 +205,15 @@ import java.util.Map;
} }
@Nullable @Nullable
private static TtmlNode findRubyTextNode(TtmlNode rubyContainerNode) { private static TtmlNode findRubyTextNode(
TtmlNode rubyContainerNode, Map<String, TtmlStyle> globalStyles) {
Deque<TtmlNode> childNodesStack = new ArrayDeque<>(); Deque<TtmlNode> childNodesStack = new ArrayDeque<>();
childNodesStack.push(rubyContainerNode); childNodesStack.push(rubyContainerNode);
while (!childNodesStack.isEmpty()) { while (!childNodesStack.isEmpty()) {
TtmlNode childNode = childNodesStack.pop(); TtmlNode childNode = childNodesStack.pop();
if (childNode.style != null && childNode.style.getRubyType() == TtmlStyle.RUBY_TYPE_TEXT) { @Nullable
TtmlStyle style = resolveStyle(childNode.style, childNode.getStyleIds(), globalStyles);
if (style != null && style.getRubyType() == TtmlStyle.RUBY_TYPE_TEXT) {
return childNode; return childNode;
} }
for (int i = childNode.getChildCount() - 1; i >= 0; i--) { for (int i = childNode.getChildCount() - 1; i >= 0; i--) {
@ -217,9 +225,10 @@ import java.util.Map;
} }
@Nullable @Nullable
private static TtmlNode findRubyContainerNode(@Nullable TtmlNode node) { private static TtmlNode findRubyContainerNode(
@Nullable TtmlNode node, Map<String, TtmlStyle> globalStyles) {
while (node != null) { while (node != null) {
@Nullable TtmlStyle style = node.style; @Nullable TtmlStyle style = resolveStyle(node.style, node.getStyleIds(), globalStyles);
if (style != null && style.getRubyType() == TtmlStyle.RUBY_TYPE_CONTAINER) { if (style != null && style.getRubyType() == TtmlStyle.RUBY_TYPE_CONTAINER) {
return node; return node;
} }

View File

@ -656,15 +656,19 @@ public final class TtmlDecoderTest {
Spanned thirdCue = getOnlyCueTextAtTimeUs(subtitle, 30_000_000); Spanned thirdCue = getOnlyCueTextAtTimeUs(subtitle, 30_000_000);
assertThat(thirdCue.toString()).isEqualTo("Cue with annotated text."); assertThat(thirdCue.toString()).isEqualTo("Cue with annotated text.");
assertThat(thirdCue).hasNoRubySpanBetween(0, thirdCue.length()); assertThat(thirdCue).hasRubySpanBetween("Cue with ".length(), "Cue with annotated".length());
Spanned fourthCue = getOnlyCueTextAtTimeUs(subtitle, 40_000_000); Spanned fourthCue = getOnlyCueTextAtTimeUs(subtitle, 40_000_000);
assertThat(fourthCue.toString()).isEqualTo("Cue with text."); assertThat(fourthCue.toString()).isEqualTo("Cue with annotated text.");
assertThat(fourthCue).hasNoRubySpanBetween(0, fourthCue.length()); assertThat(fourthCue).hasNoRubySpanBetween(0, fourthCue.length());
Spanned fifthCue = getOnlyCueTextAtTimeUs(subtitle, 50_000_000); Spanned fifthCue = getOnlyCueTextAtTimeUs(subtitle, 50_000_000);
assertThat(fifthCue.toString()).isEqualTo("Cue with annotated text."); assertThat(fifthCue.toString()).isEqualTo("Cue with text.");
assertThat(fifthCue).hasNoRubySpanBetween(0, fifthCue.length()); assertThat(fifthCue).hasNoRubySpanBetween(0, fifthCue.length());
Spanned sixthCue = getOnlyCueTextAtTimeUs(subtitle, 60_000_000);
assertThat(sixthCue.toString()).isEqualTo("Cue with annotated text.");
assertThat(sixthCue).hasNoRubySpanBetween(0, sixthCue.length());
} }
private static Spanned getOnlyCueTextAtTimeUs(Subtitle subtitle, long timeUs) { private static Spanned getOnlyCueTextAtTimeUs(Subtitle subtitle, long timeUs) {

View File

@ -16,9 +16,15 @@
--> -->
<tt xmlns:ttm="http://www.w3.org/2006/10/ttaf1#metadata" <tt xmlns:ttm="http://www.w3.org/2006/10/ttaf1#metadata"
xmlns:ttp="http://www.w3.org/2006/10/ttaf1#parameter" xmlns:ttp="http://www.w3.org/2006/10/ttaf1#parameter"
xmlns:tts="http://www.w3.org/2006/10/ttaf1#style" xmlns:tts="http://www.w3.org/2006/10/ttaf1#style" xmlns="http://www.w3.org/ns/ttml"
xmlns="http://www.w3.org/ns/ttml"
xmlns="http://www.w3.org/2006/10/ttaf1"> xmlns="http://www.w3.org/2006/10/ttaf1">
<head>
<styling>
<style id="cont" tts:ruby="container" />
<style id="base" tts:ruby="base" />
<style id="text" tts:ruby="text" />
</styling>
</head>
<body> <body>
<div> <div>
<!-- Base before and after text, one with explicit position --> <!-- Base before and after text, one with explicit position -->
@ -47,17 +53,29 @@
</p> </p>
</div> </div>
<div> <div>
<!-- No text section -> no span --> <!-- ruby info in style block -->
<p begin="30s" end="38s"> <p begin="30s" end="38s">
Cue with
<span style="cont">
<span style="base">annotated</span>
<span style="text">rubies</span>
text.
</span>
</p>
</div>
<div>
<!-- No text section -> no span -->
<p begin="40s" end="48s">
Cue with Cue with
<span tts:ruby="container" tts:rubyPosition="before"> <span tts:ruby="container" tts:rubyPosition="before">
<span tts:ruby="base">annotated</span> <span tts:ruby="base">annotated</span>
</span> </span>
text.</p> text.
</p>
</div> </div>
<div> <div>
<!-- No base section -> text still stripped--> <!-- No base section -> text still stripped-->
<p begin="40s" end="48s"> <p begin="50s" end="58s">
Cue with Cue with
<span tts:ruby="container" tts:rubyPosition="before"> <span tts:ruby="container" tts:rubyPosition="before">
<span tts:ruby="text">rubies</span> <span tts:ruby="text">rubies</span>
@ -67,7 +85,7 @@
</div> </div>
<div> <div>
<!-- No container section -> text still stripped--> <!-- No container section -> text still stripped-->
<p begin="50s" end="58s"> <p begin="60s" end="68s">
Cue with Cue with
<span tts:ruby="text">rubies</span> <span tts:ruby="text">rubies</span>
<span tts:ruby="base">annotated</span> <span tts:ruby="base">annotated</span>