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