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> * <p>
* {@link #LINE_TYPE_NUMBER} indicates that {@link #line} is a line number, where the size of each * {@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 * 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 * or equal to 0 lines count from the start of the viewport, with 0 indicating zero offset from
* {@link #line} is negative, lines count from the end of the viewport (the last line is numbered * the start edge. When {@link #line} is negative lines count from the end of the viewport, with
* -1). For horizontal text the size of the first line of the cue is its height, and the start * -1 indicating zero offset from the end edge. For horizontal text the line spacing is the height
* and end of the viewport are the top and bottom respectively. * 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 @LineType
public final int lineType; public final int lineType;

View File

@ -404,14 +404,18 @@ public final class Cea608Decoder extends CeaDecoder {
// cc2 - 0|1|N|ATTRBTE|U // cc2 - 0|1|N|ATTRBTE|U
// N is the next row down toggle, ATTRBTE is the 4-byte encoded attribute, and U is the // 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. // underline toggle.
boolean nextRowDown = captionMode != CC_MODE_ROLL_UP && (cc2 & 0x20) != 0; boolean nextRowDown = (cc2 & 0x20) != 0;
if (row != currentCueBuilder.getRow() || nextRowDown) { if (nextRowDown) {
row++;
}
if (row != currentCueBuilder.getRow()) {
if (captionMode != CC_MODE_ROLL_UP && !currentCueBuilder.isEmpty()) { if (captionMode != CC_MODE_ROLL_UP && !currentCueBuilder.isEmpty()) {
currentCueBuilder = new CueBuilder(captionMode, captionRowCount); currentCueBuilder = new CueBuilder(captionMode, captionRowCount);
cueBuilders.add(currentCueBuilder); cueBuilders.add(currentCueBuilder);
} }
currentCueBuilder.setRow(nextRowDown ? ++row : row); currentCueBuilder.setRow(row);
} }
if ((cc2 & 0x01) == 0x01) { if ((cc2 & 0x01) == 0x01) {
@ -492,7 +496,10 @@ public final class Cea608Decoder extends CeaDecoder {
private List<Cue> getDisplayCues() { private List<Cue> getDisplayCues() {
List<Cue> displayCues = new ArrayList<>(); List<Cue> displayCues = new ArrayList<>();
for (int i = 0; i < cueBuilders.size(); i++) { 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; return displayCues;
} }
@ -727,36 +734,62 @@ public final class Cea608Decoder extends CeaDecoder {
public Cue build() { public Cue build() {
SpannableStringBuilder cueString = new SpannableStringBuilder(); 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++) { for (int i = 0; i < rolledUpCaptions.size(); i++) {
cueString.append(rolledUpCaptions.get(i)); cueString.append(rolledUpCaptions.get(i));
cueString.append('\n'); cueString.append('\n');
} }
// Add the current line.
// add the current line
cueString.append(buildSpannableString()); cueString.append(buildSpannableString());
float position = (float) (indent + tabOffset) / SCREEN_CHARWIDTH; if (cueString.length() == 0) {
// adjust the position to fit within the safe area // The cue is empty.
position = position * 0.8f + 0.1f; return null;
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;
} }
return new Cue(cueString, Alignment.ALIGN_NORMAL, line, lineType, Cue.ANCHOR_TYPE_END, float position;
position, Cue.ANCHOR_TYPE_START, Cue.DIMEN_UNSET); 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 @Override

View File

@ -256,7 +256,13 @@ import java.util.regex.Pattern;
if (s.endsWith("%")) { if (s.endsWith("%")) {
builder.setLine(WebvttParserUtil.parsePercentage(s)).setLineType(Cue.LINE_TYPE_FRACTION); builder.setLine(WebvttParserUtil.parsePercentage(s)).setLineType(Cue.LINE_TYPE_FRACTION);
} else { } 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) { if (cueLine >= 0) {
anchorPosition = Math.round(cueLine * firstLineHeight) + parentTop; anchorPosition = Math.round(cueLine * firstLineHeight) + parentTop;
} else { } else {
anchorPosition = Math.round(cueLine * firstLineHeight) + parentBottom; anchorPosition = Math.round((cueLine + 1) * firstLineHeight) + parentBottom;
} }
} }
textTop = cueLineAnchor == Cue.ANCHOR_TYPE_END ? anchorPosition - textHeight textTop = cueLineAnchor == Cue.ANCHOR_TYPE_END ? anchorPosition - textHeight