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