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/stringprintf.h"
24 bool GetTotalMilliseconds(uint64_t hours,
30 if (minutes > 59 || seconds > 59 || ms > 999) {
31 VLOG(1) <<
"Hours:" << hours <<
" Minutes:" << minutes
32 <<
" Seconds:" << seconds <<
" MS:" << ms
33 <<
" shoud have never made it to GetTotalMilliseconds";
36 *out = 60 * 60 * 1000 * hours + 60 * 1000 * minutes + 1000 * seconds + ms;
40 enum class StyleTagKind {
46 std::string GetOpenTag(StyleTagKind tag) {
48 case StyleTagKind::kUnderline:
50 case StyleTagKind::kBold:
52 case StyleTagKind::kItalic:
58 std::string GetCloseTag(StyleTagKind tag) {
60 case StyleTagKind::kUnderline:
62 case StyleTagKind::kBold:
64 case StyleTagKind::kItalic:
70 std::string WriteFragment(
const TextFragment& fragment,
71 std::list<StyleTagKind>* tags) {
73 size_t local_tag_count = 0;
74 auto has = [tags](StyleTagKind tag) {
75 return std::find(tags->begin(), tags->end(), tag) != tags->end();
77 auto push_tag = [tags, &local_tag_count, &has](StyleTagKind tag) {
83 return GetOpenTag(tag);
86 if ((fragment.style.underline ==
false && has(StyleTagKind::kUnderline)) ||
87 (fragment.style.bold ==
false && has(StyleTagKind::kBold)) ||
88 (fragment.style.italic ==
false && has(StyleTagKind::kItalic))) {
89 LOG(WARNING) <<
"WebVTT output doesn't support disabling "
90 "underline/bold/italic within a cue";
93 if (fragment.newline) {
96 for (
auto it = tags->rbegin(); it != tags->rend(); it++) {
97 ret += GetCloseTag(*it);
100 for (
const auto tag : *tags) {
101 ret += GetOpenTag(tag);
104 if (fragment.style.underline ==
true) {
105 ret += push_tag(StyleTagKind::kUnderline);
107 if (fragment.style.bold ==
true) {
108 ret += push_tag(StyleTagKind::kBold);
110 if (fragment.style.italic ==
true) {
111 ret += push_tag(StyleTagKind::kItalic);
114 if (!fragment.body.empty()) {
118 std::regex whitespace(
"\\s+", std::regex_constants::ECMAScript);
119 ret += std::regex_replace(fragment.body, whitespace, std::string(
" "));
121 for (
const auto& frag : fragment.sub_fragments) {
122 ret += WriteFragment(frag, tags);
127 while (local_tag_count > 0) {
128 ret += GetCloseTag(tags->back());
138 bool WebVttTimestampToMs(
const base::StringPiece& source, uint64_t* out) {
141 if (source.length() < 9) {
142 LOG(WARNING) <<
"Timestamp '" << source <<
"' is mal-formed";
146 const size_t minutes_begin = source.length() - 9;
147 const size_t seconds_begin = source.length() - 6;
148 const size_t milliseconds_begin = source.length() - 3;
151 uint64_t minutes = 0;
152 uint64_t seconds = 0;
155 const bool has_hours =
156 minutes_begin >= 3 && source[minutes_begin - 1] ==
':' &&
157 base::StringToUint64(source.substr(0, minutes_begin - 1), &hours);
159 if ((minutes_begin == 0 || has_hours) && source[seconds_begin - 1] ==
':' &&
160 source[milliseconds_begin - 1] ==
'.' &&
161 base::StringToUint64(source.substr(minutes_begin, 2), &minutes) &&
162 base::StringToUint64(source.substr(seconds_begin, 2), &seconds) &&
163 base::StringToUint64(source.substr(milliseconds_begin, 3), &ms)) {
164 return GetTotalMilliseconds(hours, minutes, seconds, ms, out);
167 LOG(WARNING) <<
"Timestamp '" << source <<
"' is mal-formed";
171 std::string MsToWebVttTimestamp(uint64_t ms) {
172 uint64_t remaining = ms;
174 uint64_t only_ms = remaining % 1000;
176 uint64_t only_seconds = remaining % 60;
178 uint64_t only_minutes = remaining % 60;
180 uint64_t only_hours = remaining;
182 return base::StringPrintf(
"%02" PRIu64
":%02" PRIu64
":%02" PRIu64
184 only_hours, only_minutes, only_seconds, only_ms);
187 std::string WebVttSettingsToString(
const TextSettings& settings) {
189 if (!settings.region.empty()) {
191 ret += settings.region;
194 switch (settings.line->type) {
195 case TextUnitType::kPercent:
197 ret += base::DoubleToString(settings.line->value);
200 case TextUnitType::kLines:
202 ret += base::DoubleToString(settings.line->value);
204 case TextUnitType::kPixels:
205 LOG(WARNING) <<
"WebVTT doesn't support pixel line settings";
209 if (settings.position) {
210 if (settings.position->type == TextUnitType::kPercent) {
212 ret += base::DoubleToString(settings.position->value);
215 LOG(WARNING) <<
"WebVTT only supports percent position settings";
218 if (settings.width) {
219 if (settings.width->type == TextUnitType::kPercent) {
221 ret += base::DoubleToString(settings.width->value);
224 LOG(WARNING) <<
"WebVTT only supports percent width settings";
227 if (settings.height) {
228 LOG(WARNING) <<
"WebVTT doesn't support cue heights";
230 if (settings.writing_direction != WritingDirection::kHorizontal) {
231 ret +=
" direction:";
232 if (settings.writing_direction == WritingDirection::kVerticalGrowingLeft) {
238 switch (settings.text_alignment) {
239 case TextAlignment::kStart:
240 ret +=
" align:start";
242 case TextAlignment::kEnd:
245 case TextAlignment::kLeft:
246 ret +=
" align:left";
248 case TextAlignment::kRight:
249 ret +=
" align:right";
251 case TextAlignment::kCenter:
256 DCHECK_EQ(ret[0],
' ');
262 std::string WebVttFragmentToString(
const TextFragment& fragment) {
263 std::list<StyleTagKind> tags;
264 return WriteFragment(fragment, &tags);
267 std::string WebVttGetPreamble(
const TextStreamInfo& stream_info) {
269 for (
const auto& pair : stream_info.regions()) {
274 if (pair.second.width.type != TextUnitType::kPercent ||
275 pair.second.height.type != TextUnitType::kLines ||
276 pair.second.window_anchor_x.type != TextUnitType::kPercent ||
277 pair.second.window_anchor_y.type != TextUnitType::kPercent ||
278 pair.second.region_anchor_x.type != TextUnitType::kPercent ||
279 pair.second.region_anchor_y.type != TextUnitType::kPercent) {
280 LOG(WARNING) <<
"Unsupported unit type in WebVTT region";
290 "viewportanchor:%f%%,%f%%\n"
291 "regionanchor:%f%%,%f%%",
292 pair.first.c_str(), pair.second.width.value,
293 static_cast<int>(pair.second.height.value),
294 pair.second.window_anchor_x.value, pair.second.window_anchor_y.value,
295 pair.second.region_anchor_x.value, pair.second.region_anchor_y.value);
296 if (pair.second.scroll) {
297 ret +=
"\nscroll:up";
301 if (!stream_info.css_styles().empty()) {
305 ret +=
"STYLE\n" + stream_info.css_styles();
All the methods that are virtual are virtual for mocking.