mirror of
https://github.com/androidx/media.git
synced 2025-05-17 04:29:55 +08:00
Support default style in Tx3g decoder.
The track initialization data of Tx3g includes default style values for font styles, colour, and font family. Additionally the decoder now supports vertical subtitle placements other than the Tx3g default of 85% video height. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=152930057
This commit is contained in:
parent
1a22a4be5f
commit
0a7011b390
BIN
library/core/src/androidTest/assets/tx3g/initialization
Normal file
BIN
library/core/src/androidTest/assets/tx3g/initialization
Normal file
Binary file not shown.
Binary file not shown.
BIN
library/core/src/androidTest/assets/tx3g/sample_just_text
Normal file
BIN
library/core/src/androidTest/assets/tx3g/sample_just_text
Normal file
Binary file not shown.
Binary file not shown.
BIN
library/core/src/androidTest/assets/tx3g/sample_with_tbox
Normal file
BIN
library/core/src/androidTest/assets/tx3g/sample_with_tbox
Normal file
Binary file not shown.
@ -21,11 +21,15 @@ import android.test.InstrumentationTestCase;
|
|||||||
import android.text.SpannedString;
|
import android.text.SpannedString;
|
||||||
import android.text.style.ForegroundColorSpan;
|
import android.text.style.ForegroundColorSpan;
|
||||||
import android.text.style.StyleSpan;
|
import android.text.style.StyleSpan;
|
||||||
|
import android.text.style.TypefaceSpan;
|
||||||
import android.text.style.UnderlineSpan;
|
import android.text.style.UnderlineSpan;
|
||||||
|
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.text.Cue;
|
||||||
import com.google.android.exoplayer2.text.Subtitle;
|
import com.google.android.exoplayer2.text.Subtitle;
|
||||||
import com.google.android.exoplayer2.text.SubtitleDecoderException;
|
import com.google.android.exoplayer2.text.SubtitleDecoderException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit test for {@link Tx3gDecoder}.
|
* Unit test for {@link Tx3gDecoder}.
|
||||||
@ -33,21 +37,36 @@ import java.io.IOException;
|
|||||||
public final class Tx3gDecoderTest extends InstrumentationTestCase {
|
public final class Tx3gDecoderTest extends InstrumentationTestCase {
|
||||||
|
|
||||||
private static final String NO_SUBTITLE = "tx3g/no_subtitle";
|
private static final String NO_SUBTITLE = "tx3g/no_subtitle";
|
||||||
|
private static final String SAMPLE_JUST_TEXT = "tx3g/sample_just_text";
|
||||||
private static final String SAMPLE_WITH_STYL = "tx3g/sample_with_styl";
|
private static final String SAMPLE_WITH_STYL = "tx3g/sample_with_styl";
|
||||||
|
private static final String SAMPLE_WITH_STYL_ALL_DEFAULTS = "tx3g/sample_with_styl_all_defaults";
|
||||||
private static final String SAMPLE_UTF16_BE_NO_STYL = "tx3g/sample_utf16_be_no_styl";
|
private static final String SAMPLE_UTF16_BE_NO_STYL = "tx3g/sample_utf16_be_no_styl";
|
||||||
private static final String SAMPLE_UTF16_LE_NO_STYL = "tx3g/sample_utf16_le_no_styl";
|
private static final String SAMPLE_UTF16_LE_NO_STYL = "tx3g/sample_utf16_le_no_styl";
|
||||||
private static final String SAMPLE_WITH_MULTIPLE_STYL = "tx3g/sample_with_multiple_styl";
|
private static final String SAMPLE_WITH_MULTIPLE_STYL = "tx3g/sample_with_multiple_styl";
|
||||||
private static final String SAMPLE_WITH_OTHER_EXTENSION = "tx3g/sample_with_other_extension";
|
private static final String SAMPLE_WITH_OTHER_EXTENSION = "tx3g/sample_with_other_extension";
|
||||||
|
private static final String SAMPLE_WITH_TBOX = "tx3g/sample_with_tbox";
|
||||||
|
private static final String INITIALIZATION = "tx3g/initialization";
|
||||||
|
private static final String INITIALIZATION_ALL_DEFAULTS = "tx3g/initialization_all_defaults";
|
||||||
|
|
||||||
public void testDecodeNoSubtitle() throws IOException, SubtitleDecoderException {
|
public void testDecodeNoSubtitle() throws IOException, SubtitleDecoderException {
|
||||||
Tx3gDecoder decoder = new Tx3gDecoder();
|
Tx3gDecoder decoder = new Tx3gDecoder(Collections.<byte[]>emptyList());
|
||||||
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), NO_SUBTITLE);
|
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), NO_SUBTITLE);
|
||||||
Subtitle subtitle = decoder.decode(bytes, bytes.length, false);
|
Subtitle subtitle = decoder.decode(bytes, bytes.length, false);
|
||||||
assertTrue(subtitle.getCues(0).isEmpty());
|
assertTrue(subtitle.getCues(0).isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testDecodeJustText() throws IOException, SubtitleDecoderException {
|
||||||
|
Tx3gDecoder decoder = new Tx3gDecoder(Collections.<byte[]>emptyList());
|
||||||
|
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_JUST_TEXT);
|
||||||
|
Subtitle subtitle = decoder.decode(bytes, bytes.length, false);
|
||||||
|
SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text);
|
||||||
|
assertEquals("CC Test", text.toString());
|
||||||
|
assertEquals(0, text.getSpans(0, text.length(), Object.class).length);
|
||||||
|
assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f);
|
||||||
|
}
|
||||||
|
|
||||||
public void testDecodeWithStyl() throws IOException, SubtitleDecoderException {
|
public void testDecodeWithStyl() throws IOException, SubtitleDecoderException {
|
||||||
Tx3gDecoder decoder = new Tx3gDecoder();
|
Tx3gDecoder decoder = new Tx3gDecoder(Collections.<byte[]>emptyList());
|
||||||
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_WITH_STYL);
|
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_WITH_STYL);
|
||||||
Subtitle subtitle = decoder.decode(bytes, bytes.length, false);
|
Subtitle subtitle = decoder.decode(bytes, bytes.length, false);
|
||||||
SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text);
|
SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text);
|
||||||
@ -58,28 +77,41 @@ public final class Tx3gDecoderTest extends InstrumentationTestCase {
|
|||||||
findSpan(text, 0, 6, UnderlineSpan.class);
|
findSpan(text, 0, 6, UnderlineSpan.class);
|
||||||
ForegroundColorSpan colorSpan = findSpan(text, 0, 6, ForegroundColorSpan.class);
|
ForegroundColorSpan colorSpan = findSpan(text, 0, 6, ForegroundColorSpan.class);
|
||||||
assertEquals(Color.GREEN, colorSpan.getForegroundColor());
|
assertEquals(Color.GREEN, colorSpan.getForegroundColor());
|
||||||
|
assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testDecodeWithStylAllDefaults() throws IOException, SubtitleDecoderException {
|
||||||
|
Tx3gDecoder decoder = new Tx3gDecoder(Collections.<byte[]>emptyList());
|
||||||
|
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_WITH_STYL_ALL_DEFAULTS);
|
||||||
|
Subtitle subtitle = decoder.decode(bytes, bytes.length, false);
|
||||||
|
SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text);
|
||||||
|
assertEquals("CC Test", text.toString());
|
||||||
|
assertEquals(0, text.getSpans(0, text.length(), Object.class).length);
|
||||||
|
assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testDecodeUtf16BeNoStyl() throws IOException, SubtitleDecoderException {
|
public void testDecodeUtf16BeNoStyl() throws IOException, SubtitleDecoderException {
|
||||||
Tx3gDecoder decoder = new Tx3gDecoder();
|
Tx3gDecoder decoder = new Tx3gDecoder(Collections.<byte[]>emptyList());
|
||||||
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_UTF16_BE_NO_STYL);
|
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_UTF16_BE_NO_STYL);
|
||||||
Subtitle subtitle = decoder.decode(bytes, bytes.length, false);
|
Subtitle subtitle = decoder.decode(bytes, bytes.length, false);
|
||||||
SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text);
|
SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text);
|
||||||
assertEquals("你好", text.toString());
|
assertEquals("你好", text.toString());
|
||||||
assertEquals(0, text.getSpans(0, text.length(), Object.class).length);
|
assertEquals(0, text.getSpans(0, text.length(), Object.class).length);
|
||||||
|
assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testDecodeUtf16LeNoStyl() throws IOException, SubtitleDecoderException {
|
public void testDecodeUtf16LeNoStyl() throws IOException, SubtitleDecoderException {
|
||||||
Tx3gDecoder decoder = new Tx3gDecoder();
|
Tx3gDecoder decoder = new Tx3gDecoder(Collections.<byte[]>emptyList());
|
||||||
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_UTF16_LE_NO_STYL);
|
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_UTF16_LE_NO_STYL);
|
||||||
Subtitle subtitle = decoder.decode(bytes, bytes.length, false);
|
Subtitle subtitle = decoder.decode(bytes, bytes.length, false);
|
||||||
SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text);
|
SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text);
|
||||||
assertEquals("你好", text.toString());
|
assertEquals("你好", text.toString());
|
||||||
assertEquals(0, text.getSpans(0, text.length(), Object.class).length);
|
assertEquals(0, text.getSpans(0, text.length(), Object.class).length);
|
||||||
|
assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testDecodeWithMultipleStyl() throws IOException, SubtitleDecoderException {
|
public void testDecodeWithMultipleStyl() throws IOException, SubtitleDecoderException {
|
||||||
Tx3gDecoder decoder = new Tx3gDecoder();
|
Tx3gDecoder decoder = new Tx3gDecoder(Collections.<byte[]>emptyList());
|
||||||
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_WITH_MULTIPLE_STYL);
|
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_WITH_MULTIPLE_STYL);
|
||||||
Subtitle subtitle = decoder.decode(bytes, bytes.length, false);
|
Subtitle subtitle = decoder.decode(bytes, bytes.length, false);
|
||||||
SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text);
|
SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text);
|
||||||
@ -92,10 +124,11 @@ public final class Tx3gDecoderTest extends InstrumentationTestCase {
|
|||||||
assertEquals(Color.GREEN, colorSpan.getForegroundColor());
|
assertEquals(Color.GREEN, colorSpan.getForegroundColor());
|
||||||
colorSpan = findSpan(text, 7, 12, ForegroundColorSpan.class);
|
colorSpan = findSpan(text, 7, 12, ForegroundColorSpan.class);
|
||||||
assertEquals(Color.GREEN, colorSpan.getForegroundColor());
|
assertEquals(Color.GREEN, colorSpan.getForegroundColor());
|
||||||
|
assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testDecodeWithOtherExtension() throws IOException, SubtitleDecoderException {
|
public void testDecodeWithOtherExtension() throws IOException, SubtitleDecoderException {
|
||||||
Tx3gDecoder decoder = new Tx3gDecoder();
|
Tx3gDecoder decoder = new Tx3gDecoder(Collections.<byte[]>emptyList());
|
||||||
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_WITH_OTHER_EXTENSION);
|
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_WITH_OTHER_EXTENSION);
|
||||||
Subtitle subtitle = decoder.decode(bytes, bytes.length, false);
|
Subtitle subtitle = decoder.decode(bytes, bytes.length, false);
|
||||||
SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text);
|
SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text);
|
||||||
@ -105,6 +138,62 @@ public final class Tx3gDecoderTest extends InstrumentationTestCase {
|
|||||||
assertEquals(Typeface.BOLD, styleSpan.getStyle());
|
assertEquals(Typeface.BOLD, styleSpan.getStyle());
|
||||||
ForegroundColorSpan colorSpan = findSpan(text, 0, 6, ForegroundColorSpan.class);
|
ForegroundColorSpan colorSpan = findSpan(text, 0, 6, ForegroundColorSpan.class);
|
||||||
assertEquals(Color.GREEN, colorSpan.getForegroundColor());
|
assertEquals(Color.GREEN, colorSpan.getForegroundColor());
|
||||||
|
assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testInitializationDecodeWithStyl() throws IOException, SubtitleDecoderException {
|
||||||
|
byte[] initBytes = TestUtil.getByteArray(getInstrumentation(), INITIALIZATION);
|
||||||
|
Tx3gDecoder decoder = new Tx3gDecoder(Collections.singletonList(initBytes));
|
||||||
|
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_WITH_STYL);
|
||||||
|
Subtitle subtitle = decoder.decode(bytes, bytes.length, false);
|
||||||
|
SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text);
|
||||||
|
assertEquals("CC Test", text.toString());
|
||||||
|
assertEquals(5, text.getSpans(0, text.length(), Object.class).length);
|
||||||
|
StyleSpan styleSpan = findSpan(text, 0, text.length(), StyleSpan.class);
|
||||||
|
assertEquals(Typeface.BOLD_ITALIC, styleSpan.getStyle());
|
||||||
|
findSpan(text, 0, text.length(), UnderlineSpan.class);
|
||||||
|
TypefaceSpan typefaceSpan = findSpan(text, 0, text.length(), TypefaceSpan.class);
|
||||||
|
assertEquals(C.SERIF_NAME, typefaceSpan.getFamily());
|
||||||
|
ForegroundColorSpan colorSpan = findSpan(text, 0, text.length(), ForegroundColorSpan.class);
|
||||||
|
assertEquals(Color.RED, colorSpan.getForegroundColor());
|
||||||
|
colorSpan = findSpan(text, 0, 6, ForegroundColorSpan.class);
|
||||||
|
assertEquals(Color.GREEN, colorSpan.getForegroundColor());
|
||||||
|
assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testInitializationDecodeWithTbox() throws IOException, SubtitleDecoderException {
|
||||||
|
byte[] initBytes = TestUtil.getByteArray(getInstrumentation(), INITIALIZATION);
|
||||||
|
Tx3gDecoder decoder = new Tx3gDecoder(Collections.singletonList(initBytes));
|
||||||
|
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_WITH_TBOX);
|
||||||
|
Subtitle subtitle = decoder.decode(bytes, bytes.length, false);
|
||||||
|
SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text);
|
||||||
|
assertEquals("CC Test", text.toString());
|
||||||
|
assertEquals(4, text.getSpans(0, text.length(), Object.class).length);
|
||||||
|
StyleSpan styleSpan = findSpan(text, 0, text.length(), StyleSpan.class);
|
||||||
|
assertEquals(Typeface.BOLD_ITALIC, styleSpan.getStyle());
|
||||||
|
findSpan(text, 0, text.length(), UnderlineSpan.class);
|
||||||
|
TypefaceSpan typefaceSpan = findSpan(text, 0, text.length(), TypefaceSpan.class);
|
||||||
|
assertEquals(C.SERIF_NAME, typefaceSpan.getFamily());
|
||||||
|
ForegroundColorSpan colorSpan = findSpan(text, 0, text.length(), ForegroundColorSpan.class);
|
||||||
|
assertEquals(Color.RED, colorSpan.getForegroundColor());
|
||||||
|
assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.1875f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testInitializationAllDefaultsDecodeWithStyl() throws IOException,
|
||||||
|
SubtitleDecoderException {
|
||||||
|
byte[] initBytes = TestUtil.getByteArray(getInstrumentation(), INITIALIZATION_ALL_DEFAULTS);
|
||||||
|
Tx3gDecoder decoder = new Tx3gDecoder(Collections.singletonList(initBytes));
|
||||||
|
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_WITH_STYL);
|
||||||
|
Subtitle subtitle = decoder.decode(bytes, bytes.length, false);
|
||||||
|
SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text);
|
||||||
|
assertEquals("CC Test", text.toString());
|
||||||
|
assertEquals(3, text.getSpans(0, text.length(), Object.class).length);
|
||||||
|
StyleSpan styleSpan = findSpan(text, 0, 6, StyleSpan.class);
|
||||||
|
assertEquals(Typeface.BOLD_ITALIC, styleSpan.getStyle());
|
||||||
|
findSpan(text, 0, 6, UnderlineSpan.class);
|
||||||
|
ForegroundColorSpan colorSpan = findSpan(text, 0, 6, ForegroundColorSpan.class);
|
||||||
|
assertEquals(Color.GREEN, colorSpan.getForegroundColor());
|
||||||
|
assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> T findSpan(SpannedString testObject, int expectedStart, int expectedEnd,
|
private static <T> T findSpan(SpannedString testObject, int expectedStart, int expectedEnd,
|
||||||
@ -120,4 +209,10 @@ public final class Tx3gDecoderTest extends InstrumentationTestCase {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void assertFractionalLinePosition(Cue cue, float expectedFraction) {
|
||||||
|
assertEquals(Cue.LINE_TYPE_FRACTION, cue.lineType);
|
||||||
|
assertEquals(Cue.ANCHOR_TYPE_START, cue.lineAnchor);
|
||||||
|
assertTrue(Math.abs(expectedFraction - cue.line) < 1e-6);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -81,6 +81,16 @@ public final class C {
|
|||||||
*/
|
*/
|
||||||
public static final String UTF16_NAME = "UTF-16";
|
public static final String UTF16_NAME = "UTF-16";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* * The name of the serif font family.
|
||||||
|
*/
|
||||||
|
public static final String SERIF_NAME = "serif";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* * The name of the sans-serif font family.
|
||||||
|
*/
|
||||||
|
public static final String SANS_SERIF_NAME = "sans-serif";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Crypto modes for a codec.
|
* Crypto modes for a codec.
|
||||||
*/
|
*/
|
||||||
|
@ -283,30 +283,31 @@ public final class Format implements Parcelable {
|
|||||||
public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs,
|
public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs,
|
||||||
int bitrate, @C.SelectionFlags int selectionFlags, String language, DrmInitData drmInitData) {
|
int bitrate, @C.SelectionFlags int selectionFlags, String language, DrmInitData drmInitData) {
|
||||||
return createTextSampleFormat(id, sampleMimeType, codecs, bitrate, selectionFlags, language,
|
return createTextSampleFormat(id, sampleMimeType, codecs, bitrate, selectionFlags, language,
|
||||||
NO_VALUE, drmInitData, OFFSET_SAMPLE_RELATIVE);
|
NO_VALUE, drmInitData, OFFSET_SAMPLE_RELATIVE, Collections.<byte[]>emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs,
|
public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs,
|
||||||
int bitrate, @C.SelectionFlags int selectionFlags, String language, int accessibilityChannel,
|
int bitrate, @C.SelectionFlags int selectionFlags, String language, int accessibilityChannel,
|
||||||
DrmInitData drmInitData) {
|
DrmInitData drmInitData) {
|
||||||
return createTextSampleFormat(id, sampleMimeType, codecs, bitrate, selectionFlags, language,
|
return createTextSampleFormat(id, sampleMimeType, codecs, bitrate, selectionFlags, language,
|
||||||
accessibilityChannel, drmInitData, OFFSET_SAMPLE_RELATIVE);
|
accessibilityChannel, drmInitData, OFFSET_SAMPLE_RELATIVE, Collections.<byte[]>emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs,
|
public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs,
|
||||||
int bitrate, @C.SelectionFlags int selectionFlags, String language, DrmInitData drmInitData,
|
int bitrate, @C.SelectionFlags int selectionFlags, String language, DrmInitData drmInitData,
|
||||||
long subsampleOffsetUs) {
|
long subsampleOffsetUs) {
|
||||||
return createTextSampleFormat(id, sampleMimeType, codecs, bitrate, selectionFlags, language,
|
return createTextSampleFormat(id, sampleMimeType, codecs, bitrate, selectionFlags, language,
|
||||||
NO_VALUE, drmInitData, subsampleOffsetUs);
|
NO_VALUE, drmInitData, subsampleOffsetUs, Collections.<byte[]>emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs,
|
public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs,
|
||||||
int bitrate, @C.SelectionFlags int selectionFlags, String language,
|
int bitrate, @C.SelectionFlags int selectionFlags, String language,
|
||||||
int accessibilityChannel, DrmInitData drmInitData, long subsampleOffsetUs) {
|
int accessibilityChannel, DrmInitData drmInitData, long subsampleOffsetUs,
|
||||||
|
List<byte[]> initializationData) {
|
||||||
return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
|
return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||||
NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
|
NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||||
NO_VALUE, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, null,
|
NO_VALUE, selectionFlags, language, accessibilityChannel, subsampleOffsetUs,
|
||||||
drmInitData, null);
|
initializationData, drmInitData, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Image.
|
// Image.
|
||||||
@ -438,6 +439,7 @@ public final class Format implements Parcelable {
|
|||||||
drmInitData, metadata);
|
drmInitData, metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ReferenceEquality")
|
||||||
public Format copyWithManifestFormatInfo(Format manifestFormat) {
|
public Format copyWithManifestFormatInfo(Format manifestFormat) {
|
||||||
if (this == manifestFormat) {
|
if (this == manifestFormat) {
|
||||||
// No need to copy from ourselves.
|
// No need to copy from ourselves.
|
||||||
|
@ -611,24 +611,11 @@ import java.util.List;
|
|||||||
|| childAtomType == Atom.TYPE__mp3 || childAtomType == Atom.TYPE_alac) {
|
|| childAtomType == Atom.TYPE__mp3 || childAtomType == Atom.TYPE_alac) {
|
||||||
parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, trackId,
|
parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, trackId,
|
||||||
language, isQuickTime, drmInitData, out, i);
|
language, isQuickTime, drmInitData, out, i);
|
||||||
} else if (childAtomType == Atom.TYPE_TTML) {
|
} else if (childAtomType == Atom.TYPE_TTML || childAtomType == Atom.TYPE_tx3g
|
||||||
out.format = Format.createTextSampleFormat(Integer.toString(trackId),
|
|| childAtomType == Atom.TYPE_wvtt || childAtomType == Atom.TYPE_stpp
|
||||||
MimeTypes.APPLICATION_TTML, null, Format.NO_VALUE, 0, language, drmInitData);
|
|| childAtomType == Atom.TYPE_c608) {
|
||||||
} else if (childAtomType == Atom.TYPE_tx3g) {
|
parseTextSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, trackId,
|
||||||
out.format = Format.createTextSampleFormat(Integer.toString(trackId),
|
language, drmInitData, out);
|
||||||
MimeTypes.APPLICATION_TX3G, null, Format.NO_VALUE, 0, language, drmInitData);
|
|
||||||
} else if (childAtomType == Atom.TYPE_wvtt) {
|
|
||||||
out.format = Format.createTextSampleFormat(Integer.toString(trackId),
|
|
||||||
MimeTypes.APPLICATION_MP4VTT, null, Format.NO_VALUE, 0, language, drmInitData);
|
|
||||||
} else if (childAtomType == Atom.TYPE_stpp) {
|
|
||||||
out.format = Format.createTextSampleFormat(Integer.toString(trackId),
|
|
||||||
MimeTypes.APPLICATION_TTML, null, Format.NO_VALUE, 0, language, drmInitData,
|
|
||||||
0 /* subsample timing is absolute */);
|
|
||||||
} else if (childAtomType == Atom.TYPE_c608) {
|
|
||||||
// Defined by the QuickTime File Format specification.
|
|
||||||
out.format = Format.createTextSampleFormat(Integer.toString(trackId),
|
|
||||||
MimeTypes.APPLICATION_MP4CEA608, null, Format.NO_VALUE, 0, language, drmInitData);
|
|
||||||
out.requiredSampleTransformation = Track.TRANSFORMATION_CEA608_CDAT;
|
|
||||||
} else if (childAtomType == Atom.TYPE_camm) {
|
} else if (childAtomType == Atom.TYPE_camm) {
|
||||||
out.format = Format.createSampleFormat(Integer.toString(trackId),
|
out.format = Format.createSampleFormat(Integer.toString(trackId),
|
||||||
MimeTypes.APPLICATION_CAMERA_MOTION, null, Format.NO_VALUE, drmInitData);
|
MimeTypes.APPLICATION_CAMERA_MOTION, null, Format.NO_VALUE, drmInitData);
|
||||||
@ -638,12 +625,49 @@ import java.util.List;
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void parseTextSampleEntry(ParsableByteArray parent, int atomType, int position,
|
||||||
|
int atomSize, int trackId, String language, DrmInitData drmInitData, StsdData out)
|
||||||
|
throws ParserException {
|
||||||
|
parent.setPosition(position + Atom.HEADER_SIZE + StsdData.STSD_HEADER_SIZE);
|
||||||
|
|
||||||
|
// Default values.
|
||||||
|
List<byte[]> initializationData = null;
|
||||||
|
long subsampleOffsetUs = Format.OFFSET_SAMPLE_RELATIVE;
|
||||||
|
|
||||||
|
String mimeType;
|
||||||
|
if (atomType == Atom.TYPE_TTML) {
|
||||||
|
mimeType = MimeTypes.APPLICATION_TTML;
|
||||||
|
} else if (atomType == Atom.TYPE_tx3g) {
|
||||||
|
mimeType = MimeTypes.APPLICATION_TX3G;
|
||||||
|
int sampleDescriptionLength = atomSize - Atom.HEADER_SIZE - 8;
|
||||||
|
byte[] sampleDescriptionData = new byte[sampleDescriptionLength];
|
||||||
|
parent.readBytes(sampleDescriptionData, 0, sampleDescriptionLength);
|
||||||
|
initializationData = Collections.singletonList(sampleDescriptionData);
|
||||||
|
} else if (atomType == Atom.TYPE_wvtt) {
|
||||||
|
mimeType = MimeTypes.APPLICATION_MP4VTT;
|
||||||
|
} else if (atomType == Atom.TYPE_stpp) {
|
||||||
|
mimeType = MimeTypes.APPLICATION_TTML;
|
||||||
|
subsampleOffsetUs = 0; // Subsample timing is absolute.
|
||||||
|
} else if (atomType == Atom.TYPE_c608) {
|
||||||
|
// Defined by the QuickTime File Format specification.
|
||||||
|
mimeType = MimeTypes.APPLICATION_MP4CEA608;
|
||||||
|
out.requiredSampleTransformation = Track.TRANSFORMATION_CEA608_CDAT;
|
||||||
|
} else {
|
||||||
|
// Never happens.
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
out.format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, null,
|
||||||
|
Format.NO_VALUE, 0, language, Format.NO_VALUE, drmInitData, subsampleOffsetUs,
|
||||||
|
initializationData);
|
||||||
|
}
|
||||||
|
|
||||||
private static void parseVideoSampleEntry(ParsableByteArray parent, int atomType, int position,
|
private static void parseVideoSampleEntry(ParsableByteArray parent, int atomType, int position,
|
||||||
int size, int trackId, int rotationDegrees, DrmInitData drmInitData, StsdData out,
|
int size, int trackId, int rotationDegrees, DrmInitData drmInitData, StsdData out,
|
||||||
int entryIndex) throws ParserException {
|
int entryIndex) throws ParserException {
|
||||||
parent.setPosition(position + Atom.HEADER_SIZE);
|
parent.setPosition(position + Atom.HEADER_SIZE + StsdData.STSD_HEADER_SIZE);
|
||||||
|
|
||||||
parent.skipBytes(24);
|
parent.skipBytes(16);
|
||||||
int width = parent.readUnsignedShort();
|
int width = parent.readUnsignedShort();
|
||||||
int height = parent.readUnsignedShort();
|
int height = parent.readUnsignedShort();
|
||||||
boolean pixelWidthHeightRatioFromPasp = false;
|
boolean pixelWidthHeightRatioFromPasp = false;
|
||||||
@ -784,15 +808,14 @@ import java.util.List;
|
|||||||
private static void parseAudioSampleEntry(ParsableByteArray parent, int atomType, int position,
|
private static void parseAudioSampleEntry(ParsableByteArray parent, int atomType, int position,
|
||||||
int size, int trackId, String language, boolean isQuickTime, DrmInitData drmInitData,
|
int size, int trackId, String language, boolean isQuickTime, DrmInitData drmInitData,
|
||||||
StsdData out, int entryIndex) {
|
StsdData out, int entryIndex) {
|
||||||
parent.setPosition(position + Atom.HEADER_SIZE);
|
parent.setPosition(position + Atom.HEADER_SIZE + StsdData.STSD_HEADER_SIZE);
|
||||||
|
|
||||||
int quickTimeSoundDescriptionVersion = 0;
|
int quickTimeSoundDescriptionVersion = 0;
|
||||||
if (isQuickTime) {
|
if (isQuickTime) {
|
||||||
parent.skipBytes(8);
|
|
||||||
quickTimeSoundDescriptionVersion = parent.readUnsignedShort();
|
quickTimeSoundDescriptionVersion = parent.readUnsignedShort();
|
||||||
parent.skipBytes(6);
|
parent.skipBytes(6);
|
||||||
} else {
|
} else {
|
||||||
parent.skipBytes(16);
|
parent.skipBytes(8);
|
||||||
}
|
}
|
||||||
|
|
||||||
int channelCount;
|
int channelCount;
|
||||||
@ -1177,6 +1200,8 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
private static final class StsdData {
|
private static final class StsdData {
|
||||||
|
|
||||||
|
public static final int STSD_HEADER_SIZE = 8;
|
||||||
|
|
||||||
public final TrackEncryptionBox[] trackEncryptionBoxes;
|
public final TrackEncryptionBox[] trackEncryptionBoxes;
|
||||||
|
|
||||||
public Format format;
|
public Format format;
|
||||||
|
@ -92,7 +92,7 @@ public interface SubtitleDecoderFactory {
|
|||||||
case MimeTypes.APPLICATION_SUBRIP:
|
case MimeTypes.APPLICATION_SUBRIP:
|
||||||
return new SubripDecoder();
|
return new SubripDecoder();
|
||||||
case MimeTypes.APPLICATION_TX3G:
|
case MimeTypes.APPLICATION_TX3G:
|
||||||
return new Tx3gDecoder();
|
return new Tx3gDecoder(format.initializationData);
|
||||||
case MimeTypes.APPLICATION_CEA608:
|
case MimeTypes.APPLICATION_CEA608:
|
||||||
case MimeTypes.APPLICATION_MP4CEA608:
|
case MimeTypes.APPLICATION_MP4CEA608:
|
||||||
return new Cea608Decoder(format.sampleMimeType, format.accessibilityChannel);
|
return new Cea608Decoder(format.sampleMimeType, format.accessibilityChannel);
|
||||||
|
@ -15,26 +15,28 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.text.tx3g;
|
package com.google.android.exoplayer2.text.tx3g;
|
||||||
|
|
||||||
|
import android.graphics.Color;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.style.ForegroundColorSpan;
|
import android.text.style.ForegroundColorSpan;
|
||||||
import android.text.style.StyleSpan;
|
import android.text.style.StyleSpan;
|
||||||
|
import android.text.style.TypefaceSpan;
|
||||||
import android.text.style.UnderlineSpan;
|
import android.text.style.UnderlineSpan;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.text.Cue;
|
import com.google.android.exoplayer2.text.Cue;
|
||||||
import com.google.android.exoplayer2.text.SimpleSubtitleDecoder;
|
import com.google.android.exoplayer2.text.SimpleSubtitleDecoder;
|
||||||
import com.google.android.exoplayer2.text.Subtitle;
|
import com.google.android.exoplayer2.text.Subtitle;
|
||||||
import com.google.android.exoplayer2.text.SubtitleDecoderException;
|
import com.google.android.exoplayer2.text.SubtitleDecoderException;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
|
||||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link SimpleSubtitleDecoder} for tx3g.
|
* A {@link SimpleSubtitleDecoder} for tx3g.
|
||||||
* <p>
|
* <p>
|
||||||
* Currently only supports parsing of a single text track.
|
* Currently supports parsing of a single text track with embedded styles.
|
||||||
*/
|
*/
|
||||||
public final class Tx3gDecoder extends SimpleSubtitleDecoder {
|
public final class Tx3gDecoder extends SimpleSubtitleDecoder {
|
||||||
|
|
||||||
@ -42,6 +44,8 @@ public final class Tx3gDecoder extends SimpleSubtitleDecoder {
|
|||||||
private static final char BOM_UTF16_LE = '\uFFFE';
|
private static final char BOM_UTF16_LE = '\uFFFE';
|
||||||
|
|
||||||
private static final int TYPE_STYL = Util.getIntegerCodeForString("styl");
|
private static final int TYPE_STYL = Util.getIntegerCodeForString("styl");
|
||||||
|
private static final int TYPE_TBOX = Util.getIntegerCodeForString("tbox");
|
||||||
|
private static final String TX3G_SERIF = "Serif";
|
||||||
|
|
||||||
private static final int SIZE_ATOM_HEADER = 8;
|
private static final int SIZE_ATOM_HEADER = 8;
|
||||||
private static final int SIZE_SHORT = 2;
|
private static final int SIZE_SHORT = 2;
|
||||||
@ -52,44 +56,107 @@ public final class Tx3gDecoder extends SimpleSubtitleDecoder {
|
|||||||
private static final int FONT_FACE_ITALIC = 0x0002;
|
private static final int FONT_FACE_ITALIC = 0x0002;
|
||||||
private static final int FONT_FACE_UNDERLINE = 0x0004;
|
private static final int FONT_FACE_UNDERLINE = 0x0004;
|
||||||
|
|
||||||
private final ParsableByteArray parsableByteArray;
|
private static final int SPAN_PRIORITY_LOW = (0xFF << Spanned.SPAN_PRIORITY_SHIFT);
|
||||||
|
private static final int SPAN_PRIORITY_HIGH = (0x00 << Spanned.SPAN_PRIORITY_SHIFT);
|
||||||
|
|
||||||
public Tx3gDecoder() {
|
private static final int DEFAULT_FONT_FACE = 0;
|
||||||
|
private static final int DEFAULT_COLOR = Color.WHITE;
|
||||||
|
private static final String DEFAULT_FONT_FAMILY = C.SANS_SERIF_NAME;
|
||||||
|
private static final float DEFAULT_VERTICAL_PLACEMENT = 0.85f;
|
||||||
|
|
||||||
|
private final ParsableByteArray parsableByteArray;
|
||||||
|
private boolean customVerticalPlacement;
|
||||||
|
private int defaultFontFace;
|
||||||
|
private int defaultColorRgba;
|
||||||
|
private String defaultFontFamily;
|
||||||
|
private float defaultVerticalPlacement;
|
||||||
|
private int calculatedVideoTrackHeight;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up a new {@link Tx3gDecoder} with default values.
|
||||||
|
*
|
||||||
|
* @param initializationData Sample description atom ('stsd') data with default subtitle styles.
|
||||||
|
*/
|
||||||
|
public Tx3gDecoder(List<byte[]> initializationData) {
|
||||||
super("Tx3gDecoder");
|
super("Tx3gDecoder");
|
||||||
parsableByteArray = new ParsableByteArray();
|
parsableByteArray = new ParsableByteArray();
|
||||||
|
decodeInitializationData(initializationData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void decodeInitializationData(List<byte[]> initializationData) {
|
||||||
|
if (initializationData != null && initializationData.size() == 1
|
||||||
|
&& (initializationData.get(0).length == 48 || initializationData.get(0).length == 53)) {
|
||||||
|
byte[] initializationBytes = initializationData.get(0);
|
||||||
|
defaultFontFace = initializationBytes[24];
|
||||||
|
defaultColorRgba = ((initializationBytes[26] & 0xFF) << 24)
|
||||||
|
| ((initializationBytes[27] & 0xFF) << 16)
|
||||||
|
| ((initializationBytes[28] & 0xFF) << 8)
|
||||||
|
| (initializationBytes[29] & 0xFF);
|
||||||
|
String fontFamily = new String(initializationBytes, 43, initializationBytes.length - 43);
|
||||||
|
defaultFontFamily = TX3G_SERIF.equals(fontFamily) ? C.SERIF_NAME : C.SANS_SERIF_NAME;
|
||||||
|
//font size (initializationBytes[25]) is 5% of video height
|
||||||
|
calculatedVideoTrackHeight = 20 * initializationBytes[25];
|
||||||
|
customVerticalPlacement = (initializationBytes[0] & 0x20) != 0;
|
||||||
|
if (customVerticalPlacement) {
|
||||||
|
int requestedVerticalPlacement = ((initializationBytes[10] & 0xFF) << 8)
|
||||||
|
| (initializationBytes[11] & 0xFF);
|
||||||
|
defaultVerticalPlacement = (float) requestedVerticalPlacement / calculatedVideoTrackHeight;
|
||||||
|
defaultVerticalPlacement = Util.constrainValue(defaultVerticalPlacement, 0.0f, 0.95f);
|
||||||
|
} else {
|
||||||
|
defaultVerticalPlacement = DEFAULT_VERTICAL_PLACEMENT;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
defaultFontFace = DEFAULT_FONT_FACE;
|
||||||
|
defaultColorRgba = DEFAULT_COLOR;
|
||||||
|
defaultFontFamily = DEFAULT_FONT_FAMILY;
|
||||||
|
customVerticalPlacement = false;
|
||||||
|
defaultVerticalPlacement = DEFAULT_VERTICAL_PLACEMENT;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Subtitle decode(byte[] bytes, int length, boolean reset)
|
protected Subtitle decode(byte[] bytes, int length, boolean reset)
|
||||||
throws SubtitleDecoderException {
|
throws SubtitleDecoderException {
|
||||||
try {
|
parsableByteArray.reset(bytes, length);
|
||||||
parsableByteArray.reset(bytes, length);
|
String cueTextString = readSubtitleText(parsableByteArray);
|
||||||
String cueTextString = readSubtitleText(parsableByteArray);
|
if (cueTextString.isEmpty()) {
|
||||||
if (cueTextString.isEmpty()) {
|
return Tx3gSubtitle.EMPTY;
|
||||||
return Tx3gSubtitle.EMPTY;
|
|
||||||
}
|
|
||||||
SpannableStringBuilder cueText = new SpannableStringBuilder(cueTextString);
|
|
||||||
while (parsableByteArray.bytesLeft() >= SIZE_ATOM_HEADER) {
|
|
||||||
int atomSize = parsableByteArray.readInt();
|
|
||||||
int atomType = parsableByteArray.readInt();
|
|
||||||
if (atomType == TYPE_STYL) {
|
|
||||||
Assertions.checkArgument(parsableByteArray.bytesLeft() >= SIZE_SHORT);
|
|
||||||
int styleRecordCount = parsableByteArray.readUnsignedShort();
|
|
||||||
for (int i = 0; i < styleRecordCount; i++) {
|
|
||||||
applyStyleRecord(parsableByteArray, cueText);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
parsableByteArray.skipBytes(atomSize - SIZE_ATOM_HEADER);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new Tx3gSubtitle(new Cue(cueText));
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
throw new SubtitleDecoderException("Unexpected subtitle format.", e);
|
|
||||||
}
|
}
|
||||||
|
// Attach default styles.
|
||||||
|
SpannableStringBuilder cueText = new SpannableStringBuilder(cueTextString);
|
||||||
|
attachFontFace(cueText, defaultFontFace, DEFAULT_FONT_FACE, 0, cueText.length(),
|
||||||
|
SPAN_PRIORITY_LOW);
|
||||||
|
attachColor(cueText, defaultColorRgba, DEFAULT_COLOR, 0, cueText.length(),
|
||||||
|
SPAN_PRIORITY_LOW);
|
||||||
|
attachFontFamily(cueText, defaultFontFamily, DEFAULT_FONT_FAMILY, 0, cueText.length(),
|
||||||
|
SPAN_PRIORITY_LOW);
|
||||||
|
float verticalPlacement = defaultVerticalPlacement;
|
||||||
|
// Find and attach additional styles.
|
||||||
|
while (parsableByteArray.bytesLeft() >= SIZE_ATOM_HEADER) {
|
||||||
|
int position = parsableByteArray.getPosition();
|
||||||
|
int atomSize = parsableByteArray.readInt();
|
||||||
|
int atomType = parsableByteArray.readInt();
|
||||||
|
if (atomType == TYPE_STYL) {
|
||||||
|
assertTrue(parsableByteArray.bytesLeft() >= SIZE_SHORT);
|
||||||
|
int styleRecordCount = parsableByteArray.readUnsignedShort();
|
||||||
|
for (int i = 0; i < styleRecordCount; i++) {
|
||||||
|
applyStyleRecord(parsableByteArray, cueText);
|
||||||
|
}
|
||||||
|
} else if (atomType == TYPE_TBOX && customVerticalPlacement) {
|
||||||
|
assertTrue(parsableByteArray.bytesLeft() >= SIZE_SHORT);
|
||||||
|
int requestedVerticalPlacement = parsableByteArray.readUnsignedShort();
|
||||||
|
verticalPlacement = (float) requestedVerticalPlacement / calculatedVideoTrackHeight;
|
||||||
|
verticalPlacement = Util.constrainValue(verticalPlacement, 0.0f, 0.95f);
|
||||||
|
}
|
||||||
|
parsableByteArray.setPosition(position + atomSize);
|
||||||
|
}
|
||||||
|
return new Tx3gSubtitle(new Cue(cueText, null, verticalPlacement, Cue.LINE_TYPE_FRACTION,
|
||||||
|
Cue.ANCHOR_TYPE_START, Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String readSubtitleText(ParsableByteArray parsableByteArray) {
|
private static String readSubtitleText(ParsableByteArray parsableByteArray)
|
||||||
Assertions.checkArgument(parsableByteArray.bytesLeft() >= SIZE_SHORT);
|
throws SubtitleDecoderException {
|
||||||
|
assertTrue(parsableByteArray.bytesLeft() >= SIZE_SHORT);
|
||||||
int textLength = parsableByteArray.readUnsignedShort();
|
int textLength = parsableByteArray.readUnsignedShort();
|
||||||
if (textLength == 0) {
|
if (textLength == 0) {
|
||||||
return "";
|
return "";
|
||||||
@ -103,47 +170,65 @@ public final class Tx3gDecoder extends SimpleSubtitleDecoder {
|
|||||||
return parsableByteArray.readString(textLength, Charset.forName(C.UTF8_NAME));
|
return parsableByteArray.readString(textLength, Charset.forName(C.UTF8_NAME));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void applyStyleRecord(ParsableByteArray parsableByteArray,
|
private void applyStyleRecord(ParsableByteArray parsableByteArray,
|
||||||
SpannableStringBuilder cueText) {
|
SpannableStringBuilder cueText) throws SubtitleDecoderException {
|
||||||
Assertions.checkArgument(parsableByteArray.bytesLeft() >= SIZE_STYLE_RECORD);
|
assertTrue(parsableByteArray.bytesLeft() >= SIZE_STYLE_RECORD);
|
||||||
int start = parsableByteArray.readUnsignedShort();
|
int start = parsableByteArray.readUnsignedShort();
|
||||||
int end = parsableByteArray.readUnsignedShort();
|
int end = parsableByteArray.readUnsignedShort();
|
||||||
parsableByteArray.skipBytes(2); // font identifier
|
parsableByteArray.skipBytes(2); // font identifier
|
||||||
int fontFace = parsableByteArray.readUnsignedByte();
|
int fontFace = parsableByteArray.readUnsignedByte();
|
||||||
parsableByteArray.skipBytes(1); // font size
|
parsableByteArray.skipBytes(1); // font size
|
||||||
int colorRgba = parsableByteArray.readInt();
|
int colorRgba = parsableByteArray.readInt();
|
||||||
|
attachFontFace(cueText, fontFace, defaultFontFace, start, end, SPAN_PRIORITY_HIGH);
|
||||||
if (fontFace != 0) {
|
attachColor(cueText, colorRgba, defaultColorRgba, start, end, SPAN_PRIORITY_HIGH);
|
||||||
attachFontFace(cueText, fontFace, start, end);
|
|
||||||
}
|
|
||||||
attachColor(cueText, colorRgba, start, end);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void attachFontFace(SpannableStringBuilder cueText, int fontFace, int start,
|
private static void attachFontFace(SpannableStringBuilder cueText, int fontFace,
|
||||||
int end) {
|
int defaultFontFace, int start, int end, int spanPriority) {
|
||||||
boolean isBold = (fontFace & FONT_FACE_BOLD) != 0;
|
if (fontFace != defaultFontFace) {
|
||||||
boolean isItalic = (fontFace & FONT_FACE_ITALIC) != 0;
|
final int flags = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | spanPriority;
|
||||||
if (isBold) {
|
boolean isBold = (fontFace & FONT_FACE_BOLD) != 0;
|
||||||
if (isItalic) {
|
boolean isItalic = (fontFace & FONT_FACE_ITALIC) != 0;
|
||||||
cueText.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), start, end,
|
if (isBold) {
|
||||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
if (isItalic) {
|
||||||
} else {
|
cueText.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), start, end, flags);
|
||||||
cueText.setSpan(new StyleSpan(Typeface.BOLD), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
} else {
|
||||||
|
cueText.setSpan(new StyleSpan(Typeface.BOLD), start, end, flags);
|
||||||
|
}
|
||||||
|
} else if (isItalic) {
|
||||||
|
cueText.setSpan(new StyleSpan(Typeface.ITALIC), start, end, flags);
|
||||||
|
}
|
||||||
|
boolean isUnderlined = (fontFace & FONT_FACE_UNDERLINE) != 0;
|
||||||
|
if (isUnderlined) {
|
||||||
|
cueText.setSpan(new UnderlineSpan(), start, end, flags);
|
||||||
|
}
|
||||||
|
if (!isUnderlined && !isBold && !isItalic) {
|
||||||
|
cueText.setSpan(new StyleSpan(Typeface.NORMAL), start, end, flags);
|
||||||
}
|
}
|
||||||
} else if (isItalic) {
|
|
||||||
cueText.setSpan(new StyleSpan(Typeface.ITALIC), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isUnderlined = (fontFace & FONT_FACE_UNDERLINE) != 0;
|
|
||||||
if (isUnderlined) {
|
|
||||||
cueText.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void attachColor(SpannableStringBuilder cueText, int colorRgba, int start,
|
private static void attachColor(SpannableStringBuilder cueText, int colorRgba,
|
||||||
int end) {
|
int defaultColorRgba, int start, int end, int spanPriority) {
|
||||||
int colorArgb = ((colorRgba & 0xFF) << 24) | (colorRgba >>> 8);
|
if (colorRgba != defaultColorRgba) {
|
||||||
cueText.setSpan(new ForegroundColorSpan(colorArgb), start, end,
|
int colorArgb = ((colorRgba & 0xFF) << 24) | (colorRgba >>> 8);
|
||||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
cueText.setSpan(new ForegroundColorSpan(colorArgb), start, end,
|
||||||
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | spanPriority);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ReferenceEquality")
|
||||||
|
private static void attachFontFamily(SpannableStringBuilder cueText, String fontFamily,
|
||||||
|
String defaultFontFamily, int start, int end, int spanPriority) {
|
||||||
|
if (fontFamily != defaultFontFamily) {
|
||||||
|
cueText.setSpan(new TypefaceSpan(fontFamily), start, end,
|
||||||
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | spanPriority);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertTrue(boolean checkValue) throws SubtitleDecoderException {
|
||||||
|
if (!checkValue) {
|
||||||
|
throw new SubtitleDecoderException("Unexpected subtitle format.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user