Extend WebVTT ruby-position support to include <rt> tags
PiperOrigin-RevId: 312096467
This commit is contained in:
parent
38fc7d8c0d
commit
377bf27f47
@ -128,6 +128,7 @@
|
||||
of which are supported.
|
||||
* Ignore excess characters in CEA-608 lines (max length is 32)
|
||||
([#7341](https://github.com/google/ExoPlayer/issues/7341)).
|
||||
* Add support for WebVTT's `ruby-position` CSS property.
|
||||
* DRM:
|
||||
* Add support for attaching DRM sessions to clear content in the demo app.
|
||||
* Remove `DrmSessionManager` references from all renderers.
|
||||
|
@ -538,8 +538,6 @@ public final class WebvttCueParser {
|
||||
List<StyleMatch> scratchStyleMatches) {
|
||||
int start = startTag.position;
|
||||
int end = text.length();
|
||||
scratchStyleMatches.clear();
|
||||
getApplicableStyles(styles, cueId, startTag, scratchStyleMatches);
|
||||
|
||||
switch(startTag.name) {
|
||||
case TAG_BOLD:
|
||||
@ -551,7 +549,7 @@ public final class WebvttCueParser {
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
break;
|
||||
case TAG_RUBY:
|
||||
applyRubySpans(text, start, nestedElements, scratchStyleMatches);
|
||||
applyRubySpans(text, cueId, startTag, nestedElements, styles);
|
||||
break;
|
||||
case TAG_UNDERLINE:
|
||||
text.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
@ -567,6 +565,8 @@ public final class WebvttCueParser {
|
||||
return;
|
||||
}
|
||||
|
||||
scratchStyleMatches.clear();
|
||||
getApplicableStyles(styles, cueId, startTag, scratchStyleMatches);
|
||||
for (int i = 0; i < scratchStyleMatches.size(); i++) {
|
||||
applyStyleToText(text, scratchStyleMatches.get(i).style, start, end);
|
||||
}
|
||||
@ -574,27 +574,29 @@ public final class WebvttCueParser {
|
||||
|
||||
private static void applyRubySpans(
|
||||
SpannableStringBuilder text,
|
||||
int startTagPosition,
|
||||
@Nullable String cueId,
|
||||
StartTag startTag,
|
||||
List<Element> nestedElements,
|
||||
List<StyleMatch> styleMatches) {
|
||||
@RubySpan.Position int rubyPosition = RubySpan.POSITION_OVER;
|
||||
for (int i = 0; i < styleMatches.size(); i++) {
|
||||
WebvttCssStyle style = styleMatches.get(i).style;
|
||||
if (style.getRubyPosition() != RubySpan.POSITION_UNKNOWN) {
|
||||
rubyPosition = style.getRubyPosition();
|
||||
break;
|
||||
}
|
||||
}
|
||||
List<WebvttCssStyle> styles) {
|
||||
@RubySpan.Position int rubyTagPosition = getRubyPosition(styles, cueId, startTag);
|
||||
List<Element> sortedNestedElements = new ArrayList<>(nestedElements.size());
|
||||
sortedNestedElements.addAll(nestedElements);
|
||||
Collections.sort(sortedNestedElements, Element.BY_START_POSITION_ASC);
|
||||
int deletedCharCount = 0;
|
||||
int lastRubyTextEnd = startTagPosition;
|
||||
int lastRubyTextEnd = startTag.position;
|
||||
for (int i = 0; i < sortedNestedElements.size(); i++) {
|
||||
if (!TAG_RUBY_TEXT.equals(sortedNestedElements.get(i).startTag.name)) {
|
||||
continue;
|
||||
}
|
||||
Element rubyTextElement = sortedNestedElements.get(i);
|
||||
// Use the <rt> element's ruby-position if set, otherwise the <ruby> element's and otherwise
|
||||
// default to OVER.
|
||||
@RubySpan.Position
|
||||
int rubyPosition =
|
||||
firstKnownRubyPosition(
|
||||
getRubyPosition(styles, cueId, rubyTextElement.startTag),
|
||||
rubyTagPosition,
|
||||
RubySpan.POSITION_OVER);
|
||||
// Move the rubyText from spannedText into the RubySpan.
|
||||
int adjustedRubyTextStart = rubyTextElement.startTag.position - deletedCharCount;
|
||||
int adjustedRubyTextEnd = rubyTextElement.endPosition - deletedCharCount;
|
||||
@ -611,6 +613,37 @@ public final class WebvttCueParser {
|
||||
}
|
||||
}
|
||||
|
||||
@RubySpan.Position
|
||||
private static int getRubyPosition(
|
||||
List<WebvttCssStyle> styles, @Nullable String cueId, StartTag startTag) {
|
||||
List<StyleMatch> styleMatches = new ArrayList<>();
|
||||
getApplicableStyles(styles, cueId, startTag, styleMatches);
|
||||
for (int i = 0; i < styleMatches.size(); i++) {
|
||||
WebvttCssStyle style = styleMatches.get(i).style;
|
||||
if (style.getRubyPosition() != RubySpan.POSITION_UNKNOWN) {
|
||||
return style.getRubyPosition();
|
||||
}
|
||||
}
|
||||
return RubySpan.POSITION_UNKNOWN;
|
||||
}
|
||||
|
||||
@RubySpan.Position
|
||||
private static int firstKnownRubyPosition(
|
||||
@RubySpan.Position int position1,
|
||||
@RubySpan.Position int position2,
|
||||
@RubySpan.Position int position3) {
|
||||
if (position1 != RubySpan.POSITION_UNKNOWN) {
|
||||
return position1;
|
||||
}
|
||||
if (position2 != RubySpan.POSITION_UNKNOWN) {
|
||||
return position2;
|
||||
}
|
||||
if (position3 != RubySpan.POSITION_UNKNOWN) {
|
||||
return position3;
|
||||
}
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds {@link ForegroundColorSpan}s and {@link BackgroundColorSpan}s to {@code text} for entries
|
||||
* in {@code classes} that match WebVTT's <a
|
||||
|
@ -373,18 +373,18 @@ public class WebvttDecoderTest {
|
||||
"Some text with under-ruby and over-ruby (default)".length())
|
||||
.withTextAndPosition("over", RubySpan.POSITION_OVER);
|
||||
|
||||
// Check many <rt> tags nested in a single <ruby> span.
|
||||
// Check many <rt> tags with different positions nested in a single <ruby> span.
|
||||
Cue thirdCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(4)));
|
||||
assertThat(thirdCue.text.toString()).isEqualTo("base1base2base3.");
|
||||
assertThat((Spanned) thirdCue.text)
|
||||
.hasRubySpanBetween(/* start= */ 0, "base1".length())
|
||||
.withTextAndPosition("text1", RubySpan.POSITION_OVER);
|
||||
.withTextAndPosition("over1", RubySpan.POSITION_OVER);
|
||||
assertThat((Spanned) thirdCue.text)
|
||||
.hasRubySpanBetween("base1".length(), "base1base2".length())
|
||||
.withTextAndPosition("text2", RubySpan.POSITION_OVER);
|
||||
.withTextAndPosition("under2", RubySpan.POSITION_UNDER);
|
||||
assertThat((Spanned) thirdCue.text)
|
||||
.hasRubySpanBetween("base1base2".length(), "base1base2base3".length())
|
||||
.withTextAndPosition("text3", RubySpan.POSITION_OVER);
|
||||
.withTextAndPosition("under3", RubySpan.POSITION_UNDER);
|
||||
|
||||
// Check a <ruby> span with no <rt> tags.
|
||||
Cue fourthCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(6)));
|
||||
|
2
testdata/src/test/assets/webvtt/with_rubies
vendored
2
testdata/src/test/assets/webvtt/with_rubies
vendored
@ -19,7 +19,7 @@ Some <ruby.under>text with under-ruby<rt>under</rt></ruby> and <ruby>over-ruby (
|
||||
NOTE Many individual rubies in a single <ruby> tag
|
||||
|
||||
00:00:05.000 --> 00:00:06.000
|
||||
<ruby>base1<rt>text1</rt>base2<rt>text2</rt>base3<rt>text3</rt></ruby>.
|
||||
<ruby.under>base1<rt.over>over1</rt>base2<rt>under2</rt>base3<rt.under>under3</rt></ruby>.
|
||||
|
||||
00:00:07.000 --> 00:00:08.000
|
||||
Some <ruby>text with no ruby text</ruby>.
|
||||
|
Loading…
x
Reference in New Issue
Block a user