Skip to content

Commit

Permalink
Support Marlin DRM signaling in DASH
Browse files Browse the repository at this point in the history
Only Marlin Adaptive Streaming Specification – Simple Profile is
supported.

Two additional updates:
- Remove FairPlay ContentProtection element from DASH mpd as FairPlay
  does not define a signaling in DASH.
- Updated end to end test to include all DRMs we support.

Closes #381.

Change-Id: Id12269b471ea34983b782cbd92f687332292ef59
  • Loading branch information
kqyang committed Oct 1, 2018
1 parent fc0c5dd commit 8d11e5e
Show file tree
Hide file tree
Showing 20 changed files with 149 additions and 57 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion docs/source/options/general_encryption_options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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).
1 change: 1 addition & 0 deletions packager/app/packager_main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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},
};
Expand Down
5 changes: 3 additions & 2 deletions packager/app/packager_util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion packager/app/protection_system_flags.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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).");
27 changes: 16 additions & 11 deletions packager/app/test/packager_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ def _GetStreams(self, streams, test_files=None, **kwargs):
def _GetFlags(self,
strip_parameter_set_nalus=True,
encryption=False,
fairplay=False,
protection_systems=None,
protection_scheme=None,
vp9_subsample_encryption=True,
decryption=False,
Expand Down Expand Up @@ -443,12 +443,13 @@ def _GetFlags(self,
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:
Expand Down Expand Up @@ -862,12 +863,15 @@ def testEncryption(self):
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.
Expand Down Expand Up @@ -1096,7 +1100,8 @@ def testAvcTsWithEncryptionAndFairPlay(self):
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):
Expand Down
27 changes: 0 additions & 27 deletions packager/app/test/testdata/encryption-with-fairplay/output.mpd

This file was deleted.

Binary file not shown.
Binary file not shown.
47 changes: 47 additions & 0 deletions packager/app/test/testdata/encryption-with-multi-drms/output.mpd
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>-->
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" xmlns:mas="urn:marlin:mas:1-0:services:schemas:mpd" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" minBufferTime="PT2S" type="static" mediaPresentationDuration="PT2.7360665798187256S">
<Period id="0">
<AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" subsegmentAlignment="true" par="16:9">
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="31323334-3536-3738-3930-313233343536"/>
<ContentProtection schemeIdUri="urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95">
<cenc:pssh>AAACOnBzc2gBAAAAmgTweZhAQoarkuZb4IhflQAAAAExMjM0NTY3ODkwMTIzNDU2AAACBgYCAAABAAEA/AE8AFcAUgBNAEgARQBBAEQARQBSACAAeABtAGwAbgBzAD0AIgBoAHQAdABwADoALwAvAHMAYwBoAGUAbQBhAHMALgBtAGkAYwByAG8AcwBvAGYAdAAuAGMAbwBtAC8ARABSAE0ALwAyADAAMAA3AC8AMAAzAC8AUABsAGEAeQBSAGUAYQBkAHkASABlAGEAZABlAHIAIgAgAHYAZQByAHMAaQBvAG4APQAiADQALgAwAC4AMAAuADAAIgA+ADwARABBAFQAQQA+ADwAUABSAE8AVABFAEMAVABJAE4ARgBPAD4APABLAEUAWQBMAEUATgA+ADEANgA8AC8ASwBFAFkATABFAE4APgA8AEEATABHAEkARAA+AEEARQBTAEMAVABSADwALwBBAEwARwBJAEQAPgA8AC8AUABSAE8AVABFAEMAVABJAE4ARgBPAD4APABLAEkARAA+AE4ARABNAHkATQBUAFkAMQBPAEQAYwA1AE0ARABFAHkATQB6AFEAMQBOAGcAPQA9ADwALwBLAEkARAA+ADwAQwBIAEUAQwBLAFMAVQBNAD4AbAA1AEwAbwBVAGcASwA5AEsAQwBnAD0APAAvAEMASABFAEMASwBTAFUATQA+ADwALwBEAEEAVABBAD4APAAvAFcAUgBNAEgARQBBAEQARQBSAD4A</cenc:pssh>
</ContentProtection>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>AAAAOHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABgSEDEyMzQ1Njc4OTAxMjM0NTZI49yVmwY=</cenc:pssh>
</ContentProtection>
<ContentProtection schemeIdUri="urn:uuid:5e629af5-38da-4063-8977-97ffbd9902d4">
<mas:MarlinContentIds>
<mas:MarlinContentId>urn:marlin:kid:31323334353637383930313233343536</mas:MarlinContentId>
</mas:MarlinContentIds>
</ContentProtection>
<Representation id="0" bandwidth="977743" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1">
<BaseURL>bear-640x360-video.mp4</BaseURL>
<SegmentBase indexRange="1701-1768" timescale="30000">
<Initialization range="0-1700"/>
</SegmentBase>
</Representation>
</AdaptationSet>
<AdaptationSet id="1" contentType="audio" subsegmentAlignment="true">
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="31323334-3536-3738-3930-313233343536"/>
<ContentProtection schemeIdUri="urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95">
<cenc:pssh>AAACOnBzc2gBAAAAmgTweZhAQoarkuZb4IhflQAAAAExMjM0NTY3ODkwMTIzNDU2AAACBgYCAAABAAEA/AE8AFcAUgBNAEgARQBBAEQARQBSACAAeABtAGwAbgBzAD0AIgBoAHQAdABwADoALwAvAHMAYwBoAGUAbQBhAHMALgBtAGkAYwByAG8AcwBvAGYAdAAuAGMAbwBtAC8ARABSAE0ALwAyADAAMAA3AC8AMAAzAC8AUABsAGEAeQBSAGUAYQBkAHkASABlAGEAZABlAHIAIgAgAHYAZQByAHMAaQBvAG4APQAiADQALgAwAC4AMAAuADAAIgA+ADwARABBAFQAQQA+ADwAUABSAE8AVABFAEMAVABJAE4ARgBPAD4APABLAEUAWQBMAEUATgA+ADEANgA8AC8ASwBFAFkATABFAE4APgA8AEEATABHAEkARAA+AEEARQBTAEMAVABSADwALwBBAEwARwBJAEQAPgA8AC8AUABSAE8AVABFAEMAVABJAE4ARgBPAD4APABLAEkARAA+AE4ARABNAHkATQBUAFkAMQBPAEQAYwA1AE0ARABFAHkATQB6AFEAMQBOAGcAPQA9ADwALwBLAEkARAA+ADwAQwBIAEUAQwBLAFMAVQBNAD4AbAA1AEwAbwBVAGcASwA5AEsAQwBnAD0APAAvAEMASABFAEMASwBTAFUATQA+ADwALwBEAEEAVABBAD4APAAvAFcAUgBNAEgARQBBAEQARQBSAD4A</cenc:pssh>
</ContentProtection>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>AAAAOHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABgSEDEyMzQ1Njc4OTAxMjM0NTZI49yVmwY=</cenc:pssh>
</ContentProtection>
<ContentProtection schemeIdUri="urn:uuid:5e629af5-38da-4063-8977-97ffbd9902d4">
<mas:MarlinContentIds>
<mas:MarlinContentId>urn:marlin:kid:31323334353637383930313233343536</mas:MarlinContentId>
</mas:MarlinContentIds>
</ContentProtection>
<Representation id="1" bandwidth="133663" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<BaseURL>bear-640x360-audio.mp4</BaseURL>
<SegmentBase indexRange="1577-1644" timescale="44100">
<Initialization range="0-1576"/>
</SegmentBase>
</Representation>
</AdaptationSet>
</Period>
</MPD>
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
#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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
#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
Expand Down
7 changes: 7 additions & 0 deletions packager/media/base/key_source.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 7 additions & 0 deletions packager/media/base/protection_system_ids.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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};
Expand Down
1 change: 1 addition & 0 deletions packager/media/base/protection_system_specific_info.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions packager/media/public/crypto_params.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ struct EncryptionParams {
enum class ProtectionSystem {
kCommonSystem,
kFairPlay,
kMarlin,
kPlayReady,
kWidevine,
};
Expand Down
3 changes: 3 additions & 0 deletions packager/mpd/base/mpd_builder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string, std::string> uris = {
{"cenc", kCencNamespace},
{"mas", kMarlinNamespace},
{"xlink", kXmlNamespaceXlink},
};

Expand Down
56 changes: 47 additions & 9 deletions packager/mpd/base/mpd_utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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 <typename ContentProtectionParent>
Expand Down Expand Up @@ -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) {
Expand Down
7 changes: 5 additions & 2 deletions packager/mpd/base/xml/xml_node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -141,12 +141,15 @@ bool XmlNode::AddElements(const std::vector<Element>& 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 "
Expand Down

0 comments on commit 8d11e5e

Please sign in to comment.