1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.android.exoplayer2.text.subrip; 17 18 import static com.google.common.truth.Truth.assertThat; 19 20 import androidx.test.core.app.ApplicationProvider; 21 import androidx.test.ext.junit.runners.AndroidJUnit4; 22 import com.google.android.exoplayer2.testutil.TestUtil; 23 import com.google.android.exoplayer2.text.Cue; 24 import com.google.android.exoplayer2.text.Subtitle; 25 import java.io.IOException; 26 import org.junit.Test; 27 import org.junit.runner.RunWith; 28 29 /** Unit test for {@link SubripDecoder}. */ 30 @RunWith(AndroidJUnit4.class) 31 public final class SubripDecoderTest { 32 33 private static final String EMPTY_FILE = "subrip/empty"; 34 private static final String TYPICAL_FILE = "subrip/typical"; 35 private static final String TYPICAL_WITH_BYTE_ORDER_MARK = "subrip/typical_with_byte_order_mark"; 36 private static final String TYPICAL_EXTRA_BLANK_LINE = "subrip/typical_extra_blank_line"; 37 private static final String TYPICAL_MISSING_TIMECODE = "subrip/typical_missing_timecode"; 38 private static final String TYPICAL_MISSING_SEQUENCE = "subrip/typical_missing_sequence"; 39 private static final String TYPICAL_NEGATIVE_TIMESTAMPS = "subrip/typical_negative_timestamps"; 40 private static final String TYPICAL_UNEXPECTED_END = "subrip/typical_unexpected_end"; 41 private static final String TYPICAL_WITH_TAGS = "subrip/typical_with_tags"; 42 private static final String TYPICAL_NO_HOURS_AND_MILLIS = "subrip/typical_no_hours_and_millis"; 43 44 @Test decodeEmpty()45 public void decodeEmpty() throws IOException { 46 SubripDecoder decoder = new SubripDecoder(); 47 byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), EMPTY_FILE); 48 Subtitle subtitle = decoder.decode(bytes, bytes.length, false); 49 50 assertThat(subtitle.getEventTimeCount()).isEqualTo(0); 51 assertThat(subtitle.getCues(0).isEmpty()).isTrue(); 52 } 53 54 @Test decodeTypical()55 public void decodeTypical() throws IOException { 56 SubripDecoder decoder = new SubripDecoder(); 57 byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TYPICAL_FILE); 58 Subtitle subtitle = decoder.decode(bytes, bytes.length, false); 59 60 assertThat(subtitle.getEventTimeCount()).isEqualTo(6); 61 assertTypicalCue1(subtitle, 0); 62 assertTypicalCue2(subtitle, 2); 63 assertTypicalCue3(subtitle, 4); 64 } 65 66 @Test decodeTypicalWithByteOrderMark()67 public void decodeTypicalWithByteOrderMark() throws IOException { 68 SubripDecoder decoder = new SubripDecoder(); 69 byte[] bytes = 70 TestUtil.getByteArray( 71 ApplicationProvider.getApplicationContext(), TYPICAL_WITH_BYTE_ORDER_MARK); 72 Subtitle subtitle = decoder.decode(bytes, bytes.length, false); 73 74 assertThat(subtitle.getEventTimeCount()).isEqualTo(6); 75 assertTypicalCue1(subtitle, 0); 76 assertTypicalCue2(subtitle, 2); 77 assertTypicalCue3(subtitle, 4); 78 } 79 80 @Test decodeTypicalExtraBlankLine()81 public void decodeTypicalExtraBlankLine() throws IOException { 82 SubripDecoder decoder = new SubripDecoder(); 83 byte[] bytes = 84 TestUtil.getByteArray( 85 ApplicationProvider.getApplicationContext(), TYPICAL_EXTRA_BLANK_LINE); 86 Subtitle subtitle = decoder.decode(bytes, bytes.length, false); 87 88 assertThat(subtitle.getEventTimeCount()).isEqualTo(6); 89 assertTypicalCue1(subtitle, 0); 90 assertTypicalCue2(subtitle, 2); 91 assertTypicalCue3(subtitle, 4); 92 } 93 94 @Test decodeTypicalMissingTimecode()95 public void decodeTypicalMissingTimecode() throws IOException { 96 // Parsing should succeed, parsing the first and third cues only. 97 SubripDecoder decoder = new SubripDecoder(); 98 byte[] bytes = 99 TestUtil.getByteArray( 100 ApplicationProvider.getApplicationContext(), TYPICAL_MISSING_TIMECODE); 101 Subtitle subtitle = decoder.decode(bytes, bytes.length, false); 102 103 assertThat(subtitle.getEventTimeCount()).isEqualTo(4); 104 assertTypicalCue1(subtitle, 0); 105 assertTypicalCue3(subtitle, 2); 106 } 107 108 @Test decodeTypicalMissingSequence()109 public void decodeTypicalMissingSequence() throws IOException { 110 // Parsing should succeed, parsing the first and third cues only. 111 SubripDecoder decoder = new SubripDecoder(); 112 byte[] bytes = 113 TestUtil.getByteArray( 114 ApplicationProvider.getApplicationContext(), TYPICAL_MISSING_SEQUENCE); 115 Subtitle subtitle = decoder.decode(bytes, bytes.length, false); 116 117 assertThat(subtitle.getEventTimeCount()).isEqualTo(4); 118 assertTypicalCue1(subtitle, 0); 119 assertTypicalCue3(subtitle, 2); 120 } 121 122 @Test decodeTypicalNegativeTimestamps()123 public void decodeTypicalNegativeTimestamps() throws IOException { 124 // Parsing should succeed, parsing the third cue only. 125 SubripDecoder decoder = new SubripDecoder(); 126 byte[] bytes = 127 TestUtil.getByteArray( 128 ApplicationProvider.getApplicationContext(), TYPICAL_NEGATIVE_TIMESTAMPS); 129 Subtitle subtitle = decoder.decode(bytes, bytes.length, false); 130 131 assertThat(subtitle.getEventTimeCount()).isEqualTo(2); 132 assertTypicalCue3(subtitle, 0); 133 } 134 135 @Test decodeTypicalUnexpectedEnd()136 public void decodeTypicalUnexpectedEnd() throws IOException { 137 // Parsing should succeed, parsing the first and second cues only. 138 SubripDecoder decoder = new SubripDecoder(); 139 byte[] bytes = 140 TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TYPICAL_UNEXPECTED_END); 141 Subtitle subtitle = decoder.decode(bytes, bytes.length, false); 142 143 assertThat(subtitle.getEventTimeCount()).isEqualTo(4); 144 assertTypicalCue1(subtitle, 0); 145 assertTypicalCue2(subtitle, 2); 146 } 147 148 @Test decodeCueWithTag()149 public void decodeCueWithTag() throws IOException { 150 SubripDecoder decoder = new SubripDecoder(); 151 byte[] bytes = 152 TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TYPICAL_WITH_TAGS); 153 Subtitle subtitle = decoder.decode(bytes, bytes.length, false); 154 155 assertThat(subtitle.getCues(subtitle.getEventTime(0)).get(0).text.toString()) 156 .isEqualTo("This is the first subtitle."); 157 158 assertThat(subtitle.getCues(subtitle.getEventTime(2)).get(0).text.toString()) 159 .isEqualTo("This is the second subtitle.\nSecond subtitle with second line."); 160 161 assertThat(subtitle.getCues(subtitle.getEventTime(4)).get(0).text.toString()) 162 .isEqualTo("This is the third subtitle."); 163 164 assertThat(subtitle.getCues(subtitle.getEventTime(6)).get(0).text.toString()) 165 .isEqualTo("This { \\an2} is not a valid tag due to the space after the opening bracket."); 166 167 assertThat(subtitle.getCues(subtitle.getEventTime(8)).get(0).text.toString()) 168 .isEqualTo("This is the fifth subtitle with multiple valid tags."); 169 170 assertAlignmentCue(subtitle, 10, Cue.ANCHOR_TYPE_END, Cue.ANCHOR_TYPE_START); // {/an1} 171 assertAlignmentCue(subtitle, 12, Cue.ANCHOR_TYPE_END, Cue.ANCHOR_TYPE_MIDDLE); // {/an2} 172 assertAlignmentCue(subtitle, 14, Cue.ANCHOR_TYPE_END, Cue.ANCHOR_TYPE_END); // {/an3} 173 assertAlignmentCue(subtitle, 16, Cue.ANCHOR_TYPE_MIDDLE, Cue.ANCHOR_TYPE_START); // {/an4} 174 assertAlignmentCue(subtitle, 18, Cue.ANCHOR_TYPE_MIDDLE, Cue.ANCHOR_TYPE_MIDDLE); // {/an5} 175 assertAlignmentCue(subtitle, 20, Cue.ANCHOR_TYPE_MIDDLE, Cue.ANCHOR_TYPE_END); // {/an6} 176 assertAlignmentCue(subtitle, 22, Cue.ANCHOR_TYPE_START, Cue.ANCHOR_TYPE_START); // {/an7} 177 assertAlignmentCue(subtitle, 24, Cue.ANCHOR_TYPE_START, Cue.ANCHOR_TYPE_MIDDLE); // {/an8} 178 assertAlignmentCue(subtitle, 26, Cue.ANCHOR_TYPE_START, Cue.ANCHOR_TYPE_END); // {/an9} 179 } 180 181 @Test decodeTypicalNoHoursAndMillis()182 public void decodeTypicalNoHoursAndMillis() throws IOException { 183 SubripDecoder decoder = new SubripDecoder(); 184 byte[] bytes = 185 TestUtil.getByteArray( 186 ApplicationProvider.getApplicationContext(), TYPICAL_NO_HOURS_AND_MILLIS); 187 Subtitle subtitle = decoder.decode(bytes, bytes.length, false); 188 189 assertThat(subtitle.getEventTimeCount()).isEqualTo(6); 190 assertTypicalCue1(subtitle, 0); 191 assertThat(subtitle.getEventTime(2)).isEqualTo(2_000_000); 192 assertThat(subtitle.getEventTime(3)).isEqualTo(3_000_000); 193 assertTypicalCue3(subtitle, 4); 194 } 195 assertTypicalCue1(Subtitle subtitle, int eventIndex)196 private static void assertTypicalCue1(Subtitle subtitle, int eventIndex) { 197 assertThat(subtitle.getEventTime(eventIndex)).isEqualTo(0); 198 assertThat(subtitle.getCues(subtitle.getEventTime(eventIndex)).get(0).text.toString()) 199 .isEqualTo("This is the first subtitle."); 200 assertThat(subtitle.getEventTime(eventIndex + 1)).isEqualTo(1234000); 201 } 202 assertTypicalCue2(Subtitle subtitle, int eventIndex)203 private static void assertTypicalCue2(Subtitle subtitle, int eventIndex) { 204 assertThat(subtitle.getEventTime(eventIndex)).isEqualTo(2345000); 205 assertThat(subtitle.getCues(subtitle.getEventTime(eventIndex)).get(0).text.toString()) 206 .isEqualTo("This is the second subtitle.\nSecond subtitle with second line."); 207 assertThat(subtitle.getEventTime(eventIndex + 1)).isEqualTo(3456000); 208 } 209 assertTypicalCue3(Subtitle subtitle, int eventIndex)210 private static void assertTypicalCue3(Subtitle subtitle, int eventIndex) { 211 long expectedStartTimeUs = (((2L * 60L * 60L) + 4L) * 1000L + 567L) * 1000L; 212 assertThat(subtitle.getEventTime(eventIndex)).isEqualTo(expectedStartTimeUs); 213 assertThat(subtitle.getCues(subtitle.getEventTime(eventIndex)).get(0).text.toString()) 214 .isEqualTo("This is the third subtitle."); 215 long expectedEndTimeUs = (((2L * 60L * 60L) + 8L) * 1000L + 901L) * 1000L; 216 assertThat(subtitle.getEventTime(eventIndex + 1)).isEqualTo(expectedEndTimeUs); 217 } 218 assertAlignmentCue( Subtitle subtitle, int eventIndex, @Cue.AnchorType int lineAnchor, @Cue.AnchorType int positionAnchor)219 private static void assertAlignmentCue( 220 Subtitle subtitle, 221 int eventIndex, 222 @Cue.AnchorType int lineAnchor, 223 @Cue.AnchorType int positionAnchor) { 224 long eventTimeUs = subtitle.getEventTime(eventIndex); 225 Cue cue = subtitle.getCues(eventTimeUs).get(0); 226 assertThat(cue.lineType).isEqualTo(Cue.LINE_TYPE_FRACTION); 227 assertThat(cue.lineAnchor).isEqualTo(lineAnchor); 228 assertThat(cue.line).isEqualTo(SubripDecoder.getFractionalPositionForAnchorType(lineAnchor)); 229 assertThat(cue.positionAnchor).isEqualTo(positionAnchor); 230 assertThat(cue.position) 231 .isEqualTo(SubripDecoder.getFractionalPositionForAnchorType(positionAnchor)); 232 } 233 } 234