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.webvtt; 17 18 import androidx.annotation.Nullable; 19 import com.google.android.exoplayer2.ParserException; 20 import com.google.android.exoplayer2.util.ParsableByteArray; 21 import com.google.android.exoplayer2.util.Util; 22 import java.util.regex.Matcher; 23 import java.util.regex.Pattern; 24 25 /** 26 * Utility methods for parsing WebVTT data. 27 */ 28 public final class WebvttParserUtil { 29 30 private static final Pattern COMMENT = Pattern.compile("^NOTE([ \t].*)?$"); 31 private static final String WEBVTT_HEADER = "WEBVTT"; 32 WebvttParserUtil()33 private WebvttParserUtil() {} 34 35 /** 36 * Reads and validates the first line of a WebVTT file. 37 * 38 * @param input The input from which the line should be read. 39 * @throws ParserException If the line isn't the start of a valid WebVTT file. 40 */ validateWebvttHeaderLine(ParsableByteArray input)41 public static void validateWebvttHeaderLine(ParsableByteArray input) throws ParserException { 42 int startPosition = input.getPosition(); 43 if (!isWebvttHeaderLine(input)) { 44 input.setPosition(startPosition); 45 throw new ParserException("Expected WEBVTT. Got " + input.readLine()); 46 } 47 } 48 49 /** 50 * Returns whether the given input is the first line of a WebVTT file. 51 * 52 * @param input The input from which the line should be read. 53 */ isWebvttHeaderLine(ParsableByteArray input)54 public static boolean isWebvttHeaderLine(ParsableByteArray input) { 55 @Nullable String line = input.readLine(); 56 return line != null && line.startsWith(WEBVTT_HEADER); 57 } 58 59 /** 60 * Parses a WebVTT timestamp. 61 * 62 * @param timestamp The timestamp string. 63 * @return The parsed timestamp in microseconds. 64 * @throws NumberFormatException If the timestamp could not be parsed. 65 */ parseTimestampUs(String timestamp)66 public static long parseTimestampUs(String timestamp) throws NumberFormatException { 67 long value = 0; 68 String[] parts = Util.splitAtFirst(timestamp, "\\."); 69 String[] subparts = Util.split(parts[0], ":"); 70 for (String subpart : subparts) { 71 value = (value * 60) + Long.parseLong(subpart); 72 } 73 value *= 1000; 74 if (parts.length == 2) { 75 value += Long.parseLong(parts[1]); 76 } 77 return value * 1000; 78 } 79 80 /** 81 * Parses a percentage string. 82 * 83 * @param s The percentage string. 84 * @return The parsed value, where 1.0 represents 100%. 85 * @throws NumberFormatException If the percentage could not be parsed. 86 */ parsePercentage(String s)87 public static float parsePercentage(String s) throws NumberFormatException { 88 if (!s.endsWith("%")) { 89 throw new NumberFormatException("Percentages must end with %"); 90 } 91 return Float.parseFloat(s.substring(0, s.length() - 1)) / 100; 92 } 93 94 /** 95 * Reads lines up to and including the next WebVTT cue header. 96 * 97 * @param input The input from which lines should be read. 98 * @return A {@link Matcher} for the WebVTT cue header, or null if the end of the input was 99 * reached without a cue header being found. In the case that a cue header is found, groups 1, 100 * 2 and 3 of the returned matcher contain the start time, end time and settings list. 101 */ 102 @Nullable findNextCueHeader(ParsableByteArray input)103 public static Matcher findNextCueHeader(ParsableByteArray input) { 104 @Nullable String line; 105 while ((line = input.readLine()) != null) { 106 if (COMMENT.matcher(line).matches()) { 107 // Skip until the end of the comment block. 108 while ((line = input.readLine()) != null && !line.isEmpty()) {} 109 } else { 110 Matcher cueHeaderMatcher = WebvttCueParser.CUE_HEADER_PATTERN.matcher(line); 111 if (cueHeaderMatcher.matches()) { 112 return cueHeaderMatcher; 113 } 114 } 115 } 116 return null; 117 } 118 119 } 120