itml: factorize.
[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 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31
32 #include <vlc_common.h>
33 #include <vlc_demux.h>
34
35 #include "playlist.h"
36 #include "vlc_xml.h"
37 #include "vlc_strings.h"
38 #include "vlc_url.h"
39 #include "itml.h"
40
41 struct demux_sys_t
42 {
43     int i_ntracks;
44 };
45
46 static int Control( demux_t *, int, va_list );
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_BY_EXTENSION_OR_FORCED_MSG( ".xml", "itml",
55                                       "using iTunes Media Library reader" );
56     return VLC_SUCCESS;
57 }
58
59 void Close_iTML( vlc_object_t *p_this )
60 {
61     demux_t *p_demux = (demux_t *)p_this;
62     free( p_demux->p_sys );
63 }
64
65 /**
66  * \brief demuxer function for iTML parsing
67  */
68 int Demux( demux_t *p_demux )
69 {
70     xml_t *p_xml = NULL;
71     xml_reader_t *p_xml_reader = NULL;
72     char *psz_name = NULL;
73
74     INIT_PLAYLIST_STUFF;
75     p_demux->p_sys->i_ntracks = 0;
76
77     /* create new xml parser from stream */
78     p_xml = xml_Create( p_demux );
79     if( !p_xml )
80         goto end;
81
82     p_xml_reader = xml_ReaderCreate( p_xml, p_demux->s );
83     if( !p_xml_reader )
84         goto end;
85
86     /* locating the root node */
87     do
88     {
89         if( xml_ReaderRead( p_xml_reader ) != 1 )
90         {
91             msg_Err( p_demux, "can't read xml stream" );
92             goto end;
93         }
94     } while( xml_ReaderNodeType( p_xml_reader ) != XML_READER_STARTELEM );
95
96     /* checking root node name */
97     psz_name = xml_ReaderName( p_xml_reader );
98     if( !psz_name || strcmp( psz_name, "plist" ) )
99     {
100         msg_Err( p_demux, "invalid root node name: %s", psz_name );
101         goto end;
102     }
103
104     xml_elem_hnd_t pl_elements[] =
105         { {"dict",    COMPLEX_CONTENT, {.cmplx = parse_plist_dict} } };
106     parse_plist_node( p_demux, p_current_input, NULL, p_xml_reader, "plist",
107                       pl_elements );
108
109     HANDLE_PLAY_AND_RELEASE;
110
111 end:
112     free( psz_name );
113     if( p_xml_reader )
114         xml_ReaderDelete( p_xml, p_xml_reader );
115     if( p_xml )
116         xml_Delete( p_xml );
117
118     /* Needed for correct operation of go back */
119     return 0;
120 }
121
122 /** \brief dummy function for demux callback interface */
123 static int Control( demux_t *p_demux, int i_query, va_list args )
124 {
125     VLC_UNUSED(p_demux); VLC_UNUSED(i_query); VLC_UNUSED(args);
126     return VLC_EGENERIC;
127 }
128
129 /**
130  * \brief parse the root node of the playlist
131  */
132 static bool parse_plist_node COMPLEX_INTERFACE
133 {
134     VLC_UNUSED(p_track); VLC_UNUSED(psz_element);
135     char *psz_name = NULL;
136     char *psz_value = NULL;
137     bool b_version_found = false;
138
139     /* read all playlist attributes */
140     while( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
141     {
142         psz_name = xml_ReaderName( p_xml_reader );
143         psz_value = xml_ReaderValue( p_xml_reader );
144         if( !psz_name || !psz_value )
145         {
146             msg_Err( p_demux, "invalid xml stream @ <plist>" );
147             FREE_ATT();
148             return false;
149         }
150         /* attribute: version */
151         if( !strcmp( psz_name, "version" ) )
152         {
153             b_version_found = true;
154             if( strcmp( psz_value, "1.0" ) )
155                 msg_Warn( p_demux, "unsupported iTunes Media Library version" );
156         }
157         /* unknown attribute */
158         else
159             msg_Warn( p_demux, "invalid <plist> attribute:\"%s\"", psz_name);
160
161         FREE_ATT();
162     }
163     /* attribute version is mandatory !!! */
164     if( !b_version_found )
165         msg_Warn( p_demux, "<plist> requires \"version\" attribute" );
166
167     return parse_dict( p_demux, p_input_item, NULL, p_xml_reader,
168                        "plist", p_handlers );
169 }
170
171 /**
172  * \brief parse a <dict>
173  * \param COMPLEX_INTERFACE
174  */
175 static bool parse_dict COMPLEX_INTERFACE
176 {
177     int i_node;
178     char *psz_name = NULL;
179     char *psz_value = NULL;
180     char *psz_key = NULL;
181     xml_elem_hnd_t *p_handler = NULL;
182
183     while( xml_ReaderRead( p_xml_reader ) == 1 )
184     {
185         i_node = xml_ReaderNodeType( p_xml_reader );
186         switch( i_node )
187         {
188             case XML_READER_NONE:
189                 break;
190
191             case XML_READER_STARTELEM:
192                 /*  element start tag  */
193                 psz_name = xml_ReaderName( p_xml_reader );
194                 if( !psz_name || !*psz_name )
195                 {
196                     msg_Err( p_demux, "invalid xml stream" );
197                     FREE_ATT_KEY();
198                     return false;
199                 }
200                 /* choose handler */
201                 for( p_handler = p_handlers;
202                      p_handler->name && strcmp( psz_name, p_handler->name );
203                      p_handler++ );
204                 if( !p_handler->name )
205                 {
206                     msg_Err( p_demux, "unexpected element <%s>", psz_name );
207                     FREE_ATT_KEY();
208                     return false;
209                 }
210                 FREE_NAME();
211                 /* complex content is parsed in a separate function */
212                 if( p_handler->type == COMPLEX_CONTENT )
213                 {
214                     if( p_handler->pf_handler.cmplx( p_demux,
215                                                      p_input_item,
216                                                      NULL,
217                                                      p_xml_reader,
218                                                      p_handler->name,
219                                                      NULL ) )
220                     {
221                         p_handler = NULL;
222                         FREE_ATT_KEY();
223                     }
224                     else
225                     {
226                         FREE_ATT_KEY();
227                         return false;
228                     }
229                 }
230                 break;
231
232             case XML_READER_TEXT:
233                 /* simple element content */
234                 FREE_ATT();
235                 psz_value = xml_ReaderValue( p_xml_reader );
236                 if( !psz_value )
237                 {
238                     msg_Err( p_demux, "invalid xml stream" );
239                     FREE_ATT_KEY();
240                     return false;
241                 }
242                 break;
243
244             case XML_READER_ENDELEM:
245                 /* element end tag */
246                 psz_name = xml_ReaderName( p_xml_reader );
247                 if( !psz_name )
248                 {
249                     msg_Err( p_demux, "invalid xml stream" );
250                     FREE_ATT_KEY();
251                     return false;
252                 }
253                 /* leave if the current parent node <track> is terminated */
254                 if( !strcmp( psz_name, psz_element ) )
255                 {
256                     FREE_ATT_KEY();
257                     return true;
258                 }
259                 /* there MUST have been a start tag for that element name */
260                 if( !p_handler || !p_handler->name
261                     || strcmp( p_handler->name, psz_name ))
262                 {
263                     msg_Err( p_demux, "there's no open element left for <%s>",
264                              psz_name );
265                     FREE_ATT_KEY();
266                     return false;
267                 }
268                 /* special case: key */
269                 if( !strcmp( p_handler->name, "key" ) )
270                 {
271                     psz_key = strdup( psz_value );
272                 }
273                 /* call the simple handler */
274                 else if( p_handler->pf_handler.smpl )
275                 {
276                     p_handler->pf_handler.smpl( p_track, psz_key, psz_value );
277                 }
278                 FREE_ATT();
279                 p_handler = NULL;
280                 break;
281
282             default:
283                 /* unknown/unexpected xml node */
284                 msg_Err( p_demux, "unexpected xml node %i", i_node );
285                 FREE_ATT_KEY();
286                 return false;
287         }
288         FREE_NAME();
289     }
290     msg_Err( p_demux, "unexpected end of xml data" );
291     FREE_ATT_KEY();
292     return false;
293 }
294
295 static bool parse_plist_dict COMPLEX_INTERFACE
296 {
297     VLC_UNUSED(p_track); VLC_UNUSED(psz_element); VLC_UNUSED(p_handlers);
298     xml_elem_hnd_t pl_elements[] =
299         { {"dict",    COMPLEX_CONTENT, {.cmplx = parse_tracks_dict} },
300           {"array",   SIMPLE_CONTENT,  {NULL} },
301           {"key",     SIMPLE_CONTENT,  {NULL} },
302           {"integer", SIMPLE_CONTENT,  {NULL} },
303           {"string",  SIMPLE_CONTENT,  {NULL} },
304           {"date",    SIMPLE_CONTENT,  {NULL} },
305           {"true",    SIMPLE_CONTENT,  {NULL} },
306           {"false",   SIMPLE_CONTENT,  {NULL} },
307           {NULL,      UNKNOWN_CONTENT, {NULL} }
308         };
309
310     return parse_dict( p_demux, p_input_item, NULL, p_xml_reader,
311                        "dict", pl_elements );
312 }
313
314 static bool parse_tracks_dict COMPLEX_INTERFACE
315 {
316     VLC_UNUSED(p_track); VLC_UNUSED(psz_element); VLC_UNUSED(p_handlers);
317     xml_elem_hnd_t tracks_elements[] =
318         { {"dict",    COMPLEX_CONTENT, {.cmplx = parse_track_dict} },
319           {"key",     SIMPLE_CONTENT,  {NULL} },
320           {NULL,      UNKNOWN_CONTENT, {NULL} }
321         };
322
323     parse_dict( p_demux, p_input_item, NULL, p_xml_reader,
324                 "dict", tracks_elements );
325
326     msg_Info( p_demux, "added %i tracks successfully",
327               p_demux->p_sys->i_ntracks );
328
329     return true;
330 }
331
332 static bool parse_track_dict COMPLEX_INTERFACE
333 {
334     VLC_UNUSED(psz_element); VLC_UNUSED(p_handlers);
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_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 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             memmove( psz_uri + 7, psz_uri + 17, strlen( psz_uri ) - 9 );
373             msg_Info( p_demux, "Adding '%s'", psz_uri );
374
375             p_new_input = input_item_New( p_demux, psz_uri, NULL );
376             input_item_AddSubItem( p_input_item, p_new_input );
377
378             /* add meta info */
379             add_meta( p_new_input, p_track );
380             vlc_gc_decref( p_new_input );
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     FREENULL( p_track->name );
419     FREENULL( p_track->artist );
420     FREENULL( p_track->album );
421     FREENULL( p_track->genre );
422     FREENULL( p_track->trackNum );
423     FREENULL( p_track->location );
424     p_track->duration = 0;
425     free( p_track );
426 }
427
428 static bool save_data SIMPLE_INTERFACE
429 {
430     /* exit if setting is impossible */
431     if( !psz_name || !psz_value || !p_track )
432         return 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 true;
452 }
453
454 /**
455  * \brief handles the supported <track> sub-elements
456  */
457 static bool 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 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 true;
474 }
475
476 /**
477  * \brief skips complex element content that we can't manage
478  */
479 static bool skip_element COMPLEX_INTERFACE
480 {
481     VLC_UNUSED(p_demux); VLC_UNUSED(p_input_item);
482     VLC_UNUSED(p_track); VLC_UNUSED(p_handlers);
483     char *psz_endname;
484
485     while( xml_ReaderRead( p_xml_reader ) == 1 )
486     {
487         if( xml_ReaderNodeType( p_xml_reader ) == XML_READER_ENDELEM )
488         {
489             psz_endname = xml_ReaderName( p_xml_reader );
490             if( !psz_endname )
491                 return false;
492             if( !strcmp( psz_element, psz_endname ) )
493             {
494                 free( psz_endname );
495                 return true;
496             }
497             else
498                 free( psz_endname );
499         }
500     }
501     return false;
502 }