mirror of
https://github.com/samsonjs/media.git
synced 2026-04-14 12:45:47 +00:00
Merge pull request #8720 from dlafayet:tts-shear-block
PiperOrigin-RevId: 365998615
This commit is contained in:
parent
ffb1f61ef7
commit
1fd62c685a
10 changed files with 185 additions and 13 deletions
|
|
@ -7,8 +7,8 @@
|
|||
* Add group setting to `PlayerNotificationManager`.
|
||||
* Fix `StyledPlayerView` scrubber not reappearing correctly in some cases
|
||||
([#8646](https://github.com/google/ExoPlayer/issues/8646)).
|
||||
* Fix measurement of `StyledPlayerView` and `StyledPlayerControlView`
|
||||
when `wrap_content` is used
|
||||
* Fix measurement of `StyledPlayerView` and `StyledPlayerControlView` when
|
||||
`wrap_content` is used
|
||||
([#8726](https://github.com/google/ExoPlayer/issues/8726)).
|
||||
* Audio:
|
||||
* Report unexpected discontinuities in
|
||||
|
|
@ -76,6 +76,7 @@
|
|||
* Fix CEA-708 priority handling to sort cues in the order defined by the
|
||||
spec ([#8704](https://github.com/google/ExoPlayer/issues/8704)).
|
||||
* Support TTML `textEmphasis` attributes, used for Japanese boutens.
|
||||
* Support TTML `shear` 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.
|
||||
|
|
|
|||
|
|
@ -269,6 +269,12 @@ public final class Cue {
|
|||
*/
|
||||
public final @VerticalType int verticalType;
|
||||
|
||||
/**
|
||||
* The shear angle in degrees to be applied to this Cue, expressed in graphics coordinates. This
|
||||
* results in a skew transform for the block along the inline progression axis.
|
||||
*/
|
||||
public final float shearDegrees;
|
||||
|
||||
/**
|
||||
* Creates a text cue whose {@link #textAlignment} is null, whose type parameters are set to
|
||||
* {@link #TYPE_UNSET} and whose dimension parameters are set to {@link #DIMEN_UNSET}.
|
||||
|
|
@ -370,7 +376,8 @@ public final class Cue {
|
|||
/* bitmapHeight= */ DIMEN_UNSET,
|
||||
/* windowColorSet= */ false,
|
||||
/* windowColor= */ Color.BLACK,
|
||||
/* verticalType= */ TYPE_UNSET);
|
||||
/* verticalType= */ TYPE_UNSET,
|
||||
/* shearDegrees= */ 0f);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -415,7 +422,8 @@ public final class Cue {
|
|||
/* bitmapHeight= */ DIMEN_UNSET,
|
||||
windowColorSet,
|
||||
windowColor,
|
||||
/* verticalType= */ TYPE_UNSET);
|
||||
/* verticalType= */ TYPE_UNSET,
|
||||
/* shearDegrees= */ 0f);
|
||||
}
|
||||
|
||||
private Cue(
|
||||
|
|
@ -433,7 +441,8 @@ public final class Cue {
|
|||
float bitmapHeight,
|
||||
boolean windowColorSet,
|
||||
int windowColor,
|
||||
@VerticalType int verticalType) {
|
||||
@VerticalType int verticalType,
|
||||
float shearDegrees) {
|
||||
// Exactly one of text or bitmap should be set.
|
||||
if (text == null) {
|
||||
Assertions.checkNotNull(bitmap);
|
||||
|
|
@ -455,6 +464,7 @@ public final class Cue {
|
|||
this.textSizeType = textSizeType;
|
||||
this.textSize = textSize;
|
||||
this.verticalType = verticalType;
|
||||
this.shearDegrees = shearDegrees;
|
||||
}
|
||||
|
||||
/** Returns a new {@link Cue.Builder} initialized with the same values as this Cue. */
|
||||
|
|
@ -479,6 +489,7 @@ public final class Cue {
|
|||
private boolean windowColorSet;
|
||||
@ColorInt private int windowColor;
|
||||
@VerticalType private int verticalType;
|
||||
private float shearDegrees;
|
||||
|
||||
public Builder() {
|
||||
text = null;
|
||||
|
|
@ -514,6 +525,7 @@ public final class Cue {
|
|||
windowColorSet = cue.windowColorSet;
|
||||
windowColor = cue.windowColor;
|
||||
verticalType = cue.verticalType;
|
||||
shearDegrees = cue.shearDegrees;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -794,6 +806,12 @@ public final class Cue {
|
|||
return this;
|
||||
}
|
||||
|
||||
/** Sets the shear angle for this Cue. */
|
||||
public Builder setShearDegrees(float shearDegrees) {
|
||||
this.shearDegrees = shearDegrees;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the vertical formatting for this Cue.
|
||||
*
|
||||
|
|
@ -821,7 +839,8 @@ public final class Cue {
|
|||
bitmapHeight,
|
||||
windowColorSet,
|
||||
windowColor,
|
||||
verticalType);
|
||||
verticalType,
|
||||
shearDegrees);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@
|
|||
*/
|
||||
package com.google.android.exoplayer2.text.ttml;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
import android.text.Layout;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
|
|
@ -81,7 +84,8 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
|||
private static final Pattern OFFSET_TIME =
|
||||
Pattern.compile("^([0-9]+(?:\\.[0-9]+)?)(h|m|s|ms|f|t)$");
|
||||
private static final Pattern FONT_SIZE = Pattern.compile("^(([0-9]*.)?[0-9]+)(px|em|%)$");
|
||||
private static final Pattern PERCENTAGE_COORDINATES =
|
||||
static final Pattern SIGNED_PERCENTAGE = Pattern.compile("^([-+]?\\d+\\.?\\d*?)%$");
|
||||
static final Pattern PERCENTAGE_COORDINATES =
|
||||
Pattern.compile("^(\\d+\\.?\\d*?)% (\\d+\\.?\\d*?)%$");
|
||||
private static final Pattern PIXEL_COORDINATES =
|
||||
Pattern.compile("^(\\d+\\.?\\d*?)px (\\d+\\.?\\d*?)px$");
|
||||
|
|
@ -614,6 +618,9 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
|||
createIfNull(style)
|
||||
.setTextEmphasis(TextEmphasis.parse(Util.toLowerInvariant(attributeValue)));
|
||||
break;
|
||||
case TtmlNode.ATTR_TTS_SHEAR:
|
||||
style = createIfNull(style).setShearPercentage(parseShear(attributeValue));
|
||||
break;
|
||||
default:
|
||||
// ignore
|
||||
break;
|
||||
|
|
@ -755,11 +762,36 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parsed shear percentage (between -100.0 and +100.0 inclusive), or {@link
|
||||
* TtmlStyle#UNSPECIFIED_SHEAR} if parsing failed.
|
||||
*/
|
||||
private static float parseShear(String expression) {
|
||||
Matcher matcher = SIGNED_PERCENTAGE.matcher(expression);
|
||||
if (!matcher.matches()) {
|
||||
Log.w(TAG, "Invalid value for shear: " + expression);
|
||||
return TtmlStyle.UNSPECIFIED_SHEAR;
|
||||
}
|
||||
try {
|
||||
String percentage = Assertions.checkNotNull(matcher.group(1));
|
||||
float value = Float.parseFloat(percentage);
|
||||
// https://www.w3.org/TR/2018/REC-ttml2-20181108/#semantics-style-procedures-shear
|
||||
// If the absolute value of the specified percentage is greater than 100%, then it must be
|
||||
// interpreted as if 100% were specified with the appropriate sign.
|
||||
value = max(-100f, value);
|
||||
value = min(100f, value);
|
||||
return value;
|
||||
} catch (NumberFormatException e) {
|
||||
Log.w(TAG, "Failed to parse shear: " + expression, e);
|
||||
return TtmlStyle.UNSPECIFIED_SHEAR;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a time expression, returning the parsed timestamp.
|
||||
* <p>
|
||||
* For the format of a time expression, see:
|
||||
* <a href="http://www.w3.org/TR/ttaf1-dfxp/#timing-value-timeExpression">timeExpression</a>
|
||||
*
|
||||
* <p>For the format of a time expression, see: <a
|
||||
* href="http://www.w3.org/TR/ttaf1-dfxp/#timing-value-timeExpression">timeExpression</a>
|
||||
*
|
||||
* @param time A string that includes the time expression.
|
||||
* @param frameAndTickRate The effective frame and tick rates of the stream.
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||
public static final String ATTR_TTS_TEXT_COMBINE = "textCombine";
|
||||
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";
|
||||
|
||||
// Values for ruby
|
||||
public static final String RUBY_CONTAINER = "container";
|
||||
|
|
@ -408,6 +409,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||
if (resolvedStyle != null) {
|
||||
TtmlRenderUtil.applyStylesToSpan(
|
||||
text, start, end, resolvedStyle, parent, globalStyles, verticalType);
|
||||
if (resolvedStyle.getShearPercentage() != TtmlStyle.UNSPECIFIED_SHEAR && TAG_P.equals(tag)) {
|
||||
// Shear style should only be applied to P nodes
|
||||
// https://www.w3.org/TR/2018/REC-ttml2-20181108/#style-attribute-shear
|
||||
// The spec doesn't specify the coordinate system to use for block shear
|
||||
// however the spec shows examples of how different values are expected to be rendered.
|
||||
// See: https://www.w3.org/TR/2018/REC-ttml2-20181108/#style-attribute-shear
|
||||
// https://www.w3.org/TR/2018/REC-ttml2-20181108/#style-attribute-fontShear
|
||||
// This maps the shear percentage to shear angle in graphics coordinates
|
||||
regionOutput.setShearDegrees((resolvedStyle.getShearPercentage() * -90) / 100);
|
||||
}
|
||||
regionOutput.setTextAlignment(resolvedStyle.getTextAlign());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import java.lang.annotation.RetentionPolicy;
|
|||
/* package */ final class TtmlStyle {
|
||||
|
||||
public static final int UNSPECIFIED = -1;
|
||||
public static final float UNSPECIFIED_SHEAR = Float.MAX_VALUE;
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
|
|
@ -87,6 +88,7 @@ import java.lang.annotation.RetentionPolicy;
|
|||
@Nullable private Layout.Alignment textAlign;
|
||||
@OptionalBoolean private int textCombine;
|
||||
@Nullable private TextEmphasis textEmphasis;
|
||||
private float shearPercentage;
|
||||
|
||||
public TtmlStyle() {
|
||||
linethrough = UNSPECIFIED;
|
||||
|
|
@ -97,6 +99,7 @@ import java.lang.annotation.RetentionPolicy;
|
|||
rubyType = UNSPECIFIED;
|
||||
rubyPosition = TextAnnotation.POSITION_UNKNOWN;
|
||||
textCombine = UNSPECIFIED;
|
||||
shearPercentage = UNSPECIFIED_SHEAR;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -185,6 +188,15 @@ import java.lang.annotation.RetentionPolicy;
|
|||
return hasBackgroundColor;
|
||||
}
|
||||
|
||||
public TtmlStyle setShearPercentage(float shearPercentage) {
|
||||
this.shearPercentage = shearPercentage;
|
||||
return this;
|
||||
}
|
||||
|
||||
public float getShearPercentage() {
|
||||
return shearPercentage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Chains this style to referential style. Local properties which are already set are never
|
||||
* overridden.
|
||||
|
|
@ -242,6 +254,9 @@ import java.lang.annotation.RetentionPolicy;
|
|||
if (textEmphasis == null) {
|
||||
textEmphasis = ancestor.textEmphasis;
|
||||
}
|
||||
if (shearPercentage == UNSPECIFIED_SHEAR) {
|
||||
shearPercentage = ancestor.shearPercentage;
|
||||
}
|
||||
// attributes not inherited as of http://www.w3.org/TR/ttml1/
|
||||
if (chaining && !hasBackgroundColor && ancestor.hasBackgroundColor) {
|
||||
setBackgroundColor(ancestor.backgroundColor);
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ public class CueTest {
|
|||
.setSize(0.8f)
|
||||
.setWindowColor(Color.CYAN)
|
||||
.setVerticalType(Cue.VERTICAL_TYPE_RL)
|
||||
.setShearDegrees(-15f)
|
||||
.build();
|
||||
|
||||
Cue modifiedCue = cue.buildUpon().build();
|
||||
|
|
@ -61,6 +62,7 @@ public class CueTest {
|
|||
assertThat(cue.windowColor).isEqualTo(Color.CYAN);
|
||||
assertThat(cue.windowColorSet).isTrue();
|
||||
assertThat(cue.verticalType).isEqualTo(Cue.VERTICAL_TYPE_RL);
|
||||
assertThat(cue.shearDegrees).isEqualTo(-15f);
|
||||
|
||||
assertThat(modifiedCue.text).isSameInstanceAs(cue.text);
|
||||
assertThat(modifiedCue.textAlignment).isEqualTo(cue.textAlignment);
|
||||
|
|
@ -74,6 +76,7 @@ public class CueTest {
|
|||
assertThat(modifiedCue.windowColor).isEqualTo(cue.windowColor);
|
||||
assertThat(modifiedCue.windowColorSet).isEqualTo(cue.windowColorSet);
|
||||
assertThat(modifiedCue.verticalType).isEqualTo(cue.verticalType);
|
||||
assertThat(modifiedCue.shearDegrees).isEqualTo(cue.shearDegrees);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ public final class TtmlDecoderTest {
|
|||
private static final String TEXT_COMBINE_FILE = "media/ttml/text_combine.xml";
|
||||
private static final String RUBIES_FILE = "media/ttml/rubies.xml";
|
||||
private static final String TEXT_EMPHASIS_FILE = "media/ttml/text_emphasis.xml";
|
||||
private static final String SHEAR_FILE = "media/ttml/shear.xml";
|
||||
|
||||
@Test
|
||||
public void inlineAttributes() throws IOException, SubtitleDecoderException {
|
||||
|
|
@ -816,6 +817,35 @@ public final class TtmlDecoderTest {
|
|||
TextAnnotation.POSITION_BEFORE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shear() throws IOException, SubtitleDecoderException {
|
||||
TtmlSubtitle subtitle = getSubtitle(SHEAR_FILE);
|
||||
|
||||
Cue firstCue = getOnlyCueAtTimeUs(subtitle, 10_000_000);
|
||||
assertThat(firstCue.shearDegrees).isZero();
|
||||
|
||||
Cue secondCue = getOnlyCueAtTimeUs(subtitle, 20_000_000);
|
||||
assertThat(secondCue.shearDegrees).isWithin(0.01f).of(-15f);
|
||||
|
||||
Cue thirdCue = getOnlyCueAtTimeUs(subtitle, 30_000_000);
|
||||
assertThat(thirdCue.shearDegrees).isWithin(0.01f).of(15f);
|
||||
|
||||
Cue fourthCue = getOnlyCueAtTimeUs(subtitle, 40_000_000);
|
||||
assertThat(fourthCue.shearDegrees).isWithin(0.01f).of(-15f);
|
||||
|
||||
Cue fifthCue = getOnlyCueAtTimeUs(subtitle, 50_000_000);
|
||||
assertThat(fifthCue.shearDegrees).isWithin(0.01f).of(-22.5f);
|
||||
|
||||
Cue sixthCue = getOnlyCueAtTimeUs(subtitle, 60_000_000);
|
||||
assertThat(sixthCue.shearDegrees).isWithin(0.01f).of(0f);
|
||||
|
||||
Cue seventhCue = getOnlyCueAtTimeUs(subtitle, 70_000_000);
|
||||
assertThat(seventhCue.shearDegrees).isWithin(0.01f).of(-90f);
|
||||
|
||||
Cue eighthCue = getOnlyCueAtTimeUs(subtitle, 80_000_000);
|
||||
assertThat(eighthCue.shearDegrees).isWithin(0.01f).of(90f);
|
||||
}
|
||||
|
||||
private static Spanned getOnlyCueTextAtTimeUs(Subtitle subtitle, long timeUs) {
|
||||
Cue cue = getOnlyCueAtTimeUs(subtitle, timeUs);
|
||||
assertThat(cue.text).isInstanceOf(Spanned.class);
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ public final class TtmlStyleTest {
|
|||
private static final Layout.Alignment TEXT_ALIGN = Layout.Alignment.ALIGN_CENTER;
|
||||
private static final boolean TEXT_COMBINE = true;
|
||||
public static final String TEXT_EMPHASIS_STYLE = "dot before";
|
||||
public static final float SHEAR_PERCENTAGE = 16f;
|
||||
|
||||
private final TtmlStyle populatedStyle =
|
||||
new TtmlStyle()
|
||||
|
|
@ -66,7 +67,8 @@ public final class TtmlStyleTest {
|
|||
.setRubyPosition(RUBY_POSITION)
|
||||
.setTextAlign(TEXT_ALIGN)
|
||||
.setTextCombine(TEXT_COMBINE)
|
||||
.setTextEmphasis(TextEmphasis.parse(TEXT_EMPHASIS_STYLE));
|
||||
.setTextEmphasis(TextEmphasis.parse(TEXT_EMPHASIS_STYLE))
|
||||
.setShearPercentage(SHEAR_PERCENTAGE);
|
||||
|
||||
@Test
|
||||
public void inheritStyle() {
|
||||
|
|
@ -94,6 +96,7 @@ public final class TtmlStyleTest {
|
|||
assertThat(style.getTextEmphasis().markShape).isEqualTo(TextEmphasisSpan.MARK_SHAPE_DOT);
|
||||
assertThat(style.getTextEmphasis().markFill).isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED);
|
||||
assertThat(style.getTextEmphasis().position).isEqualTo(POSITION_BEFORE);
|
||||
assertThat(style.getShearPercentage()).isEqualTo(SHEAR_PERCENTAGE);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -121,6 +124,7 @@ public final class TtmlStyleTest {
|
|||
assertThat(style.getTextEmphasis().markShape).isEqualTo(TextEmphasisSpan.MARK_SHAPE_DOT);
|
||||
assertThat(style.getTextEmphasis().markFill).isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED);
|
||||
assertThat(style.getTextEmphasis().position).isEqualTo(POSITION_BEFORE);
|
||||
assertThat(style.getShearPercentage()).isEqualTo(SHEAR_PERCENTAGE);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -267,4 +271,16 @@ public final class TtmlStyleTest {
|
|||
assertThat(style.getTextEmphasis().markFill).isEqualTo(TextEmphasisSpan.MARK_FILL_OPEN);
|
||||
assertThat(style.getTextEmphasis().position).isEqualTo(TextAnnotation.POSITION_AFTER);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shear() {
|
||||
TtmlStyle style = new TtmlStyle();
|
||||
assertThat(style.getShearPercentage()).isEqualTo(TtmlStyle.UNSPECIFIED_SHEAR);
|
||||
style.setShearPercentage(101f);
|
||||
assertThat(style.getShearPercentage()).isEqualTo(101f);
|
||||
style.setShearPercentage(-200f);
|
||||
assertThat(style.getShearPercentage()).isEqualTo(-200f);
|
||||
style.setShearPercentage(0.1f);
|
||||
assertThat(style.getShearPercentage()).isEqualTo(0.1f);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -285,7 +285,8 @@ import java.util.Map;
|
|||
+ "writing-mode:%s;"
|
||||
+ "font-size:%s;"
|
||||
+ "background-color:%s;"
|
||||
+ "transform:translate(%s%%,%s%%);"
|
||||
+ "transform:translate(%s%%,%s%%)"
|
||||
+ "%s;"
|
||||
+ "'>",
|
||||
positionProperty,
|
||||
positionPercent,
|
||||
|
|
@ -298,7 +299,8 @@ import java.util.Map;
|
|||
cueTextSizeCssPx,
|
||||
windowCssColor,
|
||||
horizontalTranslatePercent,
|
||||
verticalTranslatePercent))
|
||||
verticalTranslatePercent,
|
||||
getBlockShearTransformFunction(cue)))
|
||||
.append(Util.formatInvariant("<span class='%s'>", DEFAULT_BACKGROUND_CSS_CLASS))
|
||||
.append(htmlAndCss.html)
|
||||
.append("</span>")
|
||||
|
|
@ -320,6 +322,17 @@ import java.util.Map;
|
|||
"base64");
|
||||
}
|
||||
|
||||
private static String getBlockShearTransformFunction(Cue cue) {
|
||||
if (cue.shearDegrees != 0.0f) {
|
||||
String direction =
|
||||
(cue.verticalType == Cue.VERTICAL_TYPE_LR || cue.verticalType == Cue.VERTICAL_TYPE_RL)
|
||||
? "skewY"
|
||||
: "skewX";
|
||||
return Util.formatInvariant("%s(%.2fdeg)", direction, cue.shearDegrees);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a text size to a CSS px value.
|
||||
*
|
||||
|
|
|
|||
32
testdata/src/test/assets/media/ttml/shear.xml
vendored
Normal file
32
testdata/src/test/assets/media/ttml/shear.xml
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<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="http://www.w3.org/2006/10/ttaf1">
|
||||
<body>
|
||||
<div>
|
||||
<p begin="10s" end="18s" tts:shear="0%">0%</p>
|
||||
</div>
|
||||
<div>
|
||||
<p begin="20s" end="28s" tts:shear="16.67%">16.67%</p>
|
||||
</div>
|
||||
<div>
|
||||
<p begin="30s" end="38s" tts:shear="-16.67%">-16.67%</p>
|
||||
</div>
|
||||
<div>
|
||||
<p begin="40s" end="48s" tts:shear="+16.67%">+16.67%</p>
|
||||
</div>
|
||||
<div>
|
||||
<p begin="50s" end="58s" tts:shear="+25%">+25%</p>
|
||||
</div>
|
||||
<div>
|
||||
<p begin="60s" end="68s" tts:shear="Invalid">Invalid</p>
|
||||
</div>
|
||||
<div>
|
||||
<p begin="70s" end="78s" tts:shear="101.01%">100.01%</p>
|
||||
</div>
|
||||
<div>
|
||||
<p begin="80s" end="88s" tts:shear="-101.1%">-101.1%</p>
|
||||
</div>
|
||||
</body>
|
||||
</tt>
|
||||
Loading…
Reference in a new issue