Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for SSA (v4+) MarginL, MarginR, MarginV style #10169

Open
wants to merge 6 commits into
base: dev-v2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions demos/main/src/main/assets/media.exolist.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this logic implying that the margin values are completely ignored if the cue contains \pos position overrides? Is that the expected behaviour?

Ditto for alignment overrides - why should they cause the margins to be ignored?

Copy link
Contributor Author

@szaboa szaboa Jun 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if this is the expected behaviour but I took VLC for reference, where it is ignored.

&& 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();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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++) {
Expand All @@ -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;
}
}