Commit 8494c3ae by Ian Baker

Merge pull request #6595 from szaboa:dev-v2-ssa-position

PiperOrigin-RevId: 283722376
parents bab89754 3f5654a1
......@@ -22,6 +22,8 @@
* Allow `AdtsExtractor` to encounter EoF when calculating average frame size
([#6700](https://github.com/google/ExoPlayer/issues/6700)).
* Make media session connector dispatch ACTION_SET_CAPTIONING_ENABLED.
* Add support for position and overlapping start/end times in SSA/ASS subtitles
([#6320](https://github.com/google/ExoPlayer/issues/6320)).
### 2.11.0 (not yet released) ###
......
......@@ -20,6 +20,7 @@ project.ext {
targetSdkVersion = 28 // TODO: Bump once b/143232359 is resolved
compileSdkVersion = 29
dexmakerVersion = '2.21.0'
guavaVersion = '23.5-android'
mockitoVersion = '2.25.0'
robolectricVersion = '4.3'
autoValueVersion = '1.6'
......
......@@ -53,6 +53,7 @@ dependencies {
androidTestImplementation 'androidx.test:runner:' + androidxTestRunnerVersion
androidTestImplementation 'androidx.test.ext:junit:' + androidxTestJUnitVersion
androidTestImplementation 'com.google.truth:truth:' + truthVersion
androidTestImplementation 'com.google.guava:guava:' + guavaVersion
androidTestImplementation 'com.linkedin.dexmaker:dexmaker:' + dexmakerVersion
androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito:' + dexmakerVersion
androidTestImplementation 'org.mockito:mockito-core:' + mockitoVersion
......@@ -60,6 +61,7 @@ dependencies {
testImplementation 'androidx.test:core:' + androidxTestCoreVersion
testImplementation 'androidx.test.ext:junit:' + androidxTestJUnitVersion
testImplementation 'com.google.truth:truth:' + truthVersion
testImplementation 'com.google.guava:guava:' + guavaVersion
testImplementation 'org.mockito:mockito-core:' + mockitoVersion
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
testImplementation project(modulePrefix + 'testutils')
......
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.google.android.exoplayer2.text.ssa;
import static com.google.android.exoplayer2.text.ssa.SsaDecoder.FORMAT_LINE_PREFIX;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
/**
* Represents a {@code Format:} line from the {@code [Events]} section
*
* <p>The indices are used to determine the location of particular properties in each {@code
* Dialogue:} line.
*/
/* package */ final class SsaDialogueFormat {
public final int startTimeIndex;
public final int endTimeIndex;
public final int styleIndex;
public final int textIndex;
public final int length;
private SsaDialogueFormat(
int startTimeIndex, int endTimeIndex, int styleIndex, int textIndex, int length) {
this.startTimeIndex = startTimeIndex;
this.endTimeIndex = endTimeIndex;
this.styleIndex = styleIndex;
this.textIndex = textIndex;
this.length = length;
}
/**
* Parses the format info from a 'Format:' line in the [Events] section.
*
* @return the parsed info, or null if {@code formatLine} doesn't contain both 'start' and 'end'.
*/
@Nullable
public static SsaDialogueFormat fromFormatLine(String formatLine) {
int startTimeIndex = C.INDEX_UNSET;
int endTimeIndex = C.INDEX_UNSET;
int styleIndex = C.INDEX_UNSET;
int textIndex = 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++) {
switch (Util.toLowerInvariant(keys[i].trim())) {
case "start":
startTimeIndex = i;
break;
case "end":
endTimeIndex = i;
break;
case "style":
styleIndex = i;
break;
case "text":
textIndex = i;
break;
}
}
return (startTimeIndex != C.INDEX_UNSET && endTimeIndex != C.INDEX_UNSET)
? new SsaDialogueFormat(startTimeIndex, endTimeIndex, styleIndex, textIndex, keys.length)
: null;
}
}
......@@ -28,14 +28,14 @@ import java.util.List;
*/
/* package */ final class SsaSubtitle implements Subtitle {
private final Cue[] cues;
private final long[] cueTimesUs;
private final List<List<Cue>> cues;
private final List<Long> cueTimesUs;
/**
* @param cues The cues in the subtitle.
* @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.cueTimesUs = cueTimesUs;
}
......@@ -43,30 +43,29 @@ import java.util.List;
@Override
public int getNextEventTimeIndex(long timeUs) {
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
public int getEventTimeCount() {
return cueTimesUs.length;
return cueTimesUs.size();
}
@Override
public long getEventTime(int index) {
Assertions.checkArgument(index >= 0);
Assertions.checkArgument(index < cueTimesUs.length);
return cueTimesUs[index];
Assertions.checkArgument(index < cueTimesUs.size());
return cueTimesUs.get(index);
}
@Override
public List<Cue> getCues(long timeUs) {
int index = Util.binarySearchFloor(cueTimesUs, timeUs, true, false);
if (index == -1 || cues[index] == Cue.EMPTY) {
// timeUs is earlier than the start of the first cue, or we have an empty cue.
if (index == -1) {
// timeUs is earlier than the start of the first cue.
return Collections.emptyList();
} else {
return Collections.singletonList(cues[index]);
return cues.get(index);
}
}
}
[Script Info]
Title: SomeTitle
PlayResX: 300
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
[Events]
Format: Layer, Start, End, Style, Name, Text
Dialogue: 0,0:00:00.00,0:00:01.23,Default,Olly,{\pos(-5,50)}First subtitle (negative \pos()).
Dialogue: 0,0:00:02.34,0:00:03.45,Default,Olly,{\move(-5,50,-5,50)}Second subtitle (negative \move()).
Dialogue: 0,0:00:04:56,0:00:08:90,Default,Olly,{\an11}Third subtitle (invalid alignment).
Dialogue: 0,0:00:09:56,0:00:12:90,Default,Olly,\pos(150,100) Fourth subtitle (no braces).
[Script Info]
Title: SomeTitle
[Events]
Format: Start, End, Text
Dialogue: 0:00:01.00,0:00:04.23,First subtitle - end overlaps second
Dialogue: 0:00:02.00,0:00:05.23,Second subtitle - beginning overlaps first
Dialogue: 0:00:08.44,0:00:09.44,Fourth subtitle - same timings as fifth
Dialogue: 0:00:06.00,0:00:08.44,Third subtitle - out of order
Dialogue: 0:00:08.44,0:00:09.44,Fifth subtitle - same timings as fourth
Dialogue: 0:00:10.72,0:00:15.65,Sixth subtitle - fully encompasses seventh
Dialogue: 0:00:13.22,0:00:14.22,Seventh subtitle - nested fully inside sixth
[Script Info]
Title: SomeTitle
PlayResX: 300
PlayResY: 202
[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
[Events]
Format: Layer, Start, End, Style, Name, Text
Dialogue: 0,0:00:00.00,0:00:01.23,Default,Olly,{\pos(150,50.5)}First subtitle.
Dialogue: 0,0:00:02.34,0:00:03.45,Default,Olly,Second subtitle{\pos(75,50.5)}.
Dialogue: 0,0:00:04:56,0:00:08:90,Default,Olly,{\pos(150,100)}Third subtitle{\pos(75,101)}, (only last counts).
Dialogue: 0,0:00:09:56,0:00:12:90,Default,Olly,{\move(150,100,150,50.5)}Fourth subtitle.
Dialogue: 0,0:00:13:56,0:00:15:90,Default,Olly,{ \pos( 150, 101 ) }Fifth subtitle {\an2}(alignment override, spaces around pos arguments).
Dialogue: 0,0:00:16:56,0:00:19:90,Default,Olly,{\pos(150,101)\an9}Sixth subtitle (multiple overrides in same braces).
[Script Info]
Title: SomeTitle
PlayResX: 300
[Events]
Format: Layer, Start, End, Style, Name, Text
Dialogue: 0,0:00:00.00,0:00:01.23,Default,Olly,{\pos(150,50)}First subtitle.
......@@ -7,6 +7,6 @@ Style: Default,Open Sans Semibold,36,&H00FFFFFF,&H000000FF,&H00020713,&H00000000
[Events]
Format: Layer, Start, End, Style, Name, Text
Dialogue: 0,0:00:00.00,0:00:01.23,Default,Olly,This is the first subtitle{ignored}.
Dialogue: 0,0:00:02.34,0:00:03.45,Default,Olly,This is the second subtitle \nwith a newline \Nand another.
Dialogue: 0,0:00:04:56,0:00:08:90,Default,Olly,This is the third subtitle, with a comma.
Dialogue: 0,0:00:00.00,0:00:01.23,Default ,Olly,This is the first subtitle{ignored}.
Dialogue: 0,0:00:02.34,0:00:03.45,Default ,Olly,This is the second subtitle \nwith a newline \Nand another.
Dialogue: 0,0:00:04:56,0:00:08:90,Default ,Olly,This is the third subtitle, with a comma.
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