]> git.sesse.net Git - vlc/blob - modules/stream_filter/dash/mpd/BasicCMParser.cpp
dash: Handle UrlTemplate in parser.
[vlc] / modules / stream_filter / dash / mpd / BasicCMParser.cpp
1 /*
2  * BasicCMParser.cpp
3  *****************************************************************************
4  * Copyright (C) 2010 - 2011 Klagenfurt University
5  *
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>
9  *
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.
14  *
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.
19  *
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  *****************************************************************************/
24 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif
27
28 #include "BasicCMParser.h"
29 #include "mpd/ContentDescription.h"
30 #include "mpd/SegmentInfoDefault.h"
31 #include "mpd/SegmentTemplate.h"
32 #include "mpd/SegmentTimeline.h"
33
34 #include <cstdlib>
35 #include <sstream>
36 #include <sstream>
37
38 #include <vlc_common.h>
39 #include <vlc_stream.h>
40 #include <vlc_strings.h>
41
42 using namespace dash::mpd;
43 using namespace dash::xml;
44
45 BasicCMParser::BasicCMParser( Node *root, stream_t *p_stream ) :
46     root( root ),
47     mpd( NULL ),
48     p_stream( p_stream ),
49     currentRepresentation( NULL )
50 {
51     this->url = p_stream->psz_access;
52     this->url += "://";
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 );
58     else
59         this->url += p_stream->psz_path;
60     this->url += '/';
61 }
62
63 BasicCMParser::~BasicCMParser   ()
64 {
65 }
66
67 bool    BasicCMParser::parse                ()
68 {
69     this->setMPD();
70     return true;
71 }
72 bool    BasicCMParser::setMPD()
73 {
74     const std::map<std::string, std::string>    attr = this->root->getAttributes();
75     this->mpd = new MPD;
76
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 );
83
84     it = attr.find("mediaPresentationDuration");
85     /*
86         Standard specifies a default of "On-Demand",
87         so anything that is not "Live" is "On-Demand"
88     */
89     this->mpd->setLive( it != attr.end() && it->second == "Live" );
90     it = attr.find( "availabilityStartTime" );
91     if ( it == attr.end() && this->mpd->isLive() == true )
92     {
93         std::cerr << "An @availabilityStartTime attribute must be specified when"
94                      " the stream @type is Live" << std::endl;
95         return false;
96     }
97 #ifdef HAVE_STRPTIME
98     if ( it != attr.end() )
99     {
100         struct tm   t;
101         char        *res = strptime( it->second.c_str(), "%Y-%m-%dT%T", &t );
102         if ( res == NULL )
103         {
104             if ( this->mpd->isLive() == true )
105             {
106                 std::cerr << "An @availabilityStartTime attribute must be specified when"
107                              " the stream @type is Live" << std::endl;
108                 return false;
109             }
110         }
111         else
112             this->mpd->setAvailabilityStartTime( mktime( &t ) );
113     }
114     it = attr.find( "availabilityEndTime" );
115     if ( it != attr.end() )
116     {
117         struct tm   t;
118         char        *res = strptime( it->second.c_str(), "%Y-%m-%dT%T", &t );
119         if ( res != NULL )
120             this->mpd->setAvailabilityEndTime( mktime( &t ) );
121     }
122 #endif
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() ) );
132
133     if ( this->mpd->isLive() )
134     {
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() ) );
139     }
140
141     this->setMPDBaseUrl(this->root);
142     this->setPeriods(this->root);
143     this->mpd->setProgramInformation( this->parseProgramInformation() );
144     return true;
145 }
146 void    BasicCMParser::setMPDBaseUrl        (Node *root)
147 {
148     std::vector<Node *> baseUrls = DOMHelper::getChildElementByTagName(root, "BaseURL");
149
150     for(size_t i = 0; i < baseUrls.size(); i++)
151     {
152         BaseUrl *url = new BaseUrl(baseUrls.at(i)->getText());
153         this->mpd->addBaseUrl(url);
154     }
155 }
156
157 void    BasicCMParser::setPeriods           (Node *root)
158 {
159     std::vector<Node *> periods = DOMHelper::getElementByTagName(root, "Period", false);
160
161     for(size_t i = 0; i < periods.size(); i++)
162     {
163         Period *period = new Period();
164         this->setGroups(periods.at(i), period);
165         this->mpd->addPeriod(period);
166     }
167 }
168
169 void BasicCMParser::parseSegmentTimeline(Node *node, SegmentInfoCommon *segmentInfo)
170 {
171     Node*   segmentTimelineNode = DOMHelper::getFirstChildElementByName( node, "SegmentTimeline" );
172     if ( segmentTimelineNode )
173     {
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();
178
179         while ( it != end )
180         {
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;
184
185             sIt = sAttr.find( "t" );
186             if ( sIt == sAttr.end() )
187             {
188                 std::cerr << "'t' attribute is mandatory for every SegmentTimeline/S element" << std::endl;
189                 delete s;
190                 ++it;
191                 continue ;
192             }
193             s->t = atoll( sIt->second.c_str() );
194             sIt = sAttr.find( "d" );
195             if ( sIt == sAttr.end() )
196             {
197                 std::cerr << "'d' attribute is mandatory for every SegmentTimeline/S element" << std::endl;
198                 delete s;
199                 ++it;
200                 continue ;
201             }
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 );
207             ++it;
208         }
209         segmentInfo->setSegmentTimeline( segmentTimeline );
210     }
211 }
212
213 void BasicCMParser::parseSegmentInfoCommon(Node *node, SegmentInfoCommon *segmentInfo)
214 {
215     const std::map<std::string, std::string>            attr = node->getAttributes();
216
217     const std::vector<Node*>            baseUrls = DOMHelper::getChildElementByTagName( node, "BaseURL" );
218     if ( baseUrls.size() > 0 )
219     {
220         std::vector<Node*>::const_iterator  it = baseUrls.begin();
221         std::vector<Node*>::const_iterator  end = baseUrls.end();
222         while ( it != end )
223         {
224             segmentInfo->appendBaseURL( (*it)->getText() );
225             ++it;
226         }
227     }
228     std::map<std::string, std::string>::const_iterator  it = attr.begin();
229
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 );
238 }
239
240 void BasicCMParser::parseSegmentInfoDefault(Node *node, Group *group)
241 {
242     Node*   segmentInfoDefaultNode = DOMHelper::getFirstChildElementByName( node, "SegmentInfoDefault" );
243
244     if ( segmentInfoDefaultNode != NULL )
245     {
246         SegmentInfoDefault* segInfoDef = new SegmentInfoDefault;
247         this->parseSegmentInfoCommon( segmentInfoDefaultNode, segInfoDef );
248
249         group->setSegmentInfoDefault( segInfoDef );
250     }
251 }
252
253 void    BasicCMParser::setGroups            (Node *root, Period *period)
254 {
255     std::vector<Node *> groups = DOMHelper::getElementByTagName(root, "Group", false);
256
257     for(size_t i = 0; i < groups.size(); i++)
258     {
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 )
262         {
263             delete group;
264             continue ;
265         }
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);
271     }
272 }
273
274 void BasicCMParser::parseTrickMode(Node *node, Representation *repr)
275 {
276     std::vector<Node *> trickModes = DOMHelper::getElementByTagName(node, "TrickMode", false);
277
278     if ( trickModes.size() == 0 )
279         return ;
280     if ( trickModes.size() > 1 )
281         std::cerr << "More than 1 TrickMode element. Only the first one will be used." << std::endl;
282
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" );
287
288     if ( it != attr.end() )
289         trickMode->setAlternatePlayoutRate( atoi( it->second.c_str() ) );
290     repr->setTrickMode( trickMode );
291 }
292
293 void    BasicCMParser::setRepresentations   (Node *root, Group *group)
294 {
295     std::vector<Node *> representations = DOMHelper::getElementByTagName(root, "Representation", false);
296
297     for(size_t i = 0; i < representations.size(); i++)
298     {
299         const std::map<std::string, std::string>    attributes = representations.at(i)->getAttributes();
300
301         Representation *rep = new Representation;
302         rep->setParentGroup( group );
303         this->currentRepresentation = rep;
304         if ( this->parseCommonAttributesElements( representations.at( i ), rep, group ) == false )
305         {
306             delete rep;
307             continue ;
308         }
309         std::map<std::string, std::string>::const_iterator  it;
310
311         it = attributes.find( "id" );
312         if ( it == attributes.end() )
313             std::cerr << "Missing mandatory attribute for Representation: @id" << std::endl;
314         else
315             rep->setId( it->second );
316
317         it = attributes.find( "bandwidth" );
318         if ( it == attributes.end() )
319         {
320             std::cerr << "Missing mandatory attribute for Representation: @bandwidth" << std::endl;
321             delete rep;
322             continue ;
323         }
324         rep->setBandwidth( atoi( it->second.c_str() ) );
325
326         it = attributes.find( "qualityRanking" );
327         if ( it != attributes.end() )
328             rep->setQualityRanking( atoi( it->second.c_str() ) );
329
330         it = attributes.find( "dependencyId" );
331         if ( it != attributes.end() )
332             this->handleDependencyId( rep, group, it->second );
333
334         if ( this->setSegmentInfo( representations.at(i), rep ) == false )
335         {
336             delete rep;
337             continue ;
338         }
339         group->addRepresentation(rep);
340     }
341 }
342
343 void    BasicCMParser::handleDependencyId( Representation *rep, const Group *group, const std::string &dependencyId )
344 {
345     if ( dependencyId.empty() == true )
346         return ;
347     std::istringstream  s( dependencyId );
348     while ( s )
349     {
350         std::string     id;
351         s >> id;
352         const Representation    *dep = group->getRepresentationById( id );
353         if ( dep )
354             rep->addDependency( dep );
355     }
356 }
357
358 bool    BasicCMParser::setSegmentInfo       (Node *root, Representation *rep)
359 {
360     Node    *segmentInfo = DOMHelper::getFirstChildElementByName( root, "SegmentInfo");
361
362     if ( segmentInfo )
363     {
364         const std::map<std::string, std::string> attr = segmentInfo->getAttributes();
365
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 )
370         {
371             delete info;
372             return false;
373         }
374         rep->setSegmentInfo( info );
375         return true;
376     }
377     std::cerr << "Missing mandatory element: Representation/SegmentInfo" << std::endl;
378     return false;
379 }
380
381 Segment*    BasicCMParser::parseSegment( Node* node )
382 {
383     const std::map<std::string, std::string>    attr = node->getAttributes();
384     std::map<std::string, std::string>::const_iterator  it;
385
386     bool        isTemplate = false;
387     Segment*    seg = NULL;
388
389     if ( node->getName() == "UrlTemplate" )
390         isTemplate = true;
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() )
395     {
396         std::string     url = it->second;
397         bool            runtimeToken = false;
398         if ( isTemplate == true )
399         {
400             if ( this->resolveUrlTemplates( url, runtimeToken ) == false )
401             {
402                 std::cerr << "Failed to substitute URLTemplate identifier." << std::endl;
403                 return NULL;
404             }
405             seg = new SegmentTemplate( runtimeToken, this->currentRepresentation );
406         }
407         else
408             seg = new Segment;
409         if ( url.find( this->p_stream->psz_access ) != 0 ) //Relative url
410             url = this->url + url;
411         seg->setSourceUrl( url );
412     }
413     return seg;
414 }
415
416 ProgramInformation* BasicCMParser::parseProgramInformation()
417 {
418     Node*   pInfoNode = DOMHelper::getFirstChildElementByName( this->root, "ProgramInformation" );
419     if ( pInfoNode == NULL )
420         return 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" );
428     if ( title )
429         pInfo->setTitle( title->getText() );
430     Node*   source = DOMHelper::getFirstChildElementByName( pInfoNode, "Source" );
431     if ( source )
432         pInfo->setSource( source->getText() );
433     Node*   copyright = DOMHelper::getFirstChildElementByName( pInfoNode, "copyright" );
434     if ( copyright )
435         pInfo->setCopyright( copyright->getText() );
436     return pInfo;
437 }
438
439 void    BasicCMParser::setInitSegment       (Node *root, SegmentInfoCommon *info)
440 {
441     const std::vector<Node *> initSeg = DOMHelper::getChildElementByTagName(root, "InitialisationSegmentURL");
442
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 )
447     {
448         Segment     *seg = parseSegment( initSeg.at(0) );
449         if ( seg != NULL )
450             info->setInitialisationSegment( seg );
451     }
452 }
453
454 bool    BasicCMParser::setSegments          (Node *root, SegmentInfo *info)
455 {
456     std::vector<Node *> segments = DOMHelper::getElementByTagName( root, "Url", false );
457     std::vector<Node *> segmentsTemplates = DOMHelper::getElementByTagName( root, "UrlTemplate", false );
458
459     if ( segments.size() == 0 && segmentsTemplates.size() == 0 )
460         return false;
461     segments.insert( segments.end(), segmentsTemplates.begin(), segmentsTemplates.end() );
462     for(size_t i = 0; i < segments.size(); i++)
463     {
464         Segment*    seg = parseSegment( segments.at( i ) );
465         if ( seg == NULL )
466             continue ;
467         if ( seg->getSourceUrl().empty() == false )
468             info->addSegment(seg);
469     }
470     return true;
471 }
472
473 bool    BasicCMParser::resolveUrlTemplates( std::string &url, bool &containRuntimeToken )
474 {
475     size_t      it = url.find( '$' );
476     containRuntimeToken = false;
477
478     while ( it != std::string::npos )
479     {
480         size_t  closing = url.find( '$', it + 1 );
481         if ( closing == std::string::npos )
482         {
483             std::cerr << "Unmatched '$' in url template: " << url << std::endl;
484             return false;
485         }
486         std::string     token = std::string( url, it, closing - it + 1 );
487         if ( token == "$$" )
488         {
489             url.replace( it, token.length(), "$" );
490             it = closing + 1;
491         }
492         else if ( token == "$RepresentationID$" )
493         {
494             if ( this->currentRepresentation->getId().empty() == false )
495             {
496                 std::cerr << "Representation doesn't have an ID. Can't substitute"
497                              " identifier $RepresentationID$" << std::endl;
498                 return false;
499             }
500             url.replace( it, token.length(), this->currentRepresentation->getId() );
501             it = it + this->currentRepresentation->getId().length();
502         }
503         else if ( token == "$Bandwidth$" )
504         {
505             if ( this->currentRepresentation->getBandwidth() < 0 )
506             {
507                 std::cerr << "Representation doesn't have a valid bandwidth. "
508                              "Can't substitute tag $Bandwidth$" << std::endl;
509                 return false;
510             }
511             std::ostringstream  oss;
512             oss << this->currentRepresentation->getBandwidth();
513             url.replace( it, token.length(), oss.str() );
514             it = it + oss.str().length();
515         }
516         else
517         {
518             if ( token == "$Index$" || token == "$Time$" )
519             {
520                 containRuntimeToken = true;
521                 it = it + token.length();
522             }
523             else
524             {
525                 std::cout << "Unhandled token " << token << std::endl;
526                 return false;
527             }
528         }
529         it = url.find( '$', it );
530     }
531     return true;
532 }
533
534 MPD*    BasicCMParser::getMPD()
535 {
536     return this->mpd;
537 }
538
539 void BasicCMParser::parseContentDescriptor(Node *node, const std::string &name, void (CommonAttributesElements::*addPtr)(ContentDescription *), CommonAttributesElements *self) const
540 {
541     std::vector<Node*>  descriptors = DOMHelper::getChildElementByTagName( node, name );
542     if ( descriptors.empty() == true )
543         return ;
544     std::vector<Node*>::const_iterator  it = descriptors.begin();
545     std::vector<Node*>::const_iterator  end = descriptors.end();
546
547     while ( it != end )
548     {
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() )
552         {
553             ++it;
554             continue ;
555         }
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 );
562         ++it;
563     }
564 }
565
566 bool    BasicCMParser::parseCommonAttributesElements( Node *node, CommonAttributesElements *common, CommonAttributesElements *parent ) const
567 {
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() )
573     {
574         if ( parent && parent->getMimeType().empty() == false )
575             common->setMimeType( parent->getMimeType() );
576         else
577         {
578             std::cerr << "Missing mandatory attribute: @mimeType" << std::endl;
579             return false;
580         }
581     }
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" );
600
601     if ( it != attr.end() && it->second.empty() == false )
602     {
603         std::istringstream  s( it->second );
604         while ( s )
605         {
606             std::string     lang;
607             s >> lang;
608             common->addLang( lang );
609         }
610     }
611     it = attr.find( "numberOfChannels" );
612     if ( it != attr.end() )
613     {
614         std::istringstream  s( it->second );
615         while ( s )
616         {
617             std::string     channel;
618             s >> channel;
619             common->addChannel( channel );
620         }
621     }
622     it = attr.find( "samplingRate" );
623     if ( it != attr.end() )
624     {
625         std::istringstream  s( it->second );
626         while ( s )
627         {
628             int         rate;
629             s >> rate;
630             common->addSampleRate( rate );
631         }
632     }
633     this->parseContentDescriptor( node, "ContentProtection",
634                                   &CommonAttributesElements::addContentProtection,
635                                   common );
636     this->parseContentDescriptor( node, "Accessibility",
637                                   &CommonAttributesElements::addAccessibility,
638                                   common );
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
645     return true;
646 }