/* 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<Ztring, Ztring> &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<Ztring, Ztring>::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<segmenttimeline> SegmentTimeLines;
std::map<Ztring, Ztring> 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+2<Index_Pos_End && media[Index_Pos+1+Index_StringSize]=='%' && media[Index_Pos+1+Index_StringSize+1]=='0')
{
Index_Size=Ztring(media.substr(Index_Pos+1+Index_StringSize+2, Index_Pos_End-(Index_Pos+1+Index_StringSize+2))).To_int8u();
}
else if (Index_Pos_End==string::npos || Index_Pos+1+Index_StringSize!=Index_Pos_End)
Index_Pos=string::npos;
}
size_t Time_Pos=media.find(__T("$Time$"));
if (Index_Pos!=string::npos || Time_Pos!=string::npos)
{
Ztring Media_Name(media);
if (Index_Pos!=string::npos)
{
Media_Name.erase(Index_Pos, 1+Index_StringSize+1);
if (Time_Pos!=string::npos && Time_Pos>Index_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()<Index_Size)
Index.insert(0, Index_Size-Index.size(), __T('0'));
if (Index_Pos!=(size_t)-1)
Media_Name_Temp.insert(Index_Pos, Index);
else
Media_Name_Temp.insert(Time_Pos, Index);
Ztring File_Name;
Ztring File_Name_With_Path;
if (!SourceDir.empty())
File_Name_With_Path+=SourceDir+PathSeparator;
File_Name+=BaseURL+Media_Name_Temp;
File_Name_With_Path+=BaseURL+Media_Name_Temp;
if (!File::Exists(File_Name_With_Path))
break;
Sequence->AddFileName(File_Name);
Index_Pos_Temp++;
}
}
else
{
int64u SegmentTimeLines_duration=0;
int64u SegmentTimeLines_startNumber=startNumber;
for (size_t SegmentTimeLines_Pos=0; SegmentTimeLines_Pos<SegmentTimeLines.size(); SegmentTimeLines_Pos++)
{
for (int64u Pos=0; Pos<=SegmentTimeLines[SegmentTimeLines_Pos].r; Pos++)
{
Ztring Media_Name_Temp(Media_Name);
size_t Time_Pos_Temp=Time_Pos;
if (Index_Pos!=string::npos)
{
Ztring Index; Index.From_Number(SegmentTimeLines_startNumber);
if (Index.size()<Index_Size)
Index.insert(0, Index_Size-Index.size(), __T('0'));
Media_Name_Temp.insert(Index_Pos, Index);
if (Time_Pos!=string::npos && Time_Pos>Index_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
↑ V1002 The 'sequence' class, containing pointers, constructor and destructor, is copied by the automatically generated operator=.
↑ V688 The 'BaseURL' function argument possesses the same name as one of the class members, which can result in a confusion.
↑ V688 The 'SourceDir' function argument possesses the same name as one of the class members, which can result in a confusion.
↑ V818 It is more efficient to use an initialization list 'BaseURL(BaseURL), SourceDir(SourceDir)' rather than an assignment operator.