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 Cue::Cue() : start_time(0), duration(0) {}
196 std::shared_ptr<MediaSample> CueToMediaSample(
const Cue& cue) {
197 const bool kKeyFrame =
true;
198 if (!cue.comment.empty()) {
199 const std::string comment = base::JoinString(cue.comment,
"\n");
201 reinterpret_cast<const uint8_t*>(comment.data()), comment.size());
204 const std::string payload = base::JoinString(cue.payload,
"\n");
206 reinterpret_cast<const uint8_t*>(payload.data()), payload.size(),
207 reinterpret_cast<const uint8_t*
>(cue.settings.data()),
208 cue.settings.size(), !kKeyFrame);
210 media_sample->set_config_id(cue.identifier);
211 media_sample->set_pts(cue.start_time);
212 media_sample->set_duration(cue.duration);
219 Cue MediaSampleToCue(
const MediaSample& sample) {
221 if (sample.data_size() == 0) {
222 std::string comment(sample.side_data(),
223 sample.side_data() + sample.side_data_size());
224 cue.comment.push_back(comment);
228 std::string payload(sample.data(), sample.data() + sample.data_size());
229 cue.payload.push_back(payload);
230 cue.identifier.assign(sample.config_id());
231 cue.start_time = sample.pts();
232 cue.duration = sample.duration();
233 if (sample.side_data_size() != 0) {
234 cue.settings.assign(sample.side_data(),
235 sample.side_data() + sample.side_data_size());
240 WebVttMediaParser::WebVttMediaParser() : state_(kHeader) {}
241 WebVttMediaParser::~WebVttMediaParser() {}
247 new_sample_cb_ = new_sample_cb;
252 if (state_ != kCuePayload && state_ != kComment)
255 if (!data_.empty()) {
258 if (state_ == kCuePayload) {
259 current_cue_.payload.push_back(data_);
261 current_cue_.comment.push_back(data_);
266 bool result = new_sample_cb_.Run(kTrackId, CueToMediaSample(current_cue_));
267 current_cue_ =
Cue();
268 state_ = kCueIdentifierOrTimingOrComment;
273 if (state_ == kParseError) {
274 LOG(WARNING) <<
"The parser is in an error state, ignoring input.";
278 data_.insert(data_.end(), buf, buf + size);
281 while (ReadLine(&data_, &line)) {
284 const bool has_arrow = line.find(
"-->") != std::string::npos;
285 if (state_ == kCueTiming) {
287 LOG(ERROR) <<
"Expected --> in: " << line;
288 state_ = kParseError;
291 }
else if (state_ != kCueIdentifierOrTimingOrComment) {
293 LOG(ERROR) <<
"Unexpected --> in " << line;
294 state_ = kParseError;
302 header_.push_back(line);
307 std::vector<std::shared_ptr<StreamInfo>> streams;
309 const int kTimescale = 1000;
314 const int kDuration = 0;
318 const char kLanguage[] =
"";
319 streams.emplace_back(
321 base::JoinString(header_,
"\n"),
326 init_cb_.Run(streams);
327 state_ = kCueIdentifierOrTimingOrComment;
331 header_.push_back(line);
334 case kCueIdentifierOrTimingOrComment: {
342 if (base::StartsWith(line,
"NOTE",
343 base::CompareCase::INSENSITIVE_ASCII)) {
345 current_cue_.comment.push_back(line);
349 current_cue_.identifier = line;
360 FALLTHROUGH_INTENDED;
364 if (!ParseTimingAndSettingsLine(line, ¤t_cue_.start_time,
365 ¤t_cue_.duration,
366 ¤t_cue_.settings)) {
367 state_ = kParseError;
370 state_ = kCuePayload;
375 state_ = kCueIdentifierOrTimingOrComment;
376 if (!new_sample_cb_.Run(kTrackId, CueToMediaSample(current_cue_))) {
377 state_ = kParseError;
380 current_cue_ =
Cue();
384 current_cue_.payload.push_back(line);
389 state_ = kCueIdentifierOrTimingOrComment;
390 if (!new_sample_cb_.Run(kTrackId, CueToMediaSample(current_cue_))) {
391 state_ = kParseError;
394 current_cue_ =
Cue();
398 current_cue_.comment.push_back(line);