diff --git a/demos/main/src/main/assets/media.exolist.json b/demos/main/src/main/assets/media.exolist.json index 09688fa73ac..4fe10806e12 100644 --- a/demos/main/src/main/assets/media.exolist.json +++ b/demos/main/src/main/assets/media.exolist.json @@ -625,6 +625,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/library/extractor/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java b/library/extractor/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java index c6df62bb5f5..5b8f7e452b8 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java @@ -273,7 +273,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); @@ -306,58 +322,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.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; @@ -376,11 +348,97 @@ 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.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/library/extractor/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDialogueFormat.java b/library/extractor/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDialogueFormat.java index 82f3dd642c9..0028c0c4a71 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDialogueFormat.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/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) + ? new SsaDialogueFormat( + startTimeIndex, + endTimeIndex, + styleIndex, + textIndex, + marginLeftIndex, + marginRightIndex, + marginVerticalIndex, + keys.length) : null; } } diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java b/library/extractor/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java index 8e09291312c..61097582fc3 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java @@ -100,6 +100,9 @@ public final boolean italic; public final boolean underline; public final boolean strikeout; + public final float marginLeft; + public final float marginRight; + public final float marginVertical; private SsaStyle( String name, @@ -109,7 +112,10 @@ private SsaStyle( boolean bold, boolean italic, boolean underline, - boolean strikeout) { + boolean strikeout, + float marginLeft, + float marginRight, + float marginVertical) { this.name = name; this.alignment = alignment; this.primaryColor = primaryColor; @@ -118,6 +124,9 @@ private SsaStyle( this.italic = italic; this.underline = underline; this.strikeout = strikeout; + this.marginLeft = marginLeft; + this.marginRight = marginRight; + this.marginVertical = marginVertical; } @Nullable @@ -151,7 +160,16 @@ && parseBooleanValue(styleValues[format.italicIndex].trim()), format.underlineIndex != C.INDEX_UNSET && parseBooleanValue(styleValues[format.underlineIndex].trim()), format.strikeoutIndex != C.INDEX_UNSET - && parseBooleanValue(styleValues[format.strikeoutIndex].trim())); + && parseBooleanValue(styleValues[format.strikeoutIndex].trim()), + 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; @@ -227,6 +245,15 @@ public static Integer parseColor(String ssaColorExpression) { return Color.argb(a, r, g, b); } + 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; + } + } + private static float parseFontSize(String fontSize) { try { return Float.parseFloat(fontSize); @@ -246,6 +273,60 @@ private static boolean parseBooleanValue(String booleanValue) { } } + 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; + } + /** * Represents a {@code Format:} line from the {@code [V4+ Styles]} section * @@ -262,6 +343,9 @@ private static boolean parseBooleanValue(String booleanValue) { public final int italicIndex; public final int underlineIndex; public final int strikeoutIndex; + public final int marginLeftIndex; + public final int marginRightIndex; + public final int marginVerticalIndex; public final int length; private Format( @@ -273,6 +357,9 @@ private Format( int italicIndex, int underlineIndex, int strikeoutIndex, + int marginLeftIndex, + int marginRightIndex, + int marginVerticalIndex, int length) { this.nameIndex = nameIndex; this.alignmentIndex = alignmentIndex; @@ -282,6 +369,9 @@ private Format( this.italicIndex = italicIndex; this.underlineIndex = underlineIndex; this.strikeoutIndex = strikeoutIndex; + this.marginLeftIndex = marginLeftIndex; + this.marginRightIndex = marginRightIndex; + this.marginVerticalIndex = marginVerticalIndex; this.length = length; } @@ -300,6 +390,9 @@ public static Format fromFormatLine(String styleFormatLine) { int italicIndex = C.INDEX_UNSET; int underlineIndex = C.INDEX_UNSET; int strikeoutIndex = C.INDEX_UNSET; + int marginLeftIndex = C.INDEX_UNSET; + int marginRightIndex = C.INDEX_UNSET; + int marginVerticalIndex = C.INDEX_UNSET; String[] keys = TextUtils.split(styleFormatLine.substring(SsaDecoder.FORMAT_LINE_PREFIX.length()), ","); for (int i = 0; i < keys.length; i++) { @@ -328,6 +421,15 @@ public static Format fromFormatLine(String styleFormatLine) { case "strikeout": strikeoutIndex = i; break; + case "marginl": + marginLeftIndex = i; + break; + case "marginr": + marginRightIndex = i; + break; + case "marginv": + marginVerticalIndex = i; + break; } } return nameIndex != C.INDEX_UNSET @@ -340,6 +442,9 @@ public static Format fromFormatLine(String styleFormatLine) { italicIndex, underlineIndex, strikeoutIndex, + marginLeftIndex, + marginRightIndex, + marginVerticalIndex, keys.length) : null; } diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java index 4b3b8cef64e..cc42a5e5e4a 100644 --- a/library/extractor/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java @@ -53,6 +53,7 @@ public final class SsaDecoderTest { 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 decodeEmpty() throws IOException { @@ -412,6 +413,80 @@ public void decodeStrikeout() throws IOException { .hasNoStrikethroughSpanBetween(0, secondCueText.length()); } + @Test + public void decodeMargins() throws IOException { + SsaDecoder decoder = new SsaDecoder(); + byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), STYLE_MARGIN); + Subtitle subtitle = decoder.decode(bytes, bytes.length, false); + + // 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 = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(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 = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2))); + 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 = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(4))); + 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 = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(6))); + 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 = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(8))); + 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 = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(10))); + 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 = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(12))); + 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 = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(14))); + 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 = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(16))); + assertThat(ninthClue.position).isEqualTo(0.5f); + assertThat(ninthClue.lineType).isEqualTo(Cue.LINE_TYPE_FRACTION); + assertThat(ninthClue.line).isEqualTo(0.5f); + } + private static void assertTypicalCue1(Subtitle subtitle, int eventIndex) { assertThat(subtitle.getEventTime(eventIndex)).isEqualTo(0); assertThat(subtitle.getCues(subtitle.getEventTime(eventIndex)).get(0).text.toString()) diff --git a/testdata/src/test/assets/media/ssa/invalid_positioning b/testdata/src/test/assets/media/ssa/invalid_positioning index ade4cce9c47..9b5ca30f9a7 100644 --- a/testdata/src/test/assets/media/ssa/invalid_positioning +++ b/testdata/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/testdata/src/test/assets/media/ssa/style_margin b/testdata/src/test/assets/media/ssa/style_margin new file mode 100644 index 00000000000..4ccdcc95ec2 --- /dev/null +++ b/testdata/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