From 7750885455a5980489e91b51d3ee72c4c28b3767 Mon Sep 17 00:00:00 2001 From: FineFindus Date: Sat, 28 Feb 2026 21:28:00 +0100 Subject: [PATCH] Fallback to `displayAlign` from `style` for TTML regions Adds support for using the `displayAlign` from the `style` of a region, if it has no explicit `displayAlign` set. Closes: https://github.com/androidx/media/issues/2559 --- RELEASENOTES.md | 2 ++ .../extractor/text/ttml/TtmlParser.java | 12 ++++++++ .../media3/extractor/text/ttml/TtmlStyle.java | 15 ++++++++++ .../extractor/text/ttml/TtmlParserTest.java | 22 +++++++++++++++ .../fallback_display_align_from_style.xml | 28 +++++++++++++++++++ 5 files changed, 79 insertions(+) create mode 100644 libraries/test_data/src/test/assets/media/ttml/fallback_display_align_from_style.xml diff --git a/RELEASENOTES.md b/RELEASENOTES.md index c65fd1bbc50..d4c73720f47 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -26,6 +26,8 @@ * Add support for skipping frames that are late during join rather than dropping in DecoderVideoRenderer. * Text: + * TTML: Fallback to `displayAlign` from `style` for regions + ([#2559](https://github.com/androidx/media/issues/2559)). * Metadata: * Image: * DataSource: diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/text/ttml/TtmlParser.java b/libraries/extractor/src/main/java/androidx/media3/extractor/text/ttml/TtmlParser.java index 00e8f82fc5c..34fa3ae4964 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/text/ttml/TtmlParser.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/text/ttml/TtmlParser.java @@ -462,6 +462,15 @@ private static TtmlRegion parseRegionAttributes( @Nullable String displayAlign = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_DISPLAY_ALIGN); + if (displayAlign == null) { + String styleId = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_STYLE); + if (styleId != null) { + TtmlStyle style = globalStyles.get(styleId); + if (style != null) { + displayAlign = style.getDisplayAlign(); + } + } + } if (displayAlign != null) { switch (Ascii.toLowerCase(displayAlign)) { case "center": @@ -642,6 +651,9 @@ private static String[] parseStyleIds(String parentStyleIds) { case TtmlNode.ATTR_TTS_EXTENT: style = createIfNull(style).setExtent(attributeValue); break; + case TtmlNode.ATTR_TTS_DISPLAY_ALIGN: + style = createIfNull(style).setDisplayAlign(attributeValue); + break; default: // ignore break; diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/text/ttml/TtmlStyle.java b/libraries/extractor/src/main/java/androidx/media3/extractor/text/ttml/TtmlStyle.java index b3b29bdf241..220cd377318 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/text/ttml/TtmlStyle.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/text/ttml/TtmlStyle.java @@ -98,6 +98,7 @@ private float shearPercentage; @Nullable private String origin; @Nullable private String extent; + @Nullable private String displayAlign; public TtmlStyle() { linethrough = UNSPECIFIED; @@ -285,6 +286,9 @@ private TtmlStyle inherit(@Nullable TtmlStyle ancestor, boolean chaining) { if (extent == null) { extent = ancestor.extent; } + if (displayAlign == null) { + displayAlign = ancestor.displayAlign; + } // attributes not inherited as of http://www.w3.org/TR/ttml1/ if (chaining && !hasBackgroundColor && ancestor.hasBackgroundColor) { setBackgroundColor(ancestor.backgroundColor); @@ -412,4 +416,15 @@ public TtmlStyle setExtent(@Nullable String extent) { public String getExtent() { return extent; } + + @CanIgnoreReturnValue + public TtmlStyle setDisplayAlign(String displayAlign) { + this.displayAlign = displayAlign; + return this; + } + + @Nullable + public String getDisplayAlign() { + return displayAlign; + } } diff --git a/libraries/extractor/src/test/java/androidx/media3/extractor/text/ttml/TtmlParserTest.java b/libraries/extractor/src/test/java/androidx/media3/extractor/text/ttml/TtmlParserTest.java index 4616f031b2a..89a16e1afc9 100644 --- a/libraries/extractor/src/test/java/androidx/media3/extractor/text/ttml/TtmlParserTest.java +++ b/libraries/extractor/src/test/java/androidx/media3/extractor/text/ttml/TtmlParserTest.java @@ -76,6 +76,8 @@ public final class TtmlParserTest { private static final String SHEAR_FILE = "media/ttml/shear.xml"; private static final String REGION_ATTRS_FROM_STYLE_FILE = "media/ttml/inherit_region_attributes_from_style.xml"; + private static final String DISPLAY_ALIGN_ATTR_FROM_STYLE_FILE = + "media/ttml/fallback_display_align_from_style.xml"; @Test public void simple_allCues() throws Exception { @@ -1111,6 +1113,26 @@ public void regionAttrsFromStyle() throws Exception { assertThat(thirdCue.size).isEqualTo(20f / 100f); } + @Test + public void regionDisplayAlignFromStyle() throws Exception { + ImmutableList allCues = getAllCues(DISPLAY_ALIGN_ATTR_FROM_STYLE_FILE); + + Cue firstCue = Iterables.getOnlyElement(allCues.get(0).cues); + assertThat(firstCue.lineAnchor).isEqualTo(Cue.ANCHOR_TYPE_START); + + Cue secondCue = Iterables.getOnlyElement(allCues.get(1).cues); + assertThat(secondCue.lineAnchor).isEqualTo(Cue.ANCHOR_TYPE_START); + + Cue thirdCue = Iterables.getOnlyElement(allCues.get(2).cues); + assertThat(thirdCue.lineAnchor).isEqualTo(Cue.ANCHOR_TYPE_MIDDLE); + + Cue fourthCue = Iterables.getOnlyElement(allCues.get(3).cues); + assertThat(fourthCue.lineAnchor).isEqualTo(Cue.ANCHOR_TYPE_END); + + Cue fithCue = Iterables.getOnlyElement(allCues.get(4).cues); + assertThat(fithCue.lineAnchor).isEqualTo(Cue.ANCHOR_TYPE_END); + } + private static Spanned getOnlyCueTextAtIndex(List allCues, int index) { Cue cue = getOnlyCueAtIndex(allCues, index); assertThat(cue.text).isInstanceOf(Spanned.class); diff --git a/libraries/test_data/src/test/assets/media/ttml/fallback_display_align_from_style.xml b/libraries/test_data/src/test/assets/media/ttml/fallback_display_align_from_style.xml new file mode 100644 index 00000000000..bd2abf8f019 --- /dev/null +++ b/libraries/test_data/src/test/assets/media/ttml/fallback_display_align_from_style.xml @@ -0,0 +1,28 @@ + + + +