diff --git a/demos/main/src/main/assets/media.exolist.json b/demos/main/src/main/assets/media.exolist.json index d3a7e2a953e..c0bdb0137be 100644 --- a/demos/main/src/main/assets/media.exolist.json +++ b/demos/main/src/main/assets/media.exolist.json @@ -690,6 +690,13 @@ "subtitle_mime_type": "text/x-ssa", "subtitle_language": "en" }, + { + "name": "SubStation Alpha margin", + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/video-avc-baseline-480.mp4", + "subtitle_uri": "https://gist.githubusercontent.com/szaboa/bca7cdc90c1492eb747032f267a5de19/raw/8985eeb544641e174da22a1dafe50cd87393512f/test-subs-margin.ass", + "subtitle_mime_type": "text/x-ssa", + "subtitle_language": "en" + }, { "name": "MPEG-4 Timed Text", "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mp4/dizzy-with-tx3g.mp4" diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/text/ssa/SsaDialogueFormat.java b/libraries/extractor/src/main/java/androidx/media3/extractor/text/ssa/SsaDialogueFormat.java index 360ab05d520..9b7d618e6c2 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/text/ssa/SsaDialogueFormat.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/text/ssa/SsaDialogueFormat.java @@ -36,14 +36,27 @@ public final int endTimeIndex; public final int styleIndex; public final int textIndex; + public final int marginLeftIndex; + public final int marginRightIndex; + public final int marginVerticalIndex; public final int length; private SsaDialogueFormat( - int startTimeIndex, int endTimeIndex, int styleIndex, int textIndex, int length) { + int startTimeIndex, + int endTimeIndex, + int styleIndex, + int textIndex, + int marginLeftIndex, + int marginRightIndex, + int marginVerticalIndex, + int length) { this.startTimeIndex = startTimeIndex; this.endTimeIndex = endTimeIndex; this.styleIndex = styleIndex; this.textIndex = textIndex; + this.marginLeftIndex = marginLeftIndex; + this.marginRightIndex = marginRightIndex; + this.marginVerticalIndex = marginVerticalIndex; this.length = length; } @@ -58,6 +71,9 @@ public static SsaDialogueFormat fromFormatLine(String formatLine) { int endTimeIndex = C.INDEX_UNSET; int styleIndex = C.INDEX_UNSET; int textIndex = C.INDEX_UNSET; + int marginLeftIndex = C.INDEX_UNSET; + int marginRightIndex = C.INDEX_UNSET; + int marginVerticalIndex = C.INDEX_UNSET; Assertions.checkArgument(formatLine.startsWith(FORMAT_LINE_PREFIX)); String[] keys = TextUtils.split(formatLine.substring(FORMAT_LINE_PREFIX.length()), ","); for (int i = 0; i < keys.length; i++) { @@ -74,12 +90,29 @@ public static SsaDialogueFormat fromFormatLine(String formatLine) { case "text": textIndex = i; break; + case "marginl": + marginLeftIndex = i; + break; + case "marginr": + marginRightIndex = i; + break; + case "marginv": + marginVerticalIndex = i; + break; } } return (startTimeIndex != C.INDEX_UNSET - && endTimeIndex != C.INDEX_UNSET - && textIndex != C.INDEX_UNSET) - ? new SsaDialogueFormat(startTimeIndex, endTimeIndex, styleIndex, textIndex, keys.length) + && endTimeIndex != C.INDEX_UNSET + && textIndex != C.INDEX_UNSET) + ? new SsaDialogueFormat( + startTimeIndex, + endTimeIndex, + styleIndex, + textIndex, + marginLeftIndex, + marginRightIndex, + marginVerticalIndex, + keys.length) : null; } } diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/text/ssa/SsaParser.java b/libraries/extractor/src/main/java/androidx/media3/extractor/text/ssa/SsaParser.java index ac60f22332a..85ba17ef83c 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/text/ssa/SsaParser.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/text/ssa/SsaParser.java @@ -351,7 +351,23 @@ private void parseDialogueLine( .replace("\\N", "\n") .replace("\\n", "\n") .replace("\\h", "\u00A0"); - Cue cue = createCue(text, style, styleOverrides, screenWidth, screenHeight); + + float dialogueMarginLeft = format.marginLeftIndex != C.INDEX_UNSET + ? SsaStyle.parseMargin(lineValues[format.marginLeftIndex]) : 0f; + float dialogueMarginRight = format.marginRightIndex != C.INDEX_UNSET + ? SsaStyle.parseMargin(lineValues[format.marginRightIndex]) : 0f; + float dialogueMarginVertical = format.marginVerticalIndex != C.INDEX_UNSET + ? SsaStyle.parseMargin(lineValues[format.marginVerticalIndex]) : 0f; + + Cue cue = createCue( + text, + style, + styleOverrides, + dialogueMarginLeft, + dialogueMarginRight, + dialogueMarginVertical, + screenWidth, + screenHeight); int startTimeIndex = addCuePlacerholderByTime(startTimeUs, cueTimesUs, cues); int endTimeIndex = addCuePlacerholderByTime(endTimeUs, cueTimesUs, cues); @@ -384,65 +400,14 @@ private static Cue createCue( String text, @Nullable SsaStyle style, SsaStyle.Overrides styleOverrides, + float dialogueMarginLeft, + float dialogueMarginRight, + float dialogueMarginVertical, float screenWidth, float screenHeight) { SpannableString spannableText = new SpannableString(text); Cue.Builder cue = new Cue.Builder().setText(spannableText); - if (style != null) { - if (style.primaryColor != null) { - spannableText.setSpan( - new ForegroundColorSpan(style.primaryColor), - /* start= */ 0, - /* end= */ spannableText.length(), - SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); - } - if (style.borderStyle == SsaStyle.SSA_BORDER_STYLE_BOX && style.outlineColor != null) { - spannableText.setSpan( - new BackgroundColorSpan(style.outlineColor), - /* start= */ 0, - /* end= */ spannableText.length(), - SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); - } - if (style.fontSize != Cue.DIMEN_UNSET && screenHeight != Cue.DIMEN_UNSET) { - cue.setTextSize( - style.fontSize / screenHeight, Cue.TEXT_SIZE_TYPE_FRACTIONAL_IGNORE_PADDING); - } - if (style.bold && style.italic) { - spannableText.setSpan( - new StyleSpan(Typeface.BOLD_ITALIC), - /* start= */ 0, - /* end= */ spannableText.length(), - SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); - } else if (style.bold) { - spannableText.setSpan( - new StyleSpan(Typeface.BOLD), - /* start= */ 0, - /* end= */ spannableText.length(), - SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); - } else if (style.italic) { - spannableText.setSpan( - new StyleSpan(Typeface.ITALIC), - /* start= */ 0, - /* end= */ spannableText.length(), - SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); - } - if (style.underline) { - spannableText.setSpan( - new UnderlineSpan(), - /* start= */ 0, - /* end= */ spannableText.length(), - SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); - } - if (style.strikeout) { - spannableText.setSpan( - new StrikethroughSpan(), - /* start= */ 0, - /* end= */ spannableText.length(), - SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } - @SsaStyle.SsaAlignment int alignment; if (styleOverrides.alignment != SsaStyle.SSA_ALIGNMENT_UNKNOWN) { alignment = styleOverrides.alignment; @@ -461,11 +426,104 @@ private static Cue createCue( cue.setPosition(styleOverrides.position.x / screenWidth); cue.setLine(styleOverrides.position.y / screenHeight, LINE_TYPE_FRACTION); } else { - // TODO: Read the MarginL, MarginR and MarginV values from the Style & Dialogue lines. cue.setPosition(computeDefaultLineOrPosition(cue.getPositionAnchor())); cue.setLine(computeDefaultLineOrPosition(cue.getLineAnchor()), LINE_TYPE_FRACTION); } + // Apply margins if there are no overrides and we have valid positions. + if (styleOverrides.alignment == SsaStyle.SSA_ALIGNMENT_UNKNOWN + && styleOverrides.position == null + && cue.getPosition() != Cue.DIMEN_UNSET + && cue.getLine() != Cue.DIMEN_UNSET) { + + // Margin from Dialogue lines takes precedence over margin from Style line. + float marginLeft = dialogueMarginLeft != 0f + ? dialogueMarginLeft / screenWidth + : style != null ? style.marginLeft / screenWidth : 0f; + float marginRight = dialogueMarginRight != 0f + ? dialogueMarginRight / screenWidth + : style != null ? style.marginRight / screenWidth : 0f; + + // Apply margin left, margin right. + if (SsaStyle.hasLeftAlignment(style)) { + cue.setPosition(cue.getPosition() + marginLeft); + cue.setSize(1 - marginRight - marginLeft); + } else if (SsaStyle.hasRightAlignment(style)) { + cue.setPosition(cue.getPosition() - marginRight); + cue.setSize(1 - marginRight - marginLeft); + } else { + // Center alignment or unknown. + cue.setPosition(cue.getPosition() + (marginLeft - marginRight) / 2); + cue.setSize(1 - marginRight - marginLeft); + } + + // Apply margin vertical, ignore it when alignment is middle. + if (!SsaStyle.hasMiddleAlignment(style)) { + float marginVertical = dialogueMarginVertical != 0f ? dialogueMarginVertical / screenHeight + : style != null ? style.marginVertical / screenHeight : 0f; + cue.setLine( + cue.getLine() - (SsaStyle.hasTopAlignment(style) ? -marginVertical : marginVertical), + LINE_TYPE_FRACTION); + } + } + + if (style == null) { + return cue.build(); + } + + // Apply rest of the styles. + if (style.primaryColor != null) { + spannableText.setSpan( + new ForegroundColorSpan(style.primaryColor), + /* start= */ 0, + /* end= */ spannableText.length(), + SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (style.borderStyle == SsaStyle.SSA_BORDER_STYLE_BOX && style.outlineColor != null) { + spannableText.setSpan( + new BackgroundColorSpan(style.outlineColor), + /* start= */ 0, + /* end= */ spannableText.length(), + SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (style.fontSize != Cue.DIMEN_UNSET && screenHeight != Cue.DIMEN_UNSET) { + cue.setTextSize( + style.fontSize / screenHeight, Cue.TEXT_SIZE_TYPE_FRACTIONAL_IGNORE_PADDING); + } + if (style.bold && style.italic) { + spannableText.setSpan( + new StyleSpan(Typeface.BOLD_ITALIC), + /* start= */ 0, + /* end= */ spannableText.length(), + SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); + } else if (style.bold) { + spannableText.setSpan( + new StyleSpan(Typeface.BOLD), + /* start= */ 0, + /* end= */ spannableText.length(), + SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); + } else if (style.italic) { + spannableText.setSpan( + new StyleSpan(Typeface.ITALIC), + /* start= */ 0, + /* end= */ spannableText.length(), + SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (style.underline) { + spannableText.setSpan( + new UnderlineSpan(), + /* start= */ 0, + /* end= */ spannableText.length(), + SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (style.strikeout) { + spannableText.setSpan( + new StrikethroughSpan(), + /* start= */ 0, + /* end= */ spannableText.length(), + SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); + } + return cue.build(); } diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/text/ssa/SsaStyle.java b/libraries/extractor/src/main/java/androidx/media3/extractor/text/ssa/SsaStyle.java index 88fcd2a7356..11cba2abfb0 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/text/ssa/SsaStyle.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/text/ssa/SsaStyle.java @@ -128,6 +128,9 @@ public final boolean underline; public final boolean strikeout; public final @SsaBorderStyle int borderStyle; + public final float marginLeft; + public final float marginRight; + public final float marginVertical; private SsaStyle( String name, @@ -139,7 +142,10 @@ private SsaStyle( boolean italic, boolean underline, boolean strikeout, - @SsaBorderStyle int borderStyle) { + @SsaBorderStyle int borderStyle, + float marginLeft, + float marginRight, + float marginVertical) { this.name = name; this.alignment = alignment; this.primaryColor = primaryColor; @@ -150,6 +156,9 @@ private SsaStyle( this.underline = underline; this.strikeout = strikeout; this.borderStyle = borderStyle; + this.marginLeft = marginLeft; + this.marginRight = marginRight; + this.marginVertical = marginVertical; } @Nullable @@ -189,7 +198,16 @@ && parseBooleanValue(styleValues[format.underlineIndex].trim()), && parseBooleanValue(styleValues[format.strikeoutIndex].trim()), format.borderStyleIndex != C.INDEX_UNSET ? parseBorderStyle(styleValues[format.borderStyleIndex].trim()) - : SSA_BORDER_STYLE_UNKNOWN); + : SSA_BORDER_STYLE_UNKNOWN, + format.marginLeftIndex != C.INDEX_UNSET + ? parseMargin(styleValues[format.marginLeftIndex].trim()) + : Cue.DIMEN_UNSET, + format.marginRightIndex != C.INDEX_UNSET + ? parseMargin(styleValues[format.marginRightIndex].trim()) + : Cue.DIMEN_UNSET, + format.marginVerticalIndex != C.INDEX_UNSET + ? parseMargin(styleValues[format.marginVerticalIndex].trim()) + : Cue.DIMEN_UNSET); } catch (RuntimeException e) { Log.w(TAG, "Skipping malformed 'Style:' line: '" + styleLine + "'", e); return null; @@ -298,6 +316,69 @@ private static float parseFontSize(String fontSize) { } } + public static float parseMargin(String floatValue) { + try { + return Float.parseFloat(floatValue); + } catch (NumberFormatException e) { + Log.w(TAG, "Failed to parse margin value: '" + floatValue + "'", e); + return 0f; + } + } + + public static boolean hasMiddleAlignment(@Nullable SsaStyle style) { + if (style == null) { + return false; + } + return style.alignment == SSA_ALIGNMENT_MIDDLE_LEFT + || style.alignment == SSA_ALIGNMENT_MIDDLE_CENTER + || style.alignment == SSA_ALIGNMENT_MIDDLE_RIGHT; + } + + public static boolean hasTopAlignment(@Nullable SsaStyle style) { + if (style == null) { + return false; + } + return style.alignment == SSA_ALIGNMENT_TOP_LEFT + || style.alignment == SSA_ALIGNMENT_TOP_CENTER + || style.alignment == SSA_ALIGNMENT_TOP_RIGHT; + } + + public static boolean hasBottomAlignment(@Nullable SsaStyle style) { + if (style == null) { + return false; + } + return style.alignment == SSA_ALIGNMENT_BOTTOM_LEFT + || style.alignment == SSA_ALIGNMENT_BOTTOM_CENTER + || style.alignment == SSA_ALIGNMENT_BOTTOM_RIGHT; + } + + public static boolean hasLeftAlignment(@Nullable SsaStyle style) { + if (style == null) { + return false; + } + return style.alignment == SSA_ALIGNMENT_TOP_LEFT + || style.alignment == SSA_ALIGNMENT_MIDDLE_LEFT + || style.alignment == SSA_ALIGNMENT_BOTTOM_LEFT; + } + + public static boolean hasRightAlignment(@Nullable SsaStyle style) { + if (style == null) { + return false; + } + return style.alignment == SSA_ALIGNMENT_TOP_RIGHT + || style.alignment == SSA_ALIGNMENT_MIDDLE_RIGHT + || style.alignment == SSA_ALIGNMENT_BOTTOM_RIGHT; + } + + public static boolean hasCenterAlignment(@Nullable SsaStyle style) { + if (style == null) { + return false; + } + return style.alignment == SSA_ALIGNMENT_TOP_CENTER + || style.alignment == SSA_ALIGNMENT_MIDDLE_CENTER + || style.alignment == SSA_ALIGNMENT_BOTTOM_CENTER; + } + private static boolean parseBooleanValue(String booleanValue) { try { int value = Integer.parseInt(booleanValue); @@ -326,6 +407,9 @@ private static boolean parseBooleanValue(String booleanValue) { public final int underlineIndex; public final int strikeoutIndex; public final int borderStyleIndex; + public final int marginLeftIndex; + public final int marginRightIndex; + public final int marginVerticalIndex; public final int length; private Format( @@ -339,6 +423,9 @@ private Format( int underlineIndex, int strikeoutIndex, int borderStyleIndex, + int marginLeftIndex, + int marginRightIndex, + int marginVerticalIndex, int length) { this.nameIndex = nameIndex; this.alignmentIndex = alignmentIndex; @@ -350,6 +437,9 @@ private Format( this.underlineIndex = underlineIndex; this.strikeoutIndex = strikeoutIndex; this.borderStyleIndex = borderStyleIndex; + this.marginLeftIndex = marginLeftIndex; + this.marginRightIndex = marginRightIndex; + this.marginVerticalIndex = marginVerticalIndex; this.length = length; } @@ -370,6 +460,9 @@ public static Format fromFormatLine(String styleFormatLine) { int underlineIndex = C.INDEX_UNSET; int strikeoutIndex = C.INDEX_UNSET; int borderStyleIndex = C.INDEX_UNSET; + int marginLeftIndex = C.INDEX_UNSET; + int marginRightIndex = C.INDEX_UNSET; + int marginVerticalIndex = C.INDEX_UNSET; String[] keys = TextUtils.split(styleFormatLine.substring(SsaParser.FORMAT_LINE_PREFIX.length()), ","); for (int i = 0; i < keys.length; i++) { @@ -404,21 +497,33 @@ public static Format fromFormatLine(String styleFormatLine) { case "borderstyle": borderStyleIndex = i; break; + case "marginl": + marginLeftIndex = i; + break; + case "marginr": + marginRightIndex = i; + break; + case "marginv": + marginVerticalIndex = i; + break; } } return nameIndex != C.INDEX_UNSET ? new Format( - nameIndex, - alignmentIndex, - primaryColorIndex, - outlineColorIndex, - fontSizeIndex, - boldIndex, - italicIndex, - underlineIndex, - strikeoutIndex, - borderStyleIndex, - keys.length) + nameIndex, + alignmentIndex, + primaryColorIndex, + outlineColorIndex, + fontSizeIndex, + boldIndex, + italicIndex, + underlineIndex, + strikeoutIndex, + borderStyleIndex, + marginLeftIndex, + marginRightIndex, + marginVerticalIndex, + keys.length) : null; } } diff --git a/libraries/extractor/src/test/java/androidx/media3/extractor/text/ssa/SsaParserTest.java b/libraries/extractor/src/test/java/androidx/media3/extractor/text/ssa/SsaParserTest.java index 914fc7673a2..888ff44fb80 100644 --- a/libraries/extractor/src/test/java/androidx/media3/extractor/text/ssa/SsaParserTest.java +++ b/libraries/extractor/src/test/java/androidx/media3/extractor/text/ssa/SsaParserTest.java @@ -23,7 +23,9 @@ import android.text.Layout; import android.text.Spanned; import androidx.media3.common.text.Cue; +import androidx.media3.common.util.Consumer; import androidx.media3.extractor.text.CuesWithTiming; +import androidx.media3.extractor.text.Subtitle; import androidx.media3.extractor.text.SubtitleParser; import androidx.media3.extractor.text.SubtitleParser.OutputOptions; import androidx.media3.test.utils.TestUtil; @@ -62,6 +64,7 @@ public final class SsaParserTest { private static final String STYLE_BOLD_ITALIC = "media/ssa/style_bold_italic"; private static final String STYLE_UNDERLINE = "media/ssa/style_underline"; private static final String STYLE_STRIKEOUT = "media/ssa/style_strikeout"; + private static final String STYLE_MARGIN = "media/ssa/style_margin"; @Test public void cuesReplacementBehaviorIsMerge() throws IOException { @@ -99,6 +102,81 @@ public void parseEmptyStyleLine() throws IOException { assertThat(cue.lineType).isEqualTo(Cue.LINE_TYPE_FRACTION); } + @Test + public void parseMargins() throws IOException { + SsaParser parser = new SsaParser(); + byte[] bytes = + TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), STYLE_MARGIN); + ImmutableList allCues = parseAllCues(parser, bytes); + + // PlayResX = 1280px, PlayResY = 720px + + // Alignment 1, position anchor = start, position = (0.05f, 0.95f) + // margin_left = 128px = 0.1f, margin_right 256px = 0.2f + Cue firstCue = allCues.get(0).cues.get(0); + assertThat(firstCue.position).isEqualTo(0.15f); // = 0.05f + margin_left + assertThat(firstCue.lineType).isEqualTo(Cue.LINE_TYPE_FRACTION); + assertThat(firstCue.line).isEqualTo(0.95f); + assertThat(firstCue.size).isEqualTo(0.7f); // = 1f - margin_right - margin_left + + // Alignment 6, position anchor = end, position = (0.95f, 0.5f) + // margin_left = 128px = 0.1f, margin_right = 256px = 0.2f + Cue secondClue = allCues.get(1).cues.get(0); + assertThat(secondClue.position).isEqualTo(0.75f); // = 1f - margin_right + assertThat(secondClue.lineType).isEqualTo(Cue.LINE_TYPE_FRACTION); + assertThat(secondClue.line).isEqualTo(0.5f); + assertThat(secondClue.size).isEqualTo(0.7f); // = 1f - margin_right - margin_left + + // Alignment 2, position anchor = middle, position = (0.5f, 0.95f) + // margin_left = 128px = 0.1f, margin_right = 256px = 0.2f + Cue thirdClue = allCues.get(2).cues.get(0); + assertThat(thirdClue.position).isEqualTo(0.45f); // 0.5f + (margin_left - margin_right)/2 + assertThat(thirdClue.lineType).isEqualTo(Cue.LINE_TYPE_FRACTION); + assertThat(thirdClue.line).isEqualTo(0.95f); + assertThat(thirdClue.size).isEqualTo(0.7f); // = 1f - margin_right - margin_left + + // Alignment 5, position anchor = middle, position = (0.5f, 0.5f) + // margin_vertical = 144px = 0.2f but needs to be ignored when alignment is middle [4,5,6] + Cue fourthClue = allCues.get(3).cues.get(0); + assertThat(fourthClue.position).isEqualTo(0.5f); + assertThat(fourthClue.lineType).isEqualTo(Cue.LINE_TYPE_FRACTION); + assertThat(fourthClue.line).isEqualTo(0.5f); + + // Alignment 2, position anchor = middle, position = (0.5f, 0.95f) + // margin_vertical = 144px = 0.2f, to be applied from bottom when alignment is bottom [1,2,3] + Cue fifthClue = allCues.get(4).cues.get(0); + assertThat(fifthClue.position).isEqualTo(0.5f); + assertThat(fifthClue.lineType).isEqualTo(Cue.LINE_TYPE_FRACTION); + assertThat(fifthClue.line).isEqualTo(0.75f); // = 0.95f - margin_vertical + + // Alignment 9, position anchor = end, position = (0.95f, 0.05f) + // margin_vertical = 144px = 0.2f, to be applied from top when alignment is top [7,8,9] + Cue sixthClue = allCues.get(5).cues.get(0); + assertThat(sixthClue.position).isEqualTo(0.95f); + assertThat(sixthClue.lineType).isEqualTo(Cue.LINE_TYPE_FRACTION); + assertThat(sixthClue.line).isEqualTo(0.25f); // = 0.05f + margin_vertical + + // Alignment 2, position anchor = middle, position = (0.5f, 0.95f) + // margin_left = 128px = 0.1f, margin_vertical = 144px = 0.2f, margin_right = 0f (from Dialogue) + Cue seventhClue = allCues.get(6).cues.get(0); + assertThat(seventhClue.position).isEqualTo(0.55f); // 0.5f + (margin_left - margin_right)/2 + assertThat(seventhClue.lineType).isEqualTo(Cue.LINE_TYPE_FRACTION); + assertThat(seventhClue.line).isEqualTo(0.75f); // 0.95f - margin_vertical + assertThat(seventhClue.size).isEqualTo(0.9f); // 1f - margin_right - margin_left + + // Position override {\pos(640,180)} -> ignore margins + Cue eighthClue = allCues.get(7).cues.get(0); + assertThat(eighthClue.position).isEqualTo(0.5f); + assertThat(eighthClue.lineType).isEqualTo(Cue.LINE_TYPE_FRACTION); + assertThat(eighthClue.line).isEqualTo(0.25f); + + // Alignment override {\an5}, position = (0.5f, 0.5f) -> ignore margins + Cue ninthClue = allCues.get(8).cues.get(0); + assertThat(ninthClue.position).isEqualTo(0.5f); + assertThat(ninthClue.lineType).isEqualTo(Cue.LINE_TYPE_FRACTION); + assertThat(ninthClue.line).isEqualTo(0.5f); + } + @Test public void parseTypical() throws IOException { SsaParser parser = new SsaParser(); diff --git a/libraries/test_data/src/test/assets/media/ssa/invalid_positioning b/libraries/test_data/src/test/assets/media/ssa/invalid_positioning index ade4cce9c47..9b5ca30f9a7 100644 --- a/libraries/test_data/src/test/assets/media/ssa/invalid_positioning +++ b/libraries/test_data/src/test/assets/media/ssa/invalid_positioning @@ -6,7 +6,7 @@ PlayResY: 200 [V4+ Styles] ! Alignment is set to 4 - i.e. middle-left Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding -Style: Default,Open Sans Semibold,36,&H00FFFFFF,&H000000FF,&H00020713,&H00000000,-1,0,0,0,100,100,0,0,1,1.7,0,4,0,0,28,1 +Style: Default,Open Sans Semibold,36,&H00FFFFFF,&H000000FF,&H00020713,&H00000000,-1,0,0,0,100,100,0,0,1,1.7,0,4,0,0,0,1 [Events] Format: Layer, Start, End, Style, Name, Text diff --git a/libraries/test_data/src/test/assets/media/ssa/style_margin b/libraries/test_data/src/test/assets/media/ssa/style_margin new file mode 100644 index 00000000000..4ccdcc95ec2 --- /dev/null +++ b/libraries/test_data/src/test/assets/media/ssa/style_margin @@ -0,0 +1,30 @@ +[Script Info] +Title: SSA/ASS Test +Original Script: Arnold Szabo +Script Type: V4.00+ +PlayResX: 1280 +PlayResY: 720 + +[V4+ Styles] +Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding +Style: AlignmentLeft ,Roboto,30,&H000000FF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,3,0,1, 128,256,0 ,1 +Style: AlignmentRight ,Roboto,30,&H000000FF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,3,0,6, 128,256,0 ,1 +Style: AlignmentCenter ,Roboto,30,&H000000FF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,3,0,2, 128,256,0 ,1 +Style: AlignmentMiddle ,Roboto,30,&H000000FF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,3,0,5, 0,0,144 ,1 +Style: AlignmentBottom ,Roboto,30,&H000000FF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,3,0,2, 0,0,144 ,1 +Style: AlignmentTop ,Roboto,30,&H000000FF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,3,0,9, 0,0,144 ,1 +Style: DialogueMargin ,Roboto,30,&H000000FF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,3,0,2, 0,0,0 ,1 +Style: PositionOverride ,Roboto,30,&H000000FF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,3,0,2, 128,144,0 ,1 +Style: AlignmentOverride ,Roboto,30,&H000000FF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,3,0,2, 128,144,0 ,1 + +[Events] +Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text +Dialogue: 0,0:00:00.95,0:00:02.11,AlignmentLeft ,Arnold,0,0,0,, Margin with alignment left - long text long text long text long text long text long text long text text long text long text ong text long text long text long text long text long text long text text long text long text ong text long text long text long text long text long text long text text long text long text ong text long text long text long text long text long text long text text long text long text +Dialogue: 0,0:00:02.20,0:00:03.40,AlignmentRight ,Arnold,0,0,0,, Margin with alignment right - long text long text long text long text long text long text long text text long text long text ong text long text long text long text long text long text long text text long text long text ong text long text long text long text long text long text long text text long text long text ong text long text long text long text long text long text long text text long text long text +Dialogue: 0,0:00:03.45,0:00:04.40,AlignmentCenter ,Arnold,0,0,0,, Margin with alignment center - long text long text long text long text long text long text long text text long text long text ong text long text long text long text long text long text long text text long text long text ong text long text long text long text long text long text long text text long text long text ong text long text long text long text long text long text long text text long text long text +Dialogue: 0,0:00:04.50,0:00:06.50,AlignmentMiddle ,Arnold,0,0,0,, Middle alignment - ignore vertical margin +Dialogue: 0,0:00:07.50,0:00:10.00,AlignmentBottom ,Arnold,0,0,0,, Bottom alignment - apply vertical margin from bottom +Dialogue: 0,0:00:11.50,0:00:14.00,AlignmentTop ,Arnold,0,0,0,, Top alignment - apply vertical margin from top +Dialogue: 0,0:00:15.50,0:00:17.00,DialogueMargin ,Arnold,128,0,144,, Margin defined in dialogue +Dialogue: 0,0:00:18.50,0:00:20.00,PositionOverride ,Arnold,0,0,0,, {\pos(640,180)} Position override - ignore margins +Dialogue: 0,0:00:21.00,0:00:22.00,AlignmentOverride ,Arnold,0,0,0,, {\an5} Alignment override - ignore margins \ No newline at end of file