Limit CEA-608 captions to 32 chars per line

ANSI/CTA-608-E R-2014 spec defines exactly 32 columns on the screen,
and limits all lines to this length.

See 3.2.2 definition of 'Column'.

issue:#7341
PiperOrigin-RevId: 311549881
This commit is contained in:
ibaker 2020-05-14 17:44:08 +01:00 committed by Oliver Woodman
parent 758e99e3f1
commit a39233d2fd
4 changed files with 87 additions and 10 deletions

View File

@ -123,6 +123,8 @@
* Stop parsing unsupported WebVTT CSS properties. The spec provides an * Stop parsing unsupported WebVTT CSS properties. The spec provides an
[exhaustive list](https://www.w3.org/TR/webvtt1/#the-cue-pseudo-element) [exhaustive list](https://www.w3.org/TR/webvtt1/#the-cue-pseudo-element)
of which are supported. of which are supported.
* Ignore excess characters in CEA-608 lines (max length is 32)
([#7341](https://github.com/google/ExoPlayer/issues/7341)).
* DRM: * DRM:
* Add support for attaching DRM sessions to clear content in the demo app. * Add support for attaching DRM sessions to clear content in the demo app.
* Remove `DrmSessionManager` references from all renderers. * Remove `DrmSessionManager` references from all renderers.
@ -188,8 +190,7 @@
* IMA extension: Upgrade to IMA SDK version 3.18.2, and migrate to new * IMA extension: Upgrade to IMA SDK version 3.18.2, and migrate to new
preloading APIs ([#6429](https://github.com/google/ExoPlayer/issues/6429)). preloading APIs ([#6429](https://github.com/google/ExoPlayer/issues/6429)).
* IMA extension: * IMA extension:
* Upgrade to IMA SDK version 3.19.0, and migrate to new * Upgrade to IMA SDK version 3.19.0, and migrate to new preloading APIs
preloading APIs
([#6429](https://github.com/google/ExoPlayer/issues/6429)). ([#6429](https://github.com/google/ExoPlayer/issues/6429)).
* Add support for timing out ad preloading, to avoid playback getting * Add support for timing out ad preloading, to avoid playback getting
stuck if an ad group unexpectedly fails to load. stuck if an ad group unexpectedly fails to load.

View File

@ -1291,6 +1291,36 @@ public final class Util {
return (toUnsignedLong(mostSignificantBits) << 32) | toUnsignedLong(leastSignificantBits); return (toUnsignedLong(mostSignificantBits) << 32) | toUnsignedLong(leastSignificantBits);
} }
/**
* Truncates a sequence of ASCII characters to a maximum length.
*
* <p><b>Note:</b> This is not safe to use in general on Unicode text because it may separate
* characters from combining characters or split up surrogate pairs.
*
* @param sequence The character sequence to truncate.
* @param maxLength The max length to truncate to.
* @return {@code sequence} directly if {@code sequence.length() <= maxLength}, otherwise {@code
* sequence.subsequence(0, maxLength}.
*/
public static CharSequence truncateAscii(CharSequence sequence, int maxLength) {
return sequence.length() <= maxLength ? sequence : sequence.subSequence(0, maxLength);
}
/**
* Truncates a string of ASCII characters to a maximum length.
*
* <p><b>Note:</b> This is not safe to use in general on Unicode text because it may separate
* characters from combining characters or split up surrogate pairs.
*
* @param string The string to truncate.
* @param maxLength The max length to truncate to.
* @return {@code string} directly if {@code string.length() <= maxLength}, otherwise {@code
* string.substring(0, maxLength}.
*/
public static String truncateAscii(String string, int maxLength) {
return string.length() <= maxLength ? string : string.substring(0, maxLength);
}
/** /**
* Returns a byte array containing values parsed from the hex string provided. * Returns a byte array containing values parsed from the hex string provided.
* *

View File

@ -24,9 +24,14 @@ import static com.google.android.exoplayer2.util.Util.parseXsDuration;
import static com.google.android.exoplayer2.util.Util.unescapeFileName; import static com.google.android.exoplayer2.util.Util.unescapeFileName;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.StrikethroughSpan;
import android.text.style.UnderlineSpan;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.testutil.truth.SpannedSubject;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.util.ArrayList; import java.util.ArrayList;
@ -713,6 +718,40 @@ public class UtilTest {
assertThat(Util.toLong(0xFEDCBA, 0x87654321)).isEqualTo(0xFEDCBA_87654321L); assertThat(Util.toLong(0xFEDCBA, 0x87654321)).isEqualTo(0xFEDCBA_87654321L);
} }
@Test
public void truncateAscii_shortInput_returnsInput() {
String input = "a short string";
assertThat(Util.truncateAscii(input, 100)).isSameInstanceAs(input);
assertThat(Util.truncateAscii((CharSequence) input, 100)).isSameInstanceAs(input);
}
@Test
public void truncateAscii_longInput_truncated() {
String input = "a much longer string";
assertThat(Util.truncateAscii(input, 5)).isEqualTo("a muc");
assertThat(Util.truncateAscii((CharSequence) input, 5).toString()).isEqualTo("a muc");
}
@Test
public void truncateAscii_preservesStylingSpans() {
SpannableString input = new SpannableString("a short string");
input.setSpan(new UnderlineSpan(), 0, 10, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
input.setSpan(new StrikethroughSpan(), 4, 10, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
CharSequence result = Util.truncateAscii(input, 7);
assertThat(result).isInstanceOf(SpannableString.class);
assertThat(result.toString()).isEqualTo("a short");
SpannedSubject.assertThat((Spanned) result)
.hasUnderlineSpanBetween(0, 7)
.withFlags(Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
SpannedSubject.assertThat((Spanned) result)
.hasStrikethroughSpanBetween(4, 7)
.withFlags(Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
}
@Test @Test
public void toHexString_returnsHexString() { public void toHexString_returnsHexString() {
byte[] bytes = TestUtil.createByteArray(0x12, 0xFC, 0x06); byte[] bytes = TestUtil.createByteArray(0x12, 0xFC, 0x06);

View File

@ -37,6 +37,7 @@ import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -868,8 +869,12 @@ public final class Cea608Decoder extends CeaDecoder {
} }
public void append(char text) { public void append(char text) {
// Don't accept more than 32 chars. We'll trim further, considering indent & tabOffset, in
// build().
if (captionStringBuilder.length() < SCREEN_CHARWIDTH) {
captionStringBuilder.append(text); captionStringBuilder.append(text);
} }
}
public void rollUp() { public void rollUp() {
rolledUpCaptions.add(buildCurrentLine()); rolledUpCaptions.add(buildCurrentLine());
@ -883,14 +888,17 @@ public final class Cea608Decoder extends CeaDecoder {
@Nullable @Nullable
public Cue build(@Cue.AnchorType int forcedPositionAnchor) { public Cue build(@Cue.AnchorType int forcedPositionAnchor) {
// The number of empty columns before the start of the text, in the range [0-31].
int startPadding = indent + tabOffset;
int maxTextLength = SCREEN_CHARWIDTH - startPadding;
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(Util.truncateAscii(rolledUpCaptions.get(i), maxTextLength));
cueString.append('\n'); cueString.append('\n');
} }
// Add the current line. // Add the current line.
cueString.append(buildCurrentLine()); cueString.append(Util.truncateAscii(buildCurrentLine(), maxTextLength));
if (cueString.length() == 0) { if (cueString.length() == 0) {
// The cue is empty. // The cue is empty.
@ -898,8 +906,7 @@ public final class Cea608Decoder extends CeaDecoder {
} }
int positionAnchor; 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. // The number of empty columns after the end of the text, in the same range.
int endPadding = SCREEN_CHARWIDTH - startPadding - cueString.length(); int endPadding = SCREEN_CHARWIDTH - startPadding - cueString.length();
int startEndPaddingDelta = startPadding - endPadding; int startEndPaddingDelta = startPadding - endPadding;