diff --git a/README.md b/README.md index 5b8dd06d7e..59de1f72ca 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ Shaka Packager supports: - [Widevine](http://www.widevine.com/) - [PlayReady](https://www.microsoft.com/playready/)¹ - [FairPlay](https://developer.apple.com/streaming/fps/)¹ + - [Marlin](https://www.intertrust.com/marlin-drm/)¹ - Encryption standards: - [CENC](https://en.wikipedia.org/wiki/MPEG_Common_Encryption) - [SAMPLE-AES](https://developer.apple.com/library/content/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption/Intro/Intro.html) diff --git a/docs/source/options/general_encryption_options.rst b/docs/source/options/general_encryption_options.rst index bbd383518e..580442d050 100644 --- a/docs/source/options/general_encryption_options.rst +++ b/docs/source/options/general_encryption_options.rst @@ -17,4 +17,5 @@ General encryption options --protection_systems Protection systems to be generated. Supported protection systems include - Widevine, PlayReady, FairPlay, and CommonSystem (https://goo.gl/s8RIhr). + Widevine, PlayReady, FairPlay, Marlin, and + CommonSystem (https://goo.gl/s8RIhr). diff --git a/packager/app/packager_main.cc b/packager/app/packager_main.cc index 2a1c193998..e76f6ce67f 100644 --- a/packager/app/packager_main.cc +++ b/packager/app/packager_main.cc @@ -266,6 +266,7 @@ bool ParseProtectionSystems( {"common", EncryptionParams::ProtectionSystem::kCommonSystem}, {"commonsystem", EncryptionParams::ProtectionSystem::kCommonSystem}, {"fairplay", EncryptionParams::ProtectionSystem::kFairPlay}, + {"marlin", EncryptionParams::ProtectionSystem::kMarlin}, {"playready", EncryptionParams::ProtectionSystem::kPlayReady}, {"widevine", EncryptionParams::ProtectionSystem::kWidevine}, }; diff --git a/packager/app/packager_util.cc b/packager/app/packager_util.cc index 0176f0eb80..0d2d4ef16d 100644 --- a/packager/app/packager_util.cc +++ b/packager/app/packager_util.cc @@ -16,8 +16,6 @@ #include "packager/media/base/raw_key_source.h" #include "packager/media/base/request_signer.h" #include "packager/media/base/widevine_key_source.h" -#include "packager/media/chunking/chunking_handler.h" -#include "packager/media/crypto/encryption_handler.h" #include "packager/mpd/base/mpd_options.h" #include "packager/status.h" @@ -55,6 +53,9 @@ int GetProtectionSystemsFlag( case EncryptionParams::ProtectionSystem::kFairPlay: protection_systems_flags |= FAIRPLAY_PROTECTION_SYSTEM_FLAG; break; + case EncryptionParams::ProtectionSystem::kMarlin: + protection_systems_flags |= MARLIN_PROTECTION_SYSTEM_FLAG; + break; case EncryptionParams::ProtectionSystem::kPlayReady: protection_systems_flags |= PLAYREADY_PROTECTION_SYSTEM_FLAG; break; diff --git a/packager/app/protection_system_flags.cc b/packager/app/protection_system_flags.cc index 755aa41087..ecb9bd9d0f 100644 --- a/packager/app/protection_system_flags.cc +++ b/packager/app/protection_system_flags.cc @@ -12,4 +12,5 @@ DEFINE_string( protection_systems, "", "Protection systems to be generated. Supported protection systems include " - "Widevine, PlayReady, FairPlay, and CommonSystem (https://goo.gl/s8RIhr)."); + "Widevine, PlayReady, FairPlay, Marlin and " + "CommonSystem (https://goo.gl/s8RIhr)."); diff --git a/packager/app/test/packager_test.py b/packager/app/test/packager_test.py index 51e3dbb3e5..fecd395acf 100755 --- a/packager/app/test/packager_test.py +++ b/packager/app/test/packager_test.py @@ -398,7 +398,7 @@ class PackagerAppTest(unittest.TestCase): def _GetFlags(self, strip_parameter_set_nalus=True, encryption=False, - fairplay=False, + protection_systems=None, protection_scheme=None, vp9_subsample_encryption=True, decryption=False, @@ -443,12 +443,13 @@ class PackagerAppTest(unittest.TestCase): if not random_iv: flags.append('--iv=' + self.encryption_iv) - if fairplay: - fairplay_key_uri = ('skd://www.license.com/' - 'getkey?KeyId=31323334-3536-3738-3930-313233343536') - flags += [ - '--protection_systems=FairPlay', '--hls_key_uri=' + fairplay_key_uri - ] + if protection_systems: + flags += ['--protection_systems=' + protection_systems] + if 'FairPlay' in protection_systems: + fairplay_key_uri = ('skd://www.license.com/getkey?' + 'KeyId=31323334-3536-3738-3930-313233343536') + flags += ['--hls_key_uri=' + fairplay_key_uri] + if protection_scheme: flags += ['--protection_scheme', protection_scheme] if not vp9_subsample_encryption: @@ -862,12 +863,15 @@ class PackagerFunctionalTest(PackagerAppTest): self._GetFlags(encryption=True, output_dash=True)) self._CheckTestResults('encryption', verify_decryption=True) - def testEncryptionWithFairplay(self): + def testEncryptionWithMultiDrms(self): self.assertPackageSuccess( self._GetStreams(['audio', 'video']), self._GetFlags( - encryption=True, fairplay=True, output_dash=True, output_hls=True)) - self._CheckTestResults('encryption-with-fairplay') + encryption=True, + protection_systems='Widevine,PlayReady,FairPlay,Marlin', + output_dash=True, + output_hls=True)) + self._CheckTestResults('encryption-with-multi-drms') # Test deprecated flag --enable_fixed_key_encryption, which is still # supported currently. @@ -1096,7 +1100,8 @@ class PackagerFunctionalTest(PackagerAppTest): segmented=True, hls=True, test_files=['bear-640x360.ts']), - self._GetFlags(encryption=True, output_hls=True, fairplay=True)) + self._GetFlags( + encryption=True, protection_systems='FairPlay', output_hls=True)) self._CheckTestResults('avc-ts-with-encryption-and-fairplay') def testAvcAc3TsWithEncryption(self): diff --git a/packager/app/test/testdata/encryption-with-fairplay/output.mpd b/packager/app/test/testdata/encryption-with-fairplay/output.mpd deleted file mode 100644 index 13dbbdda48..0000000000 --- a/packager/app/test/testdata/encryption-with-fairplay/output.mpd +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - bear-640x360-video.mp4 - - - - - - - - - - - bear-640x360-audio.mp4 - - - - - - - diff --git a/packager/app/test/testdata/encryption-with-fairplay/bear-640x360-audio.mp4 b/packager/app/test/testdata/encryption-with-multi-drms/bear-640x360-audio.mp4 similarity index 97% rename from packager/app/test/testdata/encryption-with-fairplay/bear-640x360-audio.mp4 rename to packager/app/test/testdata/encryption-with-multi-drms/bear-640x360-audio.mp4 index 22fcb0a3ac..1c199b8264 100644 Binary files a/packager/app/test/testdata/encryption-with-fairplay/bear-640x360-audio.mp4 and b/packager/app/test/testdata/encryption-with-multi-drms/bear-640x360-audio.mp4 differ diff --git a/packager/app/test/testdata/encryption-with-fairplay/bear-640x360-video.mp4 b/packager/app/test/testdata/encryption-with-multi-drms/bear-640x360-video.mp4 similarity index 99% rename from packager/app/test/testdata/encryption-with-fairplay/bear-640x360-video.mp4 rename to packager/app/test/testdata/encryption-with-multi-drms/bear-640x360-video.mp4 index 81691dafee..fd44066ee9 100644 Binary files a/packager/app/test/testdata/encryption-with-fairplay/bear-640x360-video.mp4 and b/packager/app/test/testdata/encryption-with-multi-drms/bear-640x360-video.mp4 differ diff --git a/packager/app/test/testdata/encryption-with-fairplay/output.m3u8 b/packager/app/test/testdata/encryption-with-multi-drms/output.m3u8 similarity index 100% rename from packager/app/test/testdata/encryption-with-fairplay/output.m3u8 rename to packager/app/test/testdata/encryption-with-multi-drms/output.m3u8 diff --git a/packager/app/test/testdata/encryption-with-multi-drms/output.mpd b/packager/app/test/testdata/encryption-with-multi-drms/output.mpd new file mode 100644 index 0000000000..4f9bab2413 --- /dev/null +++ b/packager/app/test/testdata/encryption-with-multi-drms/output.mpd @@ -0,0 +1,47 @@ + + + + + + + + AAACOnBzc2gBAAAAmgTweZhAQoarkuZb4IhflQAAAAExMjM0NTY3ODkwMTIzNDU2AAACBgYCAAABAAEA/AE8AFcAUgBNAEgARQBBAEQARQBSACAAeABtAGwAbgBzAD0AIgBoAHQAdABwADoALwAvAHMAYwBoAGUAbQBhAHMALgBtAGkAYwByAG8AcwBvAGYAdAAuAGMAbwBtAC8ARABSAE0ALwAyADAAMAA3AC8AMAAzAC8AUABsAGEAeQBSAGUAYQBkAHkASABlAGEAZABlAHIAIgAgAHYAZQByAHMAaQBvAG4APQAiADQALgAwAC4AMAAuADAAIgA+ADwARABBAFQAQQA+ADwAUABSAE8AVABFAEMAVABJAE4ARgBPAD4APABLAEUAWQBMAEUATgA+ADEANgA8AC8ASwBFAFkATABFAE4APgA8AEEATABHAEkARAA+AEEARQBTAEMAVABSADwALwBBAEwARwBJAEQAPgA8AC8AUABSAE8AVABFAEMAVABJAE4ARgBPAD4APABLAEkARAA+AE4ARABNAHkATQBUAFkAMQBPAEQAYwA1AE0ARABFAHkATQB6AFEAMQBOAGcAPQA9ADwALwBLAEkARAA+ADwAQwBIAEUAQwBLAFMAVQBNAD4AbAA1AEwAbwBVAGcASwA5AEsAQwBnAD0APAAvAEMASABFAEMASwBTAFUATQA+ADwALwBEAEEAVABBAD4APAAvAFcAUgBNAEgARQBBAEQARQBSAD4A + + + AAAAOHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABgSEDEyMzQ1Njc4OTAxMjM0NTZI49yVmwY= + + + + urn:marlin:kid:31323334353637383930313233343536 + + + + bear-640x360-video.mp4 + + + + + + + + + AAACOnBzc2gBAAAAmgTweZhAQoarkuZb4IhflQAAAAExMjM0NTY3ODkwMTIzNDU2AAACBgYCAAABAAEA/AE8AFcAUgBNAEgARQBBAEQARQBSACAAeABtAGwAbgBzAD0AIgBoAHQAdABwADoALwAvAHMAYwBoAGUAbQBhAHMALgBtAGkAYwByAG8AcwBvAGYAdAAuAGMAbwBtAC8ARABSAE0ALwAyADAAMAA3AC8AMAAzAC8AUABsAGEAeQBSAGUAYQBkAHkASABlAGEAZABlAHIAIgAgAHYAZQByAHMAaQBvAG4APQAiADQALgAwAC4AMAAuADAAIgA+ADwARABBAFQAQQA+ADwAUABSAE8AVABFAEMAVABJAE4ARgBPAD4APABLAEUAWQBMAEUATgA+ADEANgA8AC8ASwBFAFkATABFAE4APgA8AEEATABHAEkARAA+AEEARQBTAEMAVABSADwALwBBAEwARwBJAEQAPgA8AC8AUABSAE8AVABFAEMAVABJAE4ARgBPAD4APABLAEkARAA+AE4ARABNAHkATQBUAFkAMQBPAEQAYwA1AE0ARABFAHkATQB6AFEAMQBOAGcAPQA9ADwALwBLAEkARAA+ADwAQwBIAEUAQwBLAFMAVQBNAD4AbAA1AEwAbwBVAGcASwA5AEsAQwBnAD0APAAvAEMASABFAEMASwBTAFUATQA+ADwALwBEAEEAVABBAD4APAAvAFcAUgBNAEgARQBBAEQARQBSAD4A + + + AAAAOHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABgSEDEyMzQ1Njc4OTAxMjM0NTZI49yVmwY= + + + + urn:marlin:kid:31323334353637383930313233343536 + + + + + bear-640x360-audio.mp4 + + + + + + + diff --git a/packager/app/test/testdata/encryption-with-fairplay/stream_0.m3u8 b/packager/app/test/testdata/encryption-with-multi-drms/stream_0.m3u8 similarity index 59% rename from packager/app/test/testdata/encryption-with-fairplay/stream_0.m3u8 rename to packager/app/test/testdata/encryption-with-multi-drms/stream_0.m3u8 index 41bfea4b0a..62ca40212a 100644 --- a/packager/app/test/testdata/encryption-with-fairplay/stream_0.m3u8 +++ b/packager/app/test/testdata/encryption-with-multi-drms/stream_0.m3u8 @@ -3,10 +3,11 @@ ## Generated with https://github.com/google/shaka-packager version -- #EXT-X-TARGETDURATION:2 #EXT-X-PLAYLIST-TYPE:VOD -#EXT-X-MAP:URI="bear-640x360-audio.mp4",BYTERANGE="951@0" +#EXT-X-MAP:URI="bear-640x360-audio.mp4",BYTERANGE="1577@0" +#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,URI="data:text/plain;base64,AAAAOHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABgSEDEyMzQ1Njc4OTAxMjM0NTZI49yVmwY=",KEYID=0x31323334353637383930313233343536,KEYFORMATVERSIONS="1",KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" #EXT-X-KEY:METHOD=SAMPLE-AES-CTR,URI="skd://www.license.com/getkey?KeyId=31323334-3536-3738-3930-313233343536",KEYFORMATVERSIONS="1",KEYFORMAT="com.apple.streamingkeydelivery" #EXTINF:1.022, -#EXT-X-BYTERANGE:17028@1019 +#EXT-X-BYTERANGE:17028@1645 bear-640x360-audio.mp4 #EXTINF:0.998, #EXT-X-BYTERANGE:16682 diff --git a/packager/app/test/testdata/encryption-with-fairplay/stream_1.m3u8 b/packager/app/test/testdata/encryption-with-multi-drms/stream_1.m3u8 similarity index 59% rename from packager/app/test/testdata/encryption-with-fairplay/stream_1.m3u8 rename to packager/app/test/testdata/encryption-with-multi-drms/stream_1.m3u8 index 8ef9329090..c9e346a3c8 100644 --- a/packager/app/test/testdata/encryption-with-fairplay/stream_1.m3u8 +++ b/packager/app/test/testdata/encryption-with-multi-drms/stream_1.m3u8 @@ -3,10 +3,11 @@ ## Generated with https://github.com/google/shaka-packager version -- #EXT-X-TARGETDURATION:2 #EXT-X-PLAYLIST-TYPE:VOD -#EXT-X-MAP:URI="bear-640x360-video.mp4",BYTERANGE="1075@0" +#EXT-X-MAP:URI="bear-640x360-video.mp4",BYTERANGE="1701@0" +#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,URI="data:text/plain;base64,AAAAOHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABgSEDEyMzQ1Njc4OTAxMjM0NTZI49yVmwY=",KEYID=0x31323334353637383930313233343536,KEYFORMATVERSIONS="1",KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" #EXT-X-KEY:METHOD=SAMPLE-AES-CTR,URI="skd://www.license.com/getkey?KeyId=31323334-3536-3738-3930-313233343536",KEYFORMATVERSIONS="1",KEYFORMAT="com.apple.streamingkeydelivery" #EXTINF:1.001, -#EXT-X-BYTERANGE:99313@1143 +#EXT-X-BYTERANGE:99313@1769 bear-640x360-video.mp4 #EXTINF:1.001, #EXT-X-BYTERANGE:122340 diff --git a/packager/media/base/key_source.cc b/packager/media/base/key_source.cc index 474aa32036..8972394c63 100644 --- a/packager/media/base/key_source.cc +++ b/packager/media/base/key_source.cc @@ -33,6 +33,13 @@ KeySource::KeySource(int protection_systems_flags, FourCC protection_scheme) { no_pssh_systems_.emplace_back(std::begin(kFairPlaySystemId), std::end(kFairPlaySystemId)); } + // We only support Marlin Adaptive Streaming Specification – Simple Profile + // with Implicit Content ID Mapping, which does not need a PSSH. Marlin + // specific PSSH with Explicit Content ID Mapping is not generated. + if (protection_systems_flags & MARLIN_PROTECTION_SYSTEM_FLAG) { + no_pssh_systems_.emplace_back(std::begin(kMarlinSystemId), + std::end(kMarlinSystemId)); + } } KeySource::~KeySource() = default; diff --git a/packager/media/base/protection_system_ids.h b/packager/media/base/protection_system_ids.h index ba4b8689f3..7948e7eceb 100644 --- a/packager/media/base/protection_system_ids.h +++ b/packager/media/base/protection_system_ids.h @@ -10,6 +10,8 @@ namespace shaka { namespace media { +// System Ids are defined in https://dashif.org/identifiers/content_protection/. + // Common SystemID defined by EME, which requires Key System implementations // supporting ISO Common Encryption to support this SystemID and format. // https://goo.gl/kUv2Xd @@ -23,6 +25,11 @@ const uint8_t kFairPlaySystemId[] = {0x29, 0x70, 0x1F, 0xE4, 0x3C, 0xC7, 0x4A, 0x34, 0x8C, 0x5B, 0xAE, 0x90, 0xC7, 0x43, 0x9A, 0x47}; +// Marlin Adaptive Streaming Specification – Simple Profile, V1.0. +const uint8_t kMarlinSystemId[] = {0x5E, 0x62, 0x9A, 0xF5, 0x38, 0xDA, + 0x40, 0x63, 0x89, 0x77, 0x97, 0xFF, + 0xBD, 0x99, 0x02, 0xD4}; + const uint8_t kPlayReadySystemId[] = {0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86, 0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95}; diff --git a/packager/media/base/protection_system_specific_info.h b/packager/media/base/protection_system_specific_info.h index f333e71949..6df39458c2 100644 --- a/packager/media/base/protection_system_specific_info.h +++ b/packager/media/base/protection_system_specific_info.h @@ -18,6 +18,7 @@ #define PLAYREADY_PROTECTION_SYSTEM_FLAG 0x02 #define WIDEVINE_PROTECTION_SYSTEM_FLAG 0x04 #define FAIRPLAY_PROTECTION_SYSTEM_FLAG 0x08 +#define MARLIN_PROTECTION_SYSTEM_FLAG 0x10 namespace shaka { namespace media { diff --git a/packager/media/public/crypto_params.h b/packager/media/public/crypto_params.h index 91e0d9b1da..6e3b31b0e8 100644 --- a/packager/media/public/crypto_params.h +++ b/packager/media/public/crypto_params.h @@ -119,6 +119,7 @@ struct EncryptionParams { enum class ProtectionSystem { kCommonSystem, kFairPlay, + kMarlin, kPlayReady, kWidevine, }; diff --git a/packager/mpd/base/mpd_builder.cc b/packager/mpd/base/mpd_builder.cc index c2ec7f69d6..926533d55a 100644 --- a/packager/mpd/base/mpd_builder.cc +++ b/packager/mpd/base/mpd_builder.cc @@ -46,10 +46,13 @@ void AddMpdNameSpaceInfo(XmlNode* mpd) { mpd->SetStringAttribute("xsi:schemaLocation", kDashSchemaMpd2011); static const char kCencNamespace[] = "urn:mpeg:cenc:2013"; + static const char kMarlinNamespace[] = + "urn:marlin:mas:1-0:services:schemas:mpd"; static const char kXmlNamespaceXlink[] = "http://www.w3.org/1999/xlink"; const std::map uris = { {"cenc", kCencNamespace}, + {"mas", kMarlinNamespace}, {"xlink", kXmlNamespaceXlink}, }; diff --git a/packager/mpd/base/mpd_utils.cc b/packager/mpd/base/mpd_utils.cc index ed0d308959..f00d85cc83 100644 --- a/packager/mpd/base/mpd_utils.cc +++ b/packager/mpd/base/mpd_utils.cc @@ -302,6 +302,42 @@ void UpdateContentProtectionPsshHelper( } namespace { + +// UUID for Marlin Adaptive Streaming Specification – Simple Profile from +// https://dashif.org/identifiers/content_protection/. +const char kMarlinUUID[] = "5e629af5-38da-4063-8977-97ffbd9902d4"; +// Unofficial FairPlay system id extracted from +// https://forums.developer.apple.com/thread/6185. +const char kFairPlayUUID[] = "29701fe4-3cc7-4a34-8c5b-ae90c7439a47"; + +Element GenerateMarlinContentIds(const std::string& key_id) { + // See https://github.com/google/shaka-packager/issues/381 for details. + static const char kMarlinContentIdName[] = "mas:MarlinContentId"; + static const char kMarlinContentIdPrefix[] = "urn:marlin:kid:"; + static const char kMarlinContentIdsName[] = "mas:MarlinContentIds"; + + Element marlin_content_id; + marlin_content_id.name = kMarlinContentIdName; + marlin_content_id.content = + kMarlinContentIdPrefix + base::HexEncode(key_id.data(), key_id.size()); + + Element marlin_content_ids; + marlin_content_ids.name = kMarlinContentIdsName; + marlin_content_ids.subelements.push_back(marlin_content_id); + + return marlin_content_ids; +} + +Element GenerateCencPsshElement(const std::string& pssh) { + std::string base64_encoded_pssh; + base::Base64Encode(base::StringPiece(pssh.data(), pssh.size()), + &base64_encoded_pssh); + Element cenc_pssh; + cenc_pssh.name = kPsshElementName; + cenc_pssh.content = base64_encoded_pssh; + return cenc_pssh; +} + // Helper function. This works because Representation and AdaptationSet both // have AddContentProtectionElement(). template @@ -349,18 +385,20 @@ void AddContentProtectionElementsHelperTemplated( ContentProtectionElement drm_content_protection; drm_content_protection.scheme_id_uri = "urn:uuid:" + entry.uuid(); + if (entry.has_name_version()) drm_content_protection.value = entry.name_version(); - if (!entry.pssh().empty()) { - std::string base64_encoded_pssh; - base::Base64Encode( - base::StringPiece(entry.pssh().data(), entry.pssh().size()), - &base64_encoded_pssh); - Element cenc_pssh; - cenc_pssh.name = kPsshElementName; - cenc_pssh.content = base64_encoded_pssh; - drm_content_protection.subelements.push_back(cenc_pssh); + if (entry.uuid() == kFairPlayUUID) { + VLOG(1) << "Skipping FairPlay ContentProtection element as FairPlay does " + "not support DASH signaling."; + continue; + } else if (entry.uuid() == kMarlinUUID) { + drm_content_protection.subelements.push_back( + GenerateMarlinContentIds(protected_content.default_key_id())); + } else if (!entry.pssh().empty()) { + drm_content_protection.subelements.push_back( + GenerateCencPsshElement(entry.pssh())); } if (!key_id_uuid_format.empty() && !is_mp4_container) { diff --git a/packager/mpd/base/xml/xml_node.cc b/packager/mpd/base/xml/xml_node.cc index 39dfb50e2d..51df13fcc9 100644 --- a/packager/mpd/base/xml/xml_node.cc +++ b/packager/mpd/base/xml/xml_node.cc @@ -141,12 +141,15 @@ bool XmlNode::AddElements(const std::vector& elements) { child_node.SetStringAttribute(attribute_it->first.c_str(), attribute_it->second); } + + // Note that somehow |SetContent| needs to be called before |AddElements| + // otherwise the added children will be overwritten by the content. + child_node.SetContent(child_element.content); + // Recursively set children for the child. if (!child_node.AddElements(child_element.subelements)) return false; - child_node.SetContent(child_element.content); - if (!xmlAddChild(node_.get(), child_node.GetRawPtr())) { LOG(ERROR) << "Failed to set child " << child_element.name << " to parent element "