1 /*******************************************************************************
2 * xspf.c : XSPF playlist import functions
3 *******************************************************************************
4 * Copyright (C) 2006-2017 VLC authors and VideoLAN
6 * Authors: Daniel Stränger <vlc at schmaller dot de>
7 * Yoann Peronneau <yoann@videolan.org>
9 * This program is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU Lesser General Public License as published by
11 * the Free Software Foundation; either version 2.1 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this program; if not, write to the Free Software Foundation,
21 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 ******************************************************************************/
24 * \file modules/demux/playlist/xspf.c
25 * \brief XSPF playlist import functions
32 #include <vlc_common.h>
33 #include <vlc_access.h>
36 #include <vlc_arrays.h>
37 #include <vlc_strings.h>
43 #define SIMPLE_INTERFACE (input_item_t *p_input,\
44 const char *psz_name,\
47 #define COMPLEX_INTERFACE (stream_t *p_stream,\
48 input_item_node_t *p_input_node,\
49 xml_reader_t *p_xml_reader,\
50 const char *psz_element,\
54 static bool parse_playlist_node COMPLEX_INTERFACE
;
55 static bool parse_tracklist_node COMPLEX_INTERFACE
;
56 static bool parse_track_node COMPLEX_INTERFACE
;
57 static bool parse_extension_node COMPLEX_INTERFACE
;
58 static bool parse_extitem_node COMPLEX_INTERFACE
;
59 static bool set_item_info SIMPLE_INTERFACE
;
60 static bool set_option SIMPLE_INTERFACE
;
61 static bool skip_element COMPLEX_INTERFACE
;
69 bool (*smpl
) SIMPLE_INTERFACE
;
70 bool (*cmplx
) COMPLEX_INTERFACE
;
77 input_item_t
**pp_tracklist
;
78 int i_tracklist_entries
;
83 static int ReadDir(stream_t
*, input_item_node_t
*);
86 * \brief XSPF submodule initialization function
88 int Import_xspf(vlc_object_t
*p_this
)
90 stream_t
*p_stream
= (stream_t
*)p_this
;
94 if( !stream_HasExtension( p_stream
, ".xspf" )
95 && !stream_IsMimeType( p_stream
->p_source
, "application/xspf+xml" ) )
98 xspf_sys_t
*sys
= calloc(1, sizeof (*sys
));
99 if (unlikely(sys
== NULL
))
102 msg_Dbg(p_stream
, "using XSPF playlist reader");
103 p_stream
->p_sys
= sys
;
104 p_stream
->pf_readdir
= ReadDir
;
105 p_stream
->pf_control
= access_vaDirectoryControlHelper
;
110 void Close_xspf(vlc_object_t
*p_this
)
112 stream_t
*p_stream
= (stream_t
*)p_this
;
113 xspf_sys_t
*p_sys
= p_stream
->p_sys
;
114 for (int i
= 0; i
< p_sys
->i_tracklist_entries
; i
++)
115 if (p_sys
->pp_tracklist
[i
])
116 input_item_Release(p_sys
->pp_tracklist
[i
]);
117 free(p_sys
->pp_tracklist
);
118 free(p_sys
->psz_base
);
123 * \brief demuxer function for XSPF parsing
125 static int ReadDir(stream_t
*p_stream
, input_item_node_t
*p_subitems
)
127 xspf_sys_t
*sys
= p_stream
->p_sys
;
129 xml_reader_t
*p_xml_reader
= NULL
;
130 const char *name
= NULL
;
132 sys
->pp_tracklist
= NULL
;
133 sys
->i_tracklist_entries
= 0;
134 sys
->i_track_id
= -1;
135 sys
->psz_base
= strdup(p_stream
->psz_url
);
137 /* create new xml parser from stream */
138 p_xml_reader
= xml_ReaderCreate(p_stream
, p_stream
->p_source
);
142 /* locating the root node */
143 if (xml_ReaderNextNode(p_xml_reader
, &name
) != XML_READER_STARTELEM
)
145 msg_Err(p_stream
, "can't read xml stream");
149 /* checking root node name */
150 if (strcmp(name
, "playlist"))
152 msg_Err(p_stream
, "invalid root node name <%s>", name
);
156 if(xml_ReaderIsEmptyElement(p_xml_reader
))
159 i_ret
= parse_playlist_node(p_stream
, p_subitems
,
160 p_xml_reader
, "playlist", false ) ? 0 : -1;
162 for (int i
= 0 ; i
< sys
->i_tracklist_entries
; i
++)
164 input_item_t
*p_new_input
= sys
->pp_tracklist
[i
];
167 input_item_node_AppendItem(p_subitems
, p_new_input
);
173 xml_ReaderDelete(p_xml_reader
);
174 return i_ret
; /* Needed for correct operation of go back */
177 static const xml_elem_hnd_t
*get_handler(const xml_elem_hnd_t
*tab
, size_t n
, const char *name
)
179 for (size_t i
= 0; i
< n
; i
++)
180 if (!strcmp(name
, tab
[i
].name
))
185 static const char *get_node_attribute(xml_reader_t
*p_xml_reader
, const char *psz_name
)
187 const char *name
, *value
;
188 while ((name
= xml_ReaderNextAttr(p_xml_reader
, &value
)) != NULL
)
190 if (!strcmp(name
, psz_name
))
197 * \brief generic node parsing of a XSPF playlist
198 * \param p_stream stream instance
199 * \param input_item_node_t current input node
200 * \param p_input_item current input item
201 * \param p_xml_reader xml reader instance
202 * \param psz_root_node current node name to parse
203 * \param pl_elements xml_elem_hnd_t handlers array
204 * \param i_pl_elements xml_elem_hnd_t array count
206 static bool parse_node(stream_t
*p_stream
,
207 input_item_node_t
*p_input_node
, input_item_t
*p_input_item
,
208 xml_reader_t
*p_xml_reader
, const char *psz_root_node
,
209 const xml_elem_hnd_t
*pl_elements
, size_t i_pl_elements
)
213 char *psz_value
= NULL
;
216 const xml_elem_hnd_t
*p_handler
= NULL
;
218 while ((i_node
= xml_ReaderNextNode(p_xml_reader
, &name
)) > XML_READER_NONE
)
220 const bool b_empty
= xml_ReaderIsEmptyElement(p_xml_reader
);
224 case XML_READER_STARTELEM
:
228 msg_Err(p_stream
, "invalid XML stream");
232 p_handler
= get_handler(pl_elements
, i_pl_elements
, name
);
235 msg_Warn(p_stream
, "skipping unexpected element <%s>", name
);
236 if(!skip_element(NULL
, NULL
, p_xml_reader
, name
, b_empty
))
241 /* complex content is parsed in a separate function */
242 if (p_handler
->cmplx
)
244 if (!p_handler
->pf_handler
.cmplx(p_stream
, p_input_node
,
245 p_xml_reader
, p_handler
->name
,
248 /* Complex reader does read the named end element */
254 case XML_READER_TEXT
:
262 psz_value
= strdup(name
);
263 if (unlikely(!psz_value
))
268 case XML_READER_ENDELEM
:
269 /* leave if the current parent node is terminated */
270 if (!strcmp(name
, psz_root_node
))
278 /* there MUST have been a start tag for that element name */
279 if (strcmp(p_handler
->name
, name
))
281 msg_Err(p_stream
, "there's no open element left for <%s>", name
);
285 if (p_handler
->pf_handler
.smpl
)
286 p_handler
->pf_handler
.smpl(p_input_item
, p_handler
->name
,
287 psz_value
, p_stream
->p_sys
);
304 * \brief parse the root node of a XSPF playlist
305 * \param p_stream stream instance
306 * \param p_input_item current input item
307 * \param p_xml_reader xml reader instance
308 * \param psz_element name of element to parse
310 static bool parse_playlist_node COMPLEX_INTERFACE
312 xspf_sys_t
*sys
= p_stream
->p_sys
;
317 /* read all playlist attributes */
318 const char *psz_version
= get_node_attribute(p_xml_reader
, "version");
319 if(!psz_version
|| (strcmp(psz_version
, "0") && strcmp(psz_version
, "1")))
321 /* attribute version is mandatory !!! */
323 msg_Warn(p_stream
, "<playlist> requires \"version\" attribute");
325 msg_Warn(p_stream
, "unsupported XSPF version %s", psz_version
);
329 const char *psz_base
= get_node_attribute(p_xml_reader
, "xml:base");
333 sys
->psz_base
= strdup(psz_base
);
336 static const xml_elem_hnd_t pl_elements
[] =
337 { {"title", {.smpl
= set_item_info
}, false },
338 {"creator", {.smpl
= set_item_info
}, false },
339 {"annotation", {.smpl
= set_item_info
}, false },
340 {"info", {NULL
}, false },
341 {"location", {NULL
}, false },
342 {"identifier", {NULL
}, false },
343 {"image", {.smpl
= set_item_info
}, false },
344 {"date", {NULL
}, false },
345 {"license", {NULL
}, false },
346 {"attribution", {.cmplx
= skip_element
}, true },
347 {"link", {NULL
}, false },
348 {"meta", {NULL
}, false },
349 {"extension", {.cmplx
= parse_extension_node
}, true },
350 {"trackList", {.cmplx
= parse_tracklist_node
}, true },
353 return parse_node(p_stream
, p_input_node
, p_input_node
->p_item
,
354 p_xml_reader
, psz_element
,
355 pl_elements
, ARRAY_SIZE(pl_elements
));
359 * \brief parses the tracklist node which only may contain <track>s
361 static bool parse_tracklist_node COMPLEX_INTERFACE
363 VLC_UNUSED(psz_element
);
368 /* parse the child elements */
369 static const xml_elem_hnd_t pl_elements
[] =
370 { {"track", {.cmplx
= parse_track_node
}, true },
373 return parse_node(p_stream
, p_input_node
, p_input_node
->p_item
,
374 p_xml_reader
, psz_element
,
375 pl_elements
, ARRAY_SIZE(pl_elements
));
379 * \brief handles the <location> elements
381 static bool parse_location SIMPLE_INTERFACE
383 VLC_UNUSED(psz_name
);
384 xspf_sys_t
*p_sys
= (xspf_sys_t
*) opaque
;
385 char* psz_uri
= ProcessMRL( psz_value
, p_sys
->psz_base
);
388 input_item_SetURI(p_input
, psz_uri
);
391 return psz_uri
!= NULL
;
395 * \brief parse one track element
396 * \param COMPLEX_INTERFACE
398 static bool parse_track_node COMPLEX_INTERFACE
400 xspf_sys_t
*p_sys
= p_stream
->p_sys
;
405 input_item_t
*p_new_input
= input_item_New(NULL
, NULL
);
409 /* increfs p_new_input */
410 input_item_node_t
*p_new_node
= input_item_node_Create(p_new_input
);
413 input_item_Release(p_new_input
);
417 /* reset i_track_id */
418 p_sys
->i_track_id
= -1;
420 static const xml_elem_hnd_t track_elements
[] =
421 { {"location", {.smpl
= parse_location
}, false },
422 {"identifier", {NULL
}, false },
423 {"title", {.smpl
= set_item_info
}, false },
424 {"creator", {.smpl
= set_item_info
}, false },
425 {"annotation", {.smpl
= set_item_info
}, false },
426 {"info", {.smpl
= set_item_info
}, false },
427 {"image", {.smpl
= set_item_info
}, false },
428 {"album", {.smpl
= set_item_info
}, false },
429 {"trackNum", {.smpl
= set_item_info
}, false },
430 {"duration", {.smpl
= set_item_info
}, false },
431 {"link", {NULL
}, false },
432 {"meta", {NULL
}, false },
433 {"extension", {.cmplx
= parse_extension_node
}, true },
436 bool b_ret
= parse_node(p_stream
, p_new_node
, p_new_input
,
437 p_xml_reader
, psz_element
,
438 track_elements
, ARRAY_SIZE(track_elements
));
441 input_item_CopyOptions(p_new_input
, p_input_node
->p_item
);
443 /* Make sure we have a URI */
444 char *psz_uri
= input_item_GetURI(p_new_input
);
446 input_item_SetURI(p_new_input
, "vlc://nop");
450 if (p_sys
->i_track_id
< 0 ||
451 p_sys
->i_track_id
== INT_MAX
||
452 (size_t)p_sys
->i_track_id
>= (SIZE_MAX
/ sizeof(p_new_input
)))
454 input_item_node_AppendNode(p_input_node
, p_new_node
);
459 /* Extend array as needed */
460 if (p_sys
->i_track_id
>= p_sys
->i_tracklist_entries
)
463 pp
= realloc(p_sys
->pp_tracklist
,
464 (p_sys
->i_track_id
+ 1) * sizeof(*pp
));
467 p_sys
->pp_tracklist
= pp
;
468 while (p_sys
->i_track_id
>= p_sys
->i_tracklist_entries
)
469 pp
[p_sys
->i_tracklist_entries
++] = NULL
;
473 if (p_sys
->i_track_id
< p_sys
->i_tracklist_entries
)
475 input_item_t
**pp_insert
= &p_sys
->pp_tracklist
[p_sys
->i_track_id
];
477 if (*pp_insert
!= NULL
)
479 msg_Warn(p_stream
, "track ID %d collision", p_sys
->i_track_id
);
480 input_item_node_AppendItem(p_input_node
, p_new_input
);
484 *pp_insert
= p_new_input
;
493 input_item_node_Delete(p_new_node
); /* decrefs p_new_input */
495 input_item_Release(p_new_input
);
501 * \brief handles the supported <track> sub-elements
503 static bool set_item_info SIMPLE_INTERFACE
506 /* exit if setting is impossible */
507 if (!psz_name
|| !psz_value
|| !p_input
)
510 vlc_xml_decode(psz_value
);
512 /* handle each info element in a separate "if" clause */
513 if (!strcmp(psz_name
, "title"))
514 input_item_SetTitle(p_input
, psz_value
);
515 else if (!strcmp(psz_name
, "creator"))
516 input_item_SetArtist(p_input
, psz_value
);
517 else if (!strcmp(psz_name
, "album"))
518 input_item_SetAlbum(p_input
, psz_value
);
519 else if (!strcmp(psz_name
, "trackNum"))
520 input_item_SetTrackNum(p_input
, psz_value
);
521 else if (!strcmp(psz_name
, "duration"))
522 p_input
->i_duration
= atol(psz_value
) * INT64_C(1000);
523 else if (!strcmp(psz_name
, "annotation"))
524 input_item_SetDescription(p_input
, psz_value
);
525 else if (!strcmp(psz_name
, "info"))
526 input_item_SetURL(p_input
, psz_value
);
527 else if (!strcmp(psz_name
, "image") && *psz_value
)
528 input_item_SetArtURL(p_input
, psz_value
);
533 * \brief handles the <vlc:option> elements
535 static bool set_option SIMPLE_INTERFACE
538 /* exit if setting is impossible */
539 if (!psz_name
|| !psz_value
|| !p_input
)
542 vlc_xml_decode(psz_value
);
544 input_item_AddOption(p_input
, psz_value
, 0);
550 * \brief handles the <vlc:id> elements
552 static bool parse_vlcid SIMPLE_INTERFACE
554 VLC_UNUSED(p_input
); VLC_UNUSED(psz_name
);
555 xspf_sys_t
*sys
= (xspf_sys_t
*) opaque
;
557 sys
->i_track_id
= atoi(psz_value
);
562 * \brief parse the vlc:node of a XSPF playlist
564 static bool parse_vlcnode_node COMPLEX_INTERFACE
566 input_item_t
*p_input_item
= p_input_node
->p_item
;
567 char *psz_title
= NULL
;
572 /* read all extension node attributes */
573 const char *psz_attr
= get_node_attribute(p_xml_reader
, "title");
576 psz_title
= strdup(psz_attr
);
577 if (likely(psz_title
!= NULL
))
578 vlc_xml_decode(psz_title
);
581 /* attribute title is mandatory */
584 msg_Warn(p_stream
, "<vlc:node> requires \"title\" attribute");
587 input_item_t
*p_new_input
=
588 input_item_NewDirectory("vlc://nop", psz_title
, ITEM_NET_UNKNOWN
);
593 input_item_node_AppendItem(p_input_node
, p_new_input
);
594 p_input_item
= p_new_input
;
597 /* parse the child elements */
598 static const xml_elem_hnd_t pl_elements
[] =
599 { {"vlc:node", {.cmplx
= parse_vlcnode_node
}, true },
600 {"vlc:item", {.cmplx
= parse_extitem_node
}, true },
601 {"vlc:id", {.smpl
= parse_vlcid
}, false },
602 {"vlc:option", {.smpl
= set_option
}, false },
605 bool b_ret
= parse_node(p_stream
, p_input_node
, p_input_item
,
606 p_xml_reader
, psz_element
,
607 pl_elements
, ARRAY_SIZE(pl_elements
));
610 input_item_Release(p_new_input
);
616 * \brief parse the extension node of a XSPF playlist
618 static bool parse_extension_node COMPLEX_INTERFACE
623 const char *psz_application
= get_node_attribute(p_xml_reader
, "application");
624 if (!psz_application
)
626 msg_Warn(p_stream
, "<extension> requires \"application\" attribute");
630 /* Skip the extension if the application is not vlc
631 This will skip all children of the current node */
632 if (strcmp(psz_application
, "http://www.videolan.org/vlc/playlist/0"))
634 msg_Dbg(p_stream
, "Skipping \"%s\" extension tag", psz_application
);
635 return skip_element( NULL
, NULL
, p_xml_reader
, psz_element
, b_empty_node
);
638 /* parse the child elements */
639 static const xml_elem_hnd_t pl_elements
[] =
640 { {"vlc:node", {.cmplx
= parse_vlcnode_node
}, true },
641 {"vlc:id", {.smpl
= parse_vlcid
}, false },
642 {"vlc:option", {.smpl
= set_option
}, false },
645 return parse_node(p_stream
, p_input_node
, p_input_node
->p_item
,
646 p_xml_reader
, psz_element
,
647 pl_elements
, ARRAY_SIZE(pl_elements
));
651 * \brief parse the extension item node of a XSPF playlist
654 static bool parse_extitem_node COMPLEX_INTERFACE
656 VLC_UNUSED(psz_element
);
657 xspf_sys_t
*sys
= p_stream
->p_sys
;
658 input_item_t
*p_new_input
= NULL
;
664 const char *psz_tid
= get_node_attribute(p_xml_reader
, "tid");
666 i_tid
= atoi(psz_tid
);
668 /* attribute href is mandatory */
669 if (!psz_tid
|| i_tid
< 0)
671 msg_Warn(p_stream
, "<vlc:item> requires valid \"tid\" attribute");
675 if (i_tid
>= sys
->i_tracklist_entries
||
676 !(p_new_input
= sys
->pp_tracklist
[ i_tid
]) )
678 msg_Warn(p_stream
, "non existing \"tid\" %d referenced", i_tid
);
682 input_item_node_AppendItem(p_input_node
, p_new_input
);
683 input_item_Release(p_new_input
);
684 sys
->pp_tracklist
[i_tid
] = NULL
;
690 * \brief skips complex element content that we can't manage
692 static bool skip_element COMPLEX_INTERFACE
694 VLC_UNUSED(p_stream
); VLC_UNUSED(p_input_node
);
699 /* Const reference changes if we read again */
700 char *psz_end
= psz_element
? strdup(psz_element
) : NULL
;
704 while(lvl
> 0 && b_ret
)
706 switch (xml_ReaderNextNode(p_xml_reader
, &name
))
708 case XML_READER_STARTELEM
:
709 if( !xml_ReaderIsEmptyElement( p_xml_reader
) )
712 case XML_READER_ENDELEM
:
715 case XML_READER_NONE
:
716 case XML_READER_ERROR
:
724 if(b_ret
) /* Ensure we end on same node type */
725 b_ret
&= (!name
|| !psz_end
|| !strcmp(psz_end
, name
));