]> git.sesse.net Git - vlc/blob - modules/demux/playlist/podcast.c
podcast: be more tolerant with faulty feeds, so just ignore invalid items instead...
[vlc] / modules / demux / playlist / podcast.c
1 /*****************************************************************************
2  * podcast.c : podcast playlist imports
3  *****************************************************************************
4  * Copyright (C) 2005-2009 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Antoine Cellerier <dionoea -at- videolan -dot- org>
8  *
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.
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 Lesser General Public License for more details.
18  *
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  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
30
31 #include <vlc_common.h>
32 #include <vlc_demux.h>
33
34 #include "playlist.h"
35 #include <vlc_xml.h>
36
37 /*****************************************************************************
38  * Local prototypes
39  *****************************************************************************/
40 static int Demux( demux_t *p_demux);
41 static mtime_t strTimeToMTime( const char *psz );
42
43 /*****************************************************************************
44  * Import_podcast: main import function
45  *****************************************************************************/
46 int Import_podcast( vlc_object_t *p_this )
47 {
48     demux_t *p_demux = (demux_t *)p_this;
49
50     if( !demux_IsForced( p_demux, "podcast" ) )
51         return VLC_EGENERIC;
52
53     p_demux->pf_demux = Demux;
54     p_demux->pf_control = Control;
55     msg_Dbg( p_demux, "using podcast reader" );
56
57     return VLC_SUCCESS;
58 }
59
60 /* "specs" : http://phobos.apple.com/static/iTunesRSS.html */
61 static int Demux( demux_t *p_demux )
62 {
63     bool b_item = false;
64     bool b_image = false;
65
66     xml_reader_t *p_xml_reader;
67     char *psz_elname = NULL;
68     char *psz_item_mrl = NULL;
69     char *psz_item_size = NULL;
70     char *psz_item_type = NULL;
71     char *psz_item_name = NULL;
72     char *psz_item_date = NULL;
73     char *psz_item_author = NULL;
74     char *psz_item_category = NULL;
75     char *psz_item_duration = NULL;
76     char *psz_item_keywords = NULL;
77     char *psz_item_subtitle = NULL;
78     char *psz_item_summary = NULL;
79     char *psz_art_url = NULL;
80     const char *node;
81     int i_type;
82     input_item_t *p_input;
83     input_item_node_t *p_subitems = NULL;
84
85     input_item_t *p_current_input = GetCurrentItem(p_demux);
86
87     p_xml_reader = xml_ReaderCreate( p_demux, p_demux->s );
88     if( !p_xml_reader )
89         goto error;
90
91     /* xml */
92     /* check root node */
93     if( xml_ReaderNextNode( p_xml_reader, &node ) != XML_READER_STARTELEM )
94     {
95         msg_Err( p_demux, "invalid file (no root node)" );
96         goto error;
97     }
98
99     if( strcmp( node, "rss" ) )
100     {
101         msg_Err( p_demux, "invalid root node <%s>", node );
102         goto error;
103     }
104
105     p_subitems = input_item_node_Create( p_current_input );
106
107     while( (i_type = xml_ReaderNextNode( p_xml_reader, &node )) > 0 )
108     {
109         switch( i_type )
110         {
111             case XML_READER_STARTELEM:
112             {
113                 free( psz_elname );
114                 psz_elname = strdup( node );
115                 if( unlikely(!node) )
116                     goto error;
117
118                 if( !strcmp( node, "item" ) )
119                     b_item = true;
120                 else if( !strcmp( node, "image" ) )
121                     b_image = true;
122
123                 // Read the attributes
124                 const char *attr, *value;
125                 while( (attr = xml_ReaderNextAttr( p_xml_reader, &value )) )
126                 {
127                     if( !strcmp( node, "enclosure" ) )
128                     {
129                         char **p = NULL;
130                         if( !strcmp( attr, "url" ) )
131                             p = &psz_item_mrl;
132                         else if( !strcmp( attr, "length" ) )
133                             p = &psz_item_size;
134                         else if( !strcmp( attr, "type" ) )
135                             p = &psz_item_type;
136                         if( p != NULL )
137                         {
138                             free( *p );
139                             *p = strdup( value );
140                         }
141                         else
142                             msg_Dbg( p_demux,"unhandled attribute %s in <%s>",
143                                      attr, node );
144                     }
145                     else
146                         msg_Dbg( p_demux,"unhandled attribute %s in <%s>",
147                                  attr, node );
148                 }
149                 break;
150             }
151
152             case XML_READER_TEXT:
153             {
154                 if(!psz_elname) break;
155
156                 /* item specific meta data */
157                 if( b_item )
158                 {
159                     char **p;
160
161                     if( !strcmp( psz_elname, "title" ) )
162                         p = &psz_item_name;
163                     else if( !strcmp( psz_elname, "itunes:author" ) ||
164                              !strcmp( psz_elname, "author" ) )
165                         /* <author> isn't standard iTunes podcast stuff */
166                         p = &psz_item_author;
167                     else if( !strcmp( psz_elname, "itunes:summary" ) ||
168                              !strcmp( psz_elname, "description" ) )
169                         /* <description> isn't standard iTunes podcast stuff */
170                         p = &psz_item_summary;
171                     else if( !strcmp( psz_elname, "pubDate" ) )
172                         p = &psz_item_date;
173                     else if( !strcmp( psz_elname, "itunes:category" ) )
174                         p = &psz_item_category;
175                     else if( !strcmp( psz_elname, "itunes:duration" ) )
176                         p = &psz_item_duration;
177                     else if( !strcmp( psz_elname, "itunes:keywords" ) )
178                         p = &psz_item_keywords;
179                     else if( !strcmp( psz_elname, "itunes:subtitle" ) )
180                         p = &psz_item_subtitle;
181                     else
182                         break;
183
184                     free( *p );
185                     *p = strdup( node );
186                 }
187                 /* toplevel meta data */
188                 else if( !b_image )
189                 {
190                     if( !strcmp( psz_elname, "title" ) )
191                         input_item_SetName( p_current_input, node );
192 #define ADD_GINFO( info, name ) \
193     else if( !strcmp( psz_elname, name ) ) \
194         input_item_AddInfo( p_current_input, _("Podcast Info"), \
195                             info, "%s", node );
196                     ADD_GINFO( _("Podcast Link"), "link" )
197                     ADD_GINFO( _("Podcast Copyright"), "copyright" )
198                     ADD_GINFO( _("Podcast Category"), "itunes:category" )
199                     ADD_GINFO( _("Podcast Keywords"), "itunes:keywords" )
200                     ADD_GINFO( _("Podcast Subtitle"), "itunes:subtitle" )
201 #undef ADD_GINFO
202                     else if( !strcmp( psz_elname, "itunes:summary" ) ||
203                              !strcmp( psz_elname, "description" ) )
204                     { /* <description> isn't standard iTunes podcast stuff */
205                         input_item_AddInfo( p_current_input,
206                             _( "Podcast Info" ), _( "Podcast Summary" ),
207                             "%s", node );
208                     }
209                 }
210                 else
211                 {
212                     if( !strcmp( psz_elname, "url" ) )
213                     {
214                         free( psz_art_url );
215                         psz_art_url = strdup( node );
216                     }
217                     else
218                         msg_Dbg( p_demux, "unhandled text in element <%s>",
219                                  psz_elname );
220                 }
221                 break;
222             }
223
224             // End element
225             case XML_READER_ENDELEM:
226             {
227                 FREENULL( psz_elname );
228
229                 if( !strcmp( node, "item" ) )
230                 {
231                     if( psz_item_mrl == NULL )
232                     {
233                         if (psz_item_name)
234                             msg_Warn( p_demux, "invalid XML item, skipping %s",
235                                       psz_item_name );
236                         else
237                             msg_Warn( p_demux, "invalid XML item, skipped" );
238                         FREENULL( psz_item_name );
239                         FREENULL( psz_item_size );
240                         FREENULL( psz_item_type );
241                         FREENULL( psz_item_date );
242                         FREENULL( psz_item_author );
243                         FREENULL( psz_item_category );
244                         FREENULL( psz_item_duration );
245                         FREENULL( psz_item_keywords );
246                         FREENULL( psz_item_subtitle );
247                         FREENULL( psz_item_summary );
248                         FREENULL( psz_art_url );
249                         FREENULL( psz_elname );
250                         continue;
251                     }
252
253                     p_input = input_item_New( psz_item_mrl, psz_item_name );
254                     FREENULL( psz_item_mrl );
255                     FREENULL( psz_item_name );
256
257                     if( p_input == NULL )
258                         break; /* FIXME: meta data memory leaks? */
259
260                     /* Set the duration if available */
261                     if( psz_item_duration )
262                         input_item_SetDuration( p_input, strTimeToMTime( psz_item_duration ) );
263
264 #define ADD_INFO( info, field ) \
265     if( field ) { \
266         input_item_AddInfo( p_input, _( "Podcast Info" ), (info), "%s", \
267                             (field) ); \
268         FREENULL( field ); }
269                     ADD_INFO( _("Podcast Publication Date"), psz_item_date  );
270                     ADD_INFO( _("Podcast Author"), psz_item_author );
271                     ADD_INFO( _("Podcast Subcategory"), psz_item_category );
272                     ADD_INFO( _("Podcast Duration"), psz_item_duration );
273                     ADD_INFO( _("Podcast Keywords"), psz_item_keywords );
274                     ADD_INFO( _("Podcast Subtitle"), psz_item_subtitle );
275                     ADD_INFO( _("Podcast Summary"), psz_item_summary );
276                     ADD_INFO( _("Podcast Type"), psz_item_type );
277 #undef ADD_INFO
278
279                     /* Add the global art url to this item, if any */
280                     if( psz_art_url )
281                         input_item_SetArtURL( p_input, psz_art_url );
282
283                     if( psz_item_size )
284                     {
285                         input_item_AddInfo( p_input,
286                                                 _( "Podcast Info" ),
287                                                 _( "Podcast Size" ),
288                                                 _("%s bytes"),
289                                                 psz_item_size );
290                         FREENULL( psz_item_size );
291                     }
292                     input_item_node_AppendItem( p_subitems, p_input );
293                     vlc_gc_decref( p_input );
294                     b_item = false;
295                 }
296                 else if( !strcmp( node, "image" ) )
297                 {
298                     b_image = false;
299                 }
300                 break;
301             }
302         }
303     }
304
305     if( i_type < 0 )
306     {
307         msg_Warn( p_demux, "error while parsing data" );
308     }
309
310     free( psz_art_url );
311     free( psz_elname );
312     xml_ReaderDelete( p_xml_reader );
313
314     input_item_node_PostAndDelete( p_subitems );
315     vlc_gc_decref(p_current_input);
316     return 0; /* Needed for correct operation of go back */
317
318 error:
319     free( psz_item_name );
320     free( psz_item_mrl );
321     free( psz_item_size );
322     free( psz_item_type );
323     free( psz_item_date );
324     free( psz_item_author );
325     free( psz_item_category );
326     free( psz_item_duration );
327     free( psz_item_keywords );
328     free( psz_item_subtitle );
329     free( psz_item_summary );
330     free( psz_art_url );
331     free( psz_elname );
332
333     if( p_xml_reader )
334         xml_ReaderDelete( p_xml_reader );
335     if( p_subitems )
336         input_item_node_Delete( p_subitems );
337
338     vlc_gc_decref(p_current_input);
339     return -1;
340 }
341
342 static mtime_t strTimeToMTime( const char *psz )
343 {
344     int h, m, s;
345     switch( sscanf( psz, "%u:%u:%u", &h, &m, &s ) )
346     {
347     case 3:
348         return (mtime_t)( ( h*60 + m )*60 + s ) * 1000000;
349     case 2:
350         return (mtime_t)( h*60 + m ) * 1000000;
351         break;
352     default:
353         return -1;
354     }
355 }