7 #include "packager/media/formats/ttml/ttml_generator.h"
9 #include "packager/base/base64.h"
10 #include "packager/base/strings/stringprintf.h"
11 #include "packager/media/base/rcheck.h"
19 constexpr
const char* kRegionIdPrefix =
"_shaka_region_";
21 std::string ToTtmlTime(int64_t time, uint32_t timescale) {
22 int64_t remaining = time * 1000 / timescale;
24 const int ms = remaining % 1000;
26 const int sec = remaining % 60;
28 const int min = remaining % 60;
30 const int hr = remaining;
32 return base::StringPrintf(
"%02d:%02d:%02d.%02d", hr, min, sec, ms);
35 std::string ToTtmlSize(
const TextNumber& x,
const TextNumber& y) {
36 const char* kSuffixMap[] = {
"px",
"em",
"%"};
37 return base::StringPrintf(
"%.0f%s %.0f%s", x.value,
38 kSuffixMap[
static_cast<int>(x.type)], y.value,
39 kSuffixMap[
static_cast<int>(y.type)]);
44 const char* TtmlGenerator::kTtNamespace =
"http://www.w3.org/ns/ttml";
46 TtmlGenerator::TtmlGenerator() {}
48 TtmlGenerator::~TtmlGenerator() {}
50 void TtmlGenerator::Initialize(
const std::map<std::string, TextRegion>& regions,
51 const std::string& language,
52 uint32_t time_scale) {
55 time_scale_ = time_scale;
58 void TtmlGenerator::AddSample(
const TextSample& sample) {
59 samples_.emplace_back(sample);
62 void TtmlGenerator::Reset() {
66 bool TtmlGenerator::Dump(std::string* result)
const {
67 xml::XmlNode root(
"tt");
68 RCHECK(root.SetStringAttribute(
"xmlns", kTtNamespace));
69 RCHECK(root.SetStringAttribute(
"xmlns:tts",
70 "http://www.w3.org/ns/ttml#styling"));
73 xml::XmlNode head(
"head");
74 RCHECK(root.SetStringAttribute(
"xml:lang", language_));
75 for (
const auto& pair : regions_) {
76 if (!did_log && (pair.second.region_anchor_x.value != 0 &&
77 pair.second.region_anchor_y.value != 0)) {
78 LOG(WARNING) <<
"TTML doesn't support non-0 region anchor";
82 xml::XmlNode region(
"region");
84 ToTtmlSize(pair.second.window_anchor_x, pair.second.window_anchor_y);
85 const auto extent = ToTtmlSize(pair.second.width, pair.second.height);
86 RCHECK(region.SetStringAttribute(
"xml:id", pair.first));
87 RCHECK(region.SetStringAttribute(
"tts:origin", origin));
88 RCHECK(region.SetStringAttribute(
"tts:extent", extent));
89 RCHECK(head.AddChild(std::move(region)));
91 RCHECK(root.AddChild(std::move(head)));
93 size_t image_count = 0;
94 xml::XmlNode metadata(
"metadata");
95 xml::XmlNode body(
"body");
96 xml::XmlNode div(
"div");
97 for (
const auto& sample : samples_) {
98 RCHECK(AddSampleToXml(sample, &div, &metadata, &image_count));
100 RCHECK(body.AddChild(std::move(div)));
101 if (image_count > 0) {
102 RCHECK(root.SetStringAttribute(
103 "xmlns:smpte",
"http://www.smpte-ra.org/schemas/2052-1/2010/smpte-tt"));
104 RCHECK(root.AddChild(std::move(metadata)));
106 RCHECK(root.AddChild(std::move(body)));
108 *result = root.ToString(
"");
112 bool TtmlGenerator::AddSampleToXml(
const TextSample& sample,
114 xml::XmlNode* metadata,
115 size_t* image_count)
const {
117 RCHECK(p.SetStringAttribute(
"xml:space",
"preserve"));
118 RCHECK(p.SetStringAttribute(
"begin",
119 ToTtmlTime(sample.start_time(), time_scale_)));
121 p.SetStringAttribute(
"end", ToTtmlTime(sample.EndTime(), time_scale_)));
122 RCHECK(ConvertFragmentToXml(sample.body(), &p, metadata, image_count));
123 if (!sample.id().empty())
124 RCHECK(p.SetStringAttribute(
"xml:id", sample.id()));
126 const auto& settings = sample.settings();
127 if (settings.line || settings.position || settings.width || settings.height) {
129 if (!settings.region.empty()) {
131 <<
"Using both text regions and positioning isn't supported in TTML";
134 const auto origin = ToTtmlSize(
135 settings.position.value_or(TextNumber(0, TextUnitType::kPixels)),
136 settings.line.value_or(TextNumber(0, TextUnitType::kPixels)));
137 const auto extent = ToTtmlSize(
138 settings.width.value_or(TextNumber(100, TextUnitType::kPercent)),
139 settings.height.value_or(TextNumber(100, TextUnitType::kPercent)));
141 const std::string
id = kRegionIdPrefix + std::to_string(region_id_++);
142 xml::XmlNode region(
"region");
143 RCHECK(region.SetStringAttribute(
"xml:id",
id));
144 RCHECK(region.SetStringAttribute(
"tts:origin", origin));
145 RCHECK(region.SetStringAttribute(
"tts:extent", extent));
146 RCHECK(p.SetStringAttribute(
"region",
id));
147 RCHECK(body->AddChild(std::move(region)));
148 }
else if (!settings.region.empty()) {
149 RCHECK(p.SetStringAttribute(
"region", settings.region));
152 if (settings.writing_direction != WritingDirection::kHorizontal) {
154 settings.writing_direction == WritingDirection::kVerticalGrowingLeft
157 RCHECK(p.SetStringAttribute(
"tts:writingMode", dir));
159 if (settings.text_alignment != TextAlignment::kStart) {
160 switch (settings.text_alignment) {
161 case TextAlignment::kStart:
162 case TextAlignment::kCenter:
163 RCHECK(p.SetStringAttribute(
"tts:textAlign",
"center"));
165 case TextAlignment::kEnd:
166 RCHECK(p.SetStringAttribute(
"tts:textAlign",
"end"));
168 case TextAlignment::kLeft:
169 RCHECK(p.SetStringAttribute(
"tts:textAlign",
"left"));
171 case TextAlignment::kRight:
172 RCHECK(p.SetStringAttribute(
"tts:textAlign",
"right"));
177 RCHECK(body->AddChild(std::move(p)));
181 bool TtmlGenerator::ConvertFragmentToXml(
const TextFragment& body,
182 xml::XmlNode* parent,
183 xml::XmlNode* metadata,
184 size_t* image_count)
const {
186 xml::XmlNode br(
"br");
187 return parent->AddChild(std::move(br));
191 xml::XmlNode span(
"span");
192 xml::XmlNode* node = parent;
193 if (body.style.bold || body.style.italic || body.style.underline) {
195 if (body.style.bold) {
196 RCHECK(span.SetStringAttribute(
"tts:fontWeight",
197 *body.style.bold ?
"bold" :
"normal"));
199 if (body.style.italic) {
200 RCHECK(span.SetStringAttribute(
"tts:fontStyle",
201 *body.style.italic ?
"italic" :
"normal"));
203 if (body.style.underline) {
204 RCHECK(span.SetStringAttribute(
205 "tts:textDecoration",
206 *body.style.underline ?
"underline" :
"noUnderline"));
210 if (!body.body.empty()) {
211 node->AddContent(body.body);
212 }
else if (!body.image.empty()) {
213 std::string image_data(body.image.begin(), body.image.end());
214 std::string base64_data;
215 base::Base64Encode(image_data, &base64_data);
216 std::string
id =
"img_" + std::to_string(++*image_count);
218 xml::XmlNode image_xml(
"smpte:image");
219 RCHECK(image_xml.SetStringAttribute(
"imageType",
"PNG"));
220 RCHECK(image_xml.SetStringAttribute(
"encoding",
"Base64"));
221 RCHECK(image_xml.SetStringAttribute(
"xml:id",
id));
222 image_xml.SetContent(base64_data);
223 RCHECK(metadata->AddChild(std::move(image_xml)));
225 RCHECK(node->SetStringAttribute(
"smpte:backgroundImage",
"#" +
id));
227 for (
const auto& frag : body.sub_fragments) {
228 if (!ConvertFragmentToXml(frag, node, metadata, image_count))
233 if (body.style.bold || body.style.italic || body.style.underline)
234 RCHECK(parent->AddChild(std::move(span)));
All the methods that are virtual are virtual for mocking.