playlist demuxers: return -1 in case of error, 0 in case of eof, 1 else; like any...
[vlc.git] / modules / demux / playlist / itml.c
1 /*******************************************************************************
2  * itml.c : iTunes Music Library import functions
3  *******************************************************************************
4  * Copyright (C) 2007 the VideoLAN team
5  * $Id: $
6  *
7  * Authors: Yoann Peronneau <yoann@videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 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 General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, 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 #include <vlc/vlc.h>
29 #include <vlc_demux.h>
30
31 #include "playlist.h"
32 #include "vlc_xml.h"
33 #include "vlc_strings.h"
34 #include "vlc_url.h"
35 #include "itml.h"
36
37 struct demux_sys_t
38 {
39     int i_ntracks;
40 };
41
42 static int Control( demux_t *, int, va_list );
43 static int Demux( demux_t * );
44
45 /**
46  * \brief iTML submodule initialization function
47  */
48 int E_(Import_iTML)( vlc_object_t *p_this )
49 {
50     DEMUX_BY_EXTENSION_OR_FORCED_MSG( ".xml", "itml",
51                                       "using iTunes Media Library reader" );
52     return VLC_SUCCESS;
53 }
54
55 void E_(Close_iTML)( vlc_object_t *p_this )
56 {
57     demux_t *p_demux = (demux_t *)p_this;
58     free( p_demux->p_sys );
59 }
60
61 /**
62  * \brief demuxer function for iTML parsing
63  */
64 int Demux( demux_t *p_demux )
65 {
66     int i_ret = VLC_SUCCESS;
67     xml_t *p_xml = NULL;
68     xml_reader_t *p_xml_reader = NULL;
69     char *psz_name = NULL;
70     INIT_PLAYLIST_STUFF;
71     p_demux->p_sys->i_ntracks = 0;
72
73     /* create new xml parser from stream */
74     p_xml = xml_Create( p_demux );
75     if( !p_xml )
76         i_ret = VLC_ENOMOD;
77     else
78     {
79         p_xml_reader = xml_ReaderCreate( p_xml, p_demux->s );
80         if( !p_xml_reader )
81             i_ret = VLC_EGENERIC;
82     }
83
84     /* locating the root node */
85     if( i_ret == VLC_SUCCESS )
86     {
87         do
88         {
89             if( xml_ReaderRead( p_xml_reader ) != 1 )
90             {
91                 msg_Err( p_demux, "can't read xml stream" );
92                 i_ret = VLC_EGENERIC;
93             }
94         } while( i_ret == VLC_SUCCESS &&
95                  xml_ReaderNodeType( p_xml_reader ) != XML_READER_STARTELEM );
96     }
97     /* checking root node name */
98     if( i_ret == VLC_SUCCESS )
99     {
100         psz_name = xml_ReaderName( p_xml_reader );
101         if( !psz_name || strcmp( psz_name, "plist" ) )
102         {
103             msg_Err( p_demux, "invalid root node name: %s", psz_name );
104             i_ret = VLC_EGENERIC;
105         }
106         FREE_NAME();
107     }
108
109     if( i_ret == VLC_SUCCESS )
110     {
111         xml_elem_hnd_t pl_elements[] =
112             { {"dict",    COMPLEX_CONTENT, {.cmplx = parse_plist_dict} } };
113         i_ret = parse_plist_node( p_demux, p_playlist, p_current_input,
114                                      NULL, p_xml_reader, "plist",
115                                      pl_elements );
116         HANDLE_PLAY_AND_RELEASE;
117     }
118
119     if( p_xml_reader )
120         xml_ReaderDelete( p_xml, p_xml_reader );
121     if( p_xml )
122         xml_Delete( p_xml );
123     return 0; /* Needed for correct operation of go back */
124 }
125
126 /** \brief dummy function for demux callback interface */
127 static int Control( demux_t *p_demux, int i_query, va_list args )
128 {
129     return VLC_EGENERIC;
130 }
131
132 /**
133  * \brief parse the root node of the playlist
134  */
135 static vlc_bool_t parse_plist_node COMPLEX_INTERFACE
136 {
137     char *psz_name = NULL;
138     char *psz_value = NULL;
139     vlc_bool_t b_version_found = VLC_FALSE;
140
141     /* read all playlist attributes */
142     while( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
143     {
144         psz_name = xml_ReaderName( p_xml_reader );
145         psz_value = xml_ReaderValue( p_xml_reader );
146         if( !psz_name || !psz_value )
147         {
148             msg_Err( p_demux, "invalid xml stream @ <plist>" );
149             FREE_ATT();
150             return VLC_FALSE;
151         }
152         /* attribute: version */
153         if( !strcmp( psz_name, "version" ) )
154         {
155             b_version_found = VLC_TRUE;
156             if( strcmp( psz_value, "1.0" ) )
157                 msg_Warn( p_demux, "unsupported iTunes Media Library version" );
158         }
159         /* unknown attribute */
160         else
161             msg_Warn( p_demux, "invalid <plist> attribute:\"%s\"", psz_name);
162
163         FREE_ATT();
164     }
165     /* attribute version is mandatory !!! */
166     if( !b_version_found )
167         msg_Warn( p_demux, "<plist> requires \"version\" attribute" );
168
169     return parse_dict( p_demux, p_playlist, p_input_item, NULL, p_xml_reader,
170                        "plist", p_handlers );
171 }
172
173 /**
174  * \brief parse a <dict>
175  * \param COMPLEX_INTERFACE
176  */
177 static vlc_bool_t parse_dict COMPLEX_INTERFACE
178 {
179     int i_node;
180     char *psz_name = NULL;
181     char *psz_value = NULL;
182     char *psz_key = NULL;
183     xml_elem_hnd_t *p_handler = NULL;
184
185     while( xml_ReaderRead( p_xml_reader ) == 1 )
186     {
187         i_node = xml_ReaderNodeType( p_xml_reader );
188         switch( i_node )
189         {
190             case XML_READER_NONE:
191                 break;
192
193             case XML_READER_STARTELEM:
194                 /*  element start tag  */
195                 psz_name = xml_ReaderName( p_xml_reader );
196                 if( !psz_name || !*psz_name )
197                 {
198                     msg_Err( p_demux, "invalid xml stream" );
199                     FREE_ATT_KEY();
200                     return VLC_FALSE;
201                 }
202                 /* choose handler */
203                 for( p_handler = p_handlers;
204                      p_handler->name && strcmp( psz_name, p_handler->name );
205                      p_handler++ );
206                 if( !p_handler->name )
207                 {
208                     msg_Err( p_demux, "unexpected element <%s>", psz_name );
209                     FREE_ATT_KEY();
210                     return VLC_FALSE;
211                 }
212                 FREE_NAME();
213                 /* complex content is parsed in a separate function */
214                 if( p_handler->type == COMPLEX_CONTENT )
215                 {
216                     if( p_handler->pf_handler.cmplx( p_demux,
217                                                      p_playlist,
218                                                      p_input_item,
219                                                      NULL,
220                                                      p_xml_reader,
221                                                      p_handler->name,
222                                                      NULL ) )
223                     {
224                         p_handler = NULL;
225                         FREE_ATT_KEY();
226                     }
227                     else
228                     {
229                         FREE_ATT_KEY();
230                         return VLC_FALSE;
231                     }
232                 }
233                 break;
234
235             case XML_READER_TEXT:
236                 /* simple element content */
237                 FREE_ATT();
238                 psz_value = xml_ReaderValue( p_xml_reader );
239                 if( !psz_value )
240                 {
241                     msg_Err( p_demux, "invalid xml stream" );
242                     FREE_ATT_KEY();
243                     return VLC_FALSE;
244                 }
245                 break;
246
247             case XML_READER_ENDELEM:
248                 /* element end tag */
249                 psz_name = xml_ReaderName( p_xml_reader );
250                 if( !psz_name )
251                 {
252                     msg_Err( p_demux, "invalid xml stream" );
253                     FREE_ATT_KEY();
254                     return VLC_FALSE;
255                 }
256                 /* leave if the current parent node <track> is terminated */
257                 if( !strcmp( psz_name, psz_element ) )
258                 {
259                     FREE_ATT_KEY();
260                     return VLC_TRUE;
261                 }
262                 /* there MUST have been a start tag for that element name */
263                 if( !p_handler || !p_handler->name
264                     || strcmp( p_handler->name, psz_name ))
265                 {
266                     msg_Err( p_demux, "there's no open element left for <%s>",
267                              psz_name );
268                     FREE_ATT_KEY();
269                     return VLC_FALSE;
270                 }
271                 /* special case: key */
272                 if( !strcmp( p_handler->name, "key" ) )
273                 {
274                     psz_key = strdup( psz_value );
275                 }
276                 /* call the simple handler */
277                 else if( p_handler->pf_handler.smpl )
278                 {
279                     p_handler->pf_handler.smpl( p_track, psz_key, psz_value );
280                 }
281                 FREE_ATT();
282                 p_handler = NULL;
283                 break;
284
285             default:
286                 /* unknown/unexpected xml node */
287                 msg_Err( p_demux, "unexpected xml node %i", i_node );
288                 FREE_ATT_KEY();
289                 return VLC_FALSE;
290         }
291         FREE_NAME();
292     }
293     msg_Err( p_demux, "unexpected end of xml data" );
294     FREE_ATT_KEY();
295     return VLC_FALSE;
296 }
297
298 static vlc_bool_t parse_plist_dict COMPLEX_INTERFACE
299 {
300     xml_elem_hnd_t pl_elements[] =
301         { {"dict",    COMPLEX_CONTENT, {.cmplx = parse_tracks_dict} },
302           {"array",   SIMPLE_CONTENT,  {NULL} },
303           {"key",     SIMPLE_CONTENT,  {NULL} },
304           {"integer", SIMPLE_CONTENT,  {NULL} },
305           {"string",  SIMPLE_CONTENT,  {NULL} },
306           {"date",    SIMPLE_CONTENT,  {NULL} },
307           {"true",    SIMPLE_CONTENT,  {NULL} },
308           {"false",   SIMPLE_CONTENT,  {NULL} },
309           {NULL,      UNKNOWN_CONTENT, {NULL} }
310         };
311
312     return parse_dict( p_demux, p_playlist, p_input_item, NULL, p_xml_reader,
313                        "dict", pl_elements );
314 }
315
316 static vlc_bool_t parse_tracks_dict COMPLEX_INTERFACE
317 {
318     xml_elem_hnd_t tracks_elements[] =
319         { {"dict",    COMPLEX_CONTENT, {.cmplx = parse_track_dict} },
320           {"key",     SIMPLE_CONTENT,  {NULL} },
321           {NULL,      UNKNOWN_CONTENT, {NULL} }
322         };
323
324     parse_dict( p_demux, p_playlist, p_input_item, NULL, p_xml_reader,
325                 "dict", tracks_elements );
326
327     msg_Info( p_demux, "added %i tracks successfully",
328               p_demux->p_sys->i_ntracks );
329
330     return VLC_TRUE;
331 }
332
333 static vlc_bool_t parse_track_dict COMPLEX_INTERFACE
334 {
335     input_item_t *p_new_input = NULL;
336     int i_ret = -1;
337     char *psz_uri = NULL;
338     p_track = new_track();
339
340     xml_elem_hnd_t track_elements[] =
341         { {"array",   COMPLEX_CONTENT, {.cmplx = skip_element} },
342           {"key",     SIMPLE_CONTENT,  {.smpl = save_data} },
343           {"integer", SIMPLE_CONTENT,  {.smpl = save_data} },
344           {"string",  SIMPLE_CONTENT,  {.smpl = save_data} },
345           {"date",    SIMPLE_CONTENT,  {.smpl = save_data} },
346           {"true",    SIMPLE_CONTENT,  {NULL} },
347           {"false",   SIMPLE_CONTENT,  {NULL} },
348           {NULL,      UNKNOWN_CONTENT, {NULL} }
349         };
350
351     i_ret = parse_dict( p_demux, p_playlist, p_input_item, p_track,
352                         p_xml_reader, "dict", track_elements );
353
354     msg_Dbg( p_demux, "name: %s, artist: %s, album: %s, genre: %s, trackNum: %s, location: %s",
355              p_track->name, p_track->artist, p_track->album, p_track->genre, p_track->trackNum, p_track->location );
356
357     if( !p_track->location )
358     {
359         msg_Err( p_demux, "Track needs Location" );
360         free_track( p_track );
361         return VLC_FALSE;
362     }
363
364     psz_uri = decode_URI_duplicate( p_track->location );
365
366     if( psz_uri )
367     {
368         if( strlen( psz_uri ) > 17 &&
369             !strncmp( psz_uri, "file://localhost/", 17 ) )
370         {
371             /* remove 'localhost/' */
372             strcpy( psz_uri + 7, psz_uri + 17 );
373             msg_Info( p_demux, "Adding '%s'", psz_uri );
374
375             p_new_input = input_ItemNewExt( p_playlist, psz_uri,
376                                             NULL, 0, NULL, -1 );
377             input_ItemAddSubItem( p_input_item, p_new_input );
378
379             /* add meta info */
380             add_meta( p_new_input, p_track );
381
382             p_demux->p_sys->i_ntracks++;
383         }
384         else
385         {
386             msg_Err( p_demux, "Don't know how to handle %s", psz_uri );
387         }
388         free( psz_uri );
389     }
390
391     free_track( p_track );
392     return i_ret;
393 }
394
395 static track_elem_t *new_track()
396 {
397     track_elem_t *p_track = NULL;
398     p_track = (track_elem_t *)malloc( sizeof( track_elem_t ) );
399     if( p_track )
400     {
401         p_track->name = NULL;
402         p_track->artist = NULL;
403         p_track->album = NULL;
404         p_track->genre = NULL;
405         p_track->trackNum = NULL;
406         p_track->location = NULL;
407         p_track->duration = 0;
408     }
409     return p_track;
410 }
411
412 static void free_track( track_elem_t *p_track )
413 {
414     fprintf( stderr, "free track\n" );
415     if ( !p_track )
416         return;
417
418     FREE( p_track->name )
419     FREE( p_track->artist )
420     FREE( p_track->album )
421     FREE( p_track->genre )
422     FREE( p_track->trackNum )
423     FREE( p_track->location )
424     p_track->duration = 0;
425     free( p_track );
426 }
427
428 static vlc_bool_t save_data SIMPLE_INTERFACE
429 {
430     /* exit if setting is impossible */
431     if( !psz_name || !psz_value || !p_track )
432         return VLC_FALSE;
433
434     /* re-convert xml special characters inside psz_value */
435     resolve_xml_special_chars( psz_value );
436
437 #define SAVE_INFO( name, value ) \
438     if( !strcmp( psz_name, name ) ) { p_track->value = strdup( psz_value ); }
439
440     SAVE_INFO( "Name", name )
441     else SAVE_INFO( "Artist", artist )
442     else SAVE_INFO( "Album", album )
443     else SAVE_INFO( "Genre", genre )
444     else SAVE_INFO( "Track Number", trackNum )
445     else SAVE_INFO( "Location", location )
446     else if( !strcmp( psz_name, "Total Time" ) )
447     {
448         long i_num = atol( psz_value );
449         p_track->duration = (mtime_t) i_num*1000;
450     }
451     return VLC_TRUE;
452 }
453
454 /**
455  * \brief handles the supported <track> sub-elements
456  */
457 static vlc_bool_t add_meta( input_item_t *p_input_item,
458                             track_elem_t *p_track )
459 {
460     /* exit if setting is impossible */
461     if( !p_input_item || !p_track )
462         return VLC_FALSE;
463
464 #define SET_INFO( func, prop ) \
465     if( p_track->prop ) { func( p_input_item, p_track->prop ); }
466
467     SET_INFO( input_item_SetTitle, name )
468     SET_INFO( input_item_SetArtist, artist )
469     SET_INFO( input_item_SetAlbum, album )
470     SET_INFO( input_item_SetGenre, genre )
471     SET_INFO( input_item_SetTrackNum, trackNum )
472     SET_INFO( input_item_SetDuration, duration )
473     return VLC_TRUE;
474 }
475
476 /**
477  * \brief skips complex element content that we can't manage
478  */
479 static vlc_bool_t skip_element COMPLEX_INTERFACE
480 {
481     char *psz_endname;
482
483     while( xml_ReaderRead( p_xml_reader ) == 1 )
484     {
485         if( xml_ReaderNodeType( p_xml_reader ) == XML_READER_ENDELEM )
486         {
487             psz_endname = xml_ReaderName( p_xml_reader );
488             if( !psz_endname )
489                 return VLC_FALSE;
490             if( !strcmp( psz_element, psz_endname ) )
491             {
492                 free( psz_endname );
493                 return VLC_TRUE;
494             }
495             else
496                 free( psz_endname );
497         }
498     }
499     return VLC_FALSE;
500 }