Shaka Packager SDK
ttml_generator.cc
1 // Copyright 2020 Google LLC. All rights reserved.
2 //
3 // Use of this source code is governed by a BSD-style
4 // license that can be found in the LICENSE file or at
5 // https://developers.google.com/open-source/licenses/bsd
6 
7 #include <packager/media/formats/ttml/ttml_generator.h>
8 
9 #include <absl/strings/escaping.h>
10 #include <absl/strings/str_format.h>
11 
12 #include <packager/media/base/rcheck.h>
13 
14 namespace shaka {
15 namespace media {
16 namespace ttml {
17 
18 namespace {
19 
20 constexpr const char* kRegionIdPrefix = "_shaka_region_";
21 constexpr const char* kRegionTeletextPrefix = "ttx_";
22 
23 std::string ToTtmlTime(int64_t time, int32_t timescale) {
24  int64_t remaining = time * 1000 / timescale;
25 
26  const int ms = remaining % 1000;
27  remaining /= 1000;
28  const int sec = remaining % 60;
29  remaining /= 60;
30  const int min = remaining % 60;
31  remaining /= 60;
32  const int hr = remaining;
33 
34  return absl::StrFormat("%02d:%02d:%02d.%03d", hr, min, sec, ms);
35 }
36 
37 std::string ToTtmlSize(const TextNumber& x, const TextNumber& y) {
38  const char* kSuffixMap[] = {"px", "em", "%"};
39  return absl::StrFormat("%.0f%s %.0f%s", x.value,
40  kSuffixMap[static_cast<int>(x.type)], y.value,
41  kSuffixMap[static_cast<int>(y.type)]);
42 }
43 
44 } // namespace
45 
46 const char* TtmlGenerator::kTtNamespace = "http://www.w3.org/ns/ttml";
47 
48 TtmlGenerator::TtmlGenerator() {}
49 
50 TtmlGenerator::~TtmlGenerator() {}
51 
52 void TtmlGenerator::Initialize(const std::map<std::string, TextRegion>& regions,
53  const std::string& language,
54  int32_t time_scale) {
55  regions_ = regions;
56  language_ = language;
57  time_scale_ = time_scale;
58  // Add ebu_tt_d_regions
59  float step = 74.1f / 11;
60  for (int i = 0; i < 12; i++) {
61  TextRegion region;
62  float verPos = 10.0 + int(float(step) * float(i));
63  region.width = TextNumber(80, TextUnitType::kPercent);
64  region.height = TextNumber(15, TextUnitType::kPercent);
65  region.window_anchor_x = TextNumber(10, TextUnitType::kPercent);
66  region.window_anchor_y = TextNumber(verPos, TextUnitType::kPercent);
67  const std::string id = kRegionTeletextPrefix + std::to_string(i);
68  regions_.emplace(id, region);
69  }
70 }
71 
72 void TtmlGenerator::AddSample(const TextSample& sample) {
73  samples_.emplace_back(sample);
74 }
75 
76 void TtmlGenerator::Reset() {
77  samples_.clear();
78 }
79 
80 bool TtmlGenerator::Dump(std::string* result) const {
81  xml::XmlNode root("tt");
82  bool ebuTTDFormat = isEbuTTTD();
83  RCHECK(root.SetStringAttribute("xmlns", kTtNamespace));
84  RCHECK(root.SetStringAttribute("xmlns:tts",
85  "http://www.w3.org/ns/ttml#styling"));
86  RCHECK(root.SetStringAttribute("xmlns:tts",
87  "http://www.w3.org/ns/ttml#styling"));
88  RCHECK(root.SetStringAttribute("xml:lang", language_));
89 
90  if (ebuTTDFormat) {
91  RCHECK(root.SetStringAttribute("xmlns:ttp",
92  "http://www.w3.org/ns/ttml#parameter"));
93  RCHECK(root.SetStringAttribute("xmlns:ttm",
94  "http://www.w3.org/ns/ttml#metadata"));
95  RCHECK(root.SetStringAttribute("xmlns:ebuttm", "urn:ebu:tt:metadata"));
96  RCHECK(root.SetStringAttribute("xmlns:ebutts", "urn:ebu:tt:style"));
97  RCHECK(root.SetStringAttribute("xml:space", "default"));
98  RCHECK(root.SetStringAttribute("ttp:timeBase", "media"));
99  RCHECK(root.SetStringAttribute("ttp:cellResolution", "32 15"));
100  }
101 
102  xml::XmlNode head("head");
103  xml::XmlNode styling("styling");
104  xml::XmlNode metadata("metadata");
105  xml::XmlNode layout("layout");
106  RCHECK(addRegions(layout));
107 
108  xml::XmlNode body("body");
109  if (ebuTTDFormat) {
110  RCHECK(body.SetStringAttribute("style", "default"));
111  }
112  size_t image_count = 0;
113  std::unordered_set<std::string> fragmentStyles;
114  xml::XmlNode div("div");
115  for (const auto& sample : samples_) {
116  RCHECK(
117  AddSampleToXml(sample, &div, &metadata, fragmentStyles, &image_count));
118  }
119  if (image_count > 0) {
120  RCHECK(root.SetStringAttribute(
121  "xmlns:smpte", "http://www.smpte-ra.org/schemas/2052-1/2010/smpte-tt"));
122  }
123  RCHECK(body.AddChild(std::move(div)));
124  RCHECK(head.AddChild(std::move(metadata)));
125  RCHECK(addStyling(styling, fragmentStyles));
126  RCHECK(head.AddChild(std::move(styling)));
127  RCHECK(head.AddChild(std::move(layout)));
128  RCHECK(root.AddChild(std::move(head)));
129 
130  RCHECK(root.AddChild(std::move(body)));
131 
132  *result = root.ToString(/* comment= */ "");
133  return true;
134 }
135 
136 bool TtmlGenerator::AddSampleToXml(
137  const TextSample& sample,
138  xml::XmlNode* body,
139  xml::XmlNode* metadata,
140  std::unordered_set<std::string>& fragmentStyles,
141  size_t* image_count) const {
142  xml::XmlNode p("p");
143  if (!isEbuTTTD()) {
144  RCHECK(p.SetStringAttribute("xml:space", "preserve"));
145  }
146  RCHECK(p.SetStringAttribute("begin",
147  ToTtmlTime(sample.start_time(), time_scale_)));
148  RCHECK(
149  p.SetStringAttribute("end", ToTtmlTime(sample.EndTime(), time_scale_)));
150  RCHECK(ConvertFragmentToXml(sample.body(), &p, metadata, fragmentStyles,
151  image_count));
152  if (!sample.id().empty())
153  RCHECK(p.SetStringAttribute("xml:id", sample.id()));
154 
155  const auto& settings = sample.settings();
156  bool regionFound = false;
157  if (!settings.region.empty()) {
158  auto reg = regions_.find(settings.region);
159  if (reg != regions_.end()) {
160  regionFound = true;
161  RCHECK(p.SetStringAttribute("region", settings.region));
162  }
163  }
164 
165  if (!regionFound && (settings.line || settings.position || settings.width ||
166  settings.height)) {
167  // TTML positioning needs to be from a region.
168  const auto origin = ToTtmlSize(
169  settings.position.value_or(TextNumber(0, TextUnitType::kPixels)),
170  settings.line.value_or(TextNumber(0, TextUnitType::kPixels)));
171  const auto extent = ToTtmlSize(
172  settings.width.value_or(TextNumber(100, TextUnitType::kPercent)),
173  settings.height.value_or(TextNumber(100, TextUnitType::kPercent)));
174 
175  const std::string id = kRegionIdPrefix + std::to_string(region_id_++);
176  xml::XmlNode region("region");
177  RCHECK(region.SetStringAttribute("xml:id", id));
178  RCHECK(region.SetStringAttribute("tts:origin", origin));
179  RCHECK(region.SetStringAttribute("tts:extent", extent));
180  RCHECK(p.SetStringAttribute("region", id));
181  RCHECK(body->AddChild(std::move(region)));
182  }
183 
184  if (settings.writing_direction != WritingDirection::kHorizontal) {
185  const char* dir =
186  settings.writing_direction == WritingDirection::kVerticalGrowingLeft
187  ? "tbrl"
188  : "tblr";
189  RCHECK(p.SetStringAttribute("tts:writingMode", dir));
190  }
191  if (settings.text_alignment != TextAlignment::kStart) {
192  switch (settings.text_alignment) {
193  case TextAlignment::kStart: // To avoid compiler warning.
194  case TextAlignment::kCenter:
195  RCHECK(p.SetStringAttribute("tts:textAlign", "center"));
196  break;
197  case TextAlignment::kEnd:
198  RCHECK(p.SetStringAttribute("tts:textAlign", "end"));
199  break;
200  case TextAlignment::kLeft:
201  RCHECK(p.SetStringAttribute("tts:textAlign", "left"));
202  break;
203  case TextAlignment::kRight:
204  RCHECK(p.SetStringAttribute("tts:textAlign", "right"));
205  break;
206  }
207  }
208 
209  RCHECK(body->AddChild(std::move(p)));
210  return true;
211 }
212 
213 bool TtmlGenerator::ConvertFragmentToXml(
214  const TextFragment& body,
215  xml::XmlNode* parent,
216  xml::XmlNode* metadata,
217  std::unordered_set<std::string>& fragmentStyles,
218  size_t* image_count) const {
219  if (body.newline) {
220  xml::XmlNode br("br");
221  return parent->AddChild(std::move(br));
222  }
223  xml::XmlNode span("span");
224  xml::XmlNode* node = parent;
225  bool useSpan =
226  (body.style.bold || body.style.italic || body.style.underline ||
227  !body.style.color.empty() || !body.style.backgroundColor.empty());
228  if (useSpan) {
229  node = &span;
230  if (body.style.bold) {
231  RCHECK(span.SetStringAttribute("tts:fontWeight",
232  *body.style.bold ? "bold" : "normal"));
233  }
234  if (body.style.italic) {
235  RCHECK(span.SetStringAttribute("tts:fontStyle",
236  *body.style.italic ? "italic" : "normal"));
237  }
238  if (body.style.underline) {
239  RCHECK(span.SetStringAttribute(
240  "tts:textDecoration",
241  *body.style.underline ? "underline" : "noUnderline"));
242  }
243  std::string color = "white";
244  std::string backgroundColor = "black";
245 
246  if (!body.style.color.empty()) {
247  color = body.style.color;
248  }
249 
250  if (!body.style.backgroundColor.empty()) {
251  backgroundColor = body.style.backgroundColor;
252  }
253 
254  const std::string fragStyle = color + "_" + backgroundColor;
255  fragmentStyles.insert(fragStyle);
256  RCHECK(span.SetStringAttribute("style", fragStyle));
257  }
258 
259  if (!body.body.empty()) {
260  node->AddContent(body.body);
261  } else if (!body.image.empty()) {
262  std::string image_data(body.image.begin(), body.image.end());
263  std::string base64_data;
264  absl::Base64Escape(image_data, &base64_data);
265  std::string id = "img_" + std::to_string(++*image_count);
266 
267  xml::XmlNode image_xml("smpte:image");
268  RCHECK(image_xml.SetStringAttribute("imageType", "PNG"));
269  RCHECK(image_xml.SetStringAttribute("encoding", "Base64"));
270  RCHECK(image_xml.SetStringAttribute("xml:id", id));
271  image_xml.SetContent(base64_data);
272  RCHECK(metadata->AddChild(std::move(image_xml)));
273 
274  RCHECK(node->SetStringAttribute("smpte:backgroundImage", "#" + id));
275  } else {
276  for (const auto& frag : body.sub_fragments) {
277  if (!ConvertFragmentToXml(frag, node, metadata, fragmentStyles,
278  image_count))
279  return false;
280  }
281  }
282 
283  if (useSpan)
284  RCHECK(parent->AddChild(std::move(span)));
285  return true;
286 }
287 
288 std::vector<std::string> TtmlGenerator::usedRegions() const {
289  std::vector<std::string> uRegions;
290  for (const auto& sample : samples_) {
291  if (!sample.settings().region.empty()) {
292  uRegions.push_back(sample.settings().region);
293  }
294  }
295  return uRegions;
296 }
297 
298 bool TtmlGenerator::addRegions(xml::XmlNode& layout) const {
299  auto regNames = usedRegions();
300  for (const auto& r : regions_) {
301  bool used = false;
302  for (const auto& name : regNames) {
303  if (r.first == name) {
304  used = true;
305  }
306  }
307  if (used) {
308  xml::XmlNode region("region");
309  const auto origin =
310  ToTtmlSize(r.second.window_anchor_x, r.second.window_anchor_y);
311  const auto extent = ToTtmlSize(r.second.width, r.second.height);
312  RCHECK(region.SetStringAttribute("xml:id", r.first));
313  RCHECK(region.SetStringAttribute("tts:origin", origin));
314  RCHECK(region.SetStringAttribute("tts:extent", extent));
315  RCHECK(region.SetStringAttribute("tts:overflow", "visible"));
316  RCHECK(layout.AddChild(std::move(region)));
317  }
318  }
319  return true;
320 }
321 
322 bool TtmlGenerator::addStyling(
323  xml::XmlNode& styling,
324  const std::unordered_set<std::string>& fragmentStyles) const {
325  if (fragmentStyles.empty()) {
326  return true;
327  }
328  // Add default style
329  xml::XmlNode defaultStyle("style");
330  RCHECK(defaultStyle.SetStringAttribute("xml:id", "default"));
331  RCHECK(defaultStyle.SetStringAttribute("tts:fontStyle", "normal"));
332  RCHECK(defaultStyle.SetStringAttribute("tts:fontFamily", "sansSerif"));
333  RCHECK(defaultStyle.SetStringAttribute("tts:fontSize", "100%"));
334  RCHECK(defaultStyle.SetStringAttribute("tts:lineHeight", "normal"));
335  RCHECK(defaultStyle.SetStringAttribute("tts:textAlign", "center"));
336  RCHECK(defaultStyle.SetStringAttribute("ebutts:linePadding", "0.5c"));
337  RCHECK(styling.AddChild(std::move(defaultStyle)));
338 
339  for (const auto& name : fragmentStyles) {
340  auto pos = name.find('_');
341  auto color = name.substr(0, pos);
342  auto backgroundColor = name.substr(pos + 1, name.size());
343  xml::XmlNode fragStyle("style");
344  RCHECK(fragStyle.SetStringAttribute("xml:id", name));
345  RCHECK(
346  fragStyle.SetStringAttribute("tts:backgroundColor", backgroundColor));
347  RCHECK(fragStyle.SetStringAttribute("tts:color", color));
348  RCHECK(styling.AddChild(std::move(fragStyle)));
349  }
350  return true;
351 }
352 
353 bool TtmlGenerator::isEbuTTTD() const {
354  for (const auto& sample : samples_) {
355  if (sample.settings().region.rfind(kRegionTeletextPrefix, 0) == 0) {
356  return true;
357  }
358  }
359  return false;
360 }
361 
362 } // namespace ttml
363 } // namespace media
364 } // namespace shaka
All the methods that are virtual are virtual for mocking.
Definition: crypto_flags.cc:66