Improvements for 608 positioning

- Infer likely left/center/right alignment for pop-on captions.
  This makes the rendering much better in practice, particularly
  when the captions were intended to be center aligned.
- Fix line anchoring.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=141156222
This commit is contained in:
olly 2016-12-06 02:28:57 -08:00 committed by Oliver Woodman
parent 88c0695bd0
commit e86bfd6dbe
4 changed files with 84 additions and 33 deletions

View File

@ -95,10 +95,22 @@ public class Cue {
* <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.
* or equal to 0 lines count from the start of the viewport, with 0 indicating zero offset from
* the start edge. When {@link #line} is negative lines count from the end of the viewport, with
* -1 indicating zero offset from the end edge. For horizontal text the line spacing is the height
* of the first line of the cue, and the start and end of the viewport are the top and bottom
* respectively.
* <p>
* Note that it's particularly important to consider the effect of {@link #lineAnchor} when using
* {@link #LINE_TYPE_NUMBER}. {@code (line == 0 && lineAnchor == ANCHOR_TYPE_START)} positions a
* (potentially multi-line) cue at the very top of the viewport.
* {@code (line == -1 && lineAnchor == ANCHOR_TYPE_END)} positions a (potentially multi-line) cue
* at the very bottom of the viewport. {@code (line == 0 && lineAnchor == ANCHOR_TYPE_END)}
* and {@code (line == -1 && lineAnchor == ANCHOR_TYPE_START)} position cues entirely outside of
* the viewport. {@code (line == 1 && lineAnchor == ANCHOR_TYPE_END)} positions a cue so that only
* the last line is visible at the top of the viewport.
* {@code (line == -2 && lineAnchor == ANCHOR_TYPE_START)} position a cue so that only its first
* line is visible at the bottom of the viewport.
*/
@LineType
public final int lineType;

View File

@ -404,14 +404,18 @@ public final class Cea608Decoder extends CeaDecoder {
// cc2 - 0|1|N|ATTRBTE|U
// N is the next row down toggle, ATTRBTE is the 4-byte encoded attribute, and U is the
// underline toggle. The next row down toggle isn't applicable for roll-up captions.
boolean nextRowDown = captionMode != CC_MODE_ROLL_UP && (cc2 & 0x20) != 0;
if (row != currentCueBuilder.getRow() || nextRowDown) {
// underline toggle.
boolean nextRowDown = (cc2 & 0x20) != 0;
if (nextRowDown) {
row++;
}
if (row != currentCueBuilder.getRow()) {
if (captionMode != CC_MODE_ROLL_UP && !currentCueBuilder.isEmpty()) {
currentCueBuilder = new CueBuilder(captionMode, captionRowCount);
cueBuilders.add(currentCueBuilder);
}
currentCueBuilder.setRow(nextRowDown ? ++row : row);
currentCueBuilder.setRow(row);
}
if ((cc2 & 0x01) == 0x01) {
@ -492,7 +496,10 @@ public final class Cea608Decoder extends CeaDecoder {
private List<Cue> getDisplayCues() {
List<Cue> displayCues = new ArrayList<>();
for (int i = 0; i < cueBuilders.size(); i++) {
displayCues.add(cueBuilders.get(i).build());
Cue cue = cueBuilders.get(i).build();
if (cue != null) {
displayCues.add(cue);
}
}
return displayCues;
}
@ -727,36 +734,62 @@ public final class Cea608Decoder extends CeaDecoder {
public Cue build() {
SpannableStringBuilder cueString = new SpannableStringBuilder();
// add any rolled up captions, separated by new lines
// Add any rolled up captions, separated by new lines.
for (int i = 0; i < rolledUpCaptions.size(); i++) {
cueString.append(rolledUpCaptions.get(i));
cueString.append('\n');
}
// add the current line
// Add the current line.
cueString.append(buildSpannableString());
float position = (float) (indent + tabOffset) / SCREEN_CHARWIDTH;
// adjust the position to fit within the safe area
position = position * 0.8f + 0.1f;
float line;
int lineType;
if (captionMode == CC_MODE_ROLL_UP) {
lineType = Cue.LINE_TYPE_NUMBER;
line = row - BASE_ROW;
// adjust the line to fit within the safe area
line--;
} else {
lineType = Cue.LINE_TYPE_FRACTION;
line = (float) row / BASE_ROW;
// adjust the line to fit within the safe area
line = line * 0.8f + 0.1f;
if (cueString.length() == 0) {
// The cue is empty.
return null;
}
return new Cue(cueString, Alignment.ALIGN_NORMAL, line, lineType, Cue.ANCHOR_TYPE_END,
position, Cue.ANCHOR_TYPE_START, Cue.DIMEN_UNSET);
float position;
int positionAnchor;
// The number of empty columns before the start of the text, in the range [0-31].
int startPadding = indent + tabOffset;
// The number of empty columns after the end of the text, in the same range.
int endPadding = SCREEN_CHARWIDTH - startPadding - cueString.length();
int startEndPaddingDelta = startPadding - endPadding;
if (captionMode == CC_MODE_POP_ON && Math.abs(startEndPaddingDelta) < 3) {
// Treat approximately centered pop-on captions are middle aligned.
position = 0.5f;
positionAnchor = Cue.ANCHOR_TYPE_MIDDLE;
} else if (captionMode == CC_MODE_POP_ON && startEndPaddingDelta > 0) {
// Treat pop-on captions with less padding at the end than the start as end aligned.
position = (float) (SCREEN_CHARWIDTH - endPadding) / SCREEN_CHARWIDTH;
// Adjust the position to fit within the safe area.
position = position * 0.8f + 0.1f;
positionAnchor = Cue.ANCHOR_TYPE_END;
} else {
// For all other cases assume start aligned.
position = (float) startPadding / SCREEN_CHARWIDTH;
// Adjust the position to fit within the safe area.
position = position * 0.8f + 0.1f;
positionAnchor = Cue.ANCHOR_TYPE_START;
}
int lineAnchor;
int line;
// Note: Row indices are in the range [1-15].
if (captionMode == CC_MODE_ROLL_UP || row > (BASE_ROW / 2)) {
lineAnchor = Cue.ANCHOR_TYPE_END;
line = row - BASE_ROW;
// Two line adjustments. The first is because line indices from the bottom of the window
// start from -1 rather than 0. The second is a blank row to act as the safe area.
line -= 2;
} else {
lineAnchor = Cue.ANCHOR_TYPE_START;
// Line indices from the top of the window start from 0, but we want a blank row to act as
// the safe area. As a result no adjustment is necessary.
line = row;
}
return new Cue(cueString, Alignment.ALIGN_NORMAL, line, Cue.LINE_TYPE_NUMBER, lineAnchor,
position, positionAnchor, Cue.DIMEN_UNSET);
}
@Override

View File

@ -256,7 +256,13 @@ import java.util.regex.Pattern;
if (s.endsWith("%")) {
builder.setLine(WebvttParserUtil.parsePercentage(s)).setLineType(Cue.LINE_TYPE_FRACTION);
} else {
builder.setLine(Integer.parseInt(s)).setLineType(Cue.LINE_TYPE_NUMBER);
int lineNumber = Integer.parseInt(s);
if (lineNumber < 0) {
// WebVTT defines line -1 as last visible row when lineAnchor is ANCHOR_TYPE_START, where-as
// Cue defines it to be the first row that's not visible.
lineNumber--;
}
builder.setLine(lineNumber).setLineType(Cue.LINE_TYPE_NUMBER);
}
}

View File

@ -252,7 +252,7 @@ import com.google.android.exoplayer2.util.Util;
if (cueLine >= 0) {
anchorPosition = Math.round(cueLine * firstLineHeight) + parentTop;
} else {
anchorPosition = Math.round(cueLine * firstLineHeight) + parentBottom;
anchorPosition = Math.round((cueLine + 1) * firstLineHeight) + parentBottom;
}
}
textTop = cueLineAnchor == Cue.ANCHOR_TYPE_END ? anchorPosition - textHeight