7 #include "packager/media/formats/webvtt/webvtt_parser.h" 12 #include "packager/base/logging.h" 13 #include "packager/base/strings/string_split.h" 14 #include "packager/base/strings/string_util.h" 15 #include "packager/media/base/text_stream_info.h" 16 #include "packager/media/formats/webvtt/webvtt_timestamp.h" 21 const uint64_t kStreamIndex = 0;
23 std::string BlockToString(
const std::string* block,
size_t size) {
24 std::string out =
" --- BLOCK START ---\n";
26 for (
size_t i = 0; i < size; i++) {
32 out.append(
" --- BLOCK END ---");
41 bool IsLikelyNote(
const std::string& line) {
42 return line ==
"NOTE" ||
43 base::StartsWith(line,
"NOTE ", base::CompareCase::SENSITIVE) ||
44 base::StartsWith(line,
"NOTE\t", base::CompareCase::SENSITIVE);
50 bool IsLikelyCueTiming(
const std::string& line) {
51 return line.find(
"-->") != std::string::npos;
59 bool MaybeCueId(
const std::string& line) {
60 return line.find(
"-->") == std::string::npos;
67 bool IsLikelyStyle(
const std::string& line) {
68 return base::TrimWhitespaceASCII(line, base::TRIM_TRAILING) ==
"STYLE";
75 bool IsLikelyRegion(
const std::string& line) {
76 return base::TrimWhitespaceASCII(line, base::TRIM_TRAILING) ==
"REGION";
80 WebVttParser::WebVttParser(std::unique_ptr<FileReader> source,
81 const std::string& language)
82 : reader_(
std::move(source)), language_(language) {}
84 Status WebVttParser::InitializeInternal() {
88 bool WebVttParser::ValidateOutputStreamIndex(
size_t stream_index)
const {
90 return stream_index == kStreamIndex;
93 Status WebVttParser::Run() {
95 ? FlushDownstream(kStreamIndex)
96 : Status(error::INTERNAL_ERROR,
97 "Failed to parse WebVTT source. See log for details.");
100 void WebVttParser::Cancel() {
101 keep_reading_ =
false;
104 bool WebVttParser::Parse() {
105 std::vector<std::string> block;
106 if (!reader_.Next(&block)) {
107 LOG(ERROR) <<
"Failed to read WEBVTT HEADER - No blocks in source.";
113 if (block.size() != 1) {
114 LOG(ERROR) <<
"Failed to read WEBVTT header - " 115 <<
"block size should be 1 but was " << block.size() <<
".";
118 if (block[0] !=
"WEBVTT" && block[0] !=
"\xEF\xBB\xBFWEBVTT") {
119 LOG(ERROR) <<
"Failed to read WEBVTT header - should be WEBVTT but was " 124 const Status send_stream_info_result = DispatchTextStreamInfo();
126 if (send_stream_info_result != Status::OK) {
127 LOG(ERROR) <<
"Failed to send stream info down stream:" 128 << send_stream_info_result.error_message();
132 bool saw_cue =
false;
134 while (reader_.Next(&block) && keep_reading_) {
136 if (IsLikelyNote(block[0])) {
142 if (IsLikelyStyle(block[0])) {
145 <<
"Found style block after seeing cue. Ignoring style block";
147 LOG(WARNING) <<
"Missing support for style blocks. Skipping block:\n" 148 << BlockToString(block.data(), block.size());
154 if (IsLikelyRegion(block[0])) {
157 <<
"Found region block after seeing cue. Ignoring region block";
159 LOG(WARNING) <<
"Missing support for region blocks. Skipping block:\n" 160 << BlockToString(block.data(), block.size());
166 if (block.size() >= 2 && MaybeCueId(block[0]) &&
167 IsLikelyCueTiming(block[1]) && ParseCueWithId(block)) {
173 if (IsLikelyCueTiming(block[0]) && ParseCueWithNoId(block)) {
178 LOG(ERROR) <<
"Failed to determine block classification:\n" 179 << BlockToString(block.data(), block.size());
183 return keep_reading_;
186 bool WebVttParser::ParseCueWithNoId(
const std::vector<std::string>& block) {
187 const Status status = ParseCue(
"", block.data(), block.size());
190 LOG(ERROR) <<
"Failed to parse cue: " << status.error_message();
196 bool WebVttParser::ParseCueWithId(
const std::vector<std::string>& block) {
197 const Status status = ParseCue(block[0], block.data() + 1, block.size() - 1);
200 LOG(ERROR) <<
"Failed to parse cue: " << status.error_message();
206 Status WebVttParser::ParseCue(
const std::string&
id,
207 const std::string* block,
209 const std::vector<std::string> time_and_style = base::SplitString(
210 block[0],
" ", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
212 uint64_t start_time = 0;
213 uint64_t end_time = 0;
215 const bool parsed_time =
216 time_and_style.size() >= 3 && time_and_style[1] ==
"-->" &&
217 WebVttTimestampToMs(time_and_style[0], &start_time) &&
218 WebVttTimestampToMs(time_and_style[2], &end_time);
222 error::INTERNAL_ERROR,
223 "Could not parse start time, -->, and end time from " + block[0]);
237 if (end_time <= start_time) {
238 LOG(WARNING) <<
"WebVTT input is not spec compliant. Start time (" 239 << start_time <<
") should be less than end time (" << end_time
240 <<
"). Skipping webvtt cue:" 241 << BlockToString(block, block_size);
246 std::shared_ptr<TextSample> sample = std::make_shared<TextSample>();
248 sample->SetTime(start_time, end_time);
251 for (
size_t i = 3; i < time_and_style.size(); i++) {
252 sample->AppendStyle(time_and_style[i]);
256 for (
size_t i = 1; i < block_size; i++) {
257 sample->AppendPayload(block[i]);
260 return DispatchTextSample(kStreamIndex, sample);
263 Status WebVttParser::DispatchTextStreamInfo() {
264 const int kTrackId = 0;
266 const int kTimescale = 1000;
270 const int kDuration = 0;
271 const char kWebVttCodecString[] =
"wvtt";
272 const char kCodecConfig[] =
"";
273 const int64_t kNoWidth = 0;
274 const int64_t kNoHeight = 0;
276 std::shared_ptr<StreamInfo> info = std::make_shared<TextStreamInfo>(
277 kTrackId, kTimescale, kDuration, kCodecWebVtt, kWebVttCodecString,
278 kCodecConfig, kNoWidth, kNoHeight, language_);
280 return DispatchStreamInfo(kStreamIndex, std::move(info));
All the methods that are virtual are virtual for mocking.