diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java index 8da37e7f8f3..34b3f67d362 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.text.ssa; import android.text.TextUtils; +import android.util.Pair; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.text.Cue; @@ -40,6 +41,9 @@ public final class SsaDecoder extends SimpleSubtitleDecoder { private static final Pattern SSA_TIMECODE_PATTERN = Pattern.compile( "(?:(\\d+):)?(\\d+):(\\d+)(?::|\\.)(\\d+)"); + private static final Pattern SSA_POSITION_PATTERN = Pattern.compile( + "\\\\pos\\((\\d+(\\.\\d+)?),\\s*(\\d+(\\.\\d+)?)"); + private static final String FORMAT_LINE_PREFIX = "Format: "; private static final String DIALOGUE_LINE_PREFIX = "Dialogue: "; @@ -50,6 +54,9 @@ public final class SsaDecoder extends SimpleSubtitleDecoder { private int formatEndIndex; private int formatTextIndex; + private int playResX; + private int playResY; + public SsaDecoder() { this(/* initializationData= */ null); } @@ -98,6 +105,12 @@ protected Subtitle decode(byte[] bytes, int length, boolean reset) { private void parseHeader(ParsableByteArray data) { String currentLine; while ((currentLine = data.readLine()) != null) { + if (currentLine.startsWith("PlayResX:")) { + playResX = Integer.valueOf(currentLine.substring(9).trim()); + } + if (currentLine.startsWith("PlayResY:")) { + playResY = Integer.valueOf(currentLine.substring(9).trim()); + } // TODO: Parse useful data from the header. if (currentLine.startsWith("[Events]")) { // We've reached the event body. @@ -196,11 +209,30 @@ private void parseDialogueLine(String dialogueLine, List cues, LongArray cu } } + // Parse \pos{x,y} attribute + Pair position = parsePosition(lineValues[formatTextIndex]); + String text = lineValues[formatTextIndex] .replaceAll("\\{.*?\\}", "") .replaceAll("\\\\N", "\n") .replaceAll("\\\\n", "\n"); - cues.add(new Cue(text)); + + Cue cue; + if (position != null && playResX != 0 && playResY != 0) { + cue = new Cue( + text, + /* textAlignment */ null, + 1 - position.second / playResY, + Cue.LINE_TYPE_FRACTION, + Cue.ANCHOR_TYPE_START, + position.first / playResX, + Cue.ANCHOR_TYPE_MIDDLE, + Cue.DIMEN_UNSET); + } else { + cue = new Cue(text); + } + + cues.add(cue); cueTimesUs.add(startTimeUs); if (endTimeUs != C.TIME_UNSET) { cues.add(Cue.EMPTY); @@ -226,4 +258,21 @@ public static long parseTimecodeUs(String timeString) { return timestampUs; } + /** + * Parses an SSA position attribute. + * + * @param line The string to parse. + * @return The parsed position in a pair (x,y). + */ + @Nullable + public static Pair parsePosition(String line) { + Matcher matcher = SSA_POSITION_PATTERN.matcher(line); + if (!matcher.find()) { + return null; + } + float x = Float.parseFloat(matcher.group(1)); + float y = Float.parseFloat(matcher.group(3)); + return new Pair<>(x, y); + } + }