/* Copyright (c) MediaArea.net SARL. All Rights Reserved. * * Use of this source code is governed by a BSD-style license that can * be found in the License.html file in the root of the source tree. */ // Period // AdaptationSet --> One per stream // SegmentTemplate (optional) // SegmentTimeline // S --> duration per segment, count of segments // Representation --> file name from SegmentTemplate or BaseURL // SegmentBase // SegmentList // Representation --> file name from BaseURL //--------------------------------------------------------------------------- // Pre-compilation #include "MediaInfo/PreComp.h" #ifdef __BORLANDC__ #pragma hdrstop #endif //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- #include "MediaInfo/Setup.h" //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- #if defined(MEDIAINFO_DASHMPD_YES) //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- #include "MediaInfo/Multiple/File_DashMpd.h" #include "MediaInfo/Multiple/File__ReferenceFilesHelper.h" #include "MediaInfo/MediaInfo_Config_MediaInfo.h" #include "ZenLib/FileName.h" #if defined(MEDIAINFO_REFERENCES_YES) #include "ZenLib/File.h" #endif //defined(MEDIAINFO_REFERENCES_YES) #include "tinyxml2.h" using namespace ZenLib; using namespace tinyxml2; //--------------------------------------------------------------------------- namespace MediaInfoLib { #if defined(MEDIAINFO_REFERENCES_YES) //--------------------------------------------------------------------------- static void DashMpd_Transform (Ztring &Value, std::map &Attributes) { size_t Pos1=0; for (;;) { Pos1=Value.find(__T('$'), Pos1); if (Pos1==string::npos) break; size_t Pos2=Value.find(__T('$'), Pos1+1); if (Pos2==string::npos) break; Ztring Name=Value.substr(Pos1+1, Pos2-Pos1-1); if (Name.empty()) Value.erase(Pos1, 1); else { if (Name==__T("RepresentationID")) Name=__T("id"); if (Name==__T("Bandwidth")) Name=__T("bandwidth"); std::map::iterator Attribute_It=Attributes.find(Name); if (Attribute_It!=Attributes.end()) { Value.erase(Pos1, Pos2-Pos1+1); Value.insert(Pos1, Attribute_It->second); } else Pos1+=2+Name.size(); } } } //--------------------------------------------------------------------------- static stream_t DashMpd_mimeType_StreamKind (const char* mimeType) { Ztring StreamKind; StreamKind.From_UTF8(mimeType); if (StreamKind.find(__T("video"))==0) return Stream_Video; else if (StreamKind.find(__T("audio"))==0) return Stream_Audio; else if (StreamKind.find(__T("application/ttml+xml"))==0) return Stream_Text; else return Stream_Other; } //--------------------------------------------------------------------------- static Ztring DashMpd_codecid_CodecID (const char* codecid) { Ztring CodecID; Ztring Codecs; Codecs.From_UTF8(codecid); size_t DotPos=Codecs.find(__T('.')); if (DotPos==4 && Codecs.substr(0, DotPos).find(__T("mp4"))==0) DotPos=Codecs.find(__T('.'), 5); if (DotPos==string::npos) { CodecID=Codecs; } else { CodecID=Codecs.substr(0, DotPos); //TODO per format, rfc 6381 //Sequence->Infos["Format_Profile"]=; } CodecID.FindAndReplace(__T("0x"), Ztring(), 0, Ztring_Recursive); return CodecID; } //--------------------------------------------------------------------------- struct template_generic { sequence* Sequence; Ztring SourceDir; Ztring BaseURL; Ztring initialization; Ztring media; int64u duration; int64u startNumber; int64u duration_Max; int64u startNumber_Max; struct segmenttimeline { int64u t; //start time int64u d; //duration per segment int64u r; //repeat count segmenttimeline() { t=1; d=1; r=0; } }; std::vector SegmentTimeLines; std::map Attributes_ForMedia; template_generic(const Ztring &BaseURL=Ztring(), const Ztring &SourceDir=Ztring()) { Sequence=new sequence; template_generic::BaseURL=BaseURL; template_generic::SourceDir=SourceDir; duration=1; startNumber=1; duration_Max=0; startNumber_Max=0; } template_generic(const template_generic &ToCopy) { if (this == &ToCopy) return; Sequence=new sequence; *Sequence=*ToCopy.Sequence; template_generic::BaseURL=ToCopy.BaseURL; template_generic::SourceDir=ToCopy.SourceDir; initialization=ToCopy.initialization; media=ToCopy.media; duration=ToCopy.duration; startNumber=ToCopy.startNumber; duration_Max=ToCopy.duration_Max; startNumber_Max=ToCopy.startNumber_Max; } void AdaptationSet_Attributes_Parse (XMLElement* Item); void SegmentTemplate_Attributes_Parse (XMLElement* Item); void SegmentTimeline_Attributes_Parse (XMLElement* Item); void Representation_Attributes_Parse (XMLElement* Item); void Decode (); private: template_generic &operator =(const template_generic &); }; void template_generic::AdaptationSet_Attributes_Parse (XMLElement* Item) { //Attributes - mineType const char* Attribute=Item->Attribute("mimeType"); if (Attribute) Sequence->StreamKind=DashMpd_mimeType_StreamKind(Attribute); //Attributes - codecs Attribute=Item->Attribute("codecs"); if (Attribute) Sequence->Infos["CodecID"]=DashMpd_codecid_CodecID(Attribute); //Attributes - lang Attribute=Item->Attribute("lang"); if (Attribute) Sequence->Infos["Language"].From_UTF8(Attribute); } void template_generic::SegmentTemplate_Attributes_Parse (XMLElement* Item) { //Attributes - initialization const char* Attribute=Item->Attribute("initialization"); if (Attribute) { initialization.From_UTF8(Attribute); } //Attributes - media Attribute=Item->Attribute("media"); if (Attribute) { media.From_UTF8(Attribute); } //Attributes - duration Attribute=Item->Attribute("duration"); if (Attribute) { duration=Ztring().From_UTF8(Attribute).To_int64u(); } //Attributes - startNumber Attribute=Item->Attribute("startNumber"); if (Attribute) { startNumber=Ztring().From_UTF8(Attribute).To_int64u(); } } void template_generic::SegmentTimeline_Attributes_Parse (XMLElement* Item) { segmenttimeline SegmentTimeLine; //Attributes - t (start time) const char* Attribute=Item->Attribute("t"); if (Attribute) { SegmentTimeLine.t=Ztring().From_UTF8(Attribute).To_int64u(); } else SegmentTimeLine.t=startNumber; //Attributes - d (duration per segment) Attribute=Item->Attribute("d"); if (Attribute) { SegmentTimeLine.d=Ztring().From_UTF8(Attribute).To_int64u(); } else SegmentTimeLine.d=duration; //Attributes - r (repeat count) Attribute=Item->Attribute("r"); if (Attribute) { SegmentTimeLine.r=Ztring().From_UTF8(Attribute).To_int64u(); } SegmentTimeLines.push_back(SegmentTimeLine); duration_Max+=SegmentTimeLine.d*(SegmentTimeLine.r+1); startNumber_Max+=SegmentTimeLine.r+1; } void template_generic::Representation_Attributes_Parse (XMLElement* Item) { //Attributes - id const char* Attribute=Item->Attribute("id"); if (Attribute) { Sequence->StreamID=Ztring().From_UTF8(Attribute).To_int64u(16); } //Attributes - bandwidth Attribute=Item->Attribute("bandwidth"); if (Attribute) { Sequence->Infos["BitRate"].From_UTF8(Attribute); } //Attributes - frame size Attribute=Item->Attribute("width"); if (Attribute) { Sequence->Infos["Width"].From_UTF8(Attribute); } Attribute=Item->Attribute("height"); if (Attribute) { Sequence->Infos["Height"].From_UTF8(Attribute); } //Attributes - mineType Attribute=Item->Attribute("mimeType"); if (Attribute) Sequence->StreamKind=DashMpd_mimeType_StreamKind(Attribute); //Attributes - codecs Attribute=Item->Attribute("codecs"); if (Attribute) Sequence->Infos["CodecID"]=DashMpd_codecid_CodecID(Attribute); //Attributes - lang Attribute=Item->Attribute("lang"); if (Attribute) Sequence->Infos["Language"].From_UTF8(Attribute); //Attributes - Saving all attributes for (const XMLAttribute* Attribute_Item=Item->FirstAttribute(); Attribute_Item; Attribute_Item=Attribute_Item->Next()) { Ztring Name; Name.From_UTF8(Attribute_Item->Name()); Ztring Value; Value.From_UTF8(Attribute_Item->Value()); Attributes_ForMedia[Name]=Value; } } //--------------------------------------------------------------------------- void template_generic::Decode() { //initialization - URL decoding, template adaptation and add it if (!initialization.empty()) { DashMpd_Transform(initialization, Attributes_ForMedia); Sequence->AddFileName(BaseURL+initialization); } //media - URL decoding, template adaptation and add it if (!media.empty()) { DashMpd_Transform(media, Attributes_ForMedia); size_t Index_Pos=media.find(__T("$Index")); size_t Index_StringSize=5; if (Index_Pos==string::npos) { Index_Pos=media.find(__T("$Number")); Index_StringSize++; } int8u Index_Size=1; if (Index_Pos!=string::npos) { size_t Index_Pos_End=media.find(__T('$'), Index_Pos+1+Index_StringSize); if (Index_Pos_End!=string::npos && Index_Pos+1+Index_StringSize+2Index_Pos) Time_Pos-=1+Index_StringSize+1; } if (Time_Pos!=string::npos) { Media_Name.erase(Time_Pos, 6); if (Index_Pos!=string::npos && Index_Pos>Time_Pos) Index_Pos-=6; } if (SegmentTimeLines.empty()) { int64u Index_Pos_Temp=startNumber; for (;;) { Ztring Media_Name_Temp(Media_Name); Ztring Index; Index.From_Number(Index_Pos_Temp); if (Index.size()AddFileName(File_Name); Index_Pos_Temp++; } } else { int64u SegmentTimeLines_duration=0; int64u SegmentTimeLines_startNumber=startNumber; for (size_t SegmentTimeLines_Pos=0; SegmentTimeLines_PosIndex_Pos) Time_Pos_Temp+=Index.size(); } if (Time_Pos_Temp!=string::npos) { Ztring Time; Time.From_Number(SegmentTimeLines_duration); Media_Name_Temp.insert(Time_Pos_Temp, Time); } Sequence->AddFileName(BaseURL+Media_Name_Temp); SegmentTimeLines_duration+=SegmentTimeLines[SegmentTimeLines_Pos].d; SegmentTimeLines_startNumber++; } } } } else Sequence->AddFileName(BaseURL+media); } } #endif //MEDIAINFO_REFERENCES_YES //*************************************************************************** // Constructor/Destructor //*************************************************************************** //--------------------------------------------------------------------------- File_DashMpd::File_DashMpd() :File__Analyze() { #if MEDIAINFO_EVENTS ParserIDs[0]=MediaInfo_Parser_DashMpd; StreamIDs_Width[0]=16; #endif //MEDIAINFO_EVENTS } //*************************************************************************** // Buffer - File header //*************************************************************************** //--------------------------------------------------------------------------- bool File_DashMpd::FileHeader_Begin() { XMLDocument document; if (!FileHeader_Begin_XML(document)) return false; { XMLElement* Root=document.FirstChildElement("MPD"); if (Root) { const char* Attribute=Root->Attribute("xmlns"); if (Attribute==NULL || (Ztring().From_UTF8(Attribute)!=__T("urn:mpeg:DASH:schema:MPD:2011") && Ztring().From_UTF8(Attribute)!=__T("urn:mpeg:dash:schema:mpd:2011") //Some muxers use lower case version && Ztring().From_UTF8(Attribute)!=__T("urn:3GPP:ns:PSS:AdaptiveHTTPStreamingMPD:2009"))) { Reject("DashMpd"); return false; } Accept("DashMpd"); Fill(Stream_General, 0, General_Format, "DASH MPD"); Config->File_ID_OnlyRoot_Set(false); ReferenceFiles_Accept(this, Config); #if defined(MEDIAINFO_REFERENCES_YES) //Parsing main elements Ztring BaseURL; for (XMLElement* Root_Item=Root->FirstChildElement(); Root_Item; Root_Item=Root_Item->NextSiblingElement()) { //Common information if (string(Root_Item->Value())=="BaseURL") { if (BaseURL.empty()) //Using the first one BaseURL=Root_Item->GetText(); } //Period if (string(Root_Item->Value())=="Period") { for (XMLElement* Period_Item=Root_Item->FirstChildElement(); Period_Item; Period_Item=Period_Item->NextSiblingElement()) { //AdaptationSet (=a stream) if (string(Period_Item->Value())=="AdaptationSet") { template_generic Template_Generic(BaseURL, FileName(File_Name).Path_Get()); Template_Generic.AdaptationSet_Attributes_Parse(Period_Item); //Sub for (XMLElement* AdaptationSet_Item=Period_Item->FirstChildElement(); AdaptationSet_Item; AdaptationSet_Item=AdaptationSet_Item->NextSiblingElement()) { //SegmentTemplate if (string(AdaptationSet_Item->Value())=="SegmentTemplate") { Template_Generic.SegmentTemplate_Attributes_Parse(AdaptationSet_Item); //Sub for (XMLElement* SegmentTemplate_Item=AdaptationSet_Item->FirstChildElement(); SegmentTemplate_Item; SegmentTemplate_Item=SegmentTemplate_Item->NextSiblingElement()) { //SegmentTimeline if (string(SegmentTemplate_Item->Value())=="SegmentTimeline") { //Sub for (XMLElement* SegmentTimeline_Item=SegmentTemplate_Item->FirstChildElement(); SegmentTimeline_Item; SegmentTimeline_Item=SegmentTimeline_Item->NextSiblingElement()) { //SegmentTimeline if (string(SegmentTimeline_Item->Value())=="S") { Template_Generic.SegmentTimeline_Attributes_Parse(SegmentTimeline_Item); } } } } } //Representation if (string(AdaptationSet_Item->Value())=="Representation") { template_generic Template_Generic_PerRepresentation(Template_Generic); Template_Generic_PerRepresentation.Representation_Attributes_Parse(AdaptationSet_Item); //Sub for (XMLElement* Representation_Item=AdaptationSet_Item->FirstChildElement(); Representation_Item; Representation_Item=Representation_Item->NextSiblingElement()) { //BaseURL if (string(Representation_Item->Value())=="BaseURL") { Template_Generic_PerRepresentation.Sequence->AddFileName(BaseURL+Ztring().From_UTF8(Representation_Item->GetText())); } //SegmentTemplate if (string(Representation_Item->Value())=="SegmentTemplate") { Template_Generic_PerRepresentation.SegmentTemplate_Attributes_Parse(Representation_Item); } //SegmentBase if (string(Representation_Item->Value())=="SegmentBase") { //Sub for (XMLElement* SegmentBase_Item=Representation_Item->FirstChildElement(); SegmentBase_Item; SegmentBase_Item=SegmentBase_Item->NextSiblingElement()) { //Initialization if (string(SegmentBase_Item->Value())=="Initialization") { Attribute=SegmentBase_Item->Attribute("sourceURL"); if (Attribute) Template_Generic_PerRepresentation.Sequence->AddFileName(BaseURL+Ztring().From_UTF8(Attribute), 0); } } } //SegmentList if (string(Representation_Item->Value())=="SegmentList") { //Sub for (XMLElement* SegmentBase_Item=Representation_Item->FirstChildElement(); SegmentBase_Item; SegmentBase_Item=SegmentBase_Item->NextSiblingElement()) { //Initialization if (string(SegmentBase_Item->Value())=="Initialization") { Attribute=SegmentBase_Item->Attribute("sourceURL"); if (Attribute) Template_Generic_PerRepresentation.Sequence->AddFileName(BaseURL+Ztring().From_UTF8(Attribute), 0); } //SegmentURL if (string(SegmentBase_Item->Value())=="SegmentURL") { bool IsSupported=true; Attribute=SegmentBase_Item->Attribute("mediaRange"); if (Attribute) { size_t Length=strlen(Attribute); if (Length<2 || Attribute[0]!='0' || Attribute[1]!='-') IsSupported=false; //Currently, we do not support ranges } Attribute=SegmentBase_Item->Attribute("media"); if (Attribute && IsSupported) Template_Generic_PerRepresentation.Sequence->AddFileName(BaseURL+Ztring().From_UTF8(Attribute)); } } } } Template_Generic_PerRepresentation.Decode(); ReferenceFiles->AddSequence(Template_Generic_PerRepresentation.Sequence); } } } //Representation (=a stream) if (string(Period_Item->Value())=="Representation") { sequence* Sequence=new sequence; //Attributes - mineType Attribute=Period_Item->Attribute("mimeType"); if (Attribute) Sequence->StreamKind=DashMpd_mimeType_StreamKind(Attribute); //Attributes - codecs Attribute=Period_Item->Attribute("codecs"); if (Attribute) Sequence->Infos["CodecID"]=DashMpd_codecid_CodecID(Attribute); //Attributes - lang Attribute=Period_Item->Attribute("lang"); if (Attribute) Sequence->Infos["Language"].From_UTF8(Attribute); //Sub for (XMLElement* AdaptationSet_Item=Period_Item->FirstChildElement(); AdaptationSet_Item; AdaptationSet_Item=AdaptationSet_Item->NextSiblingElement()) { //SegmentInfo if (string(AdaptationSet_Item->Value())=="SegmentInfo") { //Sub for (XMLElement* SegmentInfo_Item=AdaptationSet_Item->FirstChildElement(); SegmentInfo_Item; SegmentInfo_Item=SegmentInfo_Item->NextSiblingElement()) { //InitialisationSegmentURL if (string(SegmentInfo_Item->Value())=="InitialisationSegmentURL") { Attribute=SegmentInfo_Item->Attribute("sourceURL"); if (Attribute) Sequence->AddFileName(BaseURL+Ztring().From_UTF8(Attribute), 0); } //Url if (string(SegmentInfo_Item->Value())=="Url") { Attribute=SegmentInfo_Item->Attribute("sourceURL"); if (Attribute) Sequence->AddFileName(BaseURL+Ztring().From_UTF8(Attribute)); } } ReferenceFiles->AddSequence(Sequence); } } } } } } #endif //MEDIAINFO_REFERENCES_YES } else { Reject("DashMpd"); return false; } } Element_Offset=File_Size; //All should be OK... return true; } } //NameSpace #endif //MEDIAINFO_DASHMPD_YES