3 *****************************************************************************
4 * Copyright (C) 2010 - 2011 Klagenfurt University
6 * Created on: Aug 10, 2010
7 * Authors: Christopher Mueller <christopher.mueller@itec.uni-klu.ac.at>
8 * Christian Timmerer <christian.timmerer@itec.uni-klu.ac.at>
10 * This program is free software; you can redistribute it and/or modify it
11 * under the terms of the GNU Lesser General Public License as published
12 * by the Free Software Foundation; either version 2.1 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public License
21 * along with this program; if not, write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23 *****************************************************************************/
28 #include "BasicCMParser.h"
29 #include "mpd/ContentDescription.h"
30 #include "mpd/SegmentInfoDefault.h"
31 #include "mpd/SegmentTemplate.h"
32 #include "mpd/SegmentTimeline.h"
38 #include <vlc_common.h>
39 #include <vlc_stream.h>
40 #include <vlc_strings.h>
42 using namespace dash::mpd;
43 using namespace dash::xml;
45 BasicCMParser::BasicCMParser( Node *root, stream_t *p_stream ) :
49 currentRepresentation( NULL )
51 this->url = p_stream->psz_access;
53 //Only append without the mpd file.
54 std::string path = p_stream->psz_path;
55 size_t it = path.find_last_of( '/', path.length() - 1 );
56 if ( it != std::string::npos )
57 this->url.append( path, 0, it );
59 this->url += p_stream->psz_path;
63 BasicCMParser::~BasicCMParser ()
67 bool BasicCMParser::parse ()
69 return this->setMPD();
71 bool BasicCMParser::setMPD()
73 const std::map<std::string, std::string> attr = this->root->getAttributes();
76 std::map<std::string, std::string>::const_iterator it;
77 it = attr.find("mediaPresentationDuration");
79 Standard specifies a default of "On-Demand",
80 so anything that is not "Live" is "On-Demand"
82 this->mpd->setLive( it != attr.end() && it->second == "Live" );
83 it = attr.find( "availabilityStartTime" );
84 if ( it == attr.end() && this->mpd->isLive() == true )
86 std::cerr << "An @availabilityStartTime attribute must be specified when"
87 " the stream @type is Live" << std::endl;
91 if ( it != attr.end() )
94 char *res = strptime( it->second.c_str(), "%Y-%m-%dT%T", &t );
97 if ( this->mpd->isLive() == true )
99 std::cerr << "An @availabilityStartTime attribute must be specified when"
100 " the stream @type is Live" << std::endl;
105 this->mpd->setAvailabilityStartTime( mktime( &t ) );
107 it = attr.find( "availabilityEndTime" );
108 if ( it != attr.end() )
111 char *res = strptime( it->second.c_str(), "%Y-%m-%dT%T", &t );
113 this->mpd->setAvailabilityEndTime( mktime( &t ) );
116 it = attr.find( "mediaPresentationDuration" );
117 if ( it != attr.end() )
118 this->mpd->setDuration( str_duration( it->second.c_str() ) );
119 it = attr.find( "minimumUpdatePeriodMPD" );
120 if ( it != attr.end() )
121 this->mpd->setMinUpdatePeriod( str_duration( it->second.c_str() ) );
122 it = attr.find( "minBufferTime" );
123 if ( it != attr.end() )
124 this->mpd->setMinBufferTime( str_duration( it->second.c_str() ) );
126 if ( this->mpd->isLive() )
128 //This value is undefined when using type "On-Demand"
129 it = attr.find( "timeshiftBufferDepth" );
130 if ( it != attr.end() )
131 this->mpd->setTimeShiftBufferDepth( str_duration( it->second.c_str() ) );
134 this->setMPDBaseUrl(this->root);
135 this->setPeriods(this->root);
136 this->mpd->setProgramInformation( this->parseProgramInformation() );
139 void BasicCMParser::setMPDBaseUrl (Node *root)
141 std::vector<Node *> baseUrls = DOMHelper::getChildElementByTagName(root, "BaseURL");
143 for(size_t i = 0; i < baseUrls.size(); i++)
145 BaseUrl *url = new BaseUrl(baseUrls.at(i)->getText());
146 this->mpd->addBaseUrl(url);
150 void BasicCMParser::setPeriods (Node *root)
152 std::vector<Node *> periods = DOMHelper::getElementByTagName(root, "Period", false);
154 for(size_t i = 0; i < periods.size(); i++)
156 Period *period = new Period();
157 this->setAdaptationSet(periods.at(i), period);
158 this->mpd->addPeriod(period);
162 void BasicCMParser::parseSegmentTimeline(Node *node, SegmentInfoCommon *segmentInfo)
164 Node* segmentTimelineNode = DOMHelper::getFirstChildElementByName( node, "SegmentTimeline" );
165 if ( segmentTimelineNode )
167 SegmentTimeline *segmentTimeline = new SegmentTimeline;
168 std::vector<Node*> sNodes = DOMHelper::getChildElementByTagName( segmentTimelineNode, "S" );
169 std::vector<Node*>::const_iterator it = sNodes.begin();
170 std::vector<Node*>::const_iterator end = sNodes.end();
174 SegmentTimeline::Element* s = new SegmentTimeline::Element;
175 const std::map<std::string, std::string> sAttr = (*it)->getAttributes();
176 std::map<std::string, std::string>::const_iterator sIt;
178 sIt = sAttr.find( "t" );
179 if ( sIt == sAttr.end() )
181 std::cerr << "'t' attribute is mandatory for every SegmentTimeline/S element" << std::endl;
186 s->t = atoll( sIt->second.c_str() );
187 sIt = sAttr.find( "d" );
188 if ( sIt == sAttr.end() )
190 std::cerr << "'d' attribute is mandatory for every SegmentTimeline/S element" << std::endl;
195 s->d = atoll( sIt->second.c_str() );
196 sIt = sAttr.find( "r" );
197 if ( sIt != sAttr.end() )
198 s->r = atoi( sIt->second.c_str() );
199 segmentTimeline->addElement( s );
202 segmentInfo->setSegmentTimeline( segmentTimeline );
206 void BasicCMParser::parseSegmentInfoCommon(Node *node, SegmentInfoCommon *segmentInfo)
208 const std::map<std::string, std::string> attr = node->getAttributes();
210 const std::vector<Node*> baseUrls = DOMHelper::getChildElementByTagName( node, "BaseURL" );
211 if ( baseUrls.size() > 0 )
213 std::vector<Node*>::const_iterator it = baseUrls.begin();
214 std::vector<Node*>::const_iterator end = baseUrls.end();
217 segmentInfo->appendBaseURL( (*it)->getText() );
221 std::map<std::string, std::string>::const_iterator it = attr.begin();
223 this->setInitSegment( node, segmentInfo );
224 it = attr.find( "duration" );
225 if ( it != attr.end() )
226 segmentInfo->setDuration( str_duration( it->second.c_str() ) );
227 it = attr.find( "startIndex" );
228 if ( it != attr.end() )
229 segmentInfo->setStartIndex( atoi( it->second.c_str() ) );
230 this->parseSegmentTimeline( node, segmentInfo );
233 void BasicCMParser::parseSegmentInfoDefault(Node *node, AdaptationSet *group)
235 Node* segmentInfoDefaultNode = DOMHelper::getFirstChildElementByName( node, "SegmentInfoDefault" );
237 if ( segmentInfoDefaultNode != NULL )
239 SegmentInfoDefault* segInfoDef = new SegmentInfoDefault;
240 this->parseSegmentInfoCommon( segmentInfoDefaultNode, segInfoDef );
242 group->setSegmentInfoDefault( segInfoDef );
246 void BasicCMParser::setAdaptationSet (Node *root, Period *period)
248 std::vector<Node *> adaptSets = DOMHelper::getElementByTagName(root, "AdaptationSet", false);
249 if ( adaptSets.size() == 0 ) //In some old file, AdaptationSet may still be called Group
250 adaptSets = DOMHelper::getElementByTagName(root, "Group", false);
252 for(size_t i = 0; i < adaptSets.size(); i++)
254 const std::map<std::string, std::string> attr = adaptSets.at(i)->getAttributes();
255 AdaptationSet *adaptSet = new AdaptationSet;
256 if ( this->parseCommonAttributesElements( adaptSets.at( i ), adaptSet, NULL ) == false )
261 std::map<std::string, std::string>::const_iterator it = attr.find( "subsegmentAlignmentFlag" );
262 if ( it != attr.end() && it->second == "true" )
263 adaptSet->setSubsegmentAlignmentFlag( true ); //Otherwise it is false by default.
264 this->parseSegmentInfoDefault( adaptSets.at( i ), adaptSet );
265 this->setRepresentations(adaptSets.at(i), adaptSet);
266 period->addAdaptationSet(adaptSet);
270 void BasicCMParser::parseTrickMode(Node *node, Representation *repr)
272 std::vector<Node *> trickModes = DOMHelper::getElementByTagName(node, "TrickMode", false);
274 if ( trickModes.size() == 0 )
276 if ( trickModes.size() > 1 )
277 std::cerr << "More than 1 TrickMode element. Only the first one will be used." << std::endl;
279 Node* trickModeNode = trickModes[0];
280 TrickModeType *trickMode = new TrickModeType;
281 const std::map<std::string, std::string> attr = trickModeNode->getAttributes();
282 std::map<std::string, std::string>::const_iterator it = attr.find( "alternatePlayoutRate" );
284 if ( it != attr.end() )
285 trickMode->setAlternatePlayoutRate( atoi( it->second.c_str() ) );
286 repr->setTrickMode( trickMode );
289 void BasicCMParser::setRepresentations (Node *root, AdaptationSet *group)
291 std::vector<Node *> representations = DOMHelper::getElementByTagName(root, "Representation", false);
293 for(size_t i = 0; i < representations.size(); i++)
295 const std::map<std::string, std::string> attributes = representations.at(i)->getAttributes();
297 Representation *rep = new Representation;
298 rep->setParentGroup( group );
299 this->currentRepresentation = rep;
300 if ( this->parseCommonAttributesElements( representations.at( i ), rep, group ) == false )
305 std::map<std::string, std::string>::const_iterator it;
307 it = attributes.find( "id" );
308 if ( it == attributes.end() )
309 std::cerr << "Missing mandatory attribute for Representation: @id" << std::endl;
311 rep->setId( it->second );
313 it = attributes.find( "bandwidth" );
314 if ( it == attributes.end() )
316 std::cerr << "Missing mandatory attribute for Representation: @bandwidth" << std::endl;
320 rep->setBandwidth( atoi( it->second.c_str() ) );
322 it = attributes.find( "qualityRanking" );
323 if ( it != attributes.end() )
324 rep->setQualityRanking( atoi( it->second.c_str() ) );
326 it = attributes.find( "dependencyId" );
327 if ( it != attributes.end() )
328 this->handleDependencyId( rep, group, it->second );
330 if ( this->setSegmentInfo( representations.at(i), rep ) == false )
335 group->addRepresentation(rep);
339 void BasicCMParser::handleDependencyId(Representation *rep, const AdaptationSet *adaptationSet, const std::string &dependencyId )
341 if ( dependencyId.empty() == true )
343 std::istringstream s( dependencyId );
348 const Representation *dep = adaptationSet->getRepresentationById( id );
350 rep->addDependency( dep );
354 bool BasicCMParser::setSegmentInfo (Node *root, Representation *rep)
356 Node *segmentInfo = DOMHelper::getFirstChildElementByName( root, "SegmentInfo");
360 SegmentInfo *info = new SegmentInfo;
361 this->parseSegmentInfoCommon( segmentInfo, info );
362 //If we don't have any segment, there's no point keeping this SegmentInfo.
363 if ( this->setSegments( segmentInfo, info ) == false )
368 rep->setSegmentInfo( info );
371 std::cerr << "Missing mandatory element: Representation/SegmentInfo" << std::endl;
375 Segment* BasicCMParser::parseSegment( Node* node )
377 const std::map<std::string, std::string> attr = node->getAttributes();
378 std::map<std::string, std::string>::const_iterator it;
380 bool isTemplate = false;
383 if ( node->getName() == "UrlTemplate" )
385 it = attr.find( "sourceURL" );
386 //FIXME: When not present, the sourceUrl attribute should be computed
387 //using BaseURL and the range attribute.
388 if ( it != attr.end() )
390 std::string url = it->second;
391 bool runtimeToken = false;
392 if ( isTemplate == true )
394 if ( this->resolveUrlTemplates( url, runtimeToken ) == false )
396 std::cerr << "Failed to substitute URLTemplate identifier." << std::endl;
399 seg = new SegmentTemplate( runtimeToken, this->currentRepresentation );
402 seg = new Segment( this->currentRepresentation );
403 if ( url.find( this->p_stream->psz_access ) != 0 ) //Relative url
404 url = this->url + url;
405 seg->setSourceUrl( url );
410 ProgramInformation* BasicCMParser::parseProgramInformation()
412 Node* pInfoNode = DOMHelper::getFirstChildElementByName( this->root, "ProgramInformation" );
413 if ( pInfoNode == NULL )
415 ProgramInformation *pInfo = new ProgramInformation;
416 const std::map<std::string, std::string> attr = pInfoNode->getAttributes();
417 std::map<std::string, std::string>::const_iterator it;
418 it = attr.find( "moreInformationURL" );
419 if ( it != attr.end() )
420 pInfo->setMoreInformationUrl( it->second );
421 Node* title = DOMHelper::getFirstChildElementByName( pInfoNode, "Title" );
423 pInfo->setTitle( title->getText() );
424 Node* source = DOMHelper::getFirstChildElementByName( pInfoNode, "Source" );
426 pInfo->setSource( source->getText() );
427 Node* copyright = DOMHelper::getFirstChildElementByName( pInfoNode, "copyright" );
429 pInfo->setCopyright( copyright->getText() );
433 void BasicCMParser::setInitSegment (Node *root, SegmentInfoCommon *info)
435 const std::vector<Node *> initSeg = DOMHelper::getChildElementByTagName(root, "InitialisationSegmentURL");
437 if ( initSeg.size() > 1 )
438 std::cerr << "There could be at most one InitialisationSegmentURL per SegmentInfo"
439 " other InitialisationSegmentURL will be dropped." << std::endl;
440 if ( initSeg.size() == 1 )
442 Segment *seg = parseSegment( initSeg.at(0) );
444 info->setInitialisationSegment( seg );
448 bool BasicCMParser::setSegments (Node *root, SegmentInfo *info)
450 std::vector<Node *> segments = DOMHelper::getElementByTagName( root, "Url", false );
451 std::vector<Node *> segmentsTemplates = DOMHelper::getElementByTagName( root, "UrlTemplate", false );
453 if ( segments.size() == 0 && segmentsTemplates.size() == 0 )
455 segments.insert( segments.end(), segmentsTemplates.begin(), segmentsTemplates.end() );
456 for(size_t i = 0; i < segments.size(); i++)
458 Segment* seg = parseSegment( segments.at( i ) );
461 if ( seg->getSourceUrl().empty() == false )
462 info->addSegment(seg);
467 bool BasicCMParser::resolveUrlTemplates( std::string &url, bool &containRuntimeToken )
469 size_t it = url.find( '$' );
470 containRuntimeToken = false;
472 while ( it != std::string::npos )
474 size_t closing = url.find( '$', it + 1 );
475 if ( closing == std::string::npos )
477 std::cerr << "Unmatched '$' in url template: " << url << std::endl;
480 std::string token = std::string( url, it, closing - it + 1 );
483 url.replace( it, token.length(), "$" );
486 else if ( token == "$RepresentationID$" )
488 if ( this->currentRepresentation->getId().empty() == false )
490 std::cerr << "Representation doesn't have an ID. Can't substitute"
491 " identifier $RepresentationID$" << std::endl;
494 url.replace( it, token.length(), this->currentRepresentation->getId() );
495 it = it + this->currentRepresentation->getId().length();
497 else if ( token == "$Bandwidth$" )
499 std::ostringstream oss;
500 oss << this->currentRepresentation->getBandwidth();
501 url.replace( it, token.length(), oss.str() );
502 it = it + oss.str().length();
506 if ( token == "$Index$" || token == "$Time$" )
508 containRuntimeToken = true;
509 it = it + token.length();
513 std::cerr << "Unhandled token " << token << std::endl;
517 it = url.find( '$', it );
522 MPD* BasicCMParser::getMPD()
527 void BasicCMParser::parseContentDescriptor(Node *node, const std::string &name, void (CommonAttributesElements::*addPtr)(ContentDescription *), CommonAttributesElements *self) const
529 std::vector<Node*> descriptors = DOMHelper::getChildElementByTagName( node, name );
530 if ( descriptors.empty() == true )
532 std::vector<Node*>::const_iterator it = descriptors.begin();
533 std::vector<Node*>::const_iterator end = descriptors.end();
537 const std::map<std::string, std::string> attr = (*it)->getAttributes();
538 std::map<std::string, std::string>::const_iterator itAttr = attr.find( "schemeIdUri" );
539 if ( itAttr == attr.end() )
544 ContentDescription *desc = new ContentDescription;
545 desc->setSchemeIdUri( itAttr->second );
546 Node *schemeInfo = DOMHelper::getFirstChildElementByName( node, "SchemeInformation" );
547 if ( schemeInfo != NULL )
548 desc->setSchemeInformation( schemeInfo->getText() );
549 (self->*addPtr)( desc );
554 bool BasicCMParser::parseCommonAttributesElements( Node *node, CommonAttributesElements *common, CommonAttributesElements *parent ) const
556 const std::map<std::string, std::string> &attr = node->getAttributes();
557 std::map<std::string, std::string>::const_iterator it;
558 //Parse mandatory elements first.
559 it = attr.find( "mimeType" );
560 if ( it == attr.end() )
562 if ( parent && parent->getMimeType().empty() == false )
563 common->setMimeType( parent->getMimeType() );
564 else if ( node->getName().find( "Representation" ) != std::string::npos )
566 std::cerr << "Missing mandatory attribute: @mimeType" << std::endl;
571 common->setMimeType( it->second );
572 //Everything else is optionnal.
573 it = attr.find( "width" );
574 if ( it != attr.end() )
575 common->setWidth( atoi( it->second.c_str() ) );
576 it = attr.find( "height" );
577 if ( it != attr.end() )
578 common->setHeight( atoi( it->second.c_str() ) );
579 it = attr.find( "parx" );
580 if ( it != attr.end() )
581 common->setParX( atoi( it->second.c_str() ) );
582 it = attr.find( "pary" );
583 if ( it != attr.end() )
584 common->setParY( atoi( it->second.c_str() ) );
585 it = attr.find( "frameRate" );
586 if ( it != attr.end() )
587 common->setFrameRate( atoi( it->second.c_str() ) );
588 it = attr.find( "lang" );
590 if ( it != attr.end() && it->second.empty() == false )
592 std::istringstream s( it->second );
597 common->addLang( lang );
600 it = attr.find( "numberOfChannels" );
601 if ( it != attr.end() )
603 std::istringstream s( it->second );
608 common->addChannel( channel );
611 it = attr.find( "samplingRate" );
612 if ( it != attr.end() )
614 std::istringstream s( it->second );
619 common->addSampleRate( rate );
622 this->parseContentDescriptor( node, "ContentProtection",
623 &CommonAttributesElements::addContentProtection,
625 this->parseContentDescriptor( node, "Accessibility",
626 &CommonAttributesElements::addAccessibility,
628 this->parseContentDescriptor( node, "Rating",
629 &CommonAttributesElements::addRating, common );
630 this->parseContentDescriptor( node, "Viewpoint",
631 &CommonAttributesElements::addViewpoint, common );
632 //FIXME: Handle : group, maximumRAPPeriod startWithRAP attributes
633 //FIXME: Handle : ContentProtection Accessibility Rating Viewpoing MultipleViews elements