1 /*****************************************************************************
2 * podcast.c : podcast playlist imports
3 *****************************************************************************
4 * Copyright (C) 2005-2009 the VideoLAN team
7 * Authors: Antoine Cellerier <dionoea -at- videolan -dot- org>
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.
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.
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 *****************************************************************************/
24 /*****************************************************************************
26 *****************************************************************************/
31 #include <vlc_common.h>
32 #include <vlc_demux.h>
37 /*****************************************************************************
39 *****************************************************************************/
40 static int Demux( demux_t *p_demux);
41 static int Control( demux_t *p_demux, int i_query, va_list args );
42 static mtime_t strTimeToMTime( const char *psz );
44 /*****************************************************************************
45 * Import_podcast: main import function
46 *****************************************************************************/
47 int Import_podcast( vlc_object_t *p_this )
49 demux_t *p_demux = (demux_t *)p_this;
51 if( !demux_IsForced( p_demux, "podcast" ) )
54 p_demux->pf_demux = Demux;
55 p_demux->pf_control = Control;
56 msg_Dbg( p_demux, "using podcast reader" );
61 /*****************************************************************************
62 * Deactivate: frees unused data
63 *****************************************************************************/
64 void Close_podcast( vlc_object_t *p_this )
69 /* "specs" : http://phobos.apple.com/static/iTunesRSS.html */
70 static int Demux( demux_t *p_demux )
77 xml_reader_t *p_xml_reader = NULL;
78 char *psz_elname = NULL;
79 char *psz_item_mrl = NULL;
80 char *psz_item_size = NULL;
81 char *psz_item_type = NULL;
82 char *psz_item_name = NULL;
83 char *psz_item_date = NULL;
84 char *psz_item_author = NULL;
85 char *psz_item_category = NULL;
86 char *psz_item_duration = NULL;
87 char *psz_item_keywords = NULL;
88 char *psz_item_subtitle = NULL;
89 char *psz_item_summary = NULL;
90 char *psz_art_url = NULL;
92 input_item_t *p_input;
93 input_item_node_t *p_subitems = NULL;
95 input_item_t *p_current_input = GetCurrentItem(p_demux);
97 p_xml = xml_Create( p_demux );
101 p_xml_reader = xml_ReaderCreate( p_xml, p_demux->s );
106 /* check root node */
107 if( xml_ReaderRead( p_xml_reader ) != 1 )
109 msg_Err( p_demux, "invalid file (no root node)" );
113 while( xml_ReaderNodeType( p_xml_reader ) == XML_READER_NONE )
115 if( xml_ReaderRead( p_xml_reader ) != 1 )
117 msg_Err( p_demux, "invalid file (no root node)" );
122 if( xml_ReaderNodeType( p_xml_reader ) != XML_READER_STARTELEM ||
123 ( psz_elname = xml_ReaderName( p_xml_reader ) ) == NULL ||
124 strcmp( psz_elname, "rss" ) )
126 msg_Err( p_demux, "invalid root node %i, %s",
127 xml_ReaderNodeType( p_xml_reader ), psz_elname );
130 FREENULL( psz_elname );
132 p_subitems = input_item_node_Create( p_current_input );
134 while( (i_ret = xml_ReaderRead( p_xml_reader )) == 1 )
137 i_type = xml_ReaderNodeType( p_xml_reader );
144 case XML_READER_STARTELEM:
146 // Read the element name
148 psz_elname = xml_ReaderName( p_xml_reader );
152 if( !strcmp( psz_elname, "item" ) )
156 else if( !strcmp( psz_elname, "image" ) )
161 // Read the attributes
162 while( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
164 char *psz_name = xml_ReaderName( p_xml_reader );
165 char *psz_value = xml_ReaderValue( p_xml_reader );
166 if( !psz_name || !psz_value )
173 if( !strcmp( psz_elname, "enclosure" ) )
175 if( !strcmp( psz_name, "url" ) )
177 free( psz_item_mrl );
178 psz_item_mrl = psz_value;
180 else if( !strcmp( psz_name, "length" ) )
182 free( psz_item_size );
183 psz_item_size = psz_value;
185 else if( !strcmp( psz_name, "type" ) )
187 free( psz_item_type );
188 psz_item_type = psz_value;
192 msg_Dbg( p_demux,"unhandled attribure %s in element %s",
193 psz_name, psz_elname );
199 msg_Dbg( p_demux,"unhandled attribure %s in element %s",
200 psz_name, psz_elname );
207 case XML_READER_TEXT:
209 if(!psz_elname) break;
211 char *psz_text = xml_ReaderValue( p_xml_reader );
213 #define SET_DATA( field, name ) \
214 else if( !strcmp( psz_elname, name ) ) \
218 /* item specific meta data */
221 if( !strcmp( psz_elname, "title" ) )
223 psz_item_name = psz_text;
225 else if( !strcmp( psz_elname, "itunes:author" ) ||
226 !strcmp( psz_elname, "author" ) )
227 { /* <author> isn't standard iTunes podcast stuff */
228 psz_item_author = psz_text;
230 else if( !strcmp( psz_elname, "itunes:summary" ) ||
231 !strcmp( psz_elname, "description" ) )
232 { /* <description> isn't standard iTunes podcast stuff */
233 psz_item_summary = psz_text;
235 SET_DATA( psz_item_date, "pubDate" )
236 SET_DATA( psz_item_category, "itunes:category" )
237 SET_DATA( psz_item_duration, "itunes:duration" )
238 SET_DATA( psz_item_keywords, "itunes:keywords" )
239 SET_DATA( psz_item_subtitle, "itunes:subtitle" )
245 /* toplevel meta data */
246 else if( b_image == false )
248 if( !strcmp( psz_elname, "title" ) )
250 input_item_SetName( p_current_input, psz_text );
252 #define ADD_GINFO( info, name ) \
253 else if( !strcmp( psz_elname, name ) ) \
255 input_item_AddInfo( p_current_input, _("Podcast Info"), \
256 _( info ), "%s", psz_text ); \
258 ADD_GINFO( "Podcast Link", "link" )
259 ADD_GINFO( "Podcast Copyright", "copyright" )
260 ADD_GINFO( "Podcast Category", "itunes:category" )
261 ADD_GINFO( "Podcast Keywords", "itunes:keywords" )
262 ADD_GINFO( "Podcast Subtitle", "itunes:subtitle" )
264 else if( !strcmp( psz_elname, "itunes:summary" ) ||
265 !strcmp( psz_elname, "description" ) )
266 { /* <description> isn't standard iTunes podcast stuff */
267 input_item_AddInfo( p_current_input,
268 _( "Podcast Info" ), _( "Podcast Summary" ),
275 if( !strcmp( psz_elname, "url" ) )
278 psz_art_url = psz_text;
282 msg_Dbg( p_demux, "unhandled text in element '%s'",
290 case XML_READER_ENDELEM:
292 // Read the element name
294 psz_elname = xml_ReaderName( p_xml_reader );
297 if( !strcmp( psz_elname, "item" ) )
299 if( psz_item_mrl == NULL )
301 msg_Err( p_demux, "invalid XML (no enclosure markup)" );
304 p_input = input_item_New( p_demux, psz_item_mrl, psz_item_name );
305 if( p_input == NULL ) break;
306 #define ADD_INFO( info, field ) \
307 if( field ) { input_item_AddInfo( p_input, \
308 _( "Podcast Info" ), _( info ), "%s", field ); }
309 ADD_INFO( "Podcast Publication Date", psz_item_date );
310 ADD_INFO( "Podcast Author", psz_item_author );
311 ADD_INFO( "Podcast Subcategory", psz_item_category );
312 ADD_INFO( "Podcast Duration", psz_item_duration );
313 ADD_INFO( "Podcast Keywords", psz_item_keywords );
314 ADD_INFO( "Podcast Subtitle", psz_item_subtitle );
315 ADD_INFO( "Podcast Summary", psz_item_summary );
316 ADD_INFO( "Podcast Type", psz_item_type );
319 /* Set the duration if available */
320 if( psz_item_duration )
321 input_item_SetDuration( p_input, strTimeToMTime( psz_item_duration ) );
322 /* Add the global art url to this item, if any */
324 input_item_SetArtURL( p_input, psz_art_url );
328 input_item_AddInfo( p_input,
334 input_item_node_AppendItem( p_subitems, p_input );
335 vlc_gc_decref( p_input );
336 FREENULL( psz_item_name );
337 FREENULL( psz_item_mrl );
338 FREENULL( psz_item_size );
339 FREENULL( psz_item_type );
340 FREENULL( psz_item_date );
341 FREENULL( psz_item_author );
342 FREENULL( psz_item_category );
343 FREENULL( psz_item_duration );
344 FREENULL( psz_item_keywords );
345 FREENULL( psz_item_subtitle );
346 FREENULL( psz_item_summary );
349 else if( !strcmp( psz_elname, "image" ) )
354 psz_elname = strdup( "" );
363 msg_Warn( p_demux, "error while parsing data" );
368 xml_ReaderDelete( p_xml, p_xml_reader );
371 input_item_AddSubItemTree( p_subitems );
372 input_item_node_Delete( p_subitems );
373 vlc_gc_decref(p_current_input);
374 return 0; /* Needed for correct operation of go back */
377 free( psz_item_name );
378 free( psz_item_mrl );
379 free( psz_item_size );
380 free( psz_item_type );
381 free( psz_item_date );
382 free( psz_item_author );
383 free( psz_item_category );
384 free( psz_item_duration );
385 free( psz_item_keywords );
386 free( psz_item_subtitle );
387 free( psz_item_summary );
392 xml_ReaderDelete( p_xml, p_xml_reader );
397 input_item_node_Delete( p_subitems );
400 vlc_gc_decref(p_current_input);
404 static int Control( demux_t *p_demux, int i_query, va_list args )
406 VLC_UNUSED(p_demux); VLC_UNUSED(i_query); VLC_UNUSED(args);
410 static mtime_t strTimeToMTime( const char *psz )
413 switch( sscanf( psz, "%u:%u:%u", &h, &m, &s ) )
416 return (mtime_t)( ( h*60 + m )*60 + s ) * 1000000;
418 return (mtime_t)( h*60 + m ) * 1000000;