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 )
75 xml_reader_t *p_xml_reader;
76 char *psz_elname = NULL;
77 char *psz_item_mrl = NULL;
78 char *psz_item_size = NULL;
79 char *psz_item_type = NULL;
80 char *psz_item_name = NULL;
81 char *psz_item_date = NULL;
82 char *psz_item_author = NULL;
83 char *psz_item_category = NULL;
84 char *psz_item_duration = NULL;
85 char *psz_item_keywords = NULL;
86 char *psz_item_subtitle = NULL;
87 char *psz_item_summary = NULL;
88 char *psz_art_url = NULL;
91 input_item_t *p_input;
92 input_item_node_t *p_subitems = NULL;
94 input_item_t *p_current_input = GetCurrentItem(p_demux);
96 p_xml_reader = xml_ReaderCreate( p_demux, p_demux->s );
101 /* check root node */
102 if( xml_ReaderNextNode( p_xml_reader, &node ) != XML_READER_STARTELEM )
104 msg_Err( p_demux, "invalid file (no root node)" );
108 if( strcmp( node, "rss" ) )
110 msg_Err( p_demux, "invalid root node <%s>", node );
114 p_subitems = input_item_node_Create( p_current_input );
116 while( (i_type = xml_ReaderNextNode( p_xml_reader, &node )) > 0 )
120 case XML_READER_STARTELEM:
123 psz_elname = strdup( node );
124 if( unlikely(!node) )
127 if( !strcmp( node, "item" ) )
129 else if( !strcmp( node, "image" ) )
132 // Read the attributes
133 const char *attr, *value;
134 while( (attr = xml_ReaderNextAttr( p_xml_reader, &value )) )
136 if( !strcmp( node, "enclosure" ) )
139 if( !strcmp( attr, "url" ) )
141 else if( !strcmp( attr, "length" ) )
143 else if( !strcmp( attr, "type" ) )
148 *p = strdup( value );
151 msg_Dbg( p_demux,"unhandled attribute %s in <%s>",
155 msg_Dbg( p_demux,"unhandled attribute %s in <%s>",
161 case XML_READER_TEXT:
163 if(!psz_elname) break;
165 /* item specific meta data */
170 if( !strcmp( psz_elname, "title" ) )
172 else if( !strcmp( psz_elname, "itunes:author" ) ||
173 !strcmp( psz_elname, "author" ) )
174 /* <author> isn't standard iTunes podcast stuff */
175 p = &psz_item_author;
176 else if( !strcmp( psz_elname, "itunes:summary" ) ||
177 !strcmp( psz_elname, "description" ) )
178 /* <description> isn't standard iTunes podcast stuff */
179 p = &psz_item_summary;
180 else if( !strcmp( psz_elname, "pubDate" ) )
182 else if( !strcmp( psz_elname, "itunes:category" ) )
183 p = &psz_item_category;
184 else if( !strcmp( psz_elname, "itunes:duration" ) )
185 p = &psz_item_duration;
186 else if( !strcmp( psz_elname, "itunes:keywords" ) )
187 p = &psz_item_keywords;
188 else if( !strcmp( psz_elname, "itunes:subtitle" ) )
189 p = &psz_item_subtitle;
196 /* toplevel meta data */
199 if( !strcmp( psz_elname, "title" ) )
200 input_item_SetName( p_current_input, node );
201 #define ADD_GINFO( info, name ) \
202 else if( !strcmp( psz_elname, name ) ) \
203 input_item_AddInfo( p_current_input, _("Podcast Info"), \
205 ADD_GINFO( _("Podcast Link"), "link" )
206 ADD_GINFO( _("Podcast Copyright"), "copyright" )
207 ADD_GINFO( _("Podcast Category"), "itunes:category" )
208 ADD_GINFO( _("Podcast Keywords"), "itunes:keywords" )
209 ADD_GINFO( _("Podcast Subtitle"), "itunes:subtitle" )
211 else if( !strcmp( psz_elname, "itunes:summary" ) ||
212 !strcmp( psz_elname, "description" ) )
213 { /* <description> isn't standard iTunes podcast stuff */
214 input_item_AddInfo( p_current_input,
215 _( "Podcast Info" ), _( "Podcast Summary" ),
221 if( !strcmp( psz_elname, "url" ) )
224 psz_art_url = strdup( node );
227 msg_Dbg( p_demux, "unhandled text in element <%s>",
234 case XML_READER_ENDELEM:
236 FREENULL( psz_elname );
238 if( !strcmp( node, "item" ) )
240 if( psz_item_mrl == NULL )
242 msg_Err( p_demux, "invalid XML (no enclosure markup)" );
246 p_input = input_item_New( psz_item_mrl, psz_item_name );
247 FREENULL( psz_item_mrl );
248 FREENULL( psz_item_name );
250 if( p_input == NULL )
251 break; /* FIXME: meta data memory leaks? */
253 /* Set the duration if available */
254 if( psz_item_duration )
255 input_item_SetDuration( p_input, strTimeToMTime( psz_item_duration ) );
257 #define ADD_INFO( info, field ) \
259 input_item_AddInfo( p_input, _( "Podcast Info" ), (info), "%s", \
262 ADD_INFO( _("Podcast Publication Date"), psz_item_date );
263 ADD_INFO( _("Podcast Author"), psz_item_author );
264 ADD_INFO( _("Podcast Subcategory"), psz_item_category );
265 ADD_INFO( _("Podcast Duration"), psz_item_duration );
266 ADD_INFO( _("Podcast Keywords"), psz_item_keywords );
267 ADD_INFO( _("Podcast Subtitle"), psz_item_subtitle );
268 ADD_INFO( _("Podcast Summary"), psz_item_summary );
269 ADD_INFO( _("Podcast Type"), psz_item_type );
272 /* Add the global art url to this item, if any */
274 input_item_SetArtURL( p_input, psz_art_url );
278 input_item_AddInfo( p_input,
283 FREENULL( psz_item_size );
285 input_item_node_AppendItem( p_subitems, p_input );
286 vlc_gc_decref( p_input );
289 else if( !strcmp( node, "image" ) )
300 msg_Warn( p_demux, "error while parsing data" );
305 xml_ReaderDelete( p_xml_reader );
307 input_item_node_PostAndDelete( p_subitems );
308 vlc_gc_decref(p_current_input);
309 return 0; /* Needed for correct operation of go back */
312 free( psz_item_name );
313 free( psz_item_mrl );
314 free( psz_item_size );
315 free( psz_item_type );
316 free( psz_item_date );
317 free( psz_item_author );
318 free( psz_item_category );
319 free( psz_item_duration );
320 free( psz_item_keywords );
321 free( psz_item_subtitle );
322 free( psz_item_summary );
327 xml_ReaderDelete( p_xml_reader );
329 input_item_node_Delete( p_subitems );
331 vlc_gc_decref(p_current_input);
335 static int Control( demux_t *p_demux, int i_query, va_list args )
337 VLC_UNUSED(p_demux); VLC_UNUSED(i_query); VLC_UNUSED(args);
341 static mtime_t strTimeToMTime( const char *psz )
344 switch( sscanf( psz, "%u:%u:%u", &h, &m, &s ) )
347 return (mtime_t)( ( h*60 + m )*60 + s ) * 1000000;
349 return (mtime_t)( h*60 + m ) * 1000000;