1 /*******************************************************************************
2 * itml.c : iTunes Music Library import functions
3 *******************************************************************************
4 * Copyright (C) 2007 VLC authors and VideoLAN
7 * Authors: Yoann Peronneau <yoann@videolan.org>
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.
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.
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 *******************************************************************************/
24 * \file modules/demux/playlist/itml.c
25 * \brief iTunes Music Library import functions
32 #include <vlc_common.h>
33 #include <vlc_demux.h>
35 #include <vlc_strings.h>
37 #include <vlc_fixups.h>
47 static int Demux( demux_t * );
50 * \brief iTML submodule initialization function
52 int Import_iTML( vlc_object_t *p_this )
54 demux_t *p_demux = (demux_t *)p_this;
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" );
61 const uint8_t *p_peek;
62 const ssize_t i_peek = vlc_stream_Peek( p_demux->s, &p_peek, 128 );
64 !strnstr( (const char *) p_peek, "<!DOCTYPE plist ", i_peek ) )
72 void Close_iTML( vlc_object_t *p_this )
74 demux_t *p_demux = (demux_t *)p_this;
75 free( p_demux->p_sys );
79 * \brief demuxer function for iTML parsing
81 int Demux( demux_t *p_demux )
83 xml_reader_t *p_xml_reader;
86 input_item_t *p_current_input = GetCurrentItem(p_demux);
87 p_demux->p_sys->i_ntracks = 0;
89 /* create new xml parser from stream */
90 p_xml_reader = xml_ReaderCreate( p_demux, p_demux->s );
94 /* locating the root node */
98 type = xml_ReaderNextNode( p_xml_reader, &node );
101 msg_Err( p_demux, "can't read xml stream" );
105 while( type != XML_READER_STARTELEM );
107 /* checking root node name */
108 if( strcmp( node, "plist" ) )
110 msg_Err( p_demux, "invalid root node <%s>", node );
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",
119 input_item_node_PostAndDelete( p_subitems );
123 xml_ReaderDelete( p_xml_reader );
125 /* Needed for correct operation of go back */
130 * \brief parse the root node of the playlist
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 )
137 VLC_UNUSED(p_track); VLC_UNUSED(psz_element);
138 const char *attr, *value;
139 bool b_version_found = false;
141 /* read all playlist attributes */
142 while( (attr = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
144 /* attribute: version */
145 if( !strcmp( attr, "version" ) )
147 b_version_found = true;
148 if( strcmp( value, "1.0" ) )
149 msg_Warn( p_demux, "unsupported iTunes Media Library version" );
151 /* unknown attribute */
153 msg_Warn( p_demux, "invalid <plist> attribute:\"%s\"", attr );
156 /* attribute version is mandatory !!! */
157 if( !b_version_found )
158 msg_Warn( p_demux, "<plist> requires \"version\" attribute" );
160 return parse_dict( p_demux, p_input_node, NULL, p_xml_reader,
161 "plist", p_handlers );
165 * \brief parse a <dict>
166 * \param COMPLEX_INTERFACE
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 )
174 char *psz_value = NULL;
175 char *psz_key = NULL;
176 xml_elem_hnd_t *p_handler = NULL;
179 while( (i_node = xml_ReaderNextNode( p_xml_reader, &node )) > 0 )
183 /* element start tag */
184 case XML_READER_STARTELEM:
186 for( p_handler = p_handlers;
187 p_handler->name && strcmp( node, p_handler->name );
189 if( !p_handler->name )
191 msg_Err( p_demux, "unexpected element <%s>", node );
194 /* complex content is parsed in a separate function */
195 if( p_handler->type == COMPLEX_CONTENT )
197 if( p_handler->pf_handler.cmplx( p_demux, p_input_node, NULL,
198 p_xml_reader, p_handler->name,
209 /* simple element content */
210 case XML_READER_TEXT:
212 psz_value = strdup( node );
213 if( unlikely(!psz_value) )
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 ) )
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 ) )
229 msg_Err( p_demux, "there's no open element left for <%s>",
233 /* special case: key */
234 if( !strcmp( p_handler->name, "key" ) )
237 psz_key = strdup( psz_value );
239 /* call the simple handler */
240 else if( p_handler->pf_handler.smpl )
242 p_handler->pf_handler.smpl( p_track, psz_key, psz_value );
249 msg_Err( p_demux, "unexpected end of XML data" );
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 )
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} }
275 return parse_dict( p_demux, p_input_node, NULL, p_xml_reader,
276 "dict", pl_elements );
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 )
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} }
291 parse_dict( p_demux, p_input_node, NULL, p_xml_reader,
292 "dict", tracks_elements );
294 msg_Info( p_demux, "added %i tracks successfully",
295 p_demux->p_sys->i_ntracks );
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 )
305 VLC_UNUSED(psz_element); VLC_UNUSED(p_handlers);
306 input_item_t *p_new_input = NULL;
308 p_track = new_track();
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} }
321 i_ret = parse_dict( p_demux, p_input_node, p_track,
322 p_xml_reader, "dict", track_elements );
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 );
327 if( !p_track->location )
329 msg_Err( p_demux, "Track needs Location" );
330 free_track( p_track );
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 );
339 add_meta( p_new_input, p_track );
340 input_item_Release( p_new_input );
342 p_demux->p_sys->i_ntracks++;
344 free_track( p_track );
348 static track_elem_t *new_track()
350 track_elem_t *p_track = malloc( sizeof *p_track );
351 if( likely( p_track ) )
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;
364 static void free_track( track_elem_t *p_track )
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;
379 static bool save_data( track_elem_t *p_track, const char *psz_name,
382 /* exit if setting is impossible */
383 if( !psz_name || !psz_value || !p_track )
386 /* re-convert xml special characters inside psz_value */
387 vlc_xml_decode( psz_value );
389 #define SAVE_INFO( name, value ) \
390 if( !strcmp( psz_name, name ) ) { p_track->value = strdup( psz_value ); }
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" ) )
400 long i_num = atol( psz_value );
401 p_track->duration = (mtime_t) i_num*1000;
408 * \brief handles the supported <track> sub-elements
410 static bool add_meta( input_item_t *p_input_item, track_elem_t *p_track )
412 /* exit if setting is impossible */
413 if( !p_input_item || !p_track )
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 )
429 * \brief skips complex element content that we can't manage
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 )
435 VLC_UNUSED(p_demux); VLC_UNUSED(p_input_node);
436 VLC_UNUSED(p_track); VLC_UNUSED(p_handlers);
440 while( (type = xml_ReaderNextNode( p_xml_reader, &node )) > 0 )
441 if( type == XML_READER_ENDELEM && !strcmp( psz_element, node ) )