1 /*****************************************************************************
2 * podcast.c : podcast playlist imports
3 *****************************************************************************
4 * Copyright (C) 2005-2009 VLC authors and VideoLAN
7 * Authors: Antoine Cellerier <dionoea -at- videolan -dot- 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 /*****************************************************************************
26 *****************************************************************************/
31 #include <vlc_common.h>
32 #include <vlc_demux.h>
36 #include <vlc_strings.h>
38 /*****************************************************************************
40 *****************************************************************************/
41 static int Demux( demux_t *p_demux);
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 /* "specs" : http://phobos.apple.com/static/iTunesRSS.html */
62 static int Demux( demux_t *p_demux )
67 xml_reader_t *p_xml_reader;
68 char *psz_elname = NULL;
69 char *psz_item_mrl = NULL;
70 char *psz_item_size = NULL;
71 char *psz_item_type = NULL;
72 char *psz_item_name = NULL;
73 char *psz_item_date = NULL;
74 char *psz_item_author = NULL;
75 char *psz_item_category = NULL;
76 char *psz_item_duration = NULL;
77 char *psz_item_keywords = NULL;
78 char *psz_item_subtitle = NULL;
79 char *psz_item_summary = NULL;
80 char *psz_art_url = NULL;
83 input_item_t *p_input;
84 input_item_node_t *p_subitems = NULL;
86 input_item_t *p_current_input = GetCurrentItem(p_demux);
88 p_xml_reader = xml_ReaderCreate( p_demux, p_demux->s );
94 if( xml_ReaderNextNode( p_xml_reader, &node ) != XML_READER_STARTELEM )
96 msg_Err( p_demux, "invalid file (no root node)" );
100 if( strcmp( node, "rss" ) )
102 msg_Err( p_demux, "invalid root node <%s>", node );
106 p_subitems = input_item_node_Create( p_current_input );
108 while( (i_type = xml_ReaderNextNode( p_xml_reader, &node )) > 0 )
112 case XML_READER_STARTELEM:
115 psz_elname = strdup( node );
116 if( unlikely(!node) )
119 if( !strcmp( node, "item" ) )
121 else if( !strcmp( node, "image" ) )
124 // Read the attributes
125 const char *attr, *value;
126 while( (attr = xml_ReaderNextAttr( p_xml_reader, &value )) )
128 if( !strcmp( node, "enclosure" ) )
131 if( !strcmp( attr, "url" ) )
133 else if( !strcmp( attr, "length" ) )
135 else if( !strcmp( attr, "type" ) )
140 *p = strdup( value );
143 msg_Dbg( p_demux,"unhandled attribute %s in <%s>",
147 msg_Dbg( p_demux,"unhandled attribute %s in <%s>",
153 case XML_READER_TEXT:
155 if(!psz_elname) break;
157 /* item specific meta data */
162 if( !strcmp( psz_elname, "title" ) )
164 else if( !strcmp( psz_elname, "itunes:author" ) ||
165 !strcmp( psz_elname, "author" ) )
166 /* <author> isn't standard iTunes podcast stuff */
167 p = &psz_item_author;
168 else if( !strcmp( psz_elname, "itunes:summary" ) ||
169 !strcmp( psz_elname, "description" ) )
170 /* <description> isn't standard iTunes podcast stuff */
171 p = &psz_item_summary;
172 else if( !strcmp( psz_elname, "pubDate" ) )
174 else if( !strcmp( psz_elname, "itunes:category" ) )
175 p = &psz_item_category;
176 else if( !strcmp( psz_elname, "itunes:duration" ) )
177 p = &psz_item_duration;
178 else if( !strcmp( psz_elname, "itunes:keywords" ) )
179 p = &psz_item_keywords;
180 else if( !strcmp( psz_elname, "itunes:subtitle" ) )
181 p = &psz_item_subtitle;
188 /* toplevel meta data */
191 if( !strcmp( psz_elname, "title" ) )
192 input_item_SetName( p_current_input, node );
193 #define ADD_GINFO( info, name ) \
194 else if( !strcmp( psz_elname, name ) ) \
195 input_item_AddInfo( p_current_input, _("Podcast Info"), \
197 ADD_GINFO( _("Podcast Link"), "link" )
198 ADD_GINFO( _("Podcast Copyright"), "copyright" )
199 ADD_GINFO( _("Podcast Category"), "itunes:category" )
200 ADD_GINFO( _("Podcast Keywords"), "itunes:keywords" )
201 ADD_GINFO( _("Podcast Subtitle"), "itunes:subtitle" )
203 else if( !strcmp( psz_elname, "itunes:summary" ) ||
204 !strcmp( psz_elname, "description" ) )
205 { /* <description> isn't standard iTunes podcast stuff */
206 input_item_AddInfo( p_current_input,
207 _( "Podcast Info" ), _( "Podcast Summary" ),
213 if( !strcmp( psz_elname, "url" ) )
216 psz_art_url = strdup( node );
219 msg_Dbg( p_demux, "unhandled text in element <%s>",
226 case XML_READER_ENDELEM:
228 FREENULL( psz_elname );
230 if( !strcmp( node, "item" ) )
232 if( psz_item_mrl == NULL )
235 msg_Warn( p_demux, "invalid XML item, skipping %s",
238 msg_Warn( p_demux, "invalid XML item, skipped" );
239 FREENULL( psz_item_name );
240 FREENULL( psz_item_size );
241 FREENULL( psz_item_type );
242 FREENULL( psz_item_date );
243 FREENULL( psz_item_author );
244 FREENULL( psz_item_category );
245 FREENULL( psz_item_duration );
246 FREENULL( psz_item_keywords );
247 FREENULL( psz_item_subtitle );
248 FREENULL( psz_item_summary );
249 FREENULL( psz_art_url );
250 FREENULL( psz_elname );
254 resolve_xml_special_chars( psz_item_mrl );
255 resolve_xml_special_chars( psz_item_name );
256 p_input = input_item_New( psz_item_mrl, psz_item_name );
257 FREENULL( psz_item_mrl );
258 FREENULL( psz_item_name );
260 if( p_input == NULL )
261 break; /* FIXME: meta data memory leaks? */
263 /* Set the duration if available */
264 if( psz_item_duration )
265 input_item_SetDuration( p_input, strTimeToMTime( psz_item_duration ) );
267 #define ADD_INFO( info, field ) \
269 input_item_AddInfo( p_input, _( "Podcast Info" ), (info), "%s", \
272 ADD_INFO( _("Podcast Publication Date"), psz_item_date );
273 ADD_INFO( _("Podcast Author"), psz_item_author );
274 ADD_INFO( _("Podcast Subcategory"), psz_item_category );
275 ADD_INFO( _("Podcast Duration"), psz_item_duration );
276 ADD_INFO( _("Podcast Keywords"), psz_item_keywords );
277 ADD_INFO( _("Podcast Subtitle"), psz_item_subtitle );
278 ADD_INFO( _("Podcast Summary"), psz_item_summary );
279 ADD_INFO( _("Podcast Type"), psz_item_type );
282 /* Add the global art url to this item, if any */
285 resolve_xml_special_chars( psz_art_url );
286 input_item_SetArtURL( p_input, psz_art_url );
291 input_item_AddInfo( p_input,
296 FREENULL( psz_item_size );
298 input_item_node_AppendItem( p_subitems, p_input );
299 vlc_gc_decref( p_input );
302 else if( !strcmp( node, "image" ) )
313 msg_Warn( p_demux, "error while parsing data" );
318 xml_ReaderDelete( p_xml_reader );
320 input_item_node_PostAndDelete( p_subitems );
321 vlc_gc_decref(p_current_input);
322 return 0; /* Needed for correct operation of go back */
325 free( psz_item_name );
326 free( psz_item_mrl );
327 free( psz_item_size );
328 free( psz_item_type );
329 free( psz_item_date );
330 free( psz_item_author );
331 free( psz_item_category );
332 free( psz_item_duration );
333 free( psz_item_keywords );
334 free( psz_item_subtitle );
335 free( psz_item_summary );
340 xml_ReaderDelete( p_xml_reader );
342 input_item_node_Delete( p_subitems );
344 vlc_gc_decref(p_current_input);
348 static mtime_t strTimeToMTime( const char *psz )
351 switch( sscanf( psz, "%u:%u:%u", &h, &m, &s ) )
354 return (mtime_t)( ( h*60 + m )*60 + s ) * 1000000;
356 return (mtime_t)( h*60 + m ) * 1000000;