Expose cue settings parser
This CL exposes the cue settings parser in order to allow its usage from the MP4Webvtt extractor. Also fixes a few mistakes from the previous related CL. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=112145806
This commit is contained in:
parent
6b9a1b16f1
commit
b6b97a8683
@ -18,6 +18,7 @@ package com.google.android.exoplayer.text.webvtt;
|
||||
import com.google.android.exoplayer.text.Cue;
|
||||
|
||||
import android.text.Layout.Alignment;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* A representation of a WebVTT cue.
|
||||
@ -53,4 +54,127 @@ import android.text.Layout.Alignment;
|
||||
return (line == DIMEN_UNSET && position == DIMEN_UNSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for WebVTT cues.
|
||||
*/
|
||||
@SuppressWarnings("hiding")
|
||||
public static final class Builder {
|
||||
|
||||
private static final String TAG = "WebvttCueBuilder";
|
||||
|
||||
private long startTime;
|
||||
private long endTime;
|
||||
private CharSequence text;
|
||||
private Alignment textAlignment;
|
||||
private float line;
|
||||
private int lineType;
|
||||
private int lineAnchor;
|
||||
private float position;
|
||||
private int positionAnchor;
|
||||
private float width;
|
||||
|
||||
// Initialization methods
|
||||
|
||||
public Builder() {
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
startTime = 0;
|
||||
endTime = 0;
|
||||
text = null;
|
||||
textAlignment = null;
|
||||
line = Cue.DIMEN_UNSET;
|
||||
lineType = Cue.TYPE_UNSET;
|
||||
lineAnchor = Cue.TYPE_UNSET;
|
||||
position = Cue.DIMEN_UNSET;
|
||||
positionAnchor = Cue.TYPE_UNSET;
|
||||
width = Cue.DIMEN_UNSET;
|
||||
}
|
||||
|
||||
// Construction methods
|
||||
|
||||
public WebvttCue build() {
|
||||
if (position != Cue.DIMEN_UNSET && positionAnchor == Cue.TYPE_UNSET) {
|
||||
derivePositionAnchorFromAlignment();
|
||||
}
|
||||
return new WebvttCue(startTime, endTime, text, textAlignment, line, lineType, lineAnchor,
|
||||
position, positionAnchor, width);
|
||||
}
|
||||
|
||||
public Builder setStartTime(long time) {
|
||||
startTime = time;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setEndTime(long time) {
|
||||
endTime = time;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setText(CharSequence aText) {
|
||||
text = aText;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setTextAlignment(Alignment textAlignment) {
|
||||
this.textAlignment = textAlignment;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setLine(float line) {
|
||||
this.line = line;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setLineType(int lineType) {
|
||||
this.lineType = lineType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setLineAnchor(int lineAnchor) {
|
||||
this.lineAnchor = lineAnchor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setPosition(float position) {
|
||||
this.position = position;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setPositionAnchor(int positionAnchor) {
|
||||
this.positionAnchor = positionAnchor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setWidth(float width) {
|
||||
this.width = width;
|
||||
return this;
|
||||
}
|
||||
|
||||
private Builder derivePositionAnchorFromAlignment() {
|
||||
if (textAlignment == null) {
|
||||
positionAnchor = Cue.TYPE_UNSET;
|
||||
} else {
|
||||
switch (textAlignment) {
|
||||
case ALIGN_NORMAL:
|
||||
positionAnchor = Cue.ANCHOR_TYPE_START;
|
||||
break;
|
||||
case ALIGN_CENTER:
|
||||
positionAnchor = Cue.ANCHOR_TYPE_MIDDLE;
|
||||
break;
|
||||
case ALIGN_OPPOSITE:
|
||||
positionAnchor = Cue.ANCHOR_TYPE_END;
|
||||
break;
|
||||
default:
|
||||
Log.w(TAG, "Unrecognized alignment: " + textAlignment);
|
||||
positionAnchor = Cue.ANCHOR_TYPE_START;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Parser for webvtt cue text. (https://w3c.github.io/webvtt/#cue-text)
|
||||
* Parser for WebVTT cues. (https://w3c.github.io/webvtt/#cues)
|
||||
*/
|
||||
public final class WebvttCueParser {
|
||||
|
||||
@ -66,71 +66,50 @@ public final class WebvttCueParser {
|
||||
|
||||
private static final String TAG = "WebvttCueParser";
|
||||
|
||||
private StringBuilder textBuilder;
|
||||
private PositionHolder positionHolder;
|
||||
private final StringBuilder textBuilder;
|
||||
|
||||
public WebvttCueParser() {
|
||||
positionHolder = new PositionHolder();
|
||||
textBuilder = new StringBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the next valid Webvtt cue in a parsable array, including timestamps, settings and text.
|
||||
* Parses the next valid WebVTT cue in a parsable array, including timestamps, settings and text.
|
||||
*
|
||||
* @param webvttData parsable Webvtt file data.
|
||||
* @return a {@link WebvttCue} instance if cue content is found. {@code null} otherwise.
|
||||
* @param webvttData Parsable WebVTT file data.
|
||||
* @param cueBuilder Builder for WebVTT Cues.
|
||||
* @return True if a valid Cue was found, false otherwise.
|
||||
*/
|
||||
public WebvttCue parseNextValidCue(ParsableByteArray webvttData) {
|
||||
public boolean parseNextValidCue(ParsableByteArray webvttData, WebvttCue.Builder cueBuilder) {
|
||||
Matcher cueHeaderMatcher;
|
||||
while ((cueHeaderMatcher = findNextCueHeader(webvttData)) != null) {
|
||||
WebvttCue currentCue = parseCue(cueHeaderMatcher, webvttData);
|
||||
if (currentCue != null) {
|
||||
return currentCue;
|
||||
if (parseCue(cueHeaderMatcher, webvttData, cueBuilder, textBuilder)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private WebvttCue parseCue(Matcher cueHeaderMatcher, ParsableByteArray webvttData) {
|
||||
long cueStartTime;
|
||||
long cueEndTime;
|
||||
try {
|
||||
// Parse the cue start and end times.
|
||||
cueStartTime = WebvttParserUtil.parseTimestampUs(cueHeaderMatcher.group(1));
|
||||
cueEndTime = WebvttParserUtil.parseTimestampUs(cueHeaderMatcher.group(2));
|
||||
} catch (NumberFormatException e) {
|
||||
Log.w(TAG, "Skipping cue with bad header: " + cueHeaderMatcher.group());
|
||||
return null;
|
||||
}
|
||||
|
||||
// Default cue settings.
|
||||
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;
|
||||
|
||||
/**
|
||||
* Parses a string containing a list of cue settings.
|
||||
*
|
||||
* @param cueSettingsList String containing the settings for a given cue.
|
||||
* @param builder The {@link WebvttCue.Builder} where incremental construction takes place.
|
||||
*/
|
||||
public static void parseCueSettingsList(String cueSettingsList, WebvttCue.Builder builder) {
|
||||
// Parse the cue settings list.
|
||||
Matcher cueSettingMatcher = CUE_SETTING_PATTERN.matcher(cueHeaderMatcher.group(3));
|
||||
Matcher cueSettingMatcher = CUE_SETTING_PATTERN.matcher(cueSettingsList);
|
||||
while (cueSettingMatcher.find()) {
|
||||
String name = cueSettingMatcher.group(1);
|
||||
String value = cueSettingMatcher.group(2);
|
||||
try {
|
||||
if ("line".equals(name)) {
|
||||
parseLineAttribute(value, positionHolder);
|
||||
cueLine = positionHolder.position;
|
||||
cueLineType = positionHolder.lineType;
|
||||
cueLineAnchor = positionHolder.positionAnchor;
|
||||
parseLineAttribute(value, builder);
|
||||
} else if ("align".equals(name)) {
|
||||
cueTextAlignment = parseTextAlignment(value);
|
||||
builder.setTextAlignment(parseTextAlignment(value));
|
||||
} else if ("position".equals(name)) {
|
||||
parsePositionAttribute(value, positionHolder);
|
||||
cuePosition = positionHolder.position;
|
||||
cuePositionAnchor = positionHolder.positionAnchor;
|
||||
parsePositionAttribute(value, builder);
|
||||
} else if ("size".equals(name)) {
|
||||
cueWidth = WebvttParserUtil.parsePercentage(value);
|
||||
builder.setWidth(WebvttParserUtil.parsePercentage(value));
|
||||
} else {
|
||||
Log.w(TAG, "Unknown cue setting " + name + ":" + value);
|
||||
}
|
||||
@ -138,12 +117,44 @@ public final class WebvttCueParser {
|
||||
Log.w(TAG, "Skipping bad cue setting: " + cueSettingMatcher.group());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
/**
|
||||
* Reads lines up to and including the next WebVTT cue header.
|
||||
*
|
||||
* @param input The input from which lines should be read.
|
||||
* @return A {@link Matcher} for the WebVTT cue header, or null if the end of the input was
|
||||
* reached without a cue header being found. In the case that a cue header is found, groups 1,
|
||||
* 2 and 3 of the returned matcher contain the start time, end time and settings list.
|
||||
*/
|
||||
public static Matcher findNextCueHeader(ParsableByteArray input) {
|
||||
String line;
|
||||
while ((line = input.readLine()) != null) {
|
||||
if (COMMENT.matcher(line).matches()) {
|
||||
// Skip until the end of the comment block.
|
||||
while ((line = input.readLine()) != null && !line.isEmpty()) {}
|
||||
} else {
|
||||
Matcher cueHeaderMatcher = WebvttCueParser.CUE_HEADER_PATTERN.matcher(line);
|
||||
if (cueHeaderMatcher.matches()) {
|
||||
return cueHeaderMatcher;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean parseCue(Matcher cueHeaderMatcher, ParsableByteArray webvttData,
|
||||
WebvttCue.Builder builder, StringBuilder textBuilder) {
|
||||
try {
|
||||
// Parse the cue start and end times.
|
||||
builder.setStartTime(WebvttParserUtil.parseTimestampUs(cueHeaderMatcher.group(1)))
|
||||
.setEndTime(WebvttParserUtil.parseTimestampUs(cueHeaderMatcher.group(2)));
|
||||
} catch (NumberFormatException e) {
|
||||
Log.w(TAG, "Skipping cue with bad header: " + cueHeaderMatcher.group());
|
||||
return false;
|
||||
}
|
||||
|
||||
parseCueSettingsList(cueHeaderMatcher.group(3), builder);
|
||||
|
||||
// Parse the cue text.
|
||||
textBuilder.setLength(0);
|
||||
@ -154,11 +165,8 @@ public final class WebvttCueParser {
|
||||
}
|
||||
textBuilder.append(line.trim());
|
||||
}
|
||||
|
||||
CharSequence cueText = parseCueText(textBuilder.toString());
|
||||
|
||||
return new WebvttCue(cueStartTime, cueEndTime, cueText, cueTextAlignment, cueLine,
|
||||
cueLineType, cueLineAnchor, cuePosition, cuePositionAnchor, cueWidth);
|
||||
builder.setText(parseCueText(textBuilder.toString()));
|
||||
return true;
|
||||
}
|
||||
|
||||
/* package */ static Spanned parseCueText(String markup) {
|
||||
@ -226,77 +234,34 @@ public final class WebvttCueParser {
|
||||
return spannedText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads lines up to and including the next WebVTT cue header.
|
||||
*
|
||||
* @param input The input from which lines should be read.
|
||||
* @return A {@link Matcher} for the WebVTT cue header, or null if the end of the input was
|
||||
* reached without a cue header being found. In the case that a cue header is found, groups 1,
|
||||
* 2 and 3 of the returned matcher contain the start time, end time and settings list.
|
||||
*/
|
||||
public static Matcher findNextCueHeader(ParsableByteArray input) {
|
||||
String line;
|
||||
while ((line = input.readLine()) != null) {
|
||||
if (COMMENT.matcher(line).matches()) {
|
||||
// Skip until the end of the comment block.
|
||||
while ((line = input.readLine()) != null && !line.isEmpty()) {}
|
||||
} else {
|
||||
Matcher cueHeaderMatcher = WebvttCueParser.CUE_HEADER_PATTERN.matcher(line);
|
||||
if (cueHeaderMatcher.matches()) {
|
||||
return cueHeaderMatcher;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static final class PositionHolder {
|
||||
|
||||
public float position;
|
||||
public int positionAnchor;
|
||||
public int lineType;
|
||||
|
||||
}
|
||||
|
||||
// Internal methods
|
||||
|
||||
private static void parseLineAttribute(String s, PositionHolder out)
|
||||
private static void parseLineAttribute(String s, WebvttCue.Builder builder)
|
||||
throws NumberFormatException {
|
||||
int lineAnchor;
|
||||
int commaPosition = s.indexOf(',');
|
||||
if (commaPosition != -1) {
|
||||
lineAnchor = parsePositionAnchor(s.substring(commaPosition + 1));
|
||||
builder.setLineAnchor(parsePositionAnchor(s.substring(commaPosition + 1)));
|
||||
s = s.substring(0, commaPosition);
|
||||
} else {
|
||||
lineAnchor = Cue.TYPE_UNSET;
|
||||
builder.setLineAnchor(Cue.TYPE_UNSET);
|
||||
}
|
||||
float line;
|
||||
int lineType;
|
||||
if (s.endsWith("%")) {
|
||||
line = WebvttParserUtil.parsePercentage(s);
|
||||
lineType = Cue.LINE_TYPE_FRACTION;
|
||||
builder.setLine(WebvttParserUtil.parsePercentage(s)).setLineType(Cue.LINE_TYPE_FRACTION);
|
||||
} else {
|
||||
line = Integer.parseInt(s);
|
||||
lineType = Cue.LINE_TYPE_NUMBER;
|
||||
builder.setLine(Integer.parseInt(s)).setLineType(Cue.LINE_TYPE_NUMBER);
|
||||
}
|
||||
out.position = line;
|
||||
out.positionAnchor = lineAnchor;
|
||||
out.lineType = lineType;
|
||||
}
|
||||
|
||||
private static void parsePositionAttribute(String s, PositionHolder out)
|
||||
private static void parsePositionAttribute(String s, WebvttCue.Builder builder)
|
||||
throws NumberFormatException {
|
||||
int positionAnchor;
|
||||
int commaPosition = s.indexOf(',');
|
||||
if (commaPosition != -1) {
|
||||
positionAnchor = parsePositionAnchor(s.substring(commaPosition + 1));
|
||||
builder.setPositionAnchor(parsePositionAnchor(s.substring(commaPosition + 1)));
|
||||
s = s.substring(0, commaPosition);
|
||||
} else {
|
||||
positionAnchor = Cue.TYPE_UNSET;
|
||||
builder.setPositionAnchor(Cue.TYPE_UNSET);
|
||||
}
|
||||
out.position = WebvttParserUtil.parsePercentage(s);
|
||||
out.positionAnchor = positionAnchor;
|
||||
out.lineType = Cue.TYPE_UNSET;
|
||||
builder.setPosition(WebvttParserUtil.parsePercentage(s));
|
||||
}
|
||||
|
||||
private static int parsePositionAnchor(String s) {
|
||||
@ -329,27 +294,10 @@ public final class WebvttCueParser {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find end of tag (>). The position returned is the position of the > plus one (exclusive).
|
||||
*
|
||||
* @param markup The webvtt cue markup to be parsed.
|
||||
* @param markup The WebVTT cue markup to be parsed.
|
||||
* @param startPos the position from where to start searching for the end of tag.
|
||||
* @return the position of the end of tag plus 1 (one).
|
||||
*/
|
||||
|
@ -33,10 +33,12 @@ public final class WebvttParser implements SubtitleParser {
|
||||
|
||||
private final WebvttCueParser cueParser;
|
||||
private final ParsableByteArray parsableWebvttData;
|
||||
private final WebvttCue.Builder webvttCueBuilder;
|
||||
|
||||
public WebvttParser() {
|
||||
cueParser = new WebvttCueParser();
|
||||
parsableWebvttData = new ParsableByteArray();
|
||||
webvttCueBuilder = new WebvttCue.Builder();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -48,6 +50,7 @@ public final class WebvttParser implements SubtitleParser {
|
||||
public final WebvttSubtitle parse(byte[] bytes, int offset, int length) throws ParserException {
|
||||
parsableWebvttData.reset(bytes, offset + length);
|
||||
parsableWebvttData.setPosition(offset);
|
||||
webvttCueBuilder.reset(); // In case a previous parse run failed with a ParserException.
|
||||
|
||||
// Validate the first line of the header, and skip the remainder.
|
||||
WebvttParserUtil.validateWebvttHeaderLine(parsableWebvttData);
|
||||
@ -55,9 +58,9 @@ public final class WebvttParser implements SubtitleParser {
|
||||
|
||||
// Extract Cues
|
||||
ArrayList<WebvttCue> subtitles = new ArrayList<>();
|
||||
WebvttCue currentWebvttCue;
|
||||
while ((currentWebvttCue = cueParser.parseNextValidCue(parsableWebvttData)) != null) {
|
||||
subtitles.add(currentWebvttCue);
|
||||
while (cueParser.parseNextValidCue(parsableWebvttData, webvttCueBuilder)) {
|
||||
subtitles.add(webvttCueBuilder.build());
|
||||
webvttCueBuilder.reset();
|
||||
}
|
||||
return new WebvttSubtitle(subtitles);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user