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