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/media_sample.h"
17 #include "packager/media/base/text_stream_info.h"
19 namespace edash_packager {
25 const int kTrackId = 0;
27 const char kCR = 0x0D;
28 const char kLF = 0x0A;
33 bool ReadLine(std::string* data, std::string* line) {
34 if (data->size() == 0) {
37 size_t string_position = 0;
39 int line_break_length = 1;
40 bool found_line_break =
false;
41 while (string_position < data->size()) {
42 if (data->at(string_position) == kLF) {
43 found_line_break =
true;
47 if (data->at(string_position) == kCR) {
48 found_line_break =
true;
49 if (string_position + 1 >= data->size())
52 if (data->at(string_position + 1) == kLF)
53 line_break_length = 2;
60 if (!found_line_break)
63 *line = data->substr(0, string_position);
64 data->erase(0, string_position + line_break_length);
68 bool TimestampToMilliseconds(
const std::string& original_str,
70 const size_t kMinutesLength = 2;
71 const size_t kSecondsLength = 2;
72 const size_t kMillisecondsLength = 3;
76 const size_t kMinimalLength =
77 kMinutesLength + kSecondsLength + kMillisecondsLength + 2;
79 base::StringPiece str(original_str);
80 if (str.size() < kMinimalLength)
89 if (str.size() > kMinimalLength) {
92 const size_t hours_length = str.size() - kMinimalLength - 1;
93 if (!base::StringToInt(str.substr(0, hours_length), &hours))
95 str_index += hours_length;
97 if (str[str_index] !=
':')
102 DCHECK_EQ(str.size() - str_index, kMinimalLength);
104 if (!base::StringToInt(str.substr(str_index, kMinutesLength), &minutes))
106 if (minutes < 0 || minutes > 60)
109 str_index += kMinutesLength;
110 if (str[str_index] !=
':')
114 if (!base::StringToInt(str.substr(str_index, kSecondsLength), &seconds))
116 if (seconds < 0 || seconds > 60)
119 str_index += kSecondsLength;
120 if (str[str_index] !=
'.')
124 if (!base::StringToInt(str.substr(str_index, kMillisecondsLength),
128 str_index += kMillisecondsLength;
130 if (milliseconds < 0 || milliseconds > 999)
133 DCHECK_EQ(str.size(), str_index);
134 *time_ms = milliseconds +
136 minutes * 60 * 1000 +
137 hours * 60 * 60 * 1000;
143 bool ParseTimingAndSettingsLine(
const std::string& line,
144 uint64_t* start_time,
146 std::string* settings) {
150 std::vector<std::string> entries = base::SplitString(
151 line,
" ", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
152 if (entries.size() < 3) {
155 LOG(ERROR) <<
"Not enough tokens to be a timing " << line;
159 if (entries[1] !=
"-->") {
160 LOG(ERROR) <<
"Cannot find an arrow at the right place " << line;
164 const std::string& start_time_str = entries[0];
165 if (!TimestampToMilliseconds(start_time_str, start_time)) {
166 LOG(ERROR) <<
"Failed to parse " << start_time_str <<
" in " << line;
170 const std::string& end_time_str = entries[2];
171 uint64_t end_time = 0;
172 if (!TimestampToMilliseconds(end_time_str, &end_time)) {
173 LOG(ERROR) <<
"Failed to parse " << end_time_str <<
" in " << line;
176 *duration = end_time - *start_time;
178 entries.erase(entries.begin(), entries.begin() + 3);
179 *settings = base::JoinString(entries,
" ");
187 scoped_refptr<MediaSample> CueToMediaSample(
const Cue& cue) {
188 const bool kKeyFrame =
true;
189 if (!cue.comment.empty()) {
190 const std::string comment = base::JoinString(cue.comment,
"\n");
192 reinterpret_cast<const uint8_t*>(comment.data()), comment.size());
195 const std::string payload = base::JoinString(cue.payload,
"\n");
197 reinterpret_cast<const uint8_t*>(payload.data()),
199 reinterpret_cast<const uint8_t*
>(cue.settings.data()),
203 media_sample->set_config_id(cue.identifier);
204 media_sample->set_pts(cue.start_time);
205 media_sample->set_duration(cue.duration);
211 Cue::Cue() : start_time(0), duration(0) {}
214 WebVttMediaParser::WebVttMediaParser() : state_(kHeader) {}
215 WebVttMediaParser::~WebVttMediaParser() {}
221 new_sample_cb_ = new_sample_cb;
226 if (state_ != kCuePayload && state_ != kComment)
229 if (!data_.empty()) {
232 if (state_ == kCuePayload) {
233 current_cue_.payload.push_back(data_);
235 current_cue_.comment.push_back(data_);
240 new_sample_cb_.Run(kTrackId, CueToMediaSample(current_cue_));
241 current_cue_ =
Cue();
242 state_ = kCueIdentifierOrTimingOrComment;
246 if (state_ == kParseError) {
247 LOG(WARNING) <<
"The parser is in an error state, ignoring input.";
251 data_.insert(data_.end(), buf, buf + size);
254 while (ReadLine(&data_, &line)) {
257 const bool has_arrow = line.find(
"-->") != std::string::npos;
258 if (state_ == kCueTiming) {
260 LOG(ERROR) <<
"Expected --> in: " << line;
261 state_ = kParseError;
264 }
else if (state_ != kCueIdentifierOrTimingOrComment) {
266 LOG(ERROR) <<
"Unexpected --> in " << line;
267 state_ = kParseError;
275 header_.push_back(line);
280 std::vector<scoped_refptr<StreamInfo> > streams;
282 const int kTimescale = 1000;
287 const int kDuration = 0;
291 const char kLanguage[] =
"";
298 base::JoinString(header_,
"\n"),
302 init_cb_.Run(streams);
303 state_ = kCueIdentifierOrTimingOrComment;
307 header_.push_back(line);
310 case kCueIdentifierOrTimingOrComment: {
318 if (base::StartsWith(line,
"NOTE",
319 base::CompareCase::INSENSITIVE_ASCII)) {
321 current_cue_.comment.push_back(line);
325 current_cue_.identifier = line;
339 if (!ParseTimingAndSettingsLine(line, ¤t_cue_.start_time,
340 ¤t_cue_.duration,
341 ¤t_cue_.settings)) {
342 state_ = kParseError;
345 state_ = kCuePayload;
350 state_ = kCueIdentifierOrTimingOrComment;
351 new_sample_cb_.Run(kTrackId, CueToMediaSample(current_cue_));
352 current_cue_ =
Cue();
356 current_cue_.payload.push_back(line);
361 state_ = kCueIdentifierOrTimingOrComment;
362 new_sample_cb_.Run(kTrackId, CueToMediaSample(current_cue_));
363 current_cue_ =
Cue();
367 current_cue_.comment.push_back(line);