mirror of
https://github.com/samsonjs/media.git
synced 2026-03-25 09:25:53 +00:00
Merge #8858: Support ebutts:multiRowAlign in TTML text renderer
Imported from GitHub PR https://github.com/google/ExoPlayer/pull/8858
Fix bug in text alignment inheritance where child does not correctly inherit ancestor's setting
@icbaker
Merge 70eb4bceb73b3f07e2f8d545b4fa7961189ac52a into 45616f916b
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/ExoPlayer/pull/8877 from dlafayet:multirowalign-cue d942b50a40525fea5d11b35a33d3bbc512550960
PiperOrigin-RevId: 371306966
This commit is contained in:
parent
99492902c0
commit
8c7d6447c0
10 changed files with 159 additions and 21 deletions
|
|
@ -131,6 +131,7 @@
|
|||
([#8435](https://github.com/google/ExoPlayer/issues/8435)).
|
||||
* Ensure TTML `tts:textAlign` is correctly propagated from `<p>` nodes to
|
||||
child nodes.
|
||||
* Support TTML `ebutts:multiRowAlign` attributes.
|
||||
* MediaSession extension: Remove dependency to core module and rely on common
|
||||
only. The `TimelineQueueEditor` uses a new `MediaDescriptionConverter` for
|
||||
this purpose and does not rely on the `ConcatenatingMediaSource` anymore.
|
||||
|
|
|
|||
|
|
@ -140,6 +140,12 @@ public final class Cue {
|
|||
/** The alignment of the cue text within the cue box, or null if the alignment is undefined. */
|
||||
@Nullable public final Alignment textAlignment;
|
||||
|
||||
/**
|
||||
* The alignment of multiple lines of text relative to the longest line, or null if the alignment
|
||||
* is undefined.
|
||||
*/
|
||||
@Nullable public final Alignment multiRowAlignment;
|
||||
|
||||
/** The cue image, or null if this is a text cue. */
|
||||
@Nullable public final Bitmap bitmap;
|
||||
|
||||
|
|
@ -364,6 +370,7 @@ public final class Cue {
|
|||
this(
|
||||
text,
|
||||
textAlignment,
|
||||
/* multiRowAlignment= */ null,
|
||||
/* bitmap= */ null,
|
||||
line,
|
||||
lineType,
|
||||
|
|
@ -410,6 +417,7 @@ public final class Cue {
|
|||
this(
|
||||
text,
|
||||
textAlignment,
|
||||
/* multiRowAlignment= */ null,
|
||||
/* bitmap= */ null,
|
||||
line,
|
||||
lineType,
|
||||
|
|
@ -429,6 +437,7 @@ public final class Cue {
|
|||
private Cue(
|
||||
@Nullable CharSequence text,
|
||||
@Nullable Alignment textAlignment,
|
||||
@Nullable Alignment multiRowAlignment,
|
||||
@Nullable Bitmap bitmap,
|
||||
float line,
|
||||
@LineType int lineType,
|
||||
|
|
@ -451,6 +460,7 @@ public final class Cue {
|
|||
}
|
||||
this.text = text;
|
||||
this.textAlignment = textAlignment;
|
||||
this.multiRowAlignment = multiRowAlignment;
|
||||
this.bitmap = bitmap;
|
||||
this.line = line;
|
||||
this.lineType = lineType;
|
||||
|
|
@ -477,6 +487,7 @@ public final class Cue {
|
|||
@Nullable private CharSequence text;
|
||||
@Nullable private Bitmap bitmap;
|
||||
@Nullable private Alignment textAlignment;
|
||||
@Nullable private Alignment multiRowAlignment;
|
||||
private float line;
|
||||
@LineType private int lineType;
|
||||
@AnchorType private int lineAnchor;
|
||||
|
|
@ -495,6 +506,7 @@ public final class Cue {
|
|||
text = null;
|
||||
bitmap = null;
|
||||
textAlignment = null;
|
||||
multiRowAlignment = null;
|
||||
line = DIMEN_UNSET;
|
||||
lineType = TYPE_UNSET;
|
||||
lineAnchor = TYPE_UNSET;
|
||||
|
|
@ -513,6 +525,7 @@ public final class Cue {
|
|||
text = cue.text;
|
||||
bitmap = cue.bitmap;
|
||||
textAlignment = cue.textAlignment;
|
||||
multiRowAlignment = cue.multiRowAlignment;
|
||||
line = cue.line;
|
||||
lineType = cue.lineType;
|
||||
lineAnchor = cue.lineAnchor;
|
||||
|
|
@ -592,6 +605,18 @@ public final class Cue {
|
|||
return textAlignment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the multi-row alignment of the cue.
|
||||
*
|
||||
* <p>Passing null means the alignment is undefined.
|
||||
*
|
||||
* @see Cue#multiRowAlignment
|
||||
*/
|
||||
public Builder setMultiRowAlignment(@Nullable Layout.Alignment multiRowAlignment) {
|
||||
this.multiRowAlignment = multiRowAlignment;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the position of the cue box within the viewport in the direction orthogonal to the
|
||||
* writing direction.
|
||||
|
|
@ -827,6 +852,7 @@ public final class Cue {
|
|||
return new Cue(
|
||||
text,
|
||||
textAlignment,
|
||||
multiRowAlignment,
|
||||
bitmap,
|
||||
line,
|
||||
lineType,
|
||||
|
|
|
|||
|
|
@ -534,22 +534,10 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
|||
TtmlNode.ITALIC.equalsIgnoreCase(attributeValue));
|
||||
break;
|
||||
case TtmlNode.ATTR_TTS_TEXT_ALIGN:
|
||||
switch (Ascii.toLowerCase(attributeValue)) {
|
||||
case TtmlNode.LEFT:
|
||||
case TtmlNode.START:
|
||||
style = createIfNull(style).setTextAlign(Layout.Alignment.ALIGN_NORMAL);
|
||||
break;
|
||||
case TtmlNode.RIGHT:
|
||||
case TtmlNode.END:
|
||||
style = createIfNull(style).setTextAlign(Layout.Alignment.ALIGN_OPPOSITE);
|
||||
break;
|
||||
case TtmlNode.CENTER:
|
||||
style = createIfNull(style).setTextAlign(Layout.Alignment.ALIGN_CENTER);
|
||||
break;
|
||||
default:
|
||||
// ignore
|
||||
break;
|
||||
}
|
||||
style = createIfNull(style).setTextAlign(parseAlignment(attributeValue));
|
||||
break;
|
||||
case TtmlNode.ATTR_EBUTTS_MULTI_ROW_ALIGN:
|
||||
style = createIfNull(style).setMultiRowAlign(parseAlignment(attributeValue));
|
||||
break;
|
||||
case TtmlNode.ATTR_TTS_TEXT_COMBINE:
|
||||
switch (Ascii.toLowerCase(attributeValue)) {
|
||||
|
|
@ -632,6 +620,22 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
|||
return style == null ? new TtmlStyle() : style;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Layout.Alignment parseAlignment(String alignment) {
|
||||
switch (Ascii.toLowerCase(alignment)) {
|
||||
case TtmlNode.LEFT:
|
||||
case TtmlNode.START:
|
||||
return Layout.Alignment.ALIGN_NORMAL;
|
||||
case TtmlNode.RIGHT:
|
||||
case TtmlNode.END:
|
||||
return Layout.Alignment.ALIGN_OPPOSITE;
|
||||
case TtmlNode.CENTER:
|
||||
return Layout.Alignment.ALIGN_CENTER;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static TtmlNode parseNode(
|
||||
XmlPullParser parser,
|
||||
@Nullable TtmlNode parent,
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||
public static final String ATTR_TTS_TEXT_EMPHASIS = "textEmphasis";
|
||||
public static final String ATTR_TTS_WRITING_MODE = "writingMode";
|
||||
public static final String ATTR_TTS_SHEAR = "shear";
|
||||
public static final String ATTR_EBUTTS_MULTI_ROW_ALIGN = "multiRowAlign";
|
||||
|
||||
// Values for ruby
|
||||
public static final String RUBY_CONTAINER = "container";
|
||||
|
|
@ -376,7 +377,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||
return;
|
||||
}
|
||||
String resolvedRegionId = ANONYMOUS_REGION_ID.equals(regionId) ? inheritedRegion : regionId;
|
||||
|
||||
for (Map.Entry<String, Integer> entry : nodeEndsByRegion.entrySet()) {
|
||||
String regionId = entry.getKey();
|
||||
int start = nodeStartsByRegion.containsKey(regionId) ? nodeStartsByRegion.get(regionId) : 0;
|
||||
|
|
@ -423,6 +423,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||
if (resolvedStyle.getTextAlign() != null) {
|
||||
regionOutput.setTextAlignment(resolvedStyle.getTextAlign());
|
||||
}
|
||||
if (resolvedStyle.getMultiRowAlign() != null) {
|
||||
regionOutput.setMultiRowAlignment(resolvedStyle.getMultiRowAlign());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ import java.lang.annotation.RetentionPolicy;
|
|||
@RubyType private int rubyType;
|
||||
@TextAnnotation.Position private int rubyPosition;
|
||||
@Nullable private Layout.Alignment textAlign;
|
||||
@Nullable private Layout.Alignment multiRowAlign;
|
||||
@OptionalBoolean private int textCombine;
|
||||
@Nullable private TextEmphasis textEmphasis;
|
||||
private float shearPercentage;
|
||||
|
|
@ -244,6 +245,9 @@ import java.lang.annotation.RetentionPolicy;
|
|||
if (textAlign == null && ancestor.textAlign != null) {
|
||||
textAlign = ancestor.textAlign;
|
||||
}
|
||||
if (multiRowAlign == null && ancestor.multiRowAlign != null) {
|
||||
multiRowAlign = ancestor.multiRowAlign;
|
||||
}
|
||||
if (textCombine == UNSPECIFIED) {
|
||||
textCombine = ancestor.textCombine;
|
||||
}
|
||||
|
|
@ -308,6 +312,16 @@ import java.lang.annotation.RetentionPolicy;
|
|||
return this;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Layout.Alignment getMultiRowAlign() {
|
||||
return multiRowAlign;
|
||||
}
|
||||
|
||||
public TtmlStyle setMultiRowAlign(@Nullable Layout.Alignment multiRowAlign) {
|
||||
this.multiRowAlign = multiRowAlign;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns true if the source entity has {@code tts:textCombine=all}. */
|
||||
public boolean getTextCombine() {
|
||||
return textCombine == ON;
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ public class CueTest {
|
|||
new Cue.Builder()
|
||||
.setText(SpannedString.valueOf("text"))
|
||||
.setTextAlignment(Layout.Alignment.ALIGN_CENTER)
|
||||
.setMultiRowAlignment(Layout.Alignment.ALIGN_NORMAL)
|
||||
.setLine(5, Cue.LINE_TYPE_NUMBER)
|
||||
.setLineAnchor(Cue.ANCHOR_TYPE_END)
|
||||
.setPosition(0.4f)
|
||||
|
|
@ -52,6 +53,7 @@ public class CueTest {
|
|||
|
||||
assertThat(cue.text.toString()).isEqualTo("text");
|
||||
assertThat(cue.textAlignment).isEqualTo(Layout.Alignment.ALIGN_CENTER);
|
||||
assertThat(cue.multiRowAlignment).isEqualTo(Layout.Alignment.ALIGN_NORMAL);
|
||||
assertThat(cue.line).isEqualTo(5);
|
||||
assertThat(cue.lineType).isEqualTo(Cue.LINE_TYPE_NUMBER);
|
||||
assertThat(cue.position).isEqualTo(0.4f);
|
||||
|
|
@ -66,6 +68,7 @@ public class CueTest {
|
|||
|
||||
assertThat(modifiedCue.text).isSameInstanceAs(cue.text);
|
||||
assertThat(modifiedCue.textAlignment).isEqualTo(cue.textAlignment);
|
||||
assertThat(modifiedCue.multiRowAlignment).isEqualTo(cue.multiRowAlignment);
|
||||
assertThat(modifiedCue.line).isEqualTo(cue.line);
|
||||
assertThat(modifiedCue.lineType).isEqualTo(cue.lineType);
|
||||
assertThat(modifiedCue.position).isEqualTo(cue.position);
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ public final class TtmlDecoderTest {
|
|||
private static final String BITMAP_UNSUPPORTED_REGION_FILE =
|
||||
"media/ttml/bitmap_unsupported_region.xml";
|
||||
private static final String TEXT_ALIGN_FILE = "media/ttml/text_align.xml";
|
||||
private static final String MULTI_ROW_ALIGN_FILE = "media/ttml/multi_row_align.xml";
|
||||
private static final String VERTICAL_TEXT_FILE = "media/ttml/vertical_text.xml";
|
||||
private static final String TEXT_COMBINE_FILE = "media/ttml/text_combine.xml";
|
||||
private static final String RUBIES_FILE = "media/ttml/rubies.xml";
|
||||
|
|
@ -617,6 +618,32 @@ public final class TtmlDecoderTest {
|
|||
assertThat(ninthCue.textAlignment).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multiRowAlign() throws IOException, SubtitleDecoderException {
|
||||
TtmlSubtitle subtitle = getSubtitle(MULTI_ROW_ALIGN_FILE);
|
||||
|
||||
Cue firstCue = getOnlyCueAtTimeUs(subtitle, 10_000_000);
|
||||
assertThat(firstCue.multiRowAlignment).isEqualTo(Layout.Alignment.ALIGN_NORMAL);
|
||||
|
||||
Cue secondCue = getOnlyCueAtTimeUs(subtitle, 20_000_000);
|
||||
assertThat(secondCue.multiRowAlignment).isEqualTo(Layout.Alignment.ALIGN_CENTER);
|
||||
|
||||
Cue thirdCue = getOnlyCueAtTimeUs(subtitle, 30_000_000);
|
||||
assertThat(thirdCue.multiRowAlignment).isEqualTo(Layout.Alignment.ALIGN_OPPOSITE);
|
||||
|
||||
Cue fourthCue = getOnlyCueAtTimeUs(subtitle, 40_000_000);
|
||||
assertThat(fourthCue.multiRowAlignment).isEqualTo(Layout.Alignment.ALIGN_NORMAL);
|
||||
|
||||
Cue fifthCue = getOnlyCueAtTimeUs(subtitle, 50_000_000);
|
||||
assertThat(fifthCue.multiRowAlignment).isEqualTo(Layout.Alignment.ALIGN_OPPOSITE);
|
||||
|
||||
Cue sixthCue = getOnlyCueAtTimeUs(subtitle, 60_000_000);
|
||||
assertThat(sixthCue.multiRowAlignment).isNull();
|
||||
|
||||
Cue seventhCue = getOnlyCueAtTimeUs(subtitle, 70_000_000);
|
||||
assertThat(seventhCue.multiRowAlignment).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verticalText() throws IOException, SubtitleDecoderException {
|
||||
TtmlSubtitle subtitle = getSubtitle(VERTICAL_TEXT_FILE);
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ public final class TtmlStyleTest {
|
|||
.setRubyType(RUBY_TYPE)
|
||||
.setRubyPosition(RUBY_POSITION)
|
||||
.setTextAlign(TEXT_ALIGN)
|
||||
.setMultiRowAlign(Layout.Alignment.ALIGN_NORMAL)
|
||||
.setTextCombine(TEXT_COMBINE)
|
||||
.setTextEmphasis(TextEmphasis.parse(TEXT_EMPHASIS_STYLE))
|
||||
.setShearPercentage(SHEAR_PERCENTAGE);
|
||||
|
|
@ -85,6 +86,7 @@ public final class TtmlStyleTest {
|
|||
assertThat(style.getFontSizeUnit()).isEqualTo(FONT_SIZE_UNIT);
|
||||
assertThat(style.getRubyPosition()).isEqualTo(RUBY_POSITION);
|
||||
assertThat(style.getTextAlign()).isEqualTo(TEXT_ALIGN);
|
||||
assertThat(style.getMultiRowAlign()).isEqualTo(Layout.Alignment.ALIGN_NORMAL);
|
||||
assertThat(style.getTextCombine()).isEqualTo(TEXT_COMBINE);
|
||||
assertWithMessage("rubyType should not be inherited")
|
||||
.that(style.getRubyType())
|
||||
|
|
@ -115,6 +117,7 @@ public final class TtmlStyleTest {
|
|||
assertThat(style.getFontSizeUnit()).isEqualTo(FONT_SIZE_UNIT);
|
||||
assertThat(style.getRubyPosition()).isEqualTo(RUBY_POSITION);
|
||||
assertThat(style.getTextAlign()).isEqualTo(TEXT_ALIGN);
|
||||
assertThat(style.getMultiRowAlign()).isEqualTo(Layout.Alignment.ALIGN_NORMAL);
|
||||
assertThat(style.getTextCombine()).isEqualTo(TEXT_COMBINE);
|
||||
assertWithMessage("backgroundColor should be chained")
|
||||
.that(style.getBackgroundColor())
|
||||
|
|
@ -253,6 +256,18 @@ public final class TtmlStyleTest {
|
|||
assertThat(style.getTextAlign()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multiRowAlign() {
|
||||
TtmlStyle style = new TtmlStyle();
|
||||
assertThat(style.getMultiRowAlign()).isEqualTo(null);
|
||||
style.setMultiRowAlign(Layout.Alignment.ALIGN_CENTER);
|
||||
assertThat(style.getMultiRowAlign()).isEqualTo(Layout.Alignment.ALIGN_CENTER);
|
||||
style.setMultiRowAlign(Layout.Alignment.ALIGN_NORMAL);
|
||||
assertThat(style.getMultiRowAlign()).isEqualTo(Layout.Alignment.ALIGN_NORMAL);
|
||||
style.setMultiRowAlign(Layout.Alignment.ALIGN_OPPOSITE);
|
||||
assertThat(style.getMultiRowAlign()).isEqualTo(Layout.Alignment.ALIGN_OPPOSITE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void textCombine() {
|
||||
TtmlStyle style = new TtmlStyle();
|
||||
|
|
|
|||
|
|
@ -302,11 +302,22 @@ import java.util.Map;
|
|||
horizontalTranslatePercent,
|
||||
verticalTranslatePercent,
|
||||
getBlockShearTransformFunction(cue)))
|
||||
.append(Util.formatInvariant("<span class='%s'>", DEFAULT_BACKGROUND_CSS_CLASS))
|
||||
.append(htmlAndCss.html)
|
||||
.append("</span>")
|
||||
.append("</div>");
|
||||
.append(Util.formatInvariant("<span class='%s'>", DEFAULT_BACKGROUND_CSS_CLASS));
|
||||
|
||||
if (cue.multiRowAlignment != null) {
|
||||
html.append(
|
||||
Util.formatInvariant(
|
||||
"<span style='display:inline-block; text-align:%s;'>",
|
||||
convertAlignmentToCss(cue.multiRowAlignment)))
|
||||
.append(htmlAndCss.html)
|
||||
.append("</span>");
|
||||
} else {
|
||||
html.append(htmlAndCss.html);
|
||||
}
|
||||
|
||||
html.append("</span>").append("</div>");
|
||||
}
|
||||
|
||||
html.append("</div></body></html>");
|
||||
|
||||
StringBuilder htmlHead = new StringBuilder();
|
||||
|
|
|
|||
34
testdata/src/test/assets/media/ttml/multi_row_align.xml
vendored
Normal file
34
testdata/src/test/assets/media/ttml/multi_row_align.xml
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<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:ebutts="urn:ebu:tt:style" >
|
||||
<head>
|
||||
<region xml:id="region_tbrl" tts:extent="80.000% 80.000%" tts:origin="10.000% 10.000%" tts:writingMode="tbrl"/>
|
||||
<region xml:id="region_tblr" tts:extent="80.000% 80.000%" tts:origin="10.000% 10.000%" tts:writingMode="tblr"/>
|
||||
<region xml:id="region_lr" tts:extent="80.000% 80.000%" tts:origin="10.000% 10.000%" tts:writingMode="lr"/>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<p begin="10s" end="18s" region="region_lr" ebutts:multiRowAlign="start">multi row<br/>align start</p>
|
||||
</div>
|
||||
<div>
|
||||
<p begin="20s" end="28s" region="region_tblr" ebutts:multiRowAlign="center">multi row<br/>align center</p>
|
||||
</div>
|
||||
<div>
|
||||
<p begin="30s" end="38s" region="region_tbrl" ebutts:multiRowAlign="end">multi row<br/>align end</p>
|
||||
</div>
|
||||
<div>
|
||||
<p begin="40s" end="48s" region="region_lr" ebutts:multiRowAlign="left">multi row<br/>align left</p>
|
||||
</div>
|
||||
<div>
|
||||
<p begin="50s" end="58s" region="region_lr" ebutts:multiRowAlign="right">multi row<br/>align right</p>
|
||||
</div>
|
||||
<div>
|
||||
<p begin="60s" end="68s" region="region_lr">no multi row<br/>align</p>
|
||||
</div>
|
||||
<div>
|
||||
<p begin="70s" end="78s" region="region_lr"><span ebutts:multiRowAlign="end">align set on<br/>span (invalid)</span></p>
|
||||
</div>
|
||||
</body>
|
||||
</tt>
|
||||
Loading…
Reference in a new issue