7 #include "packager/media/formats/webvtt/webvtt_media_parser.h"
12 #include "packager/base/logging.h"
13 #include "packager/base/strings/string_number_conversions.h"
14 #include "packager/base/strings/string_split.h"
15 #include "packager/base/strings/string_util.h"
16 #include "packager/media/base/macros.h"
17 #include "packager/media/base/media_sample.h"
18 #include "packager/media/base/text_stream_info.h"
26 const int kTrackId = 0;
28 const char kCR = 0x0D;
29 const char kLF = 0x0A;
34 bool ReadLine(std::string* data, std::string* line) {
35 if (data->size() == 0) {
38 size_t string_position = 0;
40 int line_break_length = 1;
41 bool found_line_break =
false;
42 while (string_position < data->size()) {
43 if (data->at(string_position) == kLF) {
44 found_line_break =
true;
48 if (data->at(string_position) == kCR) {
49 found_line_break =
true;
50 if (string_position + 1 >= data->size())
53 if (data->at(string_position + 1) == kLF)
54 line_break_length = 2;
61 if (!found_line_break)
64 *line = data->substr(0, string_position);
65 data->erase(0, string_position + line_break_length);
69 bool TimestampToMilliseconds(
const std::string& original_str,
71 const size_t kMinimalHoursLength = 2;
72 const size_t kMinutesLength = 2;
73 const size_t kSecondsLength = 2;
74 const size_t kMillisecondsLength = 3;
78 const size_t kMinimalLength =
79 kMinutesLength + kSecondsLength + kMillisecondsLength + 2;
81 base::StringPiece str(original_str);
82 if (str.size() < kMinimalLength)
91 if (str.size() > kMinimalLength) {
94 const size_t hours_length = str.size() - kMinimalLength - 1;
95 if (hours_length < kMinimalHoursLength)
97 if (!base::StringToInt(str.substr(0, hours_length), &hours))
99 str_index += hours_length;
101 if (str[str_index] !=
':')
106 DCHECK_EQ(str.size() - str_index, kMinimalLength);
108 if (!base::StringToInt(str.substr(str_index, kMinutesLength), &minutes))
110 if (minutes < 0 || minutes > 60)
113 str_index += kMinutesLength;
114 if (str[str_index] !=
':')
118 if (!base::StringToInt(str.substr(str_index, kSecondsLength), &seconds))
120 if (seconds < 0 || seconds > 60)
123 str_index += kSecondsLength;
124 if (str[str_index] !=
'.')
128 if (!base::StringToInt(str.substr(str_index, kMillisecondsLength),
132 str_index += kMillisecondsLength;
134 if (milliseconds < 0 || milliseconds > 999)
137 DCHECK_EQ(str.size(), str_index);
138 *time_ms = milliseconds +
140 minutes * 60 * 1000 +
141 hours * 60 * 60 * 1000;
147 bool ParseTimingAndSettingsLine(
const std::string& line,
148 uint64_t* start_time,
150 std::string* settings) {
154 std::vector<std::string> entries = base::SplitString(
155 line,
" ", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
156 if (entries.size() < 3) {
159 LOG(ERROR) <<
"Not enough tokens to be a timing " << line;
163 if (entries[1] !=
"-->") {
164 LOG(ERROR) <<
"Cannot find an arrow at the right place " << line;
168 const std::string& start_time_str = entries[0];
169 if (!TimestampToMilliseconds(start_time_str, start_time)) {
170 LOG(ERROR) <<
"Failed to parse " << start_time_str <<
" in " << line;
174 const std::string& end_time_str = entries[2];
175 uint64_t end_time = 0;
176 if (!TimestampToMilliseconds(end_time_str, &end_time)) {
177 LOG(ERROR) <<
"Failed to parse " << end_time_str <<
" in " << line;
180 *duration = end_time - *start_time;
182 entries.erase(entries.begin(), entries.begin() + 3);
183 *settings = base::JoinString(entries,
" ");
189 WebVttMediaParser::WebVttMediaParser() : state_(kHeader) {}
190 WebVttMediaParser::~WebVttMediaParser() {}
196 new_sample_cb_ = new_sample_cb;
201 if (state_ != kCuePayload && state_ != kComment)
204 if (!data_.empty()) {
207 if (state_ == kCuePayload) {
208 current_cue_.payload.push_back(data_);
210 current_cue_.comment.push_back(data_);
215 bool result = new_sample_cb_.Run(kTrackId, CueToMediaSample(current_cue_));
216 current_cue_ =
Cue();
217 state_ = kCueIdentifierOrTimingOrComment;
222 if (state_ == kParseError) {
223 LOG(WARNING) <<
"The parser is in an error state, ignoring input.";
227 data_.insert(data_.end(), buf, buf + size);
230 while (ReadLine(&data_, &line)) {
233 const bool has_arrow = line.find(
"-->") != std::string::npos;
234 if (state_ == kCueTiming) {
236 LOG(ERROR) <<
"Expected --> in: " << line;
237 state_ = kParseError;
240 }
else if (state_ != kCueIdentifierOrTimingOrComment) {
242 LOG(ERROR) <<
"Unexpected --> in " << line;
243 state_ = kParseError;
251 header_.push_back(line);
256 std::vector<std::shared_ptr<StreamInfo>> streams;
258 const int kTimescale = 1000;
263 const int kDuration = 0;
267 const char kLanguage[] =
"";
268 streams.emplace_back(
270 base::JoinString(header_,
"\n"),
275 init_cb_.Run(streams);
276 state_ = kCueIdentifierOrTimingOrComment;
280 header_.push_back(line);
283 case kCueIdentifierOrTimingOrComment: {
291 if (base::StartsWith(line,
"NOTE",
292 base::CompareCase::INSENSITIVE_ASCII)) {
294 current_cue_.comment.push_back(line);
298 current_cue_.identifier = line;
309 FALLTHROUGH_INTENDED;
313 if (!ParseTimingAndSettingsLine(line, ¤t_cue_.start_time,
314 ¤t_cue_.duration,
315 ¤t_cue_.settings)) {
316 state_ = kParseError;
319 state_ = kCuePayload;
324 state_ = kCueIdentifierOrTimingOrComment;
325 if (!new_sample_cb_.Run(kTrackId, CueToMediaSample(current_cue_))) {
326 state_ = kParseError;
329 current_cue_ =
Cue();
333 current_cue_.payload.push_back(line);
338 state_ = kCueIdentifierOrTimingOrComment;
339 if (!new_sample_cb_.Run(kTrackId, CueToMediaSample(current_cue_))) {
340 state_ = kParseError;
343 current_cue_ =
Cue();
347 current_cue_.comment.push_back(line);