mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
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:
parent
08478d1163
commit
f39b1d0f90
@ -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());
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
30
testdata/src/test/assets/ttml/rubies.xml
vendored
30
testdata/src/test/assets/ttml/rubies.xml
vendored
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user