qt: playlist: use item title if available
[vlc.git] / modules / demux / playlist / itml.c
1 /*******************************************************************************
2  * itml.c : iTunes Music Library import functions
3  *******************************************************************************
4  * Copyright (C) 2007 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Yoann Peronneau <yoann@videolan.org>
8  *
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.
13  *
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.
18  *
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  *******************************************************************************/
23 /**
24  * \file modules/demux/playlist/itml.c
25  * \brief iTunes Music Library import functions
26  */
27
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31
32 #include <vlc_common.h>
33 #include <vlc_demux.h>
34 #include <vlc_xml.h>
35 #include <vlc_strings.h>
36 #include <vlc_url.h>
37 #include <vlc_fixups.h>
38
39 #include "itml.h"
40 #include "playlist.h"
41
42 struct demux_sys_t
43 {
44     int i_ntracks;
45 };
46
47 static int Demux( demux_t * );
48
49 /**
50  * \brief iTML submodule initialization function
51  */
52 int Import_iTML( vlc_object_t *p_this )
53 {
54     demux_t *p_demux = (demux_t *)p_this;
55     CHECK_FILE();
56     if( !demux_IsPathExtension( p_demux, ".xml" )
57      && !demux_IsForced( p_demux, "itml" ) )
58         return VLC_EGENERIC; \
59     STANDARD_DEMUX_INIT_MSG( "using iTunes Media Library reader" );
60
61     const uint8_t *p_peek;
62     const ssize_t i_peek = vlc_stream_Peek( p_demux->s, &p_peek, 128 );
63     if ( i_peek < 32 ||
64          !strnstr( (const char *) p_peek, "<!DOCTYPE plist ", i_peek ) )
65     {
66         Close_iTML( p_this );
67         return VLC_EGENERIC;
68     }
69     return VLC_SUCCESS;
70 }
71
72 void Close_iTML( vlc_object_t *p_this )
73 {
74     demux_t *p_demux = (demux_t *)p_this;
75     free( p_demux->p_sys );
76 }
77
78 /**
79  * \brief demuxer function for iTML parsing
80  */
81 int Demux( demux_t *p_demux )
82 {
83     xml_reader_t *p_xml_reader;
84     const char *node;
85
86     input_item_t *p_current_input = GetCurrentItem(p_demux);
87     p_demux->p_sys->i_ntracks = 0;
88
89     /* create new xml parser from stream */
90     p_xml_reader = xml_ReaderCreate( p_demux, p_demux->s );
91     if( !p_xml_reader )
92         goto end;
93
94     /* locating the root node */
95     int type;
96     do
97     {
98         type = xml_ReaderNextNode( p_xml_reader, &node );
99         if( type <= 0 )
100         {
101             msg_Err( p_demux, "can't read xml stream" );
102             goto end;
103         }
104     }
105     while( type != XML_READER_STARTELEM );
106
107     /* checking root node name */
108     if( strcmp( node, "plist" ) )
109     {
110         msg_Err( p_demux, "invalid root node <%s>", node );
111         goto end;
112     }
113
114     input_item_node_t *p_subitems = input_item_node_Create( p_current_input );
115     xml_elem_hnd_t pl_elements[] =
116         { {"dict",    COMPLEX_CONTENT, {.cmplx = parse_plist_dict} } };
117     parse_plist_node( p_demux, p_subitems, NULL, p_xml_reader, "plist",
118                       pl_elements );
119     input_item_node_PostAndDelete( p_subitems );
120
121 end:
122     if( p_xml_reader )
123         xml_ReaderDelete( p_xml_reader );
124
125     /* Needed for correct operation of go back */
126     return 0;
127 }
128
129 /**
130  * \brief parse the root node of the playlist
131  */
132 static bool parse_plist_node( demux_t *p_demux, input_item_node_t *p_input_node,
133                               track_elem_t *p_track, xml_reader_t *p_xml_reader,
134                               const char *psz_element,
135                               xml_elem_hnd_t *p_handlers )
136 {
137     VLC_UNUSED(p_track); VLC_UNUSED(psz_element);
138     const char *attr, *value;
139     bool b_version_found = false;
140
141     /* read all playlist attributes */
142     while( (attr = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
143     {
144         /* attribute: version */
145         if( !strcmp( attr, "version" ) )
146         {
147             b_version_found = true;
148             if( strcmp( value, "1.0" ) )
149                 msg_Warn( p_demux, "unsupported iTunes Media Library version" );
150         }
151         /* unknown attribute */
152         else
153             msg_Warn( p_demux, "invalid <plist> attribute:\"%s\"", attr );
154     }
155
156     /* attribute version is mandatory !!! */
157     if( !b_version_found )
158         msg_Warn( p_demux, "<plist> requires \"version\" attribute" );
159
160     return parse_dict( p_demux, p_input_node, NULL, p_xml_reader,
161                        "plist", p_handlers );
162 }
163
164 /**
165  * \brief parse a <dict>
166  * \param COMPLEX_INTERFACE
167  */
168 static bool parse_dict( demux_t *p_demux, input_item_node_t *p_input_node,
169                         track_elem_t *p_track, xml_reader_t *p_xml_reader,
170                         const char *psz_element, xml_elem_hnd_t *p_handlers )
171 {
172     int i_node;
173     const char *node;
174     char *psz_value = NULL;
175     char *psz_key = NULL;
176     xml_elem_hnd_t *p_handler = NULL;
177     bool b_ret = false;
178
179     while( (i_node = xml_ReaderNextNode( p_xml_reader, &node )) > 0 )
180     {
181         switch( i_node )
182         {
183         /*  element start tag  */
184         case XML_READER_STARTELEM:
185             /* choose handler */
186             for( p_handler = p_handlers;
187                      p_handler->name && strcmp( node, p_handler->name );
188                      p_handler++ );
189             if( !p_handler->name )
190             {
191                 msg_Err( p_demux, "unexpected element <%s>", node );
192                 goto end;
193             }
194             /* complex content is parsed in a separate function */
195             if( p_handler->type == COMPLEX_CONTENT )
196             {
197                 if( p_handler->pf_handler.cmplx( p_demux, p_input_node, NULL,
198                                                  p_xml_reader, p_handler->name,
199                                                  NULL ) )
200                 {
201                     p_handler = NULL;
202                     FREE_ATT_KEY();
203                 }
204                 else
205                     goto end;
206             }
207             break;
208
209         /* simple element content */
210         case XML_READER_TEXT:
211             free( psz_value );
212             psz_value = strdup( node );
213             if( unlikely(!psz_value) )
214                 goto end;
215             break;
216
217         /* element end tag */
218         case XML_READER_ENDELEM:
219             /* leave if the current parent node <track> is terminated */
220             if( !strcmp( node, psz_element ) )
221             {
222                 b_ret = true;
223                 goto end;
224             }
225             /* there MUST have been a start tag for that element name */
226             if( !p_handler || !p_handler->name
227                 || strcmp( p_handler->name, node ) )
228             {
229                 msg_Err( p_demux, "there's no open element left for <%s>",
230                          node );
231                 goto end;
232             }
233             /* special case: key */
234             if( !strcmp( p_handler->name, "key" ) )
235             {
236                 free( psz_key );
237                 psz_key = strdup( psz_value );
238             }
239             /* call the simple handler */
240             else if( p_handler->pf_handler.smpl )
241             {
242                 p_handler->pf_handler.smpl( p_track, psz_key, psz_value );
243             }
244             FREE_ATT();
245             p_handler = NULL;
246             break;
247         }
248     }
249     msg_Err( p_demux, "unexpected end of XML data" );
250
251 end:
252     free( psz_value );
253     free( psz_key );
254     return b_ret;
255 }
256
257 static bool parse_plist_dict( demux_t *p_demux, input_item_node_t *p_input_node,
258                               track_elem_t *p_track, xml_reader_t *p_xml_reader,
259                               const char *psz_element,
260                               xml_elem_hnd_t *p_handlers )
261 {
262     VLC_UNUSED(p_track); VLC_UNUSED(psz_element); VLC_UNUSED(p_handlers);
263     xml_elem_hnd_t pl_elements[] =
264         { {"dict",    COMPLEX_CONTENT, {.cmplx = parse_tracks_dict} },
265           {"array",   SIMPLE_CONTENT,  {NULL} },
266           {"key",     SIMPLE_CONTENT,  {NULL} },
267           {"integer", SIMPLE_CONTENT,  {NULL} },
268           {"string",  SIMPLE_CONTENT,  {NULL} },
269           {"date",    SIMPLE_CONTENT,  {NULL} },
270           {"true",    SIMPLE_CONTENT,  {NULL} },
271           {"false",   SIMPLE_CONTENT,  {NULL} },
272           {NULL,      UNKNOWN_CONTENT, {NULL} }
273         };
274
275     return parse_dict( p_demux, p_input_node, NULL, p_xml_reader,
276                        "dict", pl_elements );
277 }
278
279 static bool parse_tracks_dict( demux_t *p_demux, input_item_node_t *p_input_node,
280                                track_elem_t *p_track, xml_reader_t *p_xml_reader,
281                                const char *psz_element,
282                                xml_elem_hnd_t *p_handlers )
283 {
284     VLC_UNUSED(p_track); VLC_UNUSED(psz_element); VLC_UNUSED(p_handlers);
285     xml_elem_hnd_t tracks_elements[] =
286         { {"dict",    COMPLEX_CONTENT, {.cmplx = parse_track_dict} },
287           {"key",     SIMPLE_CONTENT,  {NULL} },
288           {NULL,      UNKNOWN_CONTENT, {NULL} }
289         };
290
291     parse_dict( p_demux, p_input_node, NULL, p_xml_reader,
292                 "dict", tracks_elements );
293
294     msg_Info( p_demux, "added %i tracks successfully",
295               p_demux->p_sys->i_ntracks );
296
297     return true;
298 }
299
300 static bool parse_track_dict( demux_t *p_demux, input_item_node_t *p_input_node,
301                               track_elem_t *p_track, xml_reader_t *p_xml_reader,
302                               const char *psz_element,
303                               xml_elem_hnd_t *p_handlers )
304 {
305     VLC_UNUSED(psz_element); VLC_UNUSED(p_handlers);
306     input_item_t *p_new_input = NULL;
307     int i_ret;
308     p_track = new_track();
309
310     xml_elem_hnd_t track_elements[] =
311         { {"array",   COMPLEX_CONTENT, {.cmplx = skip_element} },
312           {"key",     SIMPLE_CONTENT,  {.smpl = save_data} },
313           {"integer", SIMPLE_CONTENT,  {.smpl = save_data} },
314           {"string",  SIMPLE_CONTENT,  {.smpl = save_data} },
315           {"date",    SIMPLE_CONTENT,  {.smpl = save_data} },
316           {"true",    SIMPLE_CONTENT,  {NULL} },
317           {"false",   SIMPLE_CONTENT,  {NULL} },
318           {NULL,      UNKNOWN_CONTENT, {NULL} }
319         };
320
321     i_ret = parse_dict( p_demux, p_input_node, p_track,
322                         p_xml_reader, "dict", track_elements );
323
324     msg_Dbg( p_demux, "name: %s, artist: %s, album: %s, genre: %s, trackNum: %s, location: %s",
325              p_track->name, p_track->artist, p_track->album, p_track->genre, p_track->trackNum, p_track->location );
326
327     if( !p_track->location )
328     {
329         msg_Err( p_demux, "Track needs Location" );
330         free_track( p_track );
331         return false;
332     }
333
334     msg_Info( p_demux, "Adding '%s'", p_track->location );
335     p_new_input = input_item_New( p_track->location, NULL );
336     input_item_node_AppendItem( p_input_node, p_new_input );
337
338     /* add meta info */
339     add_meta( p_new_input, p_track );
340     input_item_Release( p_new_input );
341
342     p_demux->p_sys->i_ntracks++;
343
344     free_track( p_track );
345     return i_ret;
346 }
347
348 static track_elem_t *new_track()
349 {
350     track_elem_t *p_track = malloc( sizeof *p_track );
351     if( likely( p_track ) )
352     {
353         p_track->name = NULL;
354         p_track->artist = NULL;
355         p_track->album = NULL;
356         p_track->genre = NULL;
357         p_track->trackNum = NULL;
358         p_track->location = NULL;
359         p_track->duration = 0;
360     }
361     return p_track;
362 }
363
364 static void free_track( track_elem_t *p_track )
365 {
366     if ( !p_track )
367         return;
368
369     FREENULL( p_track->name );
370     FREENULL( p_track->artist );
371     FREENULL( p_track->album );
372     FREENULL( p_track->genre );
373     FREENULL( p_track->trackNum );
374     FREENULL( p_track->location );
375     p_track->duration = 0;
376     free( p_track );
377 }
378
379 static bool save_data( track_elem_t *p_track, const char *psz_name,
380                        char *psz_value)
381 {
382     /* exit if setting is impossible */
383     if( !psz_name || !psz_value || !p_track )
384         return false;
385
386     /* re-convert xml special characters inside psz_value */
387     vlc_xml_decode( psz_value );
388
389 #define SAVE_INFO( name, value ) \
390     if( !strcmp( psz_name, name ) ) { p_track->value = strdup( psz_value ); }
391
392     SAVE_INFO( "Name", name )
393     else SAVE_INFO( "Artist", artist )
394     else SAVE_INFO( "Album", album )
395     else SAVE_INFO( "Genre", genre )
396     else SAVE_INFO( "Track Number", trackNum )
397     else SAVE_INFO( "Location", location )
398     else if( !strcmp( psz_name, "Total Time" ) )
399     {
400         long i_num = atol( psz_value );
401         p_track->duration = (mtime_t) i_num*1000;
402     }
403 #undef SAVE_INFO
404     return true;
405 }
406
407 /**
408  * \brief handles the supported <track> sub-elements
409  */
410 static bool add_meta( input_item_t *p_input_item, track_elem_t *p_track )
411 {
412     /* exit if setting is impossible */
413     if( !p_input_item || !p_track )
414         return false;
415
416 #define SET_INFO( type, prop ) \
417     if( p_track->prop ) {input_item_Set##type( p_input_item, p_track->prop );}
418     SET_INFO( Title, name )
419     SET_INFO( Artist, artist )
420     SET_INFO( Album, album )
421     SET_INFO( Genre, genre )
422     SET_INFO( TrackNum, trackNum )
423     SET_INFO( Duration, duration )
424 #undef SET_INFO
425     return true;
426 }
427
428 /**
429  * \brief skips complex element content that we can't manage
430  */
431 static bool skip_element( demux_t *p_demux, input_item_node_t *p_input_node,
432                           track_elem_t *p_track, xml_reader_t *p_xml_reader,
433                           const char *psz_element, xml_elem_hnd_t *p_handlers )
434 {
435     VLC_UNUSED(p_demux); VLC_UNUSED(p_input_node);
436     VLC_UNUSED(p_track); VLC_UNUSED(p_handlers);
437     const char *node;
438     int type;
439
440     while( (type = xml_ReaderNextNode( p_xml_reader, &node )) > 0 )
441         if( type == XML_READER_ENDELEM && !strcmp( psz_element, node ) )
442             return true;
443     return false;
444 }