Shaka Packager SDK
xml_node.cc
1 // Copyright 2014 Google Inc. 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/mpd/base/xml/xml_node.h"
8 
9 #include <gflags/gflags.h>
10 #include <libxml/tree.h>
11 
12 #include <limits>
13 #include <set>
14 
15 #include "packager/base/logging.h"
16 #include "packager/base/macros.h"
17 #include "packager/base/strings/string_number_conversions.h"
18 #include "packager/base/sys_byteorder.h"
19 #include "packager/media/base/rcheck.h"
20 #include "packager/mpd/base/media_info.pb.h"
21 #include "packager/mpd/base/mpd_utils.h"
22 #include "packager/mpd/base/segment_info.h"
23 #include "packager/mpd/base/xml/scoped_xml_ptr.h"
24 
25 DEFINE_bool(segment_template_constant_duration,
26  false,
27  "Generates SegmentTemplate@duration if all segments except the "
28  "last one has the same duration if this flag is set to true.");
29 
30 DEFINE_bool(dash_add_last_segment_number_when_needed,
31  false,
32  "Adds a Supplemental Descriptor with @schemeIdUri "
33  "set to http://dashif.org/guidelines/last-segment-number with "
34  "the @value set to the last segment number.");
35 
36 namespace shaka {
37 
38 using xml::XmlNode;
39 typedef MediaInfo::AudioInfo AudioInfo;
40 typedef MediaInfo::VideoInfo VideoInfo;
41 
42 namespace {
43 const char kEC3Codec[] = "ec-3";
44 const char kAC4Codec[] = "ac-4";
45 
46 std::string RangeToString(const Range& range) {
47  return base::Uint64ToString(range.begin()) + "-" +
48  base::Uint64ToString(range.end());
49 }
50 
51 // Check if segments are continuous and all segments except the last one are of
52 // the same duration.
53 bool IsTimelineConstantDuration(const std::list<SegmentInfo>& segment_infos,
54  uint32_t start_number) {
55  if (!FLAGS_segment_template_constant_duration)
56  return false;
57 
58  DCHECK(!segment_infos.empty());
59  if (segment_infos.size() > 2)
60  return false;
61 
62  const SegmentInfo& first_segment = segment_infos.front();
63  if (first_segment.start_time != first_segment.duration * (start_number - 1))
64  return false;
65 
66  if (segment_infos.size() == 1)
67  return true;
68 
69  const SegmentInfo& last_segment = segment_infos.back();
70  if (last_segment.repeat != 0)
71  return false;
72 
73  const int64_t expected_last_segment_start_time =
74  first_segment.start_time +
75  first_segment.duration * (first_segment.repeat + 1);
76  return expected_last_segment_start_time == last_segment.start_time;
77 }
78 
79 bool PopulateSegmentTimeline(const std::list<SegmentInfo>& segment_infos,
80  XmlNode* segment_timeline) {
81  for (const SegmentInfo& segment_info : segment_infos) {
82  XmlNode s_element("S");
83  RCHECK(s_element.SetIntegerAttribute("t", segment_info.start_time));
84  RCHECK(s_element.SetIntegerAttribute("d", segment_info.duration));
85  if (segment_info.repeat > 0)
86  RCHECK(s_element.SetIntegerAttribute("r", segment_info.repeat));
87 
88  RCHECK(segment_timeline->AddChild(std::move(s_element)));
89  }
90 
91  return true;
92 }
93 
94 void CollectNamespaceFromName(const std::string& name,
95  std::set<std::string>* namespaces) {
96  const size_t pos = name.find(':');
97  if (pos != std::string::npos)
98  namespaces->insert(name.substr(0, pos));
99 }
100 
101 void TraverseAttrsAndCollectNamespaces(const xmlAttr* attr,
102  std::set<std::string>* namespaces) {
103  for (const xmlAttr* cur_attr = attr; cur_attr; cur_attr = cur_attr->next) {
104  CollectNamespaceFromName(reinterpret_cast<const char*>(cur_attr->name),
105  namespaces);
106  }
107 }
108 
109 void TraverseNodesAndCollectNamespaces(const xmlNode* node,
110  std::set<std::string>* namespaces) {
111  for (const xmlNode* cur_node = node; cur_node; cur_node = cur_node->next) {
112  CollectNamespaceFromName(reinterpret_cast<const char*>(cur_node->name),
113  namespaces);
114 
115  TraverseNodesAndCollectNamespaces(cur_node->children, namespaces);
116  TraverseAttrsAndCollectNamespaces(cur_node->properties, namespaces);
117  }
118 }
119 
120 } // namespace
121 
122 namespace xml {
123 
124 class XmlNode::Impl {
125  public:
126  scoped_xml_ptr<xmlNode> node;
127 };
128 
129 XmlNode::XmlNode(const std::string& name) : impl_(new Impl) {
130  impl_->node.reset(xmlNewNode(NULL, BAD_CAST name.c_str()));
131  DCHECK(impl_->node);
132 }
133 
134 XmlNode::XmlNode(XmlNode&&) = default;
135 
136 XmlNode::~XmlNode() {}
137 
138 XmlNode& XmlNode::operator=(XmlNode&&) = default;
139 
141  DCHECK(impl_->node);
142  DCHECK(child.impl_->node);
143  RCHECK(xmlAddChild(impl_->node.get(), child.impl_->node.get()));
144 
145  // Reaching here means the ownership of |child| transfered to |node|.
146  // Release the pointer so that it doesn't get destructed in this scope.
147  ignore_result(child.impl_->node.release());
148  return true;
149 }
150 
151 bool XmlNode::AddElements(const std::vector<Element>& elements) {
152  for (size_t element_index = 0; element_index < elements.size();
153  ++element_index) {
154  const Element& child_element = elements[element_index];
155  XmlNode child_node(child_element.name);
156  for (std::map<std::string, std::string>::const_iterator attribute_it =
157  child_element.attributes.begin();
158  attribute_it != child_element.attributes.end(); ++attribute_it) {
159  RCHECK(child_node.SetStringAttribute(attribute_it->first,
160  attribute_it->second));
161  }
162 
163  // Note that somehow |SetContent| needs to be called before |AddElements|
164  // otherwise the added children will be overwritten by the content.
165  child_node.SetContent(child_element.content);
166 
167  // Recursively set children for the child.
168  RCHECK(child_node.AddElements(child_element.subelements));
169 
170  if (!xmlAddChild(impl_->node.get(), child_node.impl_->node.get())) {
171  LOG(ERROR) << "Failed to set child " << child_element.name
172  << " to parent element "
173  << reinterpret_cast<const char*>(impl_->node->name);
174  return false;
175  }
176  // Reaching here means the ownership of |child_node| transfered to |node|.
177  // Release the pointer so that it doesn't get destructed in this scope.
178  child_node.impl_->node.release();
179  }
180  return true;
181 }
182 
183 bool XmlNode::SetStringAttribute(const std::string& attribute_name,
184  const std::string& attribute) {
185  DCHECK(impl_->node);
186  return xmlSetProp(impl_->node.get(), BAD_CAST attribute_name.c_str(),
187  BAD_CAST attribute.c_str()) != nullptr;
188 }
189 
190 bool XmlNode::SetIntegerAttribute(const std::string& attribute_name,
191  uint64_t number) {
192  DCHECK(impl_->node);
193  return xmlSetProp(impl_->node.get(), BAD_CAST attribute_name.c_str(),
194  BAD_CAST(base::Uint64ToString(number).c_str())) != nullptr;
195 }
196 
197 bool XmlNode::SetFloatingPointAttribute(const std::string& attribute_name,
198  double number) {
199  DCHECK(impl_->node);
200  return xmlSetProp(impl_->node.get(), BAD_CAST attribute_name.c_str(),
201  BAD_CAST(base::DoubleToString(number).c_str())) != nullptr;
202 }
203 
204 bool XmlNode::SetId(uint32_t id) {
205  return SetIntegerAttribute("id", id);
206 }
207 
208 void XmlNode::AddContent(const std::string& content) {
209  DCHECK(impl_->node);
210  xmlNodeAddContent(impl_->node.get(), BAD_CAST content.c_str());
211 }
212 
213 void XmlNode::SetContent(const std::string& content) {
214  DCHECK(impl_->node);
215  xmlNodeSetContent(impl_->node.get(), BAD_CAST content.c_str());
216 }
217 
218 std::set<std::string> XmlNode::ExtractReferencedNamespaces() const {
219  std::set<std::string> namespaces;
220  TraverseNodesAndCollectNamespaces(impl_->node.get(), &namespaces);
221  return namespaces;
222 }
223 
224 std::string XmlNode::ToString(const std::string& comment) const {
225  // Create an xmlDoc from xmlNodePtr. The node is copied so ownership does not
226  // transfer.
227  xml::scoped_xml_ptr<xmlDoc> doc(xmlNewDoc(BAD_CAST "1.0"));
228  if (comment.empty()) {
229  xmlDocSetRootElement(doc.get(), xmlCopyNode(impl_->node.get(), true));
230  } else {
231  xml::scoped_xml_ptr<xmlNode> comment_xml(
232  xmlNewDocComment(doc.get(), BAD_CAST comment.c_str()));
233  xmlDocSetRootElement(doc.get(), comment_xml.get());
234  xmlAddSibling(comment_xml.release(), xmlCopyNode(impl_->node.get(), true));
235  }
236 
237  // Format the xmlDoc to string.
238  static const int kNiceFormat = 1;
239  int doc_str_size = 0;
240  xmlChar* doc_str = nullptr;
241  xmlDocDumpFormatMemoryEnc(doc.get(), &doc_str, &doc_str_size, "UTF-8",
242  kNiceFormat);
243  std::string output(doc_str, doc_str + doc_str_size);
244  xmlFree(doc_str);
245  return output;
246 }
247 
248 bool XmlNode::GetAttribute(const std::string& name, std::string* value) const {
249  xml::scoped_xml_ptr<xmlChar> str(
250  xmlGetProp(impl_->node.get(), BAD_CAST name.c_str()));
251  if (!str)
252  return false;
253  *value = reinterpret_cast<const char*>(str.get());
254  return true;
255 }
256 
257 xmlNode* XmlNode::GetRawPtr() const {
258  return impl_->node.get();
259 }
260 
261 RepresentationBaseXmlNode::RepresentationBaseXmlNode(const std::string& name)
262  : XmlNode(name) {}
263 RepresentationBaseXmlNode::~RepresentationBaseXmlNode() {}
264 
265 bool RepresentationBaseXmlNode::AddContentProtectionElements(
266  const std::list<ContentProtectionElement>& content_protection_elements) {
267  for (const auto& elem : content_protection_elements) {
268  RCHECK(AddContentProtectionElement(elem));
269  }
270 
271  return true;
272 }
273 
275  const std::string& scheme_id_uri,
276  const std::string& value) {
277  return AddDescriptor("SupplementalProperty", scheme_id_uri, value);
278 }
279 
281  const std::string& scheme_id_uri,
282  const std::string& value) {
283  return AddDescriptor("EssentialProperty", scheme_id_uri, value);
284 }
285 
287  const std::string& descriptor_name,
288  const std::string& scheme_id_uri,
289  const std::string& value) {
290  XmlNode descriptor(descriptor_name);
291  RCHECK(descriptor.SetStringAttribute("schemeIdUri", scheme_id_uri));
292  if (!value.empty())
293  RCHECK(descriptor.SetStringAttribute("value", value));
294  return AddChild(std::move(descriptor));
295 }
296 
297 bool RepresentationBaseXmlNode::AddContentProtectionElement(
298  const ContentProtectionElement& content_protection_element) {
299  XmlNode content_protection_node("ContentProtection");
300 
301  // @value is an optional attribute.
302  if (!content_protection_element.value.empty()) {
303  RCHECK(content_protection_node.SetStringAttribute(
304  "value", content_protection_element.value));
305  }
306  RCHECK(content_protection_node.SetStringAttribute(
307  "schemeIdUri", content_protection_element.scheme_id_uri));
308 
309  for (const auto& pair : content_protection_element.additional_attributes) {
310  RCHECK(content_protection_node.SetStringAttribute(pair.first, pair.second));
311  }
312 
313  RCHECK(content_protection_node.AddElements(
314  content_protection_element.subelements));
315  return AddChild(std::move(content_protection_node));
316 }
317 
318 AdaptationSetXmlNode::AdaptationSetXmlNode()
319  : RepresentationBaseXmlNode("AdaptationSet") {}
320 AdaptationSetXmlNode::~AdaptationSetXmlNode() {}
321 
323  const std::string& scheme_id_uri,
324  const std::string& value) {
325  return AddDescriptor("Accessibility", scheme_id_uri, value);
326 }
327 
328 bool AdaptationSetXmlNode::AddRoleElement(const std::string& scheme_id_uri,
329  const std::string& value) {
330  return AddDescriptor("Role", scheme_id_uri, value);
331 }
332 
333 RepresentationXmlNode::RepresentationXmlNode()
334  : RepresentationBaseXmlNode("Representation") {}
335 RepresentationXmlNode::~RepresentationXmlNode() {}
336 
337 bool RepresentationXmlNode::AddVideoInfo(const VideoInfo& video_info,
338  bool set_width,
339  bool set_height,
340  bool set_frame_rate) {
341  if (!video_info.has_width() || !video_info.has_height()) {
342  LOG(ERROR) << "Missing width or height for adding a video info.";
343  return false;
344  }
345 
346  if (video_info.has_pixel_width() && video_info.has_pixel_height()) {
347  RCHECK(SetStringAttribute(
348  "sar", base::IntToString(video_info.pixel_width()) + ":" +
349  base::IntToString(video_info.pixel_height())));
350  }
351 
352  if (set_width)
353  RCHECK(SetIntegerAttribute("width", video_info.width()));
354  if (set_height)
355  RCHECK(SetIntegerAttribute("height", video_info.height()));
356  if (set_frame_rate) {
357  RCHECK(SetStringAttribute(
358  "frameRate", base::IntToString(video_info.time_scale()) + "/" +
359  base::IntToString(video_info.frame_duration())));
360  }
361 
362  if (video_info.has_playback_rate()) {
363  RCHECK(SetStringAttribute("maxPlayoutRate",
364  base::IntToString(video_info.playback_rate())));
365  // Since the trick play stream contains only key frames, there is no coding
366  // dependency on the main stream. Simply set the codingDependency to false.
367  // TODO(hmchen): propagate this attribute up to the AdaptationSet, since
368  // all are set to false.
369  RCHECK(SetStringAttribute("codingDependency", "false"));
370  }
371  return true;
372 }
373 
374 bool RepresentationXmlNode::AddAudioInfo(const AudioInfo& audio_info) {
375  return AddAudioChannelInfo(audio_info) &&
376  AddAudioSamplingRateInfo(audio_info);
377 }
378 
379 bool RepresentationXmlNode::AddVODOnlyInfo(const MediaInfo& media_info) {
380  const bool use_segment_list_text =
381  media_info.has_text_info() && media_info.has_presentation_time_offset();
382 
383  if (media_info.has_media_file_url() && !use_segment_list_text) {
384  XmlNode base_url("BaseURL");
385  base_url.SetContent(media_info.media_file_url());
386 
387  RCHECK(AddChild(std::move(base_url)));
388  }
389 
390  const bool need_segment_base =
391  media_info.has_index_range() || media_info.has_init_range() ||
392  (media_info.has_reference_time_scale() && !media_info.has_text_info());
393  DCHECK(!need_segment_base || !use_segment_list_text);
394 
395  if (need_segment_base || use_segment_list_text) {
396  XmlNode child(need_segment_base ? "SegmentBase" : "SegmentList");
397  if (media_info.has_index_range()) {
398  RCHECK(child.SetStringAttribute("indexRange",
399  RangeToString(media_info.index_range())));
400  }
401 
402  if (media_info.has_reference_time_scale()) {
403  RCHECK(child.SetIntegerAttribute("timescale",
404  media_info.reference_time_scale()));
405  }
406 
407  if (media_info.has_presentation_time_offset()) {
408  RCHECK(child.SetIntegerAttribute("presentationTimeOffset",
409  media_info.presentation_time_offset()));
410  }
411 
412  if (media_info.has_init_range()) {
413  XmlNode initialization("Initialization");
414  RCHECK(initialization.SetStringAttribute(
415  "range", RangeToString(media_info.init_range())));
416 
417  RCHECK(child.AddChild(std::move(initialization)));
418  }
419 
420  if (use_segment_list_text) {
421  XmlNode media_url("SegmentURL");
422  RCHECK(
423  media_url.SetStringAttribute("media", media_info.media_file_url()));
424  RCHECK(child.AddChild(std::move(media_url)));
425  }
426 
427  RCHECK(AddChild(std::move(child)));
428  }
429 
430  return true;
431 }
432 
434  const MediaInfo& media_info,
435  const std::list<SegmentInfo>& segment_infos,
436  uint32_t start_number) {
437  XmlNode segment_template("SegmentTemplate");
438  if (media_info.has_reference_time_scale()) {
439  RCHECK(segment_template.SetIntegerAttribute(
440  "timescale", media_info.reference_time_scale()));
441  }
442 
443  if (media_info.has_presentation_time_offset()) {
444  RCHECK(segment_template.SetIntegerAttribute(
445  "presentationTimeOffset", media_info.presentation_time_offset()));
446  }
447 
448  if (media_info.has_init_segment_url()) {
449  RCHECK(segment_template.SetStringAttribute("initialization",
450  media_info.init_segment_url()));
451  }
452 
453  if (media_info.has_segment_template_url()) {
454  RCHECK(segment_template.SetStringAttribute(
455  "media", media_info.segment_template_url()));
456  RCHECK(segment_template.SetIntegerAttribute("startNumber", start_number));
457  }
458 
459  if (!segment_infos.empty()) {
460  // Don't use SegmentTimeline if all segments except the last one are of
461  // the same duration.
462  if (IsTimelineConstantDuration(segment_infos, start_number)) {
463  RCHECK(segment_template.SetIntegerAttribute(
464  "duration", segment_infos.front().duration));
465  if (FLAGS_dash_add_last_segment_number_when_needed) {
466  uint32_t last_segment_number = start_number - 1;
467  for (const auto& segment_info_element : segment_infos)
468  last_segment_number += segment_info_element.repeat + 1;
469 
471  "http://dashif.org/guidelines/last-segment-number",
472  std::to_string(last_segment_number)));
473  }
474  } else {
475  XmlNode segment_timeline("SegmentTimeline");
476  RCHECK(PopulateSegmentTimeline(segment_infos, &segment_timeline));
477  RCHECK(segment_template.AddChild(std::move(segment_timeline)));
478  }
479  }
480  return AddChild(std::move(segment_template));
481 }
482 
483 bool RepresentationXmlNode::AddAudioChannelInfo(const AudioInfo& audio_info) {
484  std::string audio_channel_config_scheme;
485  std::string audio_channel_config_value;
486 
487  if (audio_info.codec() == kEC3Codec) {
488  const auto& codec_data = audio_info.codec_specific_data();
489  // Use MPEG scheme if the mpeg value is available and valid, fallback to
490  // EC3 channel mapping otherwise.
491  // See https://github.com/Dash-Industry-Forum/DASH-IF-IOP/issues/268
492  const uint32_t ec3_channel_mpeg_value = codec_data.channel_mpeg_value();
493  const uint32_t NO_MAPPING = 0xFFFFFFFF;
494  if (ec3_channel_mpeg_value == NO_MAPPING) {
495  // Convert EC3 channel map into string of hexadecimal digits. Spec: DASH-IF
496  // Interoperability Points v3.0 9.2.1.2.
497  const uint16_t ec3_channel_map =
498  base::HostToNet16(codec_data.channel_mask());
499  audio_channel_config_value =
500  base::HexEncode(&ec3_channel_map, sizeof(ec3_channel_map));
501  audio_channel_config_scheme =
502  "tag:dolby.com,2014:dash:audio_channel_configuration:2011";
503  } else {
504  // Calculate EC3 channel configuration descriptor value with MPEG scheme.
505  // Spec: ETSI TS 102 366 V1.4.1 Digital Audio Compression
506  // (AC-3, Enhanced AC-3) I.1.2.
507  audio_channel_config_value = base::UintToString(ec3_channel_mpeg_value);
508  audio_channel_config_scheme = "urn:mpeg:mpegB:cicp:ChannelConfiguration";
509  }
510  bool ret = AddDescriptor("AudioChannelConfiguration",
511  audio_channel_config_scheme,
512  audio_channel_config_value);
513  // Dolby Digital Plus JOC descriptor. Spec: ETSI TS 103 420 v1.2.1
514  // Backwards-compatible object audio carriage using Enhanced AC-3 Standard
515  // D.2.2.
516  if (codec_data.ec3_joc_complexity() != 0) {
517  std::string ec3_joc_complexity =
518  base::UintToString(codec_data.ec3_joc_complexity());
519  ret &= AddDescriptor("SupplementalProperty",
520  "tag:dolby.com,2018:dash:EC3_ExtensionType:2018",
521  "JOC");
522  ret &= AddDescriptor("SupplementalProperty",
523  "tag:dolby.com,2018:dash:"
524  "EC3_ExtensionComplexityIndex:2018",
525  ec3_joc_complexity);
526  }
527  return ret;
528  } else if (audio_info.codec().substr(0, 4) == kAC4Codec) {
529  const auto& codec_data = audio_info.codec_specific_data();
530  const bool ac4_ims_flag = codec_data.ac4_ims_flag();
531  // Use MPEG scheme if the mpeg value is available and valid, fallback to
532  // AC4 channel mask otherwise.
533  // See https://github.com/Dash-Industry-Forum/DASH-IF-IOP/issues/268
534  const uint32_t ac4_channel_mpeg_value = codec_data.channel_mpeg_value();
535  const uint32_t NO_MAPPING = 0xFFFFFFFF;
536  if (ac4_channel_mpeg_value == NO_MAPPING) {
537  // Calculate AC-4 channel mask. Spec: ETSI TS 103 190-2 V1.2.1 Digital
538  // Audio Compression (AC-4) Standard; Part 2: Immersive and personalized
539  // audio G.3.1.
540  const uint32_t ac4_channel_mask =
541  base::HostToNet32(codec_data.channel_mask() << 8);
542  audio_channel_config_value =
543  base::HexEncode(&ac4_channel_mask, sizeof(ac4_channel_mask) - 1);
544  // Note that the channel config schemes for EC-3 and AC-4 are different.
545  // See https://github.com/Dash-Industry-Forum/DASH-IF-IOP/issues/268.
546  audio_channel_config_scheme =
547  "tag:dolby.com,2015:dash:audio_channel_configuration:2015";
548  } else {
549  // Calculate AC-4 channel configuration descriptor value with MPEG scheme.
550  // Spec: ETSI TS 103 190-2 V1.2.1 Digital Audio Compression (AC-4) Standard;
551  // Part 2: Immersive and personalized audio G.3.2.
552  audio_channel_config_value = base::UintToString(ac4_channel_mpeg_value);
553  audio_channel_config_scheme = "urn:mpeg:mpegB:cicp:ChannelConfiguration";
554  }
555  bool ret = AddDescriptor("AudioChannelConfiguration",
556  audio_channel_config_scheme,
557  audio_channel_config_value);
558  if (ac4_ims_flag) {
559  ret &= AddDescriptor("SupplementalProperty",
560  "tag:dolby.com,2016:dash:virtualized_content:2016",
561  "1");
562  }
563  return ret;
564  } else {
565  audio_channel_config_value = base::UintToString(audio_info.num_channels());
566  audio_channel_config_scheme =
567  "urn:mpeg:dash:23003:3:audio_channel_configuration:2011";
568  }
569 
570  return AddDescriptor("AudioChannelConfiguration", audio_channel_config_scheme,
571  audio_channel_config_value);
572 }
573 
574 // MPD expects one number for sampling frequency, or if it is a range it should
575 // be space separated.
576 bool RepresentationXmlNode::AddAudioSamplingRateInfo(
577  const AudioInfo& audio_info) {
578  return !audio_info.has_sampling_frequency() ||
579  SetIntegerAttribute("audioSamplingRate",
580  audio_info.sampling_frequency());
581 }
582 
583 } // namespace xml
584 } // namespace shaka
bool AddRoleElement(const std::string &scheme_id_uri, const std::string &value) WARN_UNUSED_RESULT
Definition: xml_node.cc:328
bool AddAccessibilityElement(const std::string &scheme_id_uri, const std::string &value) WARN_UNUSED_RESULT
Definition: xml_node.cc:322
bool AddDescriptor(const std::string &descriptor_name, const std::string &scheme_id_uri, const std::string &value) WARN_UNUSED_RESULT
Definition: xml_node.cc:286
bool AddSupplementalProperty(const std::string &scheme_id_uri, const std::string &value) WARN_UNUSED_RESULT
Definition: xml_node.cc:274
bool AddEssentialProperty(const std::string &scheme_id_uri, const std::string &value) WARN_UNUSED_RESULT
Definition: xml_node.cc:280
bool AddVideoInfo(const MediaInfo::VideoInfo &video_info, bool set_width, bool set_height, bool set_frame_rate) WARN_UNUSED_RESULT
Definition: xml_node.cc:337
bool AddVODOnlyInfo(const MediaInfo &media_info) WARN_UNUSED_RESULT
Definition: xml_node.cc:379
bool AddLiveOnlyInfo(const MediaInfo &media_info, const std::list< SegmentInfo > &segment_infos, uint32_t start_number) WARN_UNUSED_RESULT
Definition: xml_node.cc:433
bool AddAudioInfo(const MediaInfo::AudioInfo &audio_info) WARN_UNUSED_RESULT
Definition: xml_node.cc:374
bool AddChild(XmlNode child) WARN_UNUSED_RESULT
Definition: xml_node.cc:140
std::set< std::string > ExtractReferencedNamespaces() const
Definition: xml_node.cc:218
void AddContent(const std::string &content)
Similar to SetContent, but appends to the end of existing content.
Definition: xml_node.cc:208
void SetContent(const std::string &content)
Definition: xml_node.cc:213
bool SetFloatingPointAttribute(const std::string &attribute_name, double number) WARN_UNUSED_RESULT
Definition: xml_node.cc:197
bool SetIntegerAttribute(const std::string &attribute_name, uint64_t number) WARN_UNUSED_RESULT
Definition: xml_node.cc:190
XmlNode(const std::string &name)
Definition: xml_node.cc:129
bool SetStringAttribute(const std::string &attribute_name, const std::string &attribute) WARN_UNUSED_RESULT
Definition: xml_node.cc:183
bool GetAttribute(const std::string &name, std::string *value) const
Definition: xml_node.cc:248
bool SetId(uint32_t id) WARN_UNUSED_RESULT
Definition: xml_node.cc:204
bool AddElements(const std::vector< Element > &elements) WARN_UNUSED_RESULT
Adds Elements to this node using the Element struct.
Definition: xml_node.cc:151
std::string ToString(const std::string &comment) const
Definition: xml_node.cc:224
All the methods that are virtual are virtual for mocking.