7 #include "packager/media/formats/webvtt/webvtt_utils.h"
13 #include <unordered_set>
15 #include "packager/base/logging.h"
16 #include "packager/base/strings/string_number_conversions.h"
17 #include "packager/base/strings/string_util.h"
18 #include "packager/base/strings/stringprintf.h"
25 bool GetTotalMilliseconds(uint64_t hours,
31 if (minutes > 59 || seconds > 59 || ms > 999) {
32 VLOG(1) <<
"Hours:" << hours <<
" Minutes:" << minutes
33 <<
" Seconds:" << seconds <<
" MS:" << ms
34 <<
" shoud have never made it to GetTotalMilliseconds";
37 *out = 60 * 60 * 1000 * hours + 60 * 1000 * minutes + 1000 * seconds + ms;
41 enum class StyleTagKind {
47 std::string GetOpenTag(StyleTagKind tag) {
49 case StyleTagKind::kUnderline:
51 case StyleTagKind::kBold:
53 case StyleTagKind::kItalic:
59 std::string GetCloseTag(StyleTagKind tag) {
61 case StyleTagKind::kUnderline:
63 case StyleTagKind::kBold:
65 case StyleTagKind::kItalic:
71 bool IsWhitespace(
char c) {
72 return c ==
'\t' || c ==
'\r' || c ==
'\n' || c ==
' ';
76 std::string CollapseWhitespace(
const std::string& data) {
78 output.resize(data.size());
79 size_t chars_written = 0;
80 bool in_whitespace =
false;
82 if (IsWhitespace(c)) {
85 output[chars_written++] =
' ';
88 in_whitespace =
false;
89 output[chars_written++] = c;
92 output.resize(chars_written);
96 std::string WriteFragment(
const TextFragment& fragment,
97 std::list<StyleTagKind>* tags) {
99 size_t local_tag_count = 0;
100 auto has = [tags](StyleTagKind tag) {
101 return std::find(tags->begin(), tags->end(), tag) != tags->end();
103 auto push_tag = [tags, &local_tag_count, &has](StyleTagKind tag) {
105 return std::string();
107 tags->push_back(tag);
109 return GetOpenTag(tag);
112 if ((fragment.style.underline ==
false && has(StyleTagKind::kUnderline)) ||
113 (fragment.style.bold ==
false && has(StyleTagKind::kBold)) ||
114 (fragment.style.italic ==
false && has(StyleTagKind::kItalic))) {
115 LOG(WARNING) <<
"WebVTT output doesn't support disabling "
116 "underline/bold/italic within a cue";
119 if (fragment.newline) {
122 for (
auto it = tags->rbegin(); it != tags->rend(); it++) {
123 ret += GetCloseTag(*it);
126 for (
const auto tag : *tags) {
127 ret += GetOpenTag(tag);
130 if (fragment.style.underline ==
true) {
131 ret += push_tag(StyleTagKind::kUnderline);
133 if (fragment.style.bold ==
true) {
134 ret += push_tag(StyleTagKind::kBold);
136 if (fragment.style.italic ==
true) {
137 ret += push_tag(StyleTagKind::kItalic);
140 if (!fragment.body.empty()) {
144 ret += CollapseWhitespace(fragment.body);
146 for (
const auto& frag : fragment.sub_fragments) {
147 ret += WriteFragment(frag, tags);
152 while (local_tag_count > 0) {
153 ret += GetCloseTag(tags->back());
163 bool WebVttTimestampToMs(
const base::StringPiece& source, uint64_t* out) {
166 if (source.length() < 9) {
167 LOG(WARNING) <<
"Timestamp '" << source <<
"' is mal-formed";
171 const size_t minutes_begin = source.length() - 9;
172 const size_t seconds_begin = source.length() - 6;
173 const size_t milliseconds_begin = source.length() - 3;
176 uint64_t minutes = 0;
177 uint64_t seconds = 0;
180 const bool has_hours =
181 minutes_begin >= 3 && source[minutes_begin - 1] ==
':' &&
182 base::StringToUint64(source.substr(0, minutes_begin - 1), &hours);
184 if ((minutes_begin == 0 || has_hours) && source[seconds_begin - 1] ==
':' &&
185 source[milliseconds_begin - 1] ==
'.' &&
186 base::StringToUint64(source.substr(minutes_begin, 2), &minutes) &&
187 base::StringToUint64(source.substr(seconds_begin, 2), &seconds) &&
188 base::StringToUint64(source.substr(milliseconds_begin, 3), &ms)) {
189 return GetTotalMilliseconds(hours, minutes, seconds, ms, out);
192 LOG(WARNING) <<
"Timestamp '" << source <<
"' is mal-formed";
196 std::string MsToWebVttTimestamp(uint64_t ms) {
197 uint64_t remaining = ms;
199 uint64_t only_ms = remaining % 1000;
201 uint64_t only_seconds = remaining % 60;
203 uint64_t only_minutes = remaining % 60;
205 uint64_t only_hours = remaining;
207 return base::StringPrintf(
"%02" PRIu64
":%02" PRIu64
":%02" PRIu64
209 only_hours, only_minutes, only_seconds, only_ms);
212 std::string WebVttSettingsToString(
const TextSettings& settings) {
214 if (!settings.region.empty()) {
216 ret += settings.region;
219 switch (settings.line->type) {
220 case TextUnitType::kPercent:
222 ret += base::DoubleToString(settings.line->value);
225 case TextUnitType::kLines:
227 ret += base::DoubleToString(settings.line->value);
229 case TextUnitType::kPixels:
230 LOG(WARNING) <<
"WebVTT doesn't support pixel line settings";
234 if (settings.position) {
235 if (settings.position->type == TextUnitType::kPercent) {
237 ret += base::DoubleToString(settings.position->value);
240 LOG(WARNING) <<
"WebVTT only supports percent position settings";
243 if (settings.width) {
244 if (settings.width->type == TextUnitType::kPercent) {
246 ret += base::DoubleToString(settings.width->value);
249 LOG(WARNING) <<
"WebVTT only supports percent width settings";
252 if (settings.height) {
253 LOG(WARNING) <<
"WebVTT doesn't support cue heights";
255 if (settings.writing_direction != WritingDirection::kHorizontal) {
256 ret +=
" direction:";
257 if (settings.writing_direction == WritingDirection::kVerticalGrowingLeft) {
263 switch (settings.text_alignment) {
264 case TextAlignment::kStart:
265 ret +=
" align:start";
267 case TextAlignment::kEnd:
270 case TextAlignment::kLeft:
271 ret +=
" align:left";
273 case TextAlignment::kRight:
274 ret +=
" align:right";
276 case TextAlignment::kCenter:
277 ret +=
" align:center";
282 DCHECK_EQ(ret[0],
' ');
288 std::string WebVttFragmentToString(
const TextFragment& fragment) {
289 std::list<StyleTagKind> tags;
290 return WriteFragment(fragment, &tags);
293 std::string WebVttGetPreamble(
const TextStreamInfo& stream_info) {
295 for (
const auto& pair : stream_info.regions()) {
300 if (pair.second.width.type != TextUnitType::kPercent ||
301 pair.second.height.type != TextUnitType::kLines ||
302 pair.second.window_anchor_x.type != TextUnitType::kPercent ||
303 pair.second.window_anchor_y.type != TextUnitType::kPercent ||
304 pair.second.region_anchor_x.type != TextUnitType::kPercent ||
305 pair.second.region_anchor_y.type != TextUnitType::kPercent) {
306 LOG(WARNING) <<
"Unsupported unit type in WebVTT region";
316 "viewportanchor:%f%%,%f%%\n"
317 "regionanchor:%f%%,%f%%",
318 pair.first.c_str(), pair.second.width.value,
319 static_cast<int>(pair.second.height.value),
320 pair.second.window_anchor_x.value, pair.second.window_anchor_y.value,
321 pair.second.region_anchor_x.value, pair.second.region_anchor_y.value);
322 if (pair.second.scroll) {
323 ret +=
"\nscroll:up";
327 if (!stream_info.css_styles().empty()) {
331 ret +=
"STYLE\n" + stream_info.css_styles();