1 /*******************************************************************************
2 * xspf.c : XSPF playlist import functions
3 *******************************************************************************
4 * Copyright (C) 2006-2011 VLC authors and VideoLAN
7 * Authors: Daniel Stränger <vlc at schmaller dot de>
8 * Yoann Peronneau <yoann@videolan.org>
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 by
12 * 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 ******************************************************************************/
25 * \file modules/demux/playlist/xspf.c
26 * \brief XSPF playlist import functions
33 #include <vlc_common.h>
34 #include <vlc_demux.h>
37 #include <vlc_strings.h>
41 #define FREE_VALUE() do { free(psz_value);psz_value=NULL; } while(0)
43 #define SIMPLE_INTERFACE (input_item_t *p_input,\
44 const char *psz_name,\
46 #define COMPLEX_INTERFACE (demux_t *p_demux,\
47 input_item_node_t *p_input_node,\
48 xml_reader_t *p_xml_reader,\
49 const char *psz_element)
52 static bool parse_playlist_node COMPLEX_INTERFACE;
53 static bool parse_tracklist_node COMPLEX_INTERFACE;
54 static bool parse_track_node COMPLEX_INTERFACE;
55 static bool parse_extension_node COMPLEX_INTERFACE;
56 static bool parse_extitem_node COMPLEX_INTERFACE;
57 static bool set_item_info SIMPLE_INTERFACE;
58 static bool set_option SIMPLE_INTERFACE;
59 static bool skip_element COMPLEX_INTERFACE;
67 bool (*smpl) SIMPLE_INTERFACE;
68 bool (*cmplx) COMPLEX_INTERFACE;
74 input_item_t **pp_tracklist;
75 int i_tracklist_entries;
80 static int Demux(demux_t *);
83 * \brief XSPF submodule initialization function
85 int Import_xspf(vlc_object_t *p_this)
87 DEMUX_BY_EXTENSION_OR_FORCED_MSG(".xspf", "xspf-open",
88 "using XSPF playlist reader");
92 void Close_xspf(vlc_object_t *p_this)
94 demux_t *p_demux = (demux_t *)p_this;
95 demux_sys_t *p_sys = p_demux->p_sys;
96 for (int i = 0; i < p_sys->i_tracklist_entries; i++)
97 if (p_sys->pp_tracklist[i])
98 vlc_gc_decref(p_sys->pp_tracklist[i]);
99 free(p_sys->pp_tracklist);
100 free(p_sys->psz_base);
105 * \brief demuxer function for XSPF parsing
107 static int Demux(demux_t *p_demux)
110 xml_reader_t *p_xml_reader = NULL;
111 const char *name = NULL;
112 input_item_t *p_current_input = GetCurrentItem(p_demux);
113 p_demux->p_sys->pp_tracklist = NULL;
114 p_demux->p_sys->i_tracklist_entries = 0;
115 p_demux->p_sys->i_track_id = -1;
116 p_demux->p_sys->psz_base = NULL;
118 /* create new xml parser from stream */
119 p_xml_reader = xml_ReaderCreate(p_demux, p_demux->s);
123 /* locating the root node */
124 if (xml_ReaderNextNode(p_xml_reader, &name) != XML_READER_STARTELEM)
126 msg_Err(p_demux, "can't read xml stream");
130 /* checking root node name */
131 if (strcmp(name, "playlist"))
133 msg_Err(p_demux, "invalid root node name <%s>", name);
137 input_item_node_t *p_subitems =
138 input_item_node_Create(p_current_input);
140 i_ret = parse_playlist_node(p_demux, p_subitems,
141 p_xml_reader, "playlist") ? 0 : -1;
143 for (int i = 0 ; i < p_demux->p_sys->i_tracklist_entries ; i++)
145 input_item_t *p_new_input = p_demux->p_sys->pp_tracklist[i];
148 input_item_node_AppendItem(p_subitems, p_new_input);
152 input_item_node_PostAndDelete(p_subitems);
155 vlc_gc_decref(p_current_input);
157 xml_ReaderDelete(p_xml_reader);
158 return i_ret; /* Needed for correct operation of go back */
161 static const xml_elem_hnd_t *get_handler(const xml_elem_hnd_t *tab, size_t n, const char *name)
163 for (size_t i = 0; i < n / sizeof(xml_elem_hnd_t); i++)
164 if (!strcmp(name, tab[i].name))
168 #define get_handler(tab, name) get_handler(tab, sizeof tab, name)
171 * \brief parse the root node of a XSPF playlist
172 * \param p_demux demuxer instance
173 * \param p_input_item current input item
174 * \param p_xml_reader xml reader instance
175 * \param psz_element name of element to parse
177 static bool parse_playlist_node COMPLEX_INTERFACE
179 input_item_t *p_input_item = p_input_node->p_item;
180 char *psz_value = NULL;
181 bool b_version_found = false;
184 const xml_elem_hnd_t *p_handler = NULL;
186 static const xml_elem_hnd_t pl_elements[] =
187 { {"title", {.smpl = set_item_info}, false },
188 {"creator", {.smpl = set_item_info}, false },
189 {"annotation", {.smpl = set_item_info}, false },
190 {"info", {NULL}, false },
191 {"location", {NULL}, false },
192 {"identifier", {NULL}, false },
193 {"image", {.smpl = set_item_info}, false },
194 {"date", {NULL}, false },
195 {"license", {NULL}, false },
196 {"attribution", {.cmplx = skip_element}, true },
197 {"link", {NULL}, false },
198 {"meta", {NULL}, false },
199 {"extension", {.cmplx = parse_extension_node}, true },
200 {"trackList", {.cmplx = parse_tracklist_node}, true },
202 /* read all playlist attributes */
203 const char *name, *value;
204 while ((name = xml_ReaderNextAttr(p_xml_reader, &value)) != NULL)
206 /* attribute: version */
207 if (!strcmp(name, "version"))
209 b_version_found = true;
210 if (strcmp(value, "0") && strcmp(value, "1"))
211 msg_Warn(p_demux, "unsupported XSPF version %s", value);
213 /* attribute: xmlns */
214 else if (!strcmp(name, "xmlns") || !strcmp(name, "xmlns:vlc"))
216 else if (!strcmp(name, "xml:base") && psz_value)
218 free(p_demux->p_sys->psz_base);
219 p_demux->p_sys->psz_base = strdup(psz_value);
221 /* unknown attribute */
223 msg_Warn(p_demux, "invalid <playlist> attribute: \"%s\"", name);
225 /* attribute version is mandatory !!! */
226 if (!b_version_found)
227 msg_Warn(p_demux, "<playlist> requires \"version\" attribute");
229 /* parse the child elements - we only take care of <trackList> */
231 while ((i_node = xml_ReaderNextNode(p_xml_reader, &name)) > 0)
234 /* element start tag */
235 case XML_READER_STARTELEM:
238 msg_Err(p_demux, "invalid XML stream");
242 p_handler = get_handler(pl_elements, name);
245 msg_Err(p_demux, "unexpected element <%s>", name);
248 /* complex content is parsed in a separate function */
249 if (p_handler->cmplx)
252 if (!p_handler->pf_handler.cmplx(p_demux, p_input_node,
253 p_xml_reader, p_handler->name))
259 /* simple element content */
260 case XML_READER_TEXT:
261 psz_value = strdup(name);
266 /* element end tag */
267 case XML_READER_ENDELEM:
268 /* leave if the current parent node <playlist> is terminated */
269 if (!strcmp(name, psz_element))
274 /* there MUST have been a start tag for that element name */
275 if (!p_handler || !p_handler->name || strcmp(p_handler->name, name))
277 msg_Err(p_demux, "there's no open element left for <%s>", name);
281 if (p_handler->pf_handler.smpl)
282 p_handler->pf_handler.smpl(p_input_item, p_handler->name, psz_value);
294 * \brief parses the tracklist node which only may contain <track>s
296 static bool parse_tracklist_node COMPLEX_INTERFACE
298 VLC_UNUSED(psz_element);
300 unsigned i_ntracks = 0;
303 /* now parse the <track>s */
304 while ((i_node = xml_ReaderNextNode(p_xml_reader, &name)) > 0)
306 if (i_node == XML_READER_STARTELEM)
308 if (strcmp(name, "track"))
310 msg_Err(p_demux, "unexpected child of <trackList>: <%s>",
315 /* parse the track data in a separate function */
316 if (parse_track_node(p_demux, p_input_node, p_xml_reader, "track"))
319 else if (i_node == XML_READER_ENDELEM)
323 /* the <trackList> has to be terminated */
324 if (i_node != XML_READER_ENDELEM)
326 msg_Err(p_demux, "there's a missing </trackList>");
329 if (strcmp(name, "trackList"))
331 msg_Err(p_demux, "expected: </trackList>, found: </%s>", name);
335 msg_Dbg(p_demux, "parsed %u tracks successfully", i_ntracks);
340 * \brief parse one track element
341 * \param COMPLEX_INTERFACE
343 static bool parse_track_node COMPLEX_INTERFACE
345 input_item_t *p_input_item = p_input_node->p_item;
347 char *psz_value = NULL;
348 const xml_elem_hnd_t *p_handler = NULL;
349 demux_sys_t *p_sys = p_demux->p_sys;
352 static const xml_elem_hnd_t track_elements[] =
353 { {"location", {NULL}, false },
354 {"identifier", {NULL}, false },
355 {"title", {.smpl = set_item_info}, false },
356 {"creator", {.smpl = set_item_info}, false },
357 {"annotation", {.smpl = set_item_info}, false },
358 {"info", {.smpl = set_item_info}, false },
359 {"image", {.smpl = set_item_info}, false },
360 {"album", {.smpl = set_item_info}, false },
361 {"trackNum", {.smpl = set_item_info}, false },
362 {"duration", {.smpl = set_item_info}, false },
363 {"link", {NULL}, false },
364 {"meta", {NULL}, false },
365 {"extension", {.cmplx = parse_extension_node}, true },
368 input_item_t *p_new_input = input_item_New(NULL, NULL);
371 input_item_node_t *p_new_node = input_item_node_Create(p_new_input);
373 /* reset i_track_id */
374 p_sys->i_track_id = -1;
376 while ((i_node = xml_ReaderNextNode(p_xml_reader, &name)) > 0)
379 /* element start tag */
380 case XML_READER_STARTELEM:
383 msg_Err(p_demux, "invalid XML stream");
387 p_handler = get_handler(track_elements, name);
390 msg_Err(p_demux, "unexpected element <%s>", name);
393 /* complex content is parsed in a separate function */
394 if (p_handler->cmplx)
398 if (!p_handler->pf_handler.cmplx(p_demux, p_new_node,
399 p_xml_reader, p_handler->name)) {
400 input_item_node_Delete(p_new_node);
408 /* simple element content */
409 case XML_READER_TEXT:
411 psz_value = strdup(name);
412 if (unlikely(!psz_value))
416 /* element end tag */
417 case XML_READER_ENDELEM:
418 /* leave if the current parent node <track> is terminated */
419 if (!strcmp(name, psz_element))
423 /* Make sure we have a URI */
424 char *psz_uri = input_item_GetURI(p_new_input);
426 input_item_SetURI(p_new_input, "vlc://nop");
430 if (p_sys->i_track_id < 0
431 || (unsigned)p_sys->i_track_id >= (SIZE_MAX / sizeof(p_new_input)))
433 input_item_node_AppendNode(p_input_node, p_new_node);
434 vlc_gc_decref(p_new_input);
438 if (p_sys->i_track_id >= p_sys->i_tracklist_entries)
441 pp = realloc(p_sys->pp_tracklist,
442 (p_sys->i_track_id + 1) * sizeof(*pp));
445 vlc_gc_decref(p_new_input);
446 input_item_node_Delete(p_new_node);
449 p_sys->pp_tracklist = pp;
450 while (p_sys->i_track_id >= p_sys->i_tracklist_entries)
451 pp[p_sys->i_tracklist_entries++] = NULL;
453 else if (p_sys->pp_tracklist[p_sys->i_track_id] != NULL)
455 msg_Err(p_demux, "track ID %d collision", p_sys->i_track_id);
456 vlc_gc_decref(p_new_input);
457 input_item_node_Delete(p_new_node);
461 p_sys->pp_tracklist[ p_sys->i_track_id ] = p_new_input;
462 input_item_node_Delete(p_new_node);
465 /* there MUST have been a start tag for that element name */
466 if (!p_handler || !p_handler->name || strcmp(p_handler->name, name))
468 msg_Err(p_demux, "there's no open element left for <%s>", name);
472 /* special case: location */
473 if (!strcmp(p_handler->name, "location"))
475 if (psz_value == NULL)
476 input_item_SetURI(p_new_input, "vlc://nop");
478 /* FIXME (#4005): This is broken. Scheme-relative (//...) locations
479 * and anchors (#...) are not resolved correctly. Also,
480 * host-relative (/...) and directory-relative locations
481 * ("relative path" in vernacular) should be resolved.
482 * Last, psz_base should default to the XSPF resource
483 * location if missing (not the current working directory).
485 if (p_sys->psz_base && !strstr(psz_value, "://"))
488 if (asprintf(&psz_tmp, "%s%s", p_sys->psz_base, psz_value)
493 input_item_SetURI(p_new_input, psz_tmp);
497 input_item_SetURI(p_new_input, psz_value);
498 input_item_CopyOptions(p_input_item, p_new_input);
502 /* there MUST be an item */
503 if (p_handler->pf_handler.smpl)
504 p_handler->pf_handler.smpl(p_new_input, p_handler->name,
511 msg_Err(p_demux, "unexpected end of xml data");
515 input_item_node_Delete(p_new_node);
521 * \brief handles the supported <track> sub-elements
523 static bool set_item_info SIMPLE_INTERFACE
525 /* exit if setting is impossible */
526 if (!psz_name || !psz_value || !p_input)
529 /* re-convert xml special characters inside psz_value */
530 resolve_xml_special_chars(psz_value);
532 /* handle each info element in a separate "if" clause */
533 if (!strcmp(psz_name, "title"))
534 input_item_SetTitle(p_input, psz_value);
535 else if (!strcmp(psz_name, "creator"))
536 input_item_SetArtist(p_input, psz_value);
537 else if (!strcmp(psz_name, "album"))
538 input_item_SetAlbum(p_input, psz_value);
539 else if (!strcmp(psz_name, "trackNum"))
540 input_item_SetTrackNum(p_input, psz_value);
541 else if (!strcmp(psz_name, "duration"))
543 long i_num = atol(psz_value);
544 input_item_SetDuration(p_input, (mtime_t) i_num*1000);
546 else if (!strcmp(psz_name, "annotation"))
547 input_item_SetDescription(p_input, psz_value);
548 else if (!strcmp(psz_name, "info"))
549 input_item_SetURL(p_input, psz_value);
550 else if (!strcmp(psz_name, "image"))
551 input_item_SetArtURL(p_input, psz_value);
556 * \brief handles the <vlc:option> elements
558 static bool set_option SIMPLE_INTERFACE
560 /* exit if setting is impossible */
561 if (!psz_name || !psz_value || !p_input)
564 /* re-convert xml special characters inside psz_value */
565 resolve_xml_special_chars(psz_value);
567 input_item_AddOption(p_input, psz_value, 0);
573 * \brief parse the extension node of a XSPF playlist
575 static bool parse_extension_node COMPLEX_INTERFACE
577 input_item_t *p_input_item = p_input_node->p_item;
578 char *psz_value = NULL;
579 char *psz_title = NULL;
580 char *psz_application = NULL;
582 bool b_release_input_item = false;
583 const xml_elem_hnd_t *p_handler = NULL;
584 input_item_t *p_new_input = NULL;
586 static const xml_elem_hnd_t pl_elements[] =
587 { {"vlc:node", {.cmplx = parse_extension_node}, true },
588 {"vlc:item", {.cmplx = parse_extitem_node}, true },
589 {"vlc:id", {NULL}, false },
590 {"vlc:option", {.smpl = set_option}, false },
593 /* read all extension node attributes */
594 const char *name, *value;
595 while ((name = xml_ReaderNextAttr(p_xml_reader, &value)) != NULL)
597 /* attribute: title */
598 if (!strcmp(name, "title"))
601 psz_title = strdup(value);
602 if (likely(psz_title != NULL))
603 resolve_xml_special_chars(psz_title);
605 /* extension attribute: application */
606 else if (!strcmp(name, "application"))
608 free(psz_application);
609 psz_application = strdup(value);
611 /* unknown attribute */
613 msg_Warn(p_demux, "invalid <%s> attribute:\"%s\"", psz_element,
617 /* attribute title is mandatory except for <extension> */
618 if (!strcmp(psz_element, "vlc:node"))
622 msg_Warn(p_demux, "<vlc:node> requires \"title\" attribute");
625 p_new_input = input_item_NewWithType("vlc://nop", psz_title,
627 ITEM_TYPE_DIRECTORY);
631 input_item_node_AppendItem(p_input_node, p_new_input);
632 p_input_item = p_new_input;
633 b_release_input_item = true;
637 else if (!strcmp(psz_element, "extension"))
639 if (!psz_application)
641 msg_Warn(p_demux, "<extension> requires \"application\" attribute");
644 /* Skip the extension if the application is not vlc
645 This will skip all children of the current node */
646 else if (strcmp(psz_application, "http://www.videolan.org/vlc/playlist/0"))
648 msg_Dbg(p_demux, "Skipping \"%s\" extension tag", psz_application);
649 free(psz_application);
650 /* Skip all children */
651 for (unsigned lvl = 1; lvl;)
652 switch (xml_ReaderNextNode(p_xml_reader, NULL))
654 case XML_READER_STARTELEM: lvl++; break;
655 case XML_READER_ENDELEM: lvl--; break;
656 case 0: case -1: return -1;
661 free(psz_application);
664 /* parse the child elements */
665 while ((i_node = xml_ReaderNextNode(p_xml_reader, &name)) > 0)
669 /* element start tag */
670 case XML_READER_STARTELEM:
673 msg_Err(p_demux, "invalid xml stream");
675 if (b_release_input_item) vlc_gc_decref(p_new_input);
679 p_handler = get_handler(pl_elements, name);
682 msg_Err(p_demux, "unexpected element <%s>", name);
684 if (b_release_input_item) vlc_gc_decref(p_new_input);
687 /* complex content is parsed in a separate function */
688 if (p_handler->cmplx)
690 if (p_handler->pf_handler.cmplx(p_demux,
701 if (b_release_input_item) vlc_gc_decref(p_new_input);
707 case XML_READER_TEXT:
708 /* simple element content */
710 psz_value = strdup(name);
711 if (unlikely(!psz_value))
714 if (b_release_input_item) vlc_gc_decref(p_new_input);
719 /* element end tag */
720 case XML_READER_ENDELEM:
721 /* leave if the current parent node is terminated */
722 if (!strcmp(name, psz_element))
725 if (b_release_input_item) vlc_gc_decref(p_new_input);
728 /* there MUST have been a start tag for that element name */
729 if (!p_handler || !p_handler->name
730 || strcmp(p_handler->name, name))
732 msg_Err(p_demux, "there's no open element left for <%s>",
735 if (b_release_input_item) vlc_gc_decref(p_new_input);
739 /* special tag <vlc:id> */
740 if (!strcmp(p_handler->name, "vlc:id"))
742 p_demux->p_sys->i_track_id = atoi(psz_value);
744 else if (p_handler->pf_handler.smpl)
746 p_handler->pf_handler.smpl(p_input_item, p_handler->name,
754 if (b_release_input_item) vlc_gc_decref(p_new_input);
759 * \brief parse the extension item node of a XSPF playlist
761 static bool parse_extitem_node COMPLEX_INTERFACE
763 VLC_UNUSED(psz_element);
764 input_item_t *p_new_input = NULL;
767 /* read all extension item attributes */
768 const char *name, *value;
769 while ((name = xml_ReaderNextAttr(p_xml_reader, &value)) != NULL)
771 /* attribute: href */
772 if (!strcmp(name, "tid"))
774 /* unknown attribute */
776 msg_Warn(p_demux, "invalid <vlc:item> attribute: \"%s\"", name);
779 /* attribute href is mandatory */
782 msg_Warn(p_demux, "<vlc:item> requires \"tid\" attribute");
786 if (i_tid >= p_demux->p_sys->i_tracklist_entries)
788 msg_Warn(p_demux, "invalid \"tid\" attribute");
792 p_new_input = p_demux->p_sys->pp_tracklist[ i_tid ];
795 input_item_node_AppendItem(p_input_node, p_new_input);
796 vlc_gc_decref(p_new_input);
797 p_demux->p_sys->pp_tracklist[i_tid] = NULL;
804 * \brief skips complex element content that we can't manage
806 static bool skip_element COMPLEX_INTERFACE
808 VLC_UNUSED(p_demux); VLC_UNUSED(p_input_node);
809 VLC_UNUSED(psz_element);
811 for (unsigned lvl = 1; lvl;)
812 switch (xml_ReaderNextNode(p_xml_reader, NULL))
814 case XML_READER_STARTELEM: lvl++; break;
815 case XML_READER_ENDELEM: lvl--; break;
816 case 0: case -1: return false;