Support MKV embedded WebVTT captions

This commit is contained in:
Marcel Dopita 2022-01-15 20:37:40 +01:00
parent b3981be8b9
commit 6b3187ccf1

View file

@ -138,6 +138,7 @@ public class MatroskaExtractor implements Extractor {
private static final String CODEC_ID_PCM_FLOAT = "A_PCM/FLOAT/IEEE";
private static final String CODEC_ID_SUBRIP = "S_TEXT/UTF8";
private static final String CODEC_ID_ASS = "S_TEXT/ASS";
private static final String CODEC_ID_VTT = "S_TEXT/WEBVTT";
private static final String CODEC_ID_VOBSUB = "S_VOBSUB";
private static final String CODEC_ID_PGS = "S_HDMV/PGS";
private static final String CODEC_ID_DVBSUB = "S_DVBSUB";
@ -323,6 +324,32 @@ public class MatroskaExtractor implements Extractor {
/** The format of an SSA timecode. */
private static final String SSA_TIMECODE_FORMAT = "%01d:%02d:%02d:%02d";
/**
* A template for the prefix that must be added to each VTT sample.
*
* <p>The display time of each subtitle is passed as {@code timeUs} to {@link
* TrackOutput#sampleMetadata}. The start and end timecodes in this template are relative to
* {@code timeUs}. Hence the start timecode is always zero. The 12 byte end timecode starting at
* {@link #VTT_PREFIX_END_TIMECODE_OFFSET} is set to a placeholder value, and must be replaced
* with the duration of the subtitle.
*
* <p>Equivalent to the UTF-8 string: "WEBVTT\n\n00:00:00.000 --> 00:00:00.000\n".
*/
private static final byte[] VTT_PREFIX =
new byte[]{
87, 69, 66, 86, 84, 84, 10, 10, 48, 48, 58, 48, 48, 58, 48, 48, 46, 48, 48, 48, 32, 45,
45, 62, 32, 48, 48, 58, 48, 48, 58, 48, 48, 46, 48, 48, 48, 10
};
/** The byte offset of the end timecode in {@link #VTT_PREFIX}. */
private static final int VTT_PREFIX_END_TIMECODE_OFFSET = 25;
/**
* The value by which to divide a time in microseconds to convert it to the unit of the last value
* in a VTT timecode (milliseconds).
*/
private static final long VTT_TIMECODE_LAST_VALUE_SCALING_FACTOR = 1000;
/** The format of a VTT timecode. */
private static final String VTT_TIMECODE_FORMAT = "%02d:%02d:%02d.%03d";
/** The length in bytes of a WAVEFORMATEX structure. */
private static final int WAVE_FORMAT_SIZE = 18;
/** Format tag indicating a WAVEFORMATEXTENSIBLE structure. */
@ -1342,7 +1369,8 @@ public class MatroskaExtractor implements Extractor {
track.trueHdSampleRechunker.sampleMetadata(
track.output, timeUs, flags, size, offset, track.cryptoData);
} else {
if (CODEC_ID_SUBRIP.equals(track.codecId) || CODEC_ID_ASS.equals(track.codecId)) {
if (CODEC_ID_SUBRIP.equals(track.codecId) || CODEC_ID_ASS.equals(track.codecId)
|| CODEC_ID_VTT.equals(track.codecId)) {
if (blockSampleCount > 1) {
Log.w(TAG, "Skipping subtitle sample in laced block.");
} else if (blockDurationUs == C.TIME_UNSET) {
@ -1415,6 +1443,9 @@ public class MatroskaExtractor implements Extractor {
} else if (CODEC_ID_ASS.equals(track.codecId)) {
writeSubtitleSampleData(input, SSA_PREFIX, size);
return finishWriteSampleData();
} else if (CODEC_ID_VTT.equals(track.codecId)) {
writeSubtitleSampleData(input, VTT_PREFIX, size);
return finishWriteSampleData();
}
TrackOutput output = track.output;
@ -1641,7 +1672,8 @@ public class MatroskaExtractor implements Extractor {
* <p>See documentation on {@link #SSA_DIALOGUE_FORMAT} and {@link #SUBRIP_PREFIX} for why we use
* the duration as the end timecode.
*
* @param codecId The subtitle codec; must be {@link #CODEC_ID_SUBRIP} or {@link #CODEC_ID_ASS}.
* @param codecId The subtitle codec; must be {@link #CODEC_ID_SUBRIP}, {@link #CODEC_ID_ASS} or
* {@link #CODEC_ID_VTT}.
* @param durationUs The duration of the sample, in microseconds.
* @param subtitleData The subtitle sample in which to overwrite the end timecode (output
* parameter).
@ -1662,6 +1694,12 @@ public class MatroskaExtractor implements Extractor {
durationUs, SSA_TIMECODE_FORMAT, SSA_TIMECODE_LAST_VALUE_SCALING_FACTOR);
endTimecodeOffset = SSA_PREFIX_END_TIMECODE_OFFSET;
break;
case CODEC_ID_VTT:
endTimecode =
formatSubtitleTimecode(
durationUs, VTT_TIMECODE_FORMAT, VTT_TIMECODE_LAST_VALUE_SCALING_FACTOR);
endTimecodeOffset = VTT_PREFIX_END_TIMECODE_OFFSET;
break;
default:
throw new IllegalArgumentException();
}
@ -1830,6 +1868,7 @@ public class MatroskaExtractor implements Extractor {
case CODEC_ID_PCM_FLOAT:
case CODEC_ID_SUBRIP:
case CODEC_ID_ASS:
case CODEC_ID_VTT:
case CODEC_ID_VOBSUB:
case CODEC_ID_PGS:
case CODEC_ID_DVBSUB:
@ -2157,6 +2196,9 @@ public class MatroskaExtractor implements Extractor {
mimeType = MimeTypes.TEXT_SSA;
initializationData = ImmutableList.of(SSA_DIALOGUE_FORMAT, getCodecPrivate(codecId));
break;
case CODEC_ID_VTT:
mimeType = MimeTypes.TEXT_VTT;
break;
case CODEC_ID_VOBSUB:
mimeType = MimeTypes.APPLICATION_VOBSUB;
initializationData = ImmutableList.of(getCodecPrivate(codecId));
@ -2245,6 +2287,7 @@ public class MatroskaExtractor implements Extractor {
.setColorInfo(colorInfo);
} else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)
|| MimeTypes.TEXT_SSA.equals(mimeType)
|| MimeTypes.TEXT_VTT.equals(mimeType)
|| MimeTypes.APPLICATION_VOBSUB.equals(mimeType)
|| MimeTypes.APPLICATION_PGS.equals(mimeType)
|| MimeTypes.APPLICATION_DVBSUBS.equals(mimeType)) {