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,
" ");
191 scoped_refptr<MediaSample> CueToMediaSample(
const Cue& cue) {
192 const bool kKeyFrame =
true;
193 if (!cue.comment.empty()) {
194 const std::string comment = base::JoinString(cue.comment,
"\n");
196 reinterpret_cast<const uint8_t*>(comment.data()), comment.size());
199 const std::string payload = base::JoinString(cue.payload,
"\n");
201 reinterpret_cast<const uint8_t*>(payload.data()),
203 reinterpret_cast<const uint8_t*
>(cue.settings.data()),
207 media_sample->set_config_id(cue.identifier);
208 media_sample->set_pts(cue.start_time);
209 media_sample->set_duration(cue.duration);
215 Cue::Cue() : start_time(0), duration(0) {}
218 WebVttMediaParser::WebVttMediaParser() : state_(kHeader) {}
219 WebVttMediaParser::~WebVttMediaParser() {}
225 new_sample_cb_ = new_sample_cb;
230 if (state_ != kCuePayload && state_ != kComment)
233 if (!data_.empty()) {
236 if (state_ == kCuePayload) {
237 current_cue_.payload.push_back(data_);
239 current_cue_.comment.push_back(data_);
244 bool result = new_sample_cb_.Run(kTrackId, CueToMediaSample(current_cue_));
245 current_cue_ =
Cue();
246 state_ = kCueIdentifierOrTimingOrComment;
251 if (state_ == kParseError) {
252 LOG(WARNING) <<
"The parser is in an error state, ignoring input.";
256 data_.insert(data_.end(), buf, buf + size);
259 while (ReadLine(&data_, &line)) {
262 const bool has_arrow = line.find(
"-->") != std::string::npos;
263 if (state_ == kCueTiming) {
265 LOG(ERROR) <<
"Expected --> in: " << line;
266 state_ = kParseError;
269 }
else if (state_ != kCueIdentifierOrTimingOrComment) {
271 LOG(ERROR) <<
"Unexpected --> in " << line;
272 state_ = kParseError;
280 header_.push_back(line);
285 std::vector<scoped_refptr<StreamInfo> > streams;
287 const int kTimescale = 1000;
292 const int kDuration = 0;
296 const char kLanguage[] =
"";
297 streams.push_back(
new TextStreamInfo(kTrackId, kTimescale, kDuration,
299 base::JoinString(header_,
"\n"),
304 init_cb_.Run(streams);
305 state_ = kCueIdentifierOrTimingOrComment;
309 header_.push_back(line);
312 case kCueIdentifierOrTimingOrComment: {
320 if (base::StartsWith(line,
"NOTE",
321 base::CompareCase::INSENSITIVE_ASCII)) {
323 current_cue_.comment.push_back(line);
327 current_cue_.identifier = line;
338 FALLTHROUGH_INTENDED;
342 if (!ParseTimingAndSettingsLine(line, ¤t_cue_.start_time,
343 ¤t_cue_.duration,
344 ¤t_cue_.settings)) {
345 state_ = kParseError;
348 state_ = kCuePayload;
353 state_ = kCueIdentifierOrTimingOrComment;
354 if (!new_sample_cb_.Run(kTrackId, CueToMediaSample(current_cue_))) {
355 state_ = kParseError;
358 current_cue_ =
Cue();
362 current_cue_.payload.push_back(line);
367 state_ = kCueIdentifierOrTimingOrComment;
368 if (!new_sample_cb_.Run(kTrackId, CueToMediaSample(current_cue_))) {
369 state_ = kParseError;
372 current_cue_ =
Cue();
376 current_cue_.comment.push_back(line);