mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Adding support for overlapping subtitles
This commit is contained in:
parent
579167743b
commit
0391e73a0b
2 changed files with 56 additions and 36 deletions
|
|
@ -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 {
|
||||||
|
|
||||||
@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 {
|
||||||
* @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 {
|
||||||
* @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 {
|
||||||
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 {
|
||||||
cue = new Cue(text);
|
cue = new Cue(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
cues.add(cue);
|
int startTimeIndex = insertToCueTimes(cueTimesUs, startTimeUs);
|
||||||
cueTimesUs.add(startTimeUs);
|
|
||||||
if (endTimeUs != C.TIME_UNSET) {
|
List<Cue> startCueList = new ArrayList<>();
|
||||||
cues.add(Cue.EMPTY);
|
if (startTimeIndex != 0) {
|
||||||
cueTimesUs.add(endTimeUs);
|
startCueList.addAll(cues.get(startTimeIndex - 1));
|
||||||
}
|
}
|
||||||
|
cues.add(startTimeIndex, startCueList);
|
||||||
|
|
||||||
|
if (endTimeUs != C.TIME_UNSET) {
|
||||||
|
int endTimeIndex = insertToCueTimes(cueTimesUs, 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 {
|
||||||
* @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 {
|
||||||
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;
|
||||||
*/
|
*/
|
||||||
/* 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;
|
||||||
@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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue