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"
25 const bool kFlush =
true;
28 const int kTrackId = 0;
30 const char kCR = 0x0D;
31 const char kLF = 0x0A;
36 bool ReadLine(std::string* data, std::string* line) {
37 if (data->size() == 0) {
40 size_t string_position = 0;
42 int line_break_length = 1;
43 bool found_line_break =
false;
44 while (string_position < data->size()) {
45 if (data->at(string_position) == kLF) {
46 found_line_break =
true;
50 if (data->at(string_position) == kCR) {
51 found_line_break =
true;
52 if (string_position + 1 >= data->size())
55 if (data->at(string_position + 1) == kLF)
56 line_break_length = 2;
63 if (!found_line_break)
66 *line = data->substr(0, string_position);
67 data->erase(0, string_position + line_break_length);
71 bool TimestampToMilliseconds(
const std::string& original_str,
73 const size_t kMinimalHoursLength = 2;
74 const size_t kMinutesLength = 2;
75 const size_t kSecondsLength = 2;
76 const size_t kMillisecondsLength = 3;
80 const size_t kMinimalLength =
81 kMinutesLength + kSecondsLength + kMillisecondsLength + 2;
83 base::StringPiece str(original_str);
84 if (str.size() < kMinimalLength)
93 if (str.size() > kMinimalLength) {
96 const size_t hours_length = str.size() - kMinimalLength - 1;
97 if (hours_length < kMinimalHoursLength)
99 if (!base::StringToInt(str.substr(0, hours_length), &hours))
101 str_index += hours_length;
103 if (str[str_index] !=
':')
108 DCHECK_EQ(str.size() - str_index, kMinimalLength);
110 if (!base::StringToInt(str.substr(str_index, kMinutesLength), &minutes))
112 if (minutes < 0 || minutes > 60)
115 str_index += kMinutesLength;
116 if (str[str_index] !=
':')
120 if (!base::StringToInt(str.substr(str_index, kSecondsLength), &seconds))
122 if (seconds < 0 || seconds > 60)
125 str_index += kSecondsLength;
126 if (str[str_index] !=
'.')
130 if (!base::StringToInt(str.substr(str_index, kMillisecondsLength),
134 str_index += kMillisecondsLength;
136 if (milliseconds < 0 || milliseconds > 999)
139 DCHECK_EQ(str.size(), str_index);
140 *time_ms = milliseconds +
142 minutes * 60 * 1000 +
143 hours * 60 * 60 * 1000;
149 bool ParseTimingAndSettingsLine(
const std::string& line,
150 uint64_t* start_time,
152 std::string* settings) {
156 std::vector<std::string> entries = base::SplitString(
157 line,
" ", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
158 if (entries.size() < 3) {
161 LOG(ERROR) <<
"Not enough tokens to be a timing " << line;
165 if (entries[1] !=
"-->") {
166 LOG(ERROR) <<
"Cannot find an arrow at the right place " << line;
170 const std::string& start_time_str = entries[0];
171 if (!TimestampToMilliseconds(start_time_str, start_time)) {
172 LOG(ERROR) <<
"Failed to parse " << start_time_str <<
" in " << line;
176 const std::string& end_time_str = entries[2];
177 uint64_t end_time = 0;
178 if (!TimestampToMilliseconds(end_time_str, &end_time)) {
179 LOG(ERROR) <<
"Failed to parse " << end_time_str <<
" in " << line;
182 *duration = end_time - *start_time;
184 entries.erase(entries.begin(), entries.begin() + 3);
185 *settings = base::JoinString(entries,
" ");
191 WebVttMediaParser::WebVttMediaParser()
192 : state_(kHeader), sample_converter_(new WebVttSampleConverter()) {}
193 WebVttMediaParser::~WebVttMediaParser() {}
199 new_sample_cb_ = new_sample_cb;
204 if (state_ != kCuePayload && state_ != kComment)
207 if (!data_.empty()) {
210 if (state_ == kCuePayload) {
211 current_cue_.payload += data_ +
"\n";
213 current_cue_.comment += data_ +
"\n";
218 if (!ProcessCurrentCue(kFlush)) {
219 state_ = kParseError;
223 state_ = kCueIdentifierOrTimingOrComment;
228 if (state_ == kParseError) {
229 LOG(WARNING) <<
"The parser is in an error state, ignoring input.";
233 data_.insert(data_.end(), buf, buf + size);
236 while (ReadLine(&data_, &line)) {
239 const bool has_arrow = line.find(
"-->") != std::string::npos;
240 if (state_ == kCueTiming) {
242 LOG(ERROR) <<
"Expected --> in: " << line;
243 state_ = kParseError;
246 }
else if (state_ != kCueIdentifierOrTimingOrComment) {
248 LOG(ERROR) <<
"Unexpected --> in " << line;
249 state_ = kParseError;
257 header_.push_back(line);
262 std::vector<std::shared_ptr<StreamInfo>> streams;
264 const int kTimescale = 1000;
269 const int kDuration = 0;
273 const char kLanguage[] =
"";
275 const char kWebVttCodecString[] =
"wvtt";
276 streams.emplace_back(
278 kCodecWebVtt, kWebVttCodecString,
279 base::JoinString(header_,
"\n"),
284 init_cb_.Run(streams);
285 state_ = kCueIdentifierOrTimingOrComment;
289 header_.push_back(line);
292 case kCueIdentifierOrTimingOrComment: {
300 if (base::StartsWith(line,
"NOTE",
301 base::CompareCase::INSENSITIVE_ASCII)) {
303 current_cue_.comment += line +
"\n";
307 current_cue_.identifier = line;
318 FALLTHROUGH_INTENDED;
322 if (!ParseTimingAndSettingsLine(line, ¤t_cue_.start_time,
323 ¤t_cue_.duration,
324 ¤t_cue_.settings)) {
325 state_ = kParseError;
328 state_ = kCuePayload;
333 state_ = kCueIdentifierOrTimingOrComment;
334 if (!ProcessCurrentCue(!kFlush)) {
335 state_ = kParseError;
341 current_cue_.payload += line +
"\n";
346 state_ = kCueIdentifierOrTimingOrComment;
347 if (!ProcessCurrentCue(!kFlush)) {
348 state_ = kParseError;
354 current_cue_.comment += line +
"\n";
366 void WebVttMediaParser::InjectWebVttSampleConvertForTesting(
367 std::unique_ptr<WebVttSampleConverter> converter) {
368 sample_converter_ = std::move(converter);
371 bool WebVttMediaParser::ProcessCurrentCue(
bool flush) {
372 sample_converter_->PushCue(current_cue_);
373 current_cue_ = Cue();
375 sample_converter_->Flush();
377 while (sample_converter_->ReadySamplesSize() > 0) {
378 if (!new_sample_cb_.Run(kTrackId, sample_converter_->PopSample())) {
379 LOG(ERROR) <<
"New sample callback failed.";