From bbbd61e31949ec3807903f20227a32d8b457302f Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 2 Dec 2016 10:49:26 -0800 Subject: [PATCH] Skip negative SubRip timecodes Issue: #2145 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=140868079 --- .../assets/subrip/typical_negative_timestamps | 12 +++++++ .../text/subrip/SubripDecoderTest.java | 10 ++++++ .../exoplayer2/text/cea/Cea608Decoder.java | 2 +- .../exoplayer2/text/subrip/SubripDecoder.java | 32 ++++++++----------- 4 files changed, 36 insertions(+), 20 deletions(-) create mode 100644 library/src/androidTest/assets/subrip/typical_negative_timestamps diff --git a/library/src/androidTest/assets/subrip/typical_negative_timestamps b/library/src/androidTest/assets/subrip/typical_negative_timestamps new file mode 100644 index 0000000000..2a47c0993b --- /dev/null +++ b/library/src/androidTest/assets/subrip/typical_negative_timestamps @@ -0,0 +1,12 @@ +1 +-0:00:04,567 --> -0:00:03,456 +This is the first subtitle. + +2 +-00:00:02,345 --> 00:00:01,234 +This is the second subtitle. +Second subtitle with second line. + +3 +00:00:04,567 --> 00:00:08,901 +This is the third subtitle. diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java index eca126347c..502fa9a789 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java @@ -30,6 +30,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase { private static final String TYPICAL_EXTRA_BLANK_LINE = "subrip/typical_extra_blank_line"; private static final String TYPICAL_MISSING_TIMECODE = "subrip/typical_missing_timecode"; private static final String TYPICAL_MISSING_SEQUENCE = "subrip/typical_missing_sequence"; + private static final String TYPICAL_NEGATIVE_TIMESTAMPS = "subrip/typical_negative_timestamps"; private static final String NO_END_TIMECODES_FILE = "subrip/no_end_timecodes"; public void testDecodeEmpty() throws IOException { @@ -91,6 +92,15 @@ public final class SubripDecoderTest extends InstrumentationTestCase { assertTypicalCue3(subtitle, 2); } + public void testDecodeTypicalNegativeTimestamps() throws IOException { + // Parsing should succeed, parsing the third cue only. + SubripDecoder decoder = new SubripDecoder(); + byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_NEGATIVE_TIMESTAMPS); + SubripSubtitle subtitle = decoder.decode(bytes, bytes.length); + assertEquals(2, subtitle.getEventTimeCount()); + assertTypicalCue3(subtitle, 0); + } + public void testDecodeNoEndTimecodes() throws IOException { SubripDecoder decoder = new SubripDecoder(); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), NO_END_TIMECODES_FILE); diff --git a/library/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java b/library/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java index da709a6be8..6bf7b6a944 100644 --- a/library/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java +++ b/library/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java @@ -548,7 +548,7 @@ public final class Cea608Decoder extends CeaDecoder { private static boolean isPreambleAddressCode(byte cc1, byte cc2) { // cc1 - 0|0|0|1|C|X|X|X // cc2 - 0|1|X|X|X|X|X|X - return ((cc1 & 0xF0) == 0x10) && ((cc2 & 0xC0) == 0x80); + return ((cc1 & 0xF0) == 0x10) && ((cc2 & 0xC0) == 0x40); } private static boolean isTabCtrlCode(byte cc1, byte cc2) { diff --git a/library/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java b/library/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java index 43a93353c3..a848022ba9 100644 --- a/library/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java +++ b/library/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java @@ -34,9 +34,9 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { private static final String TAG = "SubripDecoder"; - private static final Pattern SUBRIP_TIMING_LINE = Pattern.compile("(\\S*)\\s*-->\\s*(\\S*)"); - private static final Pattern SUBRIP_TIMESTAMP = - Pattern.compile("(?:(\\d+):)?(\\d+):(\\d+),(\\d+)"); + private static final String SUBRIP_TIMECODE = "(?:(\\d+):)?(\\d+):(\\d+),(\\d+)"; + private static final Pattern SUBRIP_TIMING_LINE = + Pattern.compile("\\s*(" + SUBRIP_TIMECODE + ")\\s*-->\\s*(" + SUBRIP_TIMECODE + ")?\\s*"); private final StringBuilder textBuilder; @@ -50,7 +50,6 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { ArrayList cues = new ArrayList<>(); LongArray cueTimesUs = new LongArray(); ParsableByteArray subripData = new ParsableByteArray(bytes, length); - boolean haveEndTimecode; String currentLine; while ((currentLine = subripData.readLine()) != null) { @@ -68,15 +67,14 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { } // Read and parse the timing line. - haveEndTimecode = false; + boolean haveEndTimecode = false; currentLine = subripData.readLine(); Matcher matcher = SUBRIP_TIMING_LINE.matcher(currentLine); - if (matcher.find()) { - cueTimesUs.add(parseTimecode(matcher.group(1))); - String endTimecode = matcher.group(2); - if (!TextUtils.isEmpty(endTimecode)) { + if (matcher.matches()) { + cueTimesUs.add(parseTimecode(matcher, 1)); + if (!TextUtils.isEmpty(matcher.group(6))) { haveEndTimecode = true; - cueTimesUs.add(parseTimecode(matcher.group(2))); + cueTimesUs.add(parseTimecode(matcher, 6)); } } else { Log.w(TAG, "Skipping invalid timing: " + currentLine); @@ -105,15 +103,11 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { return new SubripSubtitle(cuesArray, cueTimesUsArray); } - private static long parseTimecode(String s) throws NumberFormatException { - Matcher matcher = SUBRIP_TIMESTAMP.matcher(s); - if (!matcher.matches()) { - throw new NumberFormatException("has invalid format"); - } - long timestampMs = Long.parseLong(matcher.group(1)) * 60 * 60 * 1000; - timestampMs += Long.parseLong(matcher.group(2)) * 60 * 1000; - timestampMs += Long.parseLong(matcher.group(3)) * 1000; - timestampMs += Long.parseLong(matcher.group(4)); + private static long parseTimecode(Matcher matcher, int groupOffset) { + long timestampMs = Long.parseLong(matcher.group(groupOffset + 1)) * 60 * 60 * 1000; + timestampMs += Long.parseLong(matcher.group(groupOffset + 2)) * 60 * 1000; + timestampMs += Long.parseLong(matcher.group(groupOffset + 3)) * 1000; + timestampMs += Long.parseLong(matcher.group(groupOffset + 4)); return timestampMs * 1000; }