mirror of
https://github.com/androidx/media.git
synced 2025-05-12 18:19:50 +08:00
Further improve WebVTT parser according to WebVTT spec
This commit is contained in:
parent
71f542f7c2
commit
e4e02f9189
@ -1,7 +0,0 @@
|
||||
WEBVTT
|
||||
|
||||
00:00.000 --> 00:01.234
|
||||
This is the first subtitle.
|
||||
|
||||
00:02.345 --> 00:03.456
|
||||
This is the second subtitle.
|
@ -8,7 +8,9 @@ with multiple lines
|
||||
00:00.000 --> 00:01.234
|
||||
This is the first subtitle.
|
||||
|
||||
NOTE Single line comment
|
||||
NOTE Single line comment with a space
|
||||
|
||||
NOTE Single line comment with a tab
|
||||
|
||||
2
|
||||
00:02.345 --> 00:03.456
|
||||
|
@ -16,7 +16,13 @@ NOTE Line as percentage and line alignment
|
||||
00:04.000 --> 00:05.000 line:45%,end align:middle size:35%
|
||||
This is the third subtitle.
|
||||
|
||||
NOTE Line as absolute negative number and without line alignment
|
||||
NOTE Line as absolute negative number and without line alignment.
|
||||
|
||||
00:06.000 --> 00:07.000 line:-10 align:middle
|
||||
This is the fourth subtitle.
|
||||
|
||||
NOTE The positioning alignment should be inherited from align.
|
||||
|
||||
00:07.000 --> 00:08.000 position:10% align:end size:10%
|
||||
This is the fifth subtitle.
|
||||
|
||||
00:06.000 --> 00:07.000 line:-10 align:middle size:35%
|
||||
This is the forth subtitle.
|
@ -15,32 +15,31 @@
|
||||
*/
|
||||
package com.google.android.exoplayer.text.webvtt;
|
||||
|
||||
import android.test.InstrumentationTestCase;
|
||||
import android.text.Layout;
|
||||
|
||||
import com.google.android.exoplayer.text.Cue;
|
||||
|
||||
import android.test.InstrumentationTestCase;
|
||||
import android.text.Layout.Alignment;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Unit test for {@link WebvttParser}.
|
||||
*/
|
||||
public class WebvttParserTest extends InstrumentationTestCase {
|
||||
|
||||
private static final String TYPICAL_WEBVTT_FILE = "webvtt/typical";
|
||||
private static final String TYPICAL_WITH_IDS_WEBVTT_FILE = "webvtt/typical_with_identifiers";
|
||||
private static final String TYPICAL_WITH_TAGS_WEBVTT_FILE = "webvtt/typical_with_tags";
|
||||
private static final String TYPICAL_WITH_COMMENTS_WEBVTT_FILE = "webvtt/typical_with_comments";
|
||||
private static final String TYPICAL_WITH_METADATA_WEBVTT_FILE = "webvtt/typical_with_metadata";
|
||||
private static final String LIVE_TYPICAL_WEBVTT_FILE = "webvtt/live_typical";
|
||||
private static final String EMPTY_WEBVTT_FILE = "webvtt/empty";
|
||||
private static final String TYPICAL_FILE = "webvtt/typical";
|
||||
private static final String TYPICAL_WITH_IDS_FILE = "webvtt/typical_with_identifiers";
|
||||
private static final String TYPICAL_WITH_COMMENTS_FILE = "webvtt/typical_with_comments";
|
||||
private static final String WITH_POSITIONING_FILE = "webvtt/with_positioning";
|
||||
private static final String WITH_TAGS_FILE = "webvtt/with_tags";
|
||||
private static final String EMPTY_FILE = "webvtt/empty";
|
||||
|
||||
public void testParseNullWebvttFile() throws IOException {
|
||||
public void testParseEmpty() throws IOException {
|
||||
WebvttParser parser = new WebvttParser();
|
||||
InputStream inputStream =
|
||||
getInstrumentation().getContext().getResources().getAssets().open(EMPTY_WEBVTT_FILE);
|
||||
|
||||
InputStream inputStream = getInstrumentation().getContext().getResources().getAssets()
|
||||
.open(EMPTY_FILE);
|
||||
try {
|
||||
parser.parse(inputStream);
|
||||
fail("Expected IOException");
|
||||
@ -49,189 +48,113 @@ public class WebvttParserTest extends InstrumentationTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
public void testParseTypicalWebvttFile() throws IOException {
|
||||
public void testParseTypical() throws IOException {
|
||||
WebvttParser parser = new WebvttParser();
|
||||
InputStream inputStream =
|
||||
getInstrumentation().getContext().getResources().getAssets().open(TYPICAL_WEBVTT_FILE);
|
||||
getInstrumentation().getContext().getResources().getAssets().open(TYPICAL_FILE);
|
||||
WebvttSubtitle subtitle = parser.parse(inputStream);
|
||||
|
||||
// test event count
|
||||
assertEquals(4, subtitle.getEventTimeCount());
|
||||
|
||||
// test first cue
|
||||
assertEquals(0, subtitle.getEventTime(0));
|
||||
assertEquals("This is the first subtitle.",
|
||||
subtitle.getCues(subtitle.getEventTime(0)).get(0).text.toString());
|
||||
assertEquals(1234000, subtitle.getEventTime(1));
|
||||
|
||||
// test second cue
|
||||
assertEquals(2345000, subtitle.getEventTime(2));
|
||||
assertEquals("This is the second subtitle.",
|
||||
subtitle.getCues(subtitle.getEventTime(2)).get(0).text.toString());
|
||||
assertEquals(3456000, subtitle.getEventTime(3));
|
||||
// test cues
|
||||
assertCue(subtitle, 0, 0, 1234000, "This is the first subtitle.");
|
||||
assertCue(subtitle, 2, 2345000, 3456000, "This is the second subtitle.");
|
||||
}
|
||||
|
||||
public void testParseTypicalWithIdsWebvttFile() throws IOException {
|
||||
public void testParseTypicalWithIds() throws IOException {
|
||||
WebvttParser parser = new WebvttParser();
|
||||
InputStream inputStream =
|
||||
getInstrumentation().getContext().getResources().getAssets()
|
||||
.open(TYPICAL_WITH_IDS_WEBVTT_FILE);
|
||||
InputStream inputStream = getInstrumentation().getContext().getResources().getAssets()
|
||||
.open(TYPICAL_WITH_IDS_FILE);
|
||||
WebvttSubtitle subtitle = parser.parse(inputStream);
|
||||
|
||||
// test event count
|
||||
assertEquals(4, subtitle.getEventTimeCount());
|
||||
|
||||
// test first cue
|
||||
assertEquals(0, subtitle.getEventTime(0));
|
||||
assertEquals("This is the first subtitle.",
|
||||
subtitle.getCues(subtitle.getEventTime(0)).get(0).text.toString());
|
||||
assertEquals(1234000, subtitle.getEventTime(1));
|
||||
|
||||
// test second cue
|
||||
assertEquals(2345000, subtitle.getEventTime(2));
|
||||
assertEquals("This is the second subtitle.",
|
||||
subtitle.getCues(subtitle.getEventTime(2)).get(0).text.toString());
|
||||
assertEquals(3456000, subtitle.getEventTime(3));
|
||||
// test cues
|
||||
assertCue(subtitle, 0, 0, 1234000, "This is the first subtitle.");
|
||||
assertCue(subtitle, 2, 2345000, 3456000, "This is the second subtitle.");
|
||||
}
|
||||
|
||||
public void testParseTypicalWithTagsWebvttFile() throws IOException {
|
||||
public void testParseTypicalWithComments() throws IOException {
|
||||
WebvttParser parser = new WebvttParser();
|
||||
InputStream inputStream =
|
||||
getInstrumentation().getContext().getResources().getAssets()
|
||||
.open(TYPICAL_WITH_TAGS_WEBVTT_FILE);
|
||||
InputStream inputStream = getInstrumentation().getContext().getResources().getAssets()
|
||||
.open(TYPICAL_WITH_COMMENTS_FILE);
|
||||
WebvttSubtitle subtitle = parser.parse(inputStream);
|
||||
|
||||
// test event count
|
||||
assertEquals(4, subtitle.getEventTimeCount());
|
||||
|
||||
// test cues
|
||||
assertCue(subtitle, 0, 0, 1234000, "This is the first subtitle.");
|
||||
assertCue(subtitle, 2, 2345000, 3456000, "This is the second subtitle.");
|
||||
}
|
||||
|
||||
public void testParseWithTags() throws IOException {
|
||||
WebvttParser parser = new WebvttParser();
|
||||
InputStream inputStream = getInstrumentation().getContext().getResources().getAssets()
|
||||
.open(WITH_TAGS_FILE);
|
||||
WebvttSubtitle subtitle = parser.parse(inputStream);
|
||||
|
||||
// test event count
|
||||
assertEquals(8, subtitle.getEventTimeCount());
|
||||
|
||||
// test first cue
|
||||
assertEquals(0, subtitle.getEventTime(0));
|
||||
assertEquals("This is the first subtitle.",
|
||||
subtitle.getCues(subtitle.getEventTime(0)).get(0).text.toString());
|
||||
assertEquals(1234000, subtitle.getEventTime(1));
|
||||
|
||||
// test second cue
|
||||
assertEquals(2345000, subtitle.getEventTime(2));
|
||||
assertEquals("This is the second subtitle.",
|
||||
subtitle.getCues(subtitle.getEventTime(2)).get(0).text.toString());
|
||||
assertEquals(3456000, subtitle.getEventTime(3));
|
||||
|
||||
// test third cue
|
||||
assertEquals(4000000, subtitle.getEventTime(4));
|
||||
assertEquals("This is the third subtitle.",
|
||||
subtitle.getCues(subtitle.getEventTime(4)).get(0).text.toString());
|
||||
assertEquals(5000000, subtitle.getEventTime(5));
|
||||
|
||||
// test fourth cue
|
||||
assertEquals(6000000, subtitle.getEventTime(6));
|
||||
assertEquals("This is the <fourth> &subtitle.",
|
||||
subtitle.getCues(subtitle.getEventTime(6)).get(0).text.toString());
|
||||
assertEquals(7000000, subtitle.getEventTime(7));
|
||||
// test cues
|
||||
assertCue(subtitle, 0, 0, 1234000, "This is the first subtitle.");
|
||||
assertCue(subtitle, 2, 2345000, 3456000, "This is the second subtitle.");
|
||||
assertCue(subtitle, 4, 4000000, 5000000, "This is the third subtitle.");
|
||||
assertCue(subtitle, 6, 6000000, 7000000, "This is the <fourth> &subtitle.");
|
||||
}
|
||||
|
||||
public void testParseTypicalWithCommentsWebvttFile() throws IOException {
|
||||
public void testParseWithPositioning() throws IOException {
|
||||
WebvttParser parser = new WebvttParser();
|
||||
InputStream inputStream =
|
||||
getInstrumentation().getContext().getResources().getAssets()
|
||||
.open(TYPICAL_WITH_COMMENTS_WEBVTT_FILE);
|
||||
InputStream inputStream = getInstrumentation().getContext().getResources().getAssets()
|
||||
.open(WITH_POSITIONING_FILE);
|
||||
WebvttSubtitle subtitle = parser.parse(inputStream);
|
||||
|
||||
// test event count
|
||||
assertEquals(4, subtitle.getEventTimeCount());
|
||||
assertEquals(10, subtitle.getEventTimeCount());
|
||||
|
||||
// test first cue
|
||||
assertEquals(0, subtitle.getEventTime(0));
|
||||
assertEquals("This is the first subtitle.",
|
||||
subtitle.getCues(subtitle.getEventTime(0)).get(0).text.toString());
|
||||
assertEquals(1234000, subtitle.getEventTime(1));
|
||||
|
||||
// test second cue
|
||||
assertEquals(2345000, subtitle.getEventTime(2));
|
||||
assertEquals("This is the second subtitle.",
|
||||
subtitle.getCues(subtitle.getEventTime(2)).get(0).text.toString());
|
||||
assertEquals(3456000, subtitle.getEventTime(3));
|
||||
// test cues
|
||||
assertCue(subtitle, 0, 0, 1234000, "This is the first subtitle.", Alignment.ALIGN_NORMAL,
|
||||
Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.TYPE_UNSET, 0.1f, Cue.ANCHOR_TYPE_START, 0.35f);
|
||||
assertCue(subtitle, 2, 2345000, 3456000, "This is the second subtitle.",
|
||||
Alignment.ALIGN_OPPOSITE, Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET,
|
||||
Cue.TYPE_UNSET, 0.35f);
|
||||
assertCue(subtitle, 4, 4000000, 5000000, "This is the third subtitle.",
|
||||
Alignment.ALIGN_CENTER, 0.45f, Cue.LINE_TYPE_FRACTION, Cue.ANCHOR_TYPE_END, Cue.DIMEN_UNSET,
|
||||
Cue.TYPE_UNSET, 0.35f);
|
||||
assertCue(subtitle, 6, 6000000, 7000000, "This is the fourth subtitle.",
|
||||
Alignment.ALIGN_CENTER, -10f, Cue.LINE_TYPE_NUMBER, Cue.TYPE_UNSET, Cue.DIMEN_UNSET,
|
||||
Cue.TYPE_UNSET, Cue.DIMEN_UNSET);
|
||||
assertCue(subtitle, 8, 7000000, 8000000, "This is the fifth subtitle.",
|
||||
Alignment.ALIGN_OPPOSITE, Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.TYPE_UNSET, 0.1f,
|
||||
Cue.ANCHOR_TYPE_END, 0.1f);
|
||||
}
|
||||
|
||||
public void testParseTypicalWithMetadataWebvttFile() throws IOException {
|
||||
WebvttParser parser = new WebvttParser();
|
||||
InputStream inputStream =
|
||||
getInstrumentation().getContext().getResources().getAssets()
|
||||
.open(TYPICAL_WITH_METADATA_WEBVTT_FILE);
|
||||
WebvttSubtitle subtitle = parser.parse(inputStream);
|
||||
|
||||
// test event count
|
||||
assertEquals(8, subtitle.getEventTimeCount());
|
||||
|
||||
// test first cue
|
||||
assertEquals(0, subtitle.getEventTime(0));
|
||||
assertEquals("This is the first subtitle.",
|
||||
subtitle.getCues(subtitle.getEventTime(0)).get(0).text.toString());
|
||||
assertEquals(10,
|
||||
subtitle.getCues(subtitle.getEventTime(0)).get(0).position);
|
||||
assertEquals(Layout.Alignment.ALIGN_NORMAL,
|
||||
subtitle.getCues(subtitle.getEventTime(0)).get(0).alignment);
|
||||
assertEquals(35,
|
||||
subtitle.getCues(subtitle.getEventTime(0)).get(0).size);
|
||||
assertEquals(1234000, subtitle.getEventTime(1));
|
||||
|
||||
// test second cue
|
||||
assertEquals(2345000, subtitle.getEventTime(2));
|
||||
assertEquals("This is the second subtitle.",
|
||||
subtitle.getCues(subtitle.getEventTime(2)).get(0).text.toString());
|
||||
assertEquals(Cue.UNSET_VALUE,
|
||||
subtitle.getCues(subtitle.getEventTime(2)).get(0).position);
|
||||
assertEquals(Layout.Alignment.ALIGN_OPPOSITE,
|
||||
subtitle.getCues(subtitle.getEventTime(2)).get(0).alignment);
|
||||
assertEquals(35,
|
||||
subtitle.getCues(subtitle.getEventTime(2)).get(0).size);
|
||||
assertEquals(3456000, subtitle.getEventTime(3));
|
||||
|
||||
// test third cue
|
||||
assertEquals(4000000, subtitle.getEventTime(4));
|
||||
assertEquals("This is the third subtitle.",
|
||||
subtitle.getCues(subtitle.getEventTime(4)).get(0).text.toString());
|
||||
assertEquals(45,
|
||||
subtitle.getCues(subtitle.getEventTime(4)).get(0).line);
|
||||
assertEquals(Layout.Alignment.ALIGN_CENTER,
|
||||
subtitle.getCues(subtitle.getEventTime(4)).get(0).alignment);
|
||||
assertEquals(35,
|
||||
subtitle.getCues(subtitle.getEventTime(4)).get(0).size);
|
||||
assertEquals(5000000, subtitle.getEventTime(5));
|
||||
|
||||
// test forth cue
|
||||
assertEquals(6000000, subtitle.getEventTime(6));
|
||||
assertEquals("This is the forth subtitle.",
|
||||
subtitle.getCues(subtitle.getEventTime(6)).get(0).text.toString());
|
||||
assertEquals(-10,
|
||||
subtitle.getCues(subtitle.getEventTime(6)).get(0).line);
|
||||
assertEquals(Layout.Alignment.ALIGN_CENTER,
|
||||
subtitle.getCues(subtitle.getEventTime(6)).get(0).alignment);
|
||||
assertEquals(35,
|
||||
subtitle.getCues(subtitle.getEventTime(6)).get(0).size);
|
||||
assertEquals(7000000, subtitle.getEventTime(7));
|
||||
private static void assertCue(WebvttSubtitle subtitle, int eventTimeIndex, long startTimeUs,
|
||||
int endTimeUs, String text) {
|
||||
assertCue(subtitle, eventTimeIndex, startTimeUs, endTimeUs, text, null, Cue.DIMEN_UNSET,
|
||||
Cue.TYPE_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET);
|
||||
}
|
||||
|
||||
public void testParseLiveTypicalWebvttFile() throws IOException {
|
||||
WebvttParser parser = new WebvttParser();
|
||||
InputStream inputStream =
|
||||
getInstrumentation().getContext().getResources().getAssets().open(LIVE_TYPICAL_WEBVTT_FILE);
|
||||
WebvttSubtitle subtitle = parser.parse(inputStream);
|
||||
|
||||
// test event count
|
||||
long startTimeUs = 0;
|
||||
assertEquals(4, subtitle.getEventTimeCount());
|
||||
|
||||
// test first cue
|
||||
assertEquals(startTimeUs, subtitle.getEventTime(0));
|
||||
assertEquals("This is the first subtitle.",
|
||||
subtitle.getCues(subtitle.getEventTime(0)).get(0).text.toString());
|
||||
assertEquals(startTimeUs + 1234000, subtitle.getEventTime(1));
|
||||
|
||||
// test second cue
|
||||
assertEquals(startTimeUs + 2345000, subtitle.getEventTime(2));
|
||||
assertEquals("This is the second subtitle.",
|
||||
subtitle.getCues(subtitle.getEventTime(2)).get(0).text.toString());
|
||||
assertEquals(startTimeUs + 3456000, subtitle.getEventTime(3));
|
||||
private static void assertCue(WebvttSubtitle subtitle, int eventTimeIndex, long startTimeUs,
|
||||
int endTimeUs, String text, Alignment textAlignment, float line, int lineType, int lineAnchor,
|
||||
float position, int positionAnchor, float size) {
|
||||
assertEquals(startTimeUs, subtitle.getEventTime(eventTimeIndex));
|
||||
assertEquals(endTimeUs, subtitle.getEventTime(eventTimeIndex + 1));
|
||||
List<Cue> cues = subtitle.getCues(subtitle.getEventTime(eventTimeIndex));
|
||||
assertEquals(1, cues.size());
|
||||
// Assert cue properties
|
||||
Cue cue = cues.get(0);
|
||||
assertEquals(text, cue.text.toString());
|
||||
assertEquals(textAlignment, cue.textAlignment);
|
||||
assertEquals(line, cue.line);
|
||||
assertEquals(lineType, cue.lineType);
|
||||
assertEquals(lineAnchor, cue.lineAnchor);
|
||||
assertEquals(position, cue.position);
|
||||
assertEquals(positionAnchor, cue.positionAnchor);
|
||||
assertEquals(size, cue.size);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,30 +23,117 @@ import android.text.Layout.Alignment;
|
||||
public class Cue {
|
||||
|
||||
/**
|
||||
* Used by some methods to indicate that no value is set.
|
||||
* An unset position or width.
|
||||
*/
|
||||
public static final int UNSET_VALUE = -1;
|
||||
public static final float DIMEN_UNSET = Float.MIN_VALUE;
|
||||
/**
|
||||
* An unset anchor or line type value.
|
||||
*/
|
||||
public static final int TYPE_UNSET = Integer.MIN_VALUE;
|
||||
/**
|
||||
* Anchors the left (for horizontal positions) or top (for vertical positions) edge of the cue
|
||||
* box.
|
||||
*/
|
||||
public static final int ANCHOR_TYPE_START = 0;
|
||||
/**
|
||||
* Anchors the middle of the cue box.
|
||||
*/
|
||||
public static final int ANCHOR_TYPE_MIDDLE = 1;
|
||||
/**
|
||||
* Anchors the right (for horizontal positions) or bottom (for vertical positions) edge of the cue
|
||||
* box.
|
||||
*/
|
||||
public static final int ANCHOR_TYPE_END = 2;
|
||||
/**
|
||||
* Value for {@link #lineType} when {@link #line} is a fractional position.
|
||||
*/
|
||||
public static final int LINE_TYPE_FRACTION = 0;
|
||||
/**
|
||||
* Value for {@link #lineType} when {@link #line} is a line number.
|
||||
*/
|
||||
public static final int LINE_TYPE_NUMBER = 1;
|
||||
|
||||
/**
|
||||
* The cue text. Note the {@link CharSequence} may be decorated with styling spans.
|
||||
*/
|
||||
public final CharSequence text;
|
||||
|
||||
public final int line;
|
||||
public final int position;
|
||||
public final Alignment alignment;
|
||||
public final int size;
|
||||
/**
|
||||
* The alignment of the cue text within the cue box.
|
||||
*/
|
||||
public final Alignment textAlignment;
|
||||
/**
|
||||
* The position of the {@link #lineAnchor} of the cue box within the viewport in the direction
|
||||
* orthogonal to the writing direction, or {@link #DIMEN_UNSET}. When set, the interpretation of
|
||||
* the value depends on the value of {@link #lineType}.
|
||||
* <p>
|
||||
* For horizontal text and {@link #lineType} equal to {@link #LINE_TYPE_FRACTION}, this is the
|
||||
* fractional vertical position relative to the top of the viewport.
|
||||
*/
|
||||
public final float line;
|
||||
/**
|
||||
* The type of the {@link #line} value.
|
||||
* <p>
|
||||
* {@link #LINE_TYPE_FRACTION} indicates that {@link #line} is a fractional position within the
|
||||
* viewport.
|
||||
* <p>
|
||||
* {@link #LINE_TYPE_NUMBER} indicates that {@link #line} is a line number, where the size of each
|
||||
* line is taken to be the size of the first line of the cue. When {@link #line} is greater than
|
||||
* or equal to 0, lines count from the start of the viewport (the first line is numbered 0). When
|
||||
* {@link #line} is negative, lines count from the end of the viewport (the last line is numbered
|
||||
* -1). For horizontal text the size of the first line of the cue is its height, and the start
|
||||
* and end of the viewport are the top and bottom respectively.
|
||||
*/
|
||||
public final int lineType;
|
||||
/**
|
||||
* The cue box anchor positioned by {@link #line}. One of {@link #ANCHOR_TYPE_START},
|
||||
* {@link #ANCHOR_TYPE_MIDDLE}, {@link #ANCHOR_TYPE_END} and {@link #TYPE_UNSET}.
|
||||
* <p>
|
||||
* For the normal case of horizontal text, {@link #ANCHOR_TYPE_START}, {@link #ANCHOR_TYPE_MIDDLE}
|
||||
* and {@link #ANCHOR_TYPE_END} correspond to the top, middle and bottom of the cue box
|
||||
* respectively.
|
||||
*/
|
||||
public final int lineAnchor;
|
||||
/**
|
||||
* The fractional position of the {@link #positionAnchor} of the cue box within the viewport in
|
||||
* the direction orthogonal to {@link #line}, or {@link #DIMEN_UNSET}.
|
||||
* <p>
|
||||
* For horizontal text, this is the horizontal position relative to the left of the viewport. Note
|
||||
* that positioning is relative to the left of the viewport even in the case of right-to-left
|
||||
* text.
|
||||
*/
|
||||
public final float position;
|
||||
/**
|
||||
* The cue box anchor positioned by {@link #position}. One of {@link #ANCHOR_TYPE_START},
|
||||
* {@link #ANCHOR_TYPE_MIDDLE}, {@link #ANCHOR_TYPE_END} and {@link #TYPE_UNSET}.
|
||||
* <p>
|
||||
* For the normal case of horizontal text, {@link #ANCHOR_TYPE_START}, {@link #ANCHOR_TYPE_MIDDLE}
|
||||
* and {@link #ANCHOR_TYPE_END} correspond to the left, middle and right of the cue box
|
||||
* respectively.
|
||||
*/
|
||||
public final int positionAnchor;
|
||||
/**
|
||||
* The size of the cue box in the writing direction specified as a fraction of the viewport size
|
||||
* in that direction, or {@link #DIMEN_UNSET}.
|
||||
*/
|
||||
public final float size;
|
||||
|
||||
public Cue() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public Cue(CharSequence text) {
|
||||
this(text, UNSET_VALUE, UNSET_VALUE, null, UNSET_VALUE);
|
||||
this(text, null, DIMEN_UNSET, TYPE_UNSET, TYPE_UNSET, DIMEN_UNSET, TYPE_UNSET, DIMEN_UNSET);
|
||||
}
|
||||
|
||||
public Cue(CharSequence text, int line, int position, Alignment alignment, int size) {
|
||||
public Cue(CharSequence text, Alignment textAlignment, float line, int lineType,
|
||||
int lineAnchor, float position, int positionAnchor, float size) {
|
||||
this.text = text;
|
||||
this.textAlignment = textAlignment;
|
||||
this.line = line;
|
||||
this.lineType = lineType;
|
||||
this.lineAnchor = lineAnchor;
|
||||
this.position = position;
|
||||
this.alignment = alignment;
|
||||
this.positionAnchor = positionAnchor;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
|
@ -63,8 +63,13 @@ import android.util.Log;
|
||||
|
||||
// Previous input variables.
|
||||
private CharSequence cueText;
|
||||
private int cuePosition;
|
||||
private Alignment cueAlignment;
|
||||
private Alignment cueTextAlignment;
|
||||
private float cueLine;
|
||||
private int cueLineType;
|
||||
private int cueLineAnchor;
|
||||
private float cuePosition;
|
||||
private int cuePositionAnchor;
|
||||
private float cueSize;
|
||||
private boolean applyEmbeddedStyles;
|
||||
private int foregroundColor;
|
||||
private int backgroundColor;
|
||||
@ -120,7 +125,7 @@ import android.util.Log;
|
||||
* @param style The style to use when drawing the cue text.
|
||||
* @param textSizePx The text size to use when drawing the cue text, in pixels.
|
||||
* @param bottomPaddingFraction The bottom padding fraction to apply when {@link Cue#line} is
|
||||
* {@link Cue#UNSET_VALUE}, as a fraction of the viewport height
|
||||
* {@link Cue#DIMEN_UNSET}, as a fraction of the viewport height
|
||||
* @param canvas The canvas into which to draw.
|
||||
* @param cueBoxLeft The left position of the enclosing cue box.
|
||||
* @param cueBoxTop The top position of the enclosing cue box.
|
||||
@ -140,8 +145,13 @@ import android.util.Log;
|
||||
cueText = cueText.toString();
|
||||
}
|
||||
if (areCharSequencesEqual(this.cueText, cueText)
|
||||
&& Util.areEqual(this.cueTextAlignment, cue.textAlignment)
|
||||
&& this.cueLine == cue.line
|
||||
&& this.cueLineType == cue.lineType
|
||||
&& Util.areEqual(this.cueLineAnchor, cue.lineAnchor)
|
||||
&& this.cuePosition == cue.position
|
||||
&& Util.areEqual(this.cueAlignment, cue.alignment)
|
||||
&& Util.areEqual(this.cuePositionAnchor, cue.positionAnchor)
|
||||
&& this.cueSize == cue.size
|
||||
&& this.applyEmbeddedStyles == applyEmbeddedStyles
|
||||
&& this.foregroundColor == style.foregroundColor
|
||||
&& this.backgroundColor == style.backgroundColor
|
||||
@ -161,8 +171,13 @@ import android.util.Log;
|
||||
}
|
||||
|
||||
this.cueText = cueText;
|
||||
this.cueTextAlignment = cue.textAlignment;
|
||||
this.cueLine = cue.line;
|
||||
this.cueLineType = cue.lineType;
|
||||
this.cueLineAnchor = cue.lineAnchor;
|
||||
this.cuePosition = cue.position;
|
||||
this.cueAlignment = cue.alignment;
|
||||
this.cuePositionAnchor = cue.positionAnchor;
|
||||
this.cueSize = cue.size;
|
||||
this.applyEmbeddedStyles = applyEmbeddedStyles;
|
||||
this.foregroundColor = style.foregroundColor;
|
||||
this.backgroundColor = style.backgroundColor;
|
||||
@ -182,16 +197,19 @@ import android.util.Log;
|
||||
|
||||
textPaint.setTextSize(textSizePx);
|
||||
int textPaddingX = (int) (textSizePx * INNER_PADDING_RATIO + 0.5f);
|
||||
|
||||
int availableWidth = parentWidth - textPaddingX * 2;
|
||||
if (cueSize != Cue.DIMEN_UNSET) {
|
||||
availableWidth = (int) (availableWidth * cueSize);
|
||||
}
|
||||
if (availableWidth <= 0) {
|
||||
Log.w(TAG, "Skipped drawing subtitle cue (insufficient space)");
|
||||
return;
|
||||
}
|
||||
|
||||
Alignment layoutAlignment = cueAlignment == null ? Alignment.ALIGN_CENTER : cueAlignment;
|
||||
textLayout = new StaticLayout(cueText, textPaint, availableWidth, layoutAlignment, spacingMult,
|
||||
Alignment textAlignment = cueTextAlignment == null ? Alignment.ALIGN_CENTER : cueTextAlignment;
|
||||
textLayout = new StaticLayout(cueText, textPaint, availableWidth, textAlignment, spacingMult,
|
||||
spacingAdd, true);
|
||||
|
||||
int textHeight = textLayout.getHeight();
|
||||
int textWidth = 0;
|
||||
int lineCount = textLayout.getLineCount();
|
||||
@ -202,14 +220,13 @@ import android.util.Log;
|
||||
|
||||
int textLeft;
|
||||
int textRight;
|
||||
if (cue.position != Cue.UNSET_VALUE) {
|
||||
if (cue.alignment == Alignment.ALIGN_OPPOSITE) {
|
||||
textRight = (parentWidth * cue.position) / 100 + parentLeft;
|
||||
textLeft = Math.max(textRight - textWidth, parentLeft);
|
||||
} else {
|
||||
textLeft = (parentWidth * cue.position) / 100 + parentLeft;
|
||||
if (cuePosition != Cue.DIMEN_UNSET) {
|
||||
int anchorPosition = Math.round(parentWidth * cuePosition) + parentLeft;
|
||||
textLeft = cuePositionAnchor == Cue.ANCHOR_TYPE_END ? anchorPosition - textWidth
|
||||
: cuePositionAnchor == Cue.ANCHOR_TYPE_MIDDLE ? (anchorPosition * 2 - textWidth) / 2
|
||||
: anchorPosition;
|
||||
textLeft = Math.max(textLeft, parentLeft);
|
||||
textRight = Math.min(textLeft + textWidth, parentRight);
|
||||
}
|
||||
} else {
|
||||
textLeft = (parentWidth - textWidth) / 2;
|
||||
textRight = textLeft + textWidth;
|
||||
@ -217,12 +234,29 @@ import android.util.Log;
|
||||
|
||||
int textTop;
|
||||
int textBottom;
|
||||
if (cue.line != Cue.UNSET_VALUE) {
|
||||
textTop = (parentHeight * cue.line) / 100 + parentTop;
|
||||
if (cueLine != Cue.DIMEN_UNSET) {
|
||||
int anchorPosition;
|
||||
if (cueLineType == Cue.LINE_TYPE_FRACTION) {
|
||||
anchorPosition = Math.round(parentHeight * cueLine) + parentTop;
|
||||
} else {
|
||||
// cueLineType == Cue.LINE_TYPE_NUMBER
|
||||
int firstLineHeight = textLayout.getLineBottom(0) - textLayout.getLineTop(0);
|
||||
if (cueLine >= 0) {
|
||||
anchorPosition = Math.round(cueLine * firstLineHeight) + parentTop;
|
||||
} else {
|
||||
anchorPosition = Math.round(cueLine * firstLineHeight) + parentBottom;
|
||||
}
|
||||
}
|
||||
textTop = cueLineAnchor == Cue.ANCHOR_TYPE_END ? anchorPosition - textHeight
|
||||
: cueLineAnchor == Cue.ANCHOR_TYPE_MIDDLE ? (anchorPosition * 2 - textHeight) / 2
|
||||
: anchorPosition;
|
||||
textBottom = textTop + textHeight;
|
||||
if (textBottom > parentBottom) {
|
||||
textTop = parentBottom - textHeight;
|
||||
textBottom = parentBottom;
|
||||
} else if (textTop < parentTop) {
|
||||
textTop = parentTop;
|
||||
textBottom = parentTop + textHeight;
|
||||
}
|
||||
} else {
|
||||
textTop = parentBottom - textHeight - (int) (parentHeight * bottomPaddingFraction);
|
||||
@ -232,7 +266,7 @@ import android.util.Log;
|
||||
textWidth = textRight - textLeft;
|
||||
|
||||
// Update the derived drawing variables.
|
||||
this.textLayout = new StaticLayout(cueText, textPaint, textWidth, layoutAlignment, spacingMult,
|
||||
this.textLayout = new StaticLayout(cueText, textPaint, textWidth, textAlignment, spacingMult,
|
||||
spacingAdd, true);
|
||||
this.textLeft = textLeft;
|
||||
this.textTop = textTop;
|
||||
|
@ -38,7 +38,7 @@ public final class SubtitleLayout extends View {
|
||||
public static final float DEFAULT_TEXT_SIZE_FRACTION = 0.0533f;
|
||||
|
||||
/**
|
||||
* The default bottom padding to apply when {@link Cue#line} is {@link Cue#UNSET_VALUE}, as a
|
||||
* The default bottom padding to apply when {@link Cue#line} is {@link Cue#DIMEN_UNSET}, as a
|
||||
* fraction of the viewport height.
|
||||
*
|
||||
* @see #setBottomPaddingFraction(float)
|
||||
@ -174,7 +174,7 @@ public final class SubtitleLayout extends View {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the bottom padding fraction to apply when {@link Cue#line} is {@link Cue#UNSET_VALUE},
|
||||
* Sets the bottom padding fraction to apply when {@link Cue#line} is {@link Cue#DIMEN_UNSET},
|
||||
* as a fraction of the view's remaining height after its top and bottom padding have been
|
||||
* subtracted.
|
||||
* <p>
|
||||
|
@ -28,16 +28,17 @@ import android.text.Layout.Alignment;
|
||||
public final long endTime;
|
||||
|
||||
public WebvttCue(CharSequence text) {
|
||||
this(Cue.UNSET_VALUE, Cue.UNSET_VALUE, text);
|
||||
this(0, 0, text);
|
||||
}
|
||||
|
||||
public WebvttCue(long startTime, long endTime, CharSequence text) {
|
||||
this(startTime, endTime, text, Cue.UNSET_VALUE, Cue.UNSET_VALUE, null, Cue.UNSET_VALUE);
|
||||
this(startTime, endTime, text, null, Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.TYPE_UNSET,
|
||||
Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET);
|
||||
}
|
||||
|
||||
public WebvttCue(long startTime, long endTime, CharSequence text, int line, int position,
|
||||
Alignment alignment, int size) {
|
||||
super(text, line, position, alignment, size);
|
||||
public WebvttCue(long startTime, long endTime, CharSequence text, Alignment textAlignment,
|
||||
float line, int lineType, int lineAnchor, float position, int positionAnchor, float width) {
|
||||
super(text, textAlignment, line, lineType, lineAnchor, position, positionAnchor, width);
|
||||
this.startTime = startTime;
|
||||
this.endTime = endTime;
|
||||
}
|
||||
@ -49,7 +50,7 @@ import android.text.Layout.Alignment;
|
||||
* @return True if this cue should be placed in the default position; false otherwise.
|
||||
*/
|
||||
public boolean isNormalCue() {
|
||||
return (line == UNSET_VALUE && position == UNSET_VALUE);
|
||||
return (line == DIMEN_UNSET && position == DIMEN_UNSET);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ import com.google.android.exoplayer.util.MimeTypes;
|
||||
import android.text.Html;
|
||||
import android.text.Layout.Alignment;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
@ -43,34 +42,15 @@ public final class WebvttParser implements SubtitleParser {
|
||||
|
||||
private static final String TAG = "WebvttParser";
|
||||
|
||||
private static final String WEBVTT_FILE_HEADER_STRING = "^\uFEFF?WEBVTT((\u0020|\u0009).*)?$";
|
||||
private static final Pattern WEBVTT_FILE_HEADER =
|
||||
Pattern.compile(WEBVTT_FILE_HEADER_STRING);
|
||||
|
||||
private static final String WEBVTT_COMMENT_BLOCK_STRING = "^NOTE((\u0020|\u0009).*)?$";
|
||||
private static final Pattern WEBVTT_COMMENT_BLOCK =
|
||||
Pattern.compile(WEBVTT_COMMENT_BLOCK_STRING);
|
||||
|
||||
private static final String WEBVTT_METADATA_HEADER_STRING = "\\S*[:=]\\S*";
|
||||
private static final Pattern WEBVTT_METADATA_HEADER =
|
||||
Pattern.compile(WEBVTT_METADATA_HEADER_STRING);
|
||||
|
||||
private static final String WEBVTT_CUE_IDENTIFIER_STRING = "^(?!.*(-->)).*$";
|
||||
private static final Pattern WEBVTT_CUE_IDENTIFIER =
|
||||
Pattern.compile(WEBVTT_CUE_IDENTIFIER_STRING);
|
||||
|
||||
private static final String WEBVTT_TIMESTAMP_STRING = "(\\d+:)?[0-5]\\d:[0-5]\\d\\.\\d{3}";
|
||||
private static final Pattern WEBVTT_TIMESTAMP = Pattern.compile(WEBVTT_TIMESTAMP_STRING);
|
||||
|
||||
private static final String WEBVTT_CUE_SETTING_STRING = "\\S*:\\S*";
|
||||
private static final Pattern WEBVTT_CUE_SETTING = Pattern.compile(WEBVTT_CUE_SETTING_STRING);
|
||||
|
||||
private static final String WEBVTT_PERCENTAGE_NUMBER_STRING = "^([0-9]+|[0-9]+\\.[0-9]+)$";
|
||||
|
||||
private static final String NON_NUMERIC_STRING = ".*[^0-9].*";
|
||||
private static final Pattern HEADER = Pattern.compile("^\uFEFF?WEBVTT((\u0020|\u0009).*)?$");
|
||||
private static final Pattern COMMENT_BLOCK = Pattern.compile("^NOTE((\u0020|\u0009).*)?$");
|
||||
private static final Pattern METADATA_HEADER = Pattern.compile("\\S*[:=]\\S*");
|
||||
private static final Pattern CUE_IDENTIFIER = Pattern.compile("^(?!.*(-->)).*$");
|
||||
private static final Pattern TIMESTAMP = Pattern.compile("(\\d+:)?[0-5]\\d:[0-5]\\d\\.\\d{3}");
|
||||
private static final Pattern CUE_SETTING = Pattern.compile("\\S*:\\S*");
|
||||
|
||||
private final PositionHolder positionHolder;
|
||||
private final StringBuilder textBuilder;
|
||||
|
||||
private final boolean strictParsing;
|
||||
|
||||
/**
|
||||
@ -88,6 +68,7 @@ public final class WebvttParser implements SubtitleParser {
|
||||
*/
|
||||
public WebvttParser(boolean strictParsing) {
|
||||
this.strictParsing = strictParsing;
|
||||
positionHolder = new PositionHolder();
|
||||
textBuilder = new StringBuilder();
|
||||
}
|
||||
|
||||
@ -100,7 +81,7 @@ public final class WebvttParser implements SubtitleParser {
|
||||
|
||||
// file should start with "WEBVTT"
|
||||
line = webvttData.readLine();
|
||||
if (line == null || !WEBVTT_FILE_HEADER.matcher(line).matches()) {
|
||||
if (line == null || !HEADER.matcher(line).matches()) {
|
||||
throw new ParserException("Expected WEBVTT. Got " + line);
|
||||
}
|
||||
|
||||
@ -116,7 +97,7 @@ public final class WebvttParser implements SubtitleParser {
|
||||
}
|
||||
|
||||
if (strictParsing) {
|
||||
Matcher matcher = WEBVTT_METADATA_HEADER.matcher(line);
|
||||
Matcher matcher = METADATA_HEADER.matcher(line);
|
||||
if (!matcher.find()) {
|
||||
throw new ParserException("Unexpected line: " + line);
|
||||
}
|
||||
@ -126,38 +107,45 @@ public final class WebvttParser implements SubtitleParser {
|
||||
// process the cues and text
|
||||
while ((line = webvttData.readLine()) != null) {
|
||||
// parse webvtt comment block in case it is present
|
||||
Matcher matcher = WEBVTT_COMMENT_BLOCK.matcher(line);
|
||||
Matcher matcher = COMMENT_BLOCK.matcher(line);
|
||||
if (matcher.find()) {
|
||||
// read lines until finding an empty one (webvtt line terminator: CRLF, or LF or CR)
|
||||
while (((line = webvttData.readLine()) != null) && (!line.isEmpty())) {
|
||||
// just ignoring comment text
|
||||
while ((line = webvttData.readLine()) != null && !line.isEmpty()) {
|
||||
// ignore comment text
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// parse the cue identifier (if present) {
|
||||
matcher = WEBVTT_CUE_IDENTIFIER.matcher(line);
|
||||
matcher = CUE_IDENTIFIER.matcher(line);
|
||||
if (matcher.find()) {
|
||||
// ignore the identifier (we currently don't use it) and read the next line
|
||||
line = webvttData.readLine();
|
||||
if (line == null) {
|
||||
// end of file
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
long startTime = Cue.UNSET_VALUE;
|
||||
long endTime = Cue.UNSET_VALUE;
|
||||
CharSequence text = null;
|
||||
int lineNum = Cue.UNSET_VALUE;
|
||||
int position = Cue.UNSET_VALUE;
|
||||
Alignment alignment = null;
|
||||
int size = Cue.UNSET_VALUE;
|
||||
long cueStartTime;
|
||||
long cueEndTime;
|
||||
CharSequence cueText;
|
||||
Alignment cueTextAlignment = null;
|
||||
float cueLine = Cue.DIMEN_UNSET;
|
||||
int cueLineType = Cue.TYPE_UNSET;
|
||||
int cueLineAnchor = Cue.TYPE_UNSET;
|
||||
float cuePosition = Cue.DIMEN_UNSET;
|
||||
int cuePositionAnchor = Cue.TYPE_UNSET;
|
||||
float cueWidth = Cue.DIMEN_UNSET;
|
||||
|
||||
// parse the cue timestamps
|
||||
matcher = WEBVTT_TIMESTAMP.matcher(line);
|
||||
matcher = TIMESTAMP.matcher(line);
|
||||
|
||||
// parse start timestamp
|
||||
if (!matcher.find()) {
|
||||
throw new ParserException("Expected cue start time: " + line);
|
||||
} else {
|
||||
startTime = parseTimestampUs(matcher.group());
|
||||
cueStartTime = parseTimestampUs(matcher.group());
|
||||
}
|
||||
|
||||
// parse end timestamp
|
||||
@ -166,12 +154,12 @@ public final class WebvttParser implements SubtitleParser {
|
||||
throw new ParserException("Expected cue end time: " + line);
|
||||
} else {
|
||||
endTimeString = matcher.group();
|
||||
endTime = parseTimestampUs(endTimeString);
|
||||
cueEndTime = parseTimestampUs(endTimeString);
|
||||
}
|
||||
|
||||
// parse the (optional) cue setting list
|
||||
line = line.substring(line.indexOf(endTimeString) + endTimeString.length());
|
||||
matcher = WEBVTT_CUE_SETTING.matcher(line);
|
||||
matcher = CUE_SETTING.matcher(line);
|
||||
while (matcher.find()) {
|
||||
String match = matcher.group();
|
||||
String[] parts = match.split(":", 2);
|
||||
@ -180,52 +168,44 @@ public final class WebvttParser implements SubtitleParser {
|
||||
|
||||
try {
|
||||
if ("line".equals(name)) {
|
||||
Pair<String, Alignment> lineMetadata = parseLinePositionAttributes(value);
|
||||
value = lineMetadata.first;
|
||||
if (value.endsWith("%")) {
|
||||
lineNum = parseIntPercentage(value);
|
||||
} else {
|
||||
// Following WebVTT spec, line number can be a negative number
|
||||
int sign = 1;
|
||||
if (value.startsWith("-") && value.length() > 1) {
|
||||
sign = -1;
|
||||
value = value.substring(1);
|
||||
}
|
||||
|
||||
if (value.matches(NON_NUMERIC_STRING)) {
|
||||
Log.w(TAG, "Invalid line value: " + value);
|
||||
} else {
|
||||
lineNum = sign * Integer.parseInt(value);
|
||||
}
|
||||
}
|
||||
parseLineAttribute(value, positionHolder);
|
||||
cueLine = positionHolder.position;
|
||||
cueLineType = positionHolder.lineType;
|
||||
cueLineAnchor = positionHolder.positionAnchor;
|
||||
} else if ("align".equals(name)) {
|
||||
// TODO: handle for RTL languages
|
||||
alignment = parseAlignment(value);
|
||||
cueTextAlignment = parseTextAlignment(value);
|
||||
} else if ("position".equals(name)) {
|
||||
Pair<String, Alignment> lineMetadata = parseLinePositionAttributes(value);
|
||||
value = lineMetadata.first;
|
||||
position = parseIntPercentage(value);
|
||||
parsePositionAttribute(value, positionHolder);
|
||||
cuePosition = positionHolder.position;
|
||||
cuePositionAnchor = positionHolder.positionAnchor;
|
||||
} else if ("size".equals(name)) {
|
||||
size = parseIntPercentage(value);
|
||||
cueWidth = parsePercentage(value);
|
||||
} else {
|
||||
Log.w(TAG, "Unknown cue setting " + name + ":" + value);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
Log.w(TAG, name + " contains an invalid value " + value, e);
|
||||
Log.w(TAG, e.getMessage() + ": " + match);
|
||||
}
|
||||
}
|
||||
|
||||
if (cuePosition != Cue.DIMEN_UNSET && cuePositionAnchor == Cue.TYPE_UNSET) {
|
||||
// Computed position alignment should be derived from the text alignment if it has not been
|
||||
// set explicitly.
|
||||
cuePositionAnchor = alignmentToAnchor(cueTextAlignment);
|
||||
}
|
||||
|
||||
// parse text
|
||||
textBuilder.setLength(0);
|
||||
while (((line = webvttData.readLine()) != null) && (!line.isEmpty())) {
|
||||
while ((line = webvttData.readLine()) != null && !line.isEmpty()) {
|
||||
if (textBuilder.length() > 0) {
|
||||
textBuilder.append("<br>");
|
||||
}
|
||||
textBuilder.append(line.trim());
|
||||
}
|
||||
text = Html.fromHtml(textBuilder.toString());
|
||||
cueText = Html.fromHtml(textBuilder.toString());
|
||||
|
||||
WebvttCue cue = new WebvttCue(startTime, endTime, text, lineNum, position, alignment, size);
|
||||
WebvttCue cue = new WebvttCue(cueStartTime, cueEndTime, cueText, cueTextAlignment, cueLine,
|
||||
cueLineType, cueLineAnchor, cuePosition, cuePositionAnchor, cueWidth);
|
||||
subtitles.add(cue);
|
||||
}
|
||||
|
||||
@ -237,67 +217,116 @@ public final class WebvttParser implements SubtitleParser {
|
||||
return MimeTypes.TEXT_VTT.equals(mimeType);
|
||||
}
|
||||
|
||||
private static int parseIntPercentage(String s) throws NumberFormatException {
|
||||
if (!s.endsWith("%")) {
|
||||
throw new NumberFormatException(s + " doesn't end with '%'");
|
||||
}
|
||||
|
||||
s = s.substring(0, s.length() - 1);
|
||||
if (!s.matches(WEBVTT_PERCENTAGE_NUMBER_STRING)) {
|
||||
throw new NumberFormatException(s + " contains an invalid character");
|
||||
}
|
||||
|
||||
int value = Math.round(Float.parseFloat(s));
|
||||
if (value < 0 || value > 100) {
|
||||
throw new NumberFormatException(value + " is out of range [0-100]");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private static long parseTimestampUs(String s) throws NumberFormatException {
|
||||
if (!s.matches(WEBVTT_TIMESTAMP_STRING)) {
|
||||
throw new NumberFormatException("has invalid format");
|
||||
}
|
||||
|
||||
String[] parts = s.split("\\.", 2);
|
||||
long value = 0;
|
||||
for (String group : parts[0].split(":")) {
|
||||
value = value * 60 + Long.parseLong(group);
|
||||
String[] parts = s.split("\\.", 2);
|
||||
String[] subparts = parts[0].split(":");
|
||||
for (int i = 0; i < subparts.length; i++) {
|
||||
value = value * 60 + Long.parseLong(subparts[i]);
|
||||
}
|
||||
return (value * 1000 + Long.parseLong(parts[1])) * 1000;
|
||||
}
|
||||
|
||||
private static Pair<String, Alignment> parseLinePositionAttributes(String s) {
|
||||
String value;
|
||||
Alignment alignment = null;
|
||||
|
||||
int commaPos;
|
||||
if ((commaPos = s.indexOf(",")) > 0 && commaPos < s.length() - 1) {
|
||||
alignment = parseAlignment(s.substring(commaPos + 1));
|
||||
value = s.substring(0, commaPos);
|
||||
private static void parseLineAttribute(String s, PositionHolder out)
|
||||
throws NumberFormatException {
|
||||
int lineAnchor;
|
||||
int commaPosition = s.indexOf(",");
|
||||
if (commaPosition != -1) {
|
||||
lineAnchor = parsePositionAnchor(s.substring(commaPosition + 1));
|
||||
s = s.substring(0, commaPosition);
|
||||
} else {
|
||||
value = s;
|
||||
lineAnchor = Cue.TYPE_UNSET;
|
||||
}
|
||||
|
||||
return new Pair<String, Alignment>(value, alignment);
|
||||
}
|
||||
|
||||
private static Alignment parseAlignment(String s) {
|
||||
Alignment alignment = null;
|
||||
if ("start".equals(s)) {
|
||||
alignment = Alignment.ALIGN_NORMAL;
|
||||
} else if ("middle".equals(s)) {
|
||||
alignment = Alignment.ALIGN_CENTER;
|
||||
} else if ("end".equals(s)) {
|
||||
alignment = Alignment.ALIGN_OPPOSITE;
|
||||
} else if ("left".equals(s)) {
|
||||
alignment = Alignment.ALIGN_NORMAL;
|
||||
} else if ("right".equals(s)) {
|
||||
alignment = Alignment.ALIGN_OPPOSITE;
|
||||
float line;
|
||||
int lineType;
|
||||
if (s.endsWith("%")) {
|
||||
line = parsePercentage(s);
|
||||
lineType = Cue.LINE_TYPE_FRACTION;
|
||||
} else {
|
||||
Log.w(TAG, "Invalid align value: " + s);
|
||||
line = Integer.parseInt(s);
|
||||
lineType = Cue.LINE_TYPE_NUMBER;
|
||||
}
|
||||
return alignment;
|
||||
out.position = line;
|
||||
out.positionAnchor = lineAnchor;
|
||||
out.lineType = lineType;
|
||||
}
|
||||
|
||||
private static void parsePositionAttribute(String s, PositionHolder out)
|
||||
throws NumberFormatException {
|
||||
int positionAnchor;
|
||||
int commaPosition = s.indexOf(",");
|
||||
if (commaPosition != -1) {
|
||||
positionAnchor = parsePositionAnchor(s.substring(commaPosition + 1));
|
||||
s = s.substring(0, commaPosition);
|
||||
} else {
|
||||
positionAnchor = Cue.TYPE_UNSET;
|
||||
}
|
||||
out.position = parsePercentage(s);
|
||||
out.positionAnchor = positionAnchor;
|
||||
out.lineType = Cue.TYPE_UNSET;
|
||||
}
|
||||
|
||||
private static float parsePercentage(String s) throws NumberFormatException {
|
||||
if (!s.endsWith("%")) {
|
||||
throw new NumberFormatException("Percentages must end with %");
|
||||
}
|
||||
s = s.substring(0, s.length() - 1);
|
||||
return Float.parseFloat(s) / 100;
|
||||
}
|
||||
|
||||
private static int parsePositionAnchor(String s) {
|
||||
switch (s) {
|
||||
case "start":
|
||||
return Cue.ANCHOR_TYPE_START;
|
||||
case "middle":
|
||||
return Cue.ANCHOR_TYPE_MIDDLE;
|
||||
case "end":
|
||||
return Cue.ANCHOR_TYPE_END;
|
||||
default:
|
||||
Log.w(TAG, "Invalid anchor value: " + s);
|
||||
return Cue.TYPE_UNSET;
|
||||
}
|
||||
}
|
||||
|
||||
private static Alignment parseTextAlignment(String s) {
|
||||
switch (s) {
|
||||
case "start":
|
||||
case "left":
|
||||
return Alignment.ALIGN_NORMAL;
|
||||
case "middle":
|
||||
return Alignment.ALIGN_CENTER;
|
||||
case "end":
|
||||
case "right":
|
||||
return Alignment.ALIGN_OPPOSITE;
|
||||
default:
|
||||
Log.w(TAG, "Invalid alignment value: " + s);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static int alignmentToAnchor(Alignment alignment) {
|
||||
if (alignment == null) {
|
||||
return Cue.TYPE_UNSET;
|
||||
}
|
||||
switch (alignment) {
|
||||
case ALIGN_NORMAL:
|
||||
return Cue.ANCHOR_TYPE_START;
|
||||
case ALIGN_CENTER:
|
||||
return Cue.ANCHOR_TYPE_MIDDLE;
|
||||
case ALIGN_OPPOSITE:
|
||||
return Cue.ANCHOR_TYPE_END;
|
||||
default:
|
||||
Log.w(TAG, "Unrecognized alignment: " + alignment);
|
||||
return Cue.ANCHOR_TYPE_START;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PositionHolder {
|
||||
|
||||
public float position;
|
||||
public int positionAnchor;
|
||||
public int lineType;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user