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
11 * it 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 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
22 * Foundation, 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 ()
72 bool BasicCMParser::setMPD()
74 const std::map<std::string, std::string> attr = this->root->getAttributes();
77 std::map<std::string, std::string>::const_iterator it;
78 it = attr.find( "profile" );
79 if ( it == attr.end() )
80 it = attr.find( "profiles" ); //The standard spells it the two ways...
81 if ( it != attr.end() )
82 this->mpd->setProfile( it->second );
84 it = attr.find("mediaPresentationDuration");
86 Standard specifies a default of "On-Demand",
87 so anything that is not "Live" is "On-Demand"
89 this->mpd->setLive( it != attr.end() && it->second == "Live" );
90 it = attr.find( "availabilityStartTime" );
91 if ( it == attr.end() && this->mpd->isLive() == true )
93 std::cerr << "An @availabilityStartTime attribute must be specified when"
94 " the stream @type is Live" << std::endl;
98 if ( it != attr.end() )
101 char *res = strptime( it->second.c_str(), "%Y-%m-%dT%T", &t );
104 if ( this->mpd->isLive() == true )
106 std::cerr << "An @availabilityStartTime attribute must be specified when"
107 " the stream @type is Live" << std::endl;
112 this->mpd->setAvailabilityStartTime( mktime( &t ) );
114 it = attr.find( "availabilityEndTime" );
115 if ( it != attr.end() )
118 char *res = strptime( it->second.c_str(), "%Y-%m-%dT%T", &t );
120 this->mpd->setAvailabilityEndTime( mktime( &t ) );
123 it = attr.find( "mediaPresentationDuration" );
124 if ( it != attr.end() )
125 this->mpd->setDuration( str_duration( it->second.c_str() ) );
126 it = attr.find( "minimumUpdatePeriodMPD" );
127 if ( it != attr.end() )
128 this->mpd->setMinUpdatePeriod( str_duration( it->second.c_str() ) );
129 it = attr.find( "minBufferTime" );
130 if ( it != attr.end() )
131 this->mpd->setMinBufferTime( str_duration( it->second.c_str() ) );
133 if ( this->mpd->isLive() )
135 //This value is undefined when using type "On-Demand"
136 it = attr.find( "timeshiftBufferDepth" );
137 if ( it != attr.end() )
138 this->mpd->setTimeShiftBufferDepth( str_duration( it->second.c_str() ) );
141 this->setMPDBaseUrl(this->root);
142 this->setPeriods(this->root);
143 this->mpd->setProgramInformation( this->parseProgramInformation() );
146 void BasicCMParser::setMPDBaseUrl (Node *root)
148 std::vector<Node *> baseUrls = DOMHelper::getChildElementByTagName(root, "BaseURL");
150 for(size_t i = 0; i < baseUrls.size(); i++)
152 BaseUrl *url = new BaseUrl(baseUrls.at(i)->getText());
153 this->mpd->addBaseUrl(url);
157 void BasicCMParser::setPeriods (Node *root)
159 std::vector<Node *> periods = DOMHelper::getElementByTagName(root, "Period", false);
161 for(size_t i = 0; i < periods.size(); i++)
163 Period *period = new Period();
164 this->setGroups(periods.at(i), period);
165 this->mpd->addPeriod(period);
169 void BasicCMParser::parseSegmentTimeline(Node *node, SegmentInfoCommon *segmentInfo)
171 Node* segmentTimelineNode = DOMHelper::getFirstChildElementByName( node, "SegmentTimeline" );
172 if ( segmentTimelineNode )
174 SegmentTimeline *segmentTimeline = new SegmentTimeline;
175 std::vector<Node*> sNodes = DOMHelper::getChildElementByTagName( segmentTimelineNode, "S" );
176 std::vector<Node*>::const_iterator it = sNodes.begin();
177 std::vector<Node*>::const_iterator end = sNodes.end();
181 SegmentTimeline::Element* s = new SegmentTimeline::Element;
182 const std::map<std::string, std::string> sAttr = (*it)->getAttributes();
183 std::map<std::string, std::string>::const_iterator sIt;
185 sIt = sAttr.find( "t" );
186 if ( sIt == sAttr.end() )
188 std::cerr << "'t' attribute is mandatory for every SegmentTimeline/S element" << std::endl;
193 s->t = atoll( sIt->second.c_str() );
194 sIt = sAttr.find( "d" );
195 if ( sIt == sAttr.end() )
197 std::cerr << "'d' attribute is mandatory for every SegmentTimeline/S element" << std::endl;
202 s->d = atoll( sIt->second.c_str() );
203 sIt = sAttr.find( "r" );
204 if ( sIt != sAttr.end() )
205 s->r = atoi( sIt->second.c_str() );
206 segmentTimeline->addElement( s );
209 segmentInfo->setSegmentTimeline( segmentTimeline );
213 void BasicCMParser::parseSegmentInfoCommon(Node *node, SegmentInfoCommon *segmentInfo)
215 const std::map<std::string, std::string> attr = node->getAttributes();
217 const std::vector<Node*> baseUrls = DOMHelper::getChildElementByTagName( node, "BaseURL" );
218 if ( baseUrls.size() > 0 )
220 std::vector<Node*>::const_iterator it = baseUrls.begin();
221 std::vector<Node*>::const_iterator end = baseUrls.end();
224 segmentInfo->appendBaseURL( (*it)->getText() );
228 std::map<std::string, std::string>::const_iterator it = attr.begin();
230 this->setInitSegment( node, segmentInfo );
231 it = attr.find( "duration" );
232 if ( it != attr.end() )
233 segmentInfo->setDuration( str_duration( it->second.c_str() ) );
234 it = attr.find( "startIndex" );
235 if ( it != attr.end() )
236 segmentInfo->setStartIndex( atoi( it->second.c_str() ) );
237 this->parseSegmentTimeline( node, segmentInfo );
240 void BasicCMParser::parseSegmentInfoDefault(Node *node, Group *group)
242 Node* segmentInfoDefaultNode = DOMHelper::getFirstChildElementByName( node, "SegmentInfoDefault" );
244 if ( segmentInfoDefaultNode != NULL )
246 SegmentInfoDefault* segInfoDef = new SegmentInfoDefault;
247 this->parseSegmentInfoCommon( segmentInfoDefaultNode, segInfoDef );
249 group->setSegmentInfoDefault( segInfoDef );
253 void BasicCMParser::setGroups (Node *root, Period *period)
255 std::vector<Node *> groups = DOMHelper::getElementByTagName(root, "Group", false);
257 for(size_t i = 0; i < groups.size(); i++)
259 const std::map<std::string, std::string> attr = groups.at(i)->getAttributes();
260 Group *group = new Group;
261 if ( this->parseCommonAttributesElements( groups.at( i ), group, NULL ) == false )
266 std::map<std::string, std::string>::const_iterator it = attr.find( "subsegmentAlignmentFlag" );
267 if ( it != attr.end() && it->second == "true" )
268 group->setSubsegmentAlignmentFlag( true ); //Otherwise it is false by default.
269 this->setRepresentations(groups.at(i), group);
270 period->addGroup(group);
274 void BasicCMParser::parseTrickMode(Node *node, Representation *repr)
276 std::vector<Node *> trickModes = DOMHelper::getElementByTagName(node, "TrickMode", false);
278 if ( trickModes.size() == 0 )
280 if ( trickModes.size() > 1 )
281 std::cerr << "More than 1 TrickMode element. Only the first one will be used." << std::endl;
283 Node* trickModeNode = trickModes[0];
284 TrickModeType *trickMode = new TrickModeType;
285 const std::map<std::string, std::string> attr = trickModeNode->getAttributes();
286 std::map<std::string, std::string>::const_iterator it = attr.find( "alternatePlayoutRate" );
288 if ( it != attr.end() )
289 trickMode->setAlternatePlayoutRate( atoi( it->second.c_str() ) );
290 repr->setTrickMode( trickMode );
293 void BasicCMParser::setRepresentations (Node *root, Group *group)
295 std::vector<Node *> representations = DOMHelper::getElementByTagName(root, "Representation", false);
297 for(size_t i = 0; i < representations.size(); i++)
299 const std::map<std::string, std::string> attributes = representations.at(i)->getAttributes();
301 Representation *rep = new Representation;
302 rep->setParentGroup( group );
303 this->currentRepresentation = rep;
304 if ( this->parseCommonAttributesElements( representations.at( i ), rep, group ) == false )
309 std::map<std::string, std::string>::const_iterator it;
311 it = attributes.find( "id" );
312 if ( it == attributes.end() )
313 std::cerr << "Missing mandatory attribute for Representation: @id" << std::endl;
315 rep->setId( it->second );
317 it = attributes.find( "bandwidth" );
318 if ( it == attributes.end() )
320 std::cerr << "Missing mandatory attribute for Representation: @bandwidth" << std::endl;
324 rep->setBandwidth( atoi( it->second.c_str() ) );
326 it = attributes.find( "qualityRanking" );
327 if ( it != attributes.end() )
328 rep->setQualityRanking( atoi( it->second.c_str() ) );
330 it = attributes.find( "dependencyId" );
331 if ( it != attributes.end() )
332 this->handleDependencyId( rep, group, it->second );
334 if ( this->setSegmentInfo( representations.at(i), rep ) == false )
339 group->addRepresentation(rep);
343 void BasicCMParser::handleDependencyId( Representation *rep, const Group *group, const std::string &dependencyId )
345 if ( dependencyId.empty() == true )
347 std::istringstream s( dependencyId );
352 const Representation *dep = group->getRepresentationById( id );
354 rep->addDependency( dep );
358 bool BasicCMParser::setSegmentInfo (Node *root, Representation *rep)
360 Node *segmentInfo = DOMHelper::getFirstChildElementByName( root, "SegmentInfo");
364 const std::map<std::string, std::string> attr = segmentInfo->getAttributes();
366 SegmentInfo *info = new SegmentInfo();
367 this->parseSegmentInfoCommon( segmentInfo, info );
368 //If we don't have any segment, there's no point keeping this SegmentInfo.
369 if ( this->setSegments( segmentInfo, info ) == false )
374 rep->setSegmentInfo( info );
377 std::cerr << "Missing mandatory element: Representation/SegmentInfo" << std::endl;
381 Segment* BasicCMParser::parseSegment( Node* node )
383 const std::map<std::string, std::string> attr = node->getAttributes();
384 std::map<std::string, std::string>::const_iterator it;
386 bool isTemplate = false;
389 if ( node->getName() == "UrlTemplate" )
391 it = attr.find( "sourceURL" );
392 //FIXME: When not present, the sourceUrl attribute should be computed
393 //using BaseURL and the range attribute.
394 if ( it != attr.end() )
396 std::string url = it->second;
397 bool runtimeToken = false;
398 if ( isTemplate == true )
400 if ( this->resolveUrlTemplates( url, runtimeToken ) == false )
402 std::cerr << "Failed to substitute URLTemplate identifier." << std::endl;
405 seg = new SegmentTemplate( runtimeToken, this->currentRepresentation );
409 if ( url.find( this->p_stream->psz_access ) != 0 ) //Relative url
410 url = this->url + url;
411 seg->setSourceUrl( url );
416 ProgramInformation* BasicCMParser::parseProgramInformation()
418 Node* pInfoNode = DOMHelper::getFirstChildElementByName( this->root, "ProgramInformation" );
419 if ( pInfoNode == NULL )
421 ProgramInformation *pInfo = new ProgramInformation;
422 const std::map<std::string, std::string> attr = pInfoNode->getAttributes();
423 std::map<std::string, std::string>::const_iterator it;
424 it = attr.find( "moreInformationURL" );
425 if ( it != attr.end() )
426 pInfo->setMoreInformationUrl( it->second );
427 Node* title = DOMHelper::getFirstChildElementByName( pInfoNode, "Title" );
429 pInfo->setTitle( title->getText() );
430 Node* source = DOMHelper::getFirstChildElementByName( pInfoNode, "Source" );
432 pInfo->setSource( source->getText() );
433 Node* copyright = DOMHelper::getFirstChildElementByName( pInfoNode, "copyright" );
435 pInfo->setCopyright( copyright->getText() );
439 void BasicCMParser::setInitSegment (Node *root, SegmentInfoCommon *info)
441 const std::vector<Node *> initSeg = DOMHelper::getChildElementByTagName(root, "InitialisationSegmentURL");
443 if ( initSeg.size() > 1 )
444 std::cerr << "There could be at most one InitialisationSegmentURL per SegmentInfo"
445 " other InitialisationSegmentURL will be dropped." << std::endl;
446 if ( initSeg.size() == 1 )
448 Segment *seg = parseSegment( initSeg.at(0) );
450 info->setInitialisationSegment( seg );
454 bool BasicCMParser::setSegments (Node *root, SegmentInfo *info)
456 std::vector<Node *> segments = DOMHelper::getElementByTagName( root, "Url", false );
457 std::vector<Node *> segmentsTemplates = DOMHelper::getElementByTagName( root, "UrlTemplate", false );
459 if ( segments.size() == 0 && segmentsTemplates.size() == 0 )
461 segments.insert( segments.end(), segmentsTemplates.begin(), segmentsTemplates.end() );
462 for(size_t i = 0; i < segments.size(); i++)
464 Segment* seg = parseSegment( segments.at( i ) );
467 if ( seg->getSourceUrl().empty() == false )
468 info->addSegment(seg);
473 bool BasicCMParser::resolveUrlTemplates( std::string &url, bool &containRuntimeToken )
475 size_t it = url.find( '$' );
476 containRuntimeToken = false;
478 while ( it != std::string::npos )
480 size_t closing = url.find( '$', it + 1 );
481 if ( closing == std::string::npos )
483 std::cerr << "Unmatched '$' in url template: " << url << std::endl;
486 std::string token = std::string( url, it, closing - it + 1 );
489 url.replace( it, token.length(), "$" );
492 else if ( token == "$RepresentationID$" )
494 if ( this->currentRepresentation->getId().empty() == false )
496 std::cerr << "Representation doesn't have an ID. Can't substitute"
497 " identifier $RepresentationID$" << std::endl;
500 url.replace( it, token.length(), this->currentRepresentation->getId() );
501 it = it + this->currentRepresentation->getId().length();
503 else if ( token == "$Bandwidth$" )
505 if ( this->currentRepresentation->getBandwidth() < 0 )
507 std::cerr << "Representation doesn't have a valid bandwidth. "
508 "Can't substitute tag $Bandwidth$" << std::endl;
511 std::ostringstream oss;
512 oss << this->currentRepresentation->getBandwidth();
513 url.replace( it, token.length(), oss.str() );
514 it = it + oss.str().length();
518 if ( token == "$Index$" || token == "$Time$" )
520 containRuntimeToken = true;
521 it = it + token.length();
525 std::cout << "Unhandled token " << token << std::endl;
529 it = url.find( '$', it );
534 MPD* BasicCMParser::getMPD()
539 void BasicCMParser::parseContentDescriptor(Node *node, const std::string &name, void (CommonAttributesElements::*addPtr)(ContentDescription *), CommonAttributesElements *self) const
541 std::vector<Node*> descriptors = DOMHelper::getChildElementByTagName( node, name );
542 if ( descriptors.empty() == true )
544 std::vector<Node*>::const_iterator it = descriptors.begin();
545 std::vector<Node*>::const_iterator end = descriptors.end();
549 const std::map<std::string, std::string> attr = (*it)->getAttributes();
550 std::map<std::string, std::string>::const_iterator itAttr = attr.find( "schemeIdUri" );
551 if ( itAttr == attr.end() )
556 ContentDescription *desc = new ContentDescription;
557 desc->setSchemeIdUri( itAttr->second );
558 Node *schemeInfo = DOMHelper::getFirstChildElementByName( node, "SchemeInformation" );
559 if ( schemeInfo != NULL )
560 desc->setSchemeInformation( schemeInfo->getText() );
561 (self->*addPtr)( desc );
566 bool BasicCMParser::parseCommonAttributesElements( Node *node, CommonAttributesElements *common, CommonAttributesElements *parent ) const
568 const std::map<std::string, std::string> &attr = node->getAttributes();
569 std::map<std::string, std::string>::const_iterator it;
570 //Parse mandatory elements first.
571 it = attr.find( "mimeType" );
572 if ( it == attr.end() )
574 if ( parent && parent->getMimeType().empty() == false )
575 common->setMimeType( parent->getMimeType() );
578 std::cerr << "Missing mandatory attribute: @mimeType" << std::endl;
582 common->setMimeType( it->second );
583 //Everything else is optionnal.
584 it = attr.find( "width" );
585 if ( it != attr.end() )
586 common->setWidth( atoi( it->second.c_str() ) );
587 it = attr.find( "height" );
588 if ( it != attr.end() )
589 common->setHeight( atoi( it->second.c_str() ) );
590 it = attr.find( "parx" );
591 if ( it != attr.end() )
592 common->setParX( atoi( it->second.c_str() ) );
593 it = attr.find( "pary" );
594 if ( it != attr.end() )
595 common->setParY( atoi( it->second.c_str() ) );
596 it = attr.find( "frameRate" );
597 if ( it != attr.end() )
598 common->setFrameRate( atoi( it->second.c_str() ) );
599 it = attr.find( "lang" );
601 if ( it != attr.end() && it->second.empty() == false )
603 std::istringstream s( it->second );
608 common->addLang( lang );
611 it = attr.find( "numberOfChannels" );
612 if ( it != attr.end() )
614 std::istringstream s( it->second );
619 common->addChannel( channel );
622 it = attr.find( "samplingRate" );
623 if ( it != attr.end() )
625 std::istringstream s( it->second );
630 common->addSampleRate( rate );
633 this->parseContentDescriptor( node, "ContentProtection",
634 &CommonAttributesElements::addContentProtection,
636 this->parseContentDescriptor( node, "Accessibility",
637 &CommonAttributesElements::addAccessibility,
639 this->parseContentDescriptor( node, "Rating",
640 &CommonAttributesElements::addRating, common );
641 this->parseContentDescriptor( node, "Viewpoint",
642 &CommonAttributesElements::addViewpoint, common );
643 //FIXME: Handle : group, maximumRAPPeriod startWithRAP attributes
644 //FIXME: Handle : ContentProtection Accessibility Rating Viewpoing MultipleViews elements