Commit 0391e73a by Arnold Szabo

Adding support for overlapping subtitles

parent 57916774
...@@ -24,7 +24,6 @@ import com.google.android.exoplayer2.text.SimpleSubtitleDecoder; ...@@ -24,7 +24,6 @@ import com.google.android.exoplayer2.text.SimpleSubtitleDecoder;
import com.google.android.exoplayer2.text.Subtitle; import com.google.android.exoplayer2.text.Subtitle;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.LongArray;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -82,19 +81,15 @@ public final class SsaDecoder extends SimpleSubtitleDecoder { ...@@ -82,19 +81,15 @@ public final class SsaDecoder extends SimpleSubtitleDecoder {
@Override @Override
protected Subtitle decode(byte[] bytes, int length, boolean reset) { protected Subtitle decode(byte[] bytes, int length, boolean reset) {
ArrayList<Cue> cues = new ArrayList<>(); ArrayList<List<Cue>> cues = new ArrayList<>();
LongArray cueTimesUs = new LongArray(); List<Long> cueTimesUs = new ArrayList<>();
ParsableByteArray data = new ParsableByteArray(bytes, length); ParsableByteArray data = new ParsableByteArray(bytes, length);
if (!haveInitializationData) { if (!haveInitializationData) {
parseHeader(data); parseHeader(data);
} }
parseEventBody(data, cues, cueTimesUs); parseEventBody(data, cues, cueTimesUs);
return new SsaSubtitle(cues, cueTimesUs);
Cue[] cuesArray = new Cue[cues.size()];
cues.toArray(cuesArray);
long[] cueTimesUsArray = cueTimesUs.toArray();
return new SsaSubtitle(cuesArray, cueTimesUsArray);
} }
/** /**
...@@ -126,7 +121,7 @@ public final class SsaDecoder extends SimpleSubtitleDecoder { ...@@ -126,7 +121,7 @@ public final class SsaDecoder extends SimpleSubtitleDecoder {
* @param cues A list to which parsed cues will be added. * @param cues A list to which parsed cues will be added.
* @param cueTimesUs An array to which parsed cue timestamps will be added. * @param cueTimesUs An array to which parsed cue timestamps will be added.
*/ */
private void parseEventBody(ParsableByteArray data, List<Cue> cues, LongArray cueTimesUs) { private void parseEventBody(ParsableByteArray data, List<List<Cue>> cues, List<Long> cueTimesUs) {
String currentLine; String currentLine;
while ((currentLine = data.readLine()) != null) { while ((currentLine = data.readLine()) != null) {
if (!haveInitializationData && currentLine.startsWith(FORMAT_LINE_PREFIX)) { if (!haveInitializationData && currentLine.startsWith(FORMAT_LINE_PREFIX)) {
...@@ -180,7 +175,7 @@ public final class SsaDecoder extends SimpleSubtitleDecoder { ...@@ -180,7 +175,7 @@ public final class SsaDecoder extends SimpleSubtitleDecoder {
* @param cues A list to which parsed cues will be added. * @param cues A list to which parsed cues will be added.
* @param cueTimesUs An array to which parsed cue timestamps will be added. * @param cueTimesUs An array to which parsed cue timestamps will be added.
*/ */
private void parseDialogueLine(String dialogueLine, List<Cue> cues, LongArray cueTimesUs) { private void parseDialogueLine(String dialogueLine, List<List<Cue>> cues, List<Long> cueTimesUs) {
if (formatKeyCount == 0) { if (formatKeyCount == 0) {
Log.w(TAG, "Skipping dialogue line before complete format: " + dialogueLine); Log.w(TAG, "Skipping dialogue line before complete format: " + dialogueLine);
return; return;
...@@ -222,7 +217,7 @@ public final class SsaDecoder extends SimpleSubtitleDecoder { ...@@ -222,7 +217,7 @@ public final class SsaDecoder extends SimpleSubtitleDecoder {
cue = new Cue( cue = new Cue(
text, text,
/* textAlignment */ null, /* textAlignment */ null,
1 - position.second / playResY, position.second / playResY,
Cue.LINE_TYPE_FRACTION, Cue.LINE_TYPE_FRACTION,
Cue.ANCHOR_TYPE_START, Cue.ANCHOR_TYPE_START,
position.first / playResX, position.first / playResX,
...@@ -232,12 +227,44 @@ public final class SsaDecoder extends SimpleSubtitleDecoder { ...@@ -232,12 +227,44 @@ public final class SsaDecoder extends SimpleSubtitleDecoder {
cue = new Cue(text); cue = new Cue(text);
} }
cues.add(cue); int startTimeIndex = insertToCueTimes(cueTimesUs, startTimeUs);
cueTimesUs.add(startTimeUs);
List<Cue> startCueList = new ArrayList<>();
if (startTimeIndex != 0) {
startCueList.addAll(cues.get(startTimeIndex - 1));
}
cues.add(startTimeIndex, startCueList);
if (endTimeUs != C.TIME_UNSET) { if (endTimeUs != C.TIME_UNSET) {
cues.add(Cue.EMPTY); int endTimeIndex = insertToCueTimes(cueTimesUs, endTimeUs);
cueTimesUs.add(endTimeUs); List<Cue> endList = new ArrayList<>(cues.get(endTimeIndex - 1));
cues.add(endTimeIndex, endList);
int i = startTimeIndex;
do {
cues.get(i).add(cue);
i++;
} while (i != endTimeIndex);
}
}
/**
* Insert the given cue time into the given array keeping the array sorted.
*
* @param cueTimes The array with sorted cue times
* @param timeUs The cue time to be inserted
* @return The index where the cue time was inserted
*/
private static int insertToCueTimes(List<Long> cueTimes, long timeUs) {
for (int i = cueTimes.size() - 1; i >= 0; i--) {
if (cueTimes.get(i) <= timeUs) {
cueTimes.add(i + 1, timeUs);
return i + 1;
}
} }
cueTimes.add(0, timeUs);
return 0;
} }
/** /**
...@@ -246,7 +273,7 @@ public final class SsaDecoder extends SimpleSubtitleDecoder { ...@@ -246,7 +273,7 @@ public final class SsaDecoder extends SimpleSubtitleDecoder {
* @param timeString The string to parse. * @param timeString The string to parse.
* @return The parsed timestamp in microseconds. * @return The parsed timestamp in microseconds.
*/ */
public static long parseTimecodeUs(String timeString) { private static long parseTimecodeUs(String timeString) {
Matcher matcher = SSA_TIMECODE_PATTERN.matcher(timeString); Matcher matcher = SSA_TIMECODE_PATTERN.matcher(timeString);
if (!matcher.matches()) { if (!matcher.matches()) {
return C.TIME_UNSET; return C.TIME_UNSET;
...@@ -258,21 +285,15 @@ public final class SsaDecoder extends SimpleSubtitleDecoder { ...@@ -258,21 +285,15 @@ public final class SsaDecoder extends SimpleSubtitleDecoder {
return timestampUs; return timestampUs;
} }
/**
* Parses an SSA position attribute.
*
* @param line The string to parse.
* @return The parsed position in a pair (x,y).
*/
@Nullable @Nullable
public static Pair<Float, Float> parsePosition(String line) { public static Pair<Float, Float> parsePosition(String line){
Matcher matcher = SSA_POSITION_PATTERN.matcher(line); Matcher matcher = SSA_POSITION_PATTERN.matcher(line);
if (!matcher.find()) { if(!matcher.find()){
return null; return null;
} }
float x = Float.parseFloat(matcher.group(1)); float x = Float.parseFloat(matcher.group(1));
float y = Float.parseFloat(matcher.group(3)); float y = Float.parseFloat(matcher.group(3));
return new Pair<>(x, y); return new Pair<>(x,y);
} }
} }
...@@ -28,14 +28,14 @@ import java.util.List; ...@@ -28,14 +28,14 @@ import java.util.List;
*/ */
/* package */ final class SsaSubtitle implements Subtitle { /* package */ final class SsaSubtitle implements Subtitle {
private final Cue[] cues; private final List<List<Cue>> cues;
private final long[] cueTimesUs; private final List<Long> cueTimesUs;
/** /**
* @param cues The cues in the subtitle. * @param cues The cues in the subtitle.
* @param cueTimesUs The cue times, in microseconds. * @param cueTimesUs The cue times, in microseconds.
*/ */
public SsaSubtitle(Cue[] cues, long[] cueTimesUs) { public SsaSubtitle(List<List<Cue>> cues, List<Long> cueTimesUs) {
this.cues = cues; this.cues = cues;
this.cueTimesUs = cueTimesUs; this.cueTimesUs = cueTimesUs;
} }
...@@ -43,30 +43,29 @@ import java.util.List; ...@@ -43,30 +43,29 @@ import java.util.List;
@Override @Override
public int getNextEventTimeIndex(long timeUs) { public int getNextEventTimeIndex(long timeUs) {
int index = Util.binarySearchCeil(cueTimesUs, timeUs, false, false); int index = Util.binarySearchCeil(cueTimesUs, timeUs, false, false);
return index < cueTimesUs.length ? index : C.INDEX_UNSET; return index < cueTimesUs.size() ? index : C.INDEX_UNSET;
} }
@Override @Override
public int getEventTimeCount() { public int getEventTimeCount() {
return cueTimesUs.length; return cueTimesUs.size();
} }
@Override @Override
public long getEventTime(int index) { public long getEventTime(int index) {
Assertions.checkArgument(index >= 0); Assertions.checkArgument(index >= 0);
Assertions.checkArgument(index < cueTimesUs.length); Assertions.checkArgument(index < cueTimesUs.size());
return cueTimesUs[index]; return cueTimesUs.get(index);
} }
@Override @Override
public List<Cue> getCues(long timeUs) { public List<Cue> getCues(long timeUs) {
int index = Util.binarySearchFloor(cueTimesUs, timeUs, true, false); int index = Util.binarySearchFloor(cueTimesUs, timeUs, true, false);
if (index == -1 || cues[index] == Cue.EMPTY) { if (index == -1 || cues.get(index).isEmpty()) {
// timeUs is earlier than the start of the first cue, or we have an empty cue. // timeUs is earlier than the start of the first cue, or we have an empty cue.
return Collections.emptyList(); return Collections.emptyList();
} else { } else {
return Collections.singletonList(cues[index]); return cues.get(index);
} }
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment