asx: fix mimetype and stream Peek
[vlc.git] / modules / demux / playlist / podcast.c
blobd7bce48549ed965429d2774195e330f5ebc78503
1 /*****************************************************************************
2 * podcast.c : podcast playlist imports
3 *****************************************************************************
4 * Copyright (C) 2005-2009 VLC authors and VideoLAN
5 * $Id$
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 /*****************************************************************************
25 * Preamble
26 *****************************************************************************/
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
31 #include <vlc_common.h>
32 #include <vlc_access.h>
34 #include "playlist.h"
35 #include <vlc_xml.h>
36 #include <vlc_strings.h>
38 /*****************************************************************************
39 * Local prototypes
40 *****************************************************************************/
41 static int ReadDir( stream_t *, input_item_node_t * );
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 stream_t *p_demux = (stream_t *)p_this;
51 CHECK_FILE(p_demux);
52 if( stream_IsMimeType( p_demux->s, "text/xml" )
53 || stream_IsMimeType( p_demux->s, "application/xml" ) )
55 /* XML: check if the root node is "rss". Use a specific peeked
56 * probestream in order to not modify the source state while probing.
57 * */
58 const uint8_t *p_peek;
59 ssize_t i_peek = vlc_stream_Peek( p_demux->s, &p_peek, 2048 );
60 if( unlikely( i_peek <= 0 ) )
61 return VLC_EGENERIC;
63 stream_t *p_probestream =
64 vlc_stream_MemoryNew( p_demux, (uint8_t *)p_peek, i_peek, true );
65 if( unlikely( !p_probestream ) )
66 return VLC_EGENERIC;
68 xml_reader_t *p_xml_reader = xml_ReaderCreate( p_demux, p_probestream );
69 if( !p_xml_reader )
71 vlc_stream_Delete( p_probestream );
72 return VLC_EGENERIC;
75 const char *node;
76 int ret;
77 if( ( ret = xml_ReaderNextNode( p_xml_reader, &node ) ) != XML_READER_STARTELEM
78 || strcmp( node, "rss" ) )
80 vlc_stream_Delete( p_probestream );
81 xml_ReaderDelete( p_xml_reader );
82 return VLC_EGENERIC;
85 xml_ReaderDelete( p_xml_reader );
86 vlc_stream_Delete( p_probestream );
87 /* SUCCESS: this text/xml is a rss file */
89 else if( !stream_IsMimeType( p_demux->s, "application/rss+xml" ) )
90 return VLC_EGENERIC;
92 p_demux->pf_readdir = ReadDir;
93 p_demux->pf_control = access_vaDirectoryControlHelper;
94 msg_Dbg( p_demux, "using podcast reader" );
96 return VLC_SUCCESS;
99 /* "specs" : http://phobos.apple.com/static/iTunesRSS.html */
100 static int ReadDir( stream_t *p_demux, input_item_node_t *p_subitems )
102 bool b_item = false;
103 bool b_image = false;
105 xml_reader_t *p_xml_reader;
106 char *psz_elname = NULL;
107 char *psz_item_mrl = NULL;
108 char *psz_item_size = NULL;
109 char *psz_item_type = NULL;
110 char *psz_item_name = NULL;
111 char *psz_item_date = NULL;
112 char *psz_item_author = NULL;
113 char *psz_item_category = NULL;
114 char *psz_item_duration = NULL;
115 char *psz_item_keywords = NULL;
116 char *psz_item_subtitle = NULL;
117 char *psz_item_summary = NULL;
118 char *psz_art_url = NULL;
119 const char *node;
120 int i_type;
121 input_item_t *p_input;
123 input_item_t *p_current_input = GetCurrentItem(p_demux);
125 p_xml_reader = xml_ReaderCreate( p_demux, p_demux->s );
126 if( !p_xml_reader )
127 goto error;
129 /* xml */
130 /* check root node */
131 if( xml_ReaderNextNode( p_xml_reader, &node ) != XML_READER_STARTELEM )
133 msg_Err( p_demux, "invalid file (no root node)" );
134 goto error;
137 if( strcmp( node, "rss" ) )
139 msg_Err( p_demux, "invalid root node <%s>", node );
140 goto error;
143 while( (i_type = xml_ReaderNextNode( p_xml_reader, &node )) > 0 )
145 switch( i_type )
147 case XML_READER_STARTELEM:
149 free( psz_elname );
150 psz_elname = strdup( node );
151 if( unlikely(!psz_elname) )
152 goto error;
154 if( !strcmp( node, "item" ) )
155 b_item = true;
156 else if( !strcmp( node, "image" ) )
157 b_image = true;
159 // Read the attributes
160 const char *attr, *value;
161 while( (attr = xml_ReaderNextAttr( p_xml_reader, &value )) )
163 if( !strcmp( node, "enclosure" ) )
165 char **p = NULL;
166 if( !strcmp( attr, "url" ) )
167 p = &psz_item_mrl;
168 else if( !strcmp( attr, "length" ) )
169 p = &psz_item_size;
170 else if( !strcmp( attr, "type" ) )
171 p = &psz_item_type;
172 if( p != NULL )
174 free( *p );
175 *p = strdup( value );
177 else
178 msg_Dbg( p_demux,"unhandled attribute %s in <%s>",
179 attr, node );
181 else
182 msg_Dbg( p_demux,"unhandled attribute %s in <%s>",
183 attr, node );
185 break;
188 case XML_READER_TEXT:
190 if(!psz_elname) break;
192 /* item specific meta data */
193 if( b_item )
195 char **p;
197 if( !strcmp( psz_elname, "title" ) )
198 p = &psz_item_name;
199 else if( !strcmp( psz_elname, "itunes:author" ) ||
200 !strcmp( psz_elname, "author" ) )
201 /* <author> isn't standard iTunes podcast stuff */
202 p = &psz_item_author;
203 else if( !strcmp( psz_elname, "itunes:summary" ) ||
204 !strcmp( psz_elname, "description" ) )
205 /* <description> isn't standard iTunes podcast stuff */
206 p = &psz_item_summary;
207 else if( !strcmp( psz_elname, "pubDate" ) )
208 p = &psz_item_date;
209 else if( !strcmp( psz_elname, "itunes:category" ) )
210 p = &psz_item_category;
211 else if( !strcmp( psz_elname, "itunes:duration" ) )
212 p = &psz_item_duration;
213 else if( !strcmp( psz_elname, "itunes:keywords" ) )
214 p = &psz_item_keywords;
215 else if( !strcmp( psz_elname, "itunes:subtitle" ) )
216 p = &psz_item_subtitle;
217 else
218 break;
220 free( *p );
221 *p = strdup( node );
223 /* toplevel meta data */
224 else if( !b_image )
226 if( !strcmp( psz_elname, "title" ) )
227 input_item_SetName( p_current_input, node );
228 #define ADD_GINFO( info, name ) \
229 else if( !strcmp( psz_elname, name ) ) \
230 input_item_AddInfo( p_current_input, _("Podcast Info"), \
231 info, "%s", node );
232 ADD_GINFO( _("Podcast Link"), "link" )
233 ADD_GINFO( _("Podcast Copyright"), "copyright" )
234 ADD_GINFO( _("Podcast Category"), "itunes:category" )
235 ADD_GINFO( _("Podcast Keywords"), "itunes:keywords" )
236 ADD_GINFO( _("Podcast Subtitle"), "itunes:subtitle" )
237 #undef ADD_GINFO
238 else if( !strcmp( psz_elname, "itunes:summary" ) ||
239 !strcmp( psz_elname, "description" ) )
240 { /* <description> isn't standard iTunes podcast stuff */
241 input_item_AddInfo( p_current_input,
242 _( "Podcast Info" ), _( "Podcast Summary" ),
243 "%s", node );
246 else
248 if( !strcmp( psz_elname, "url" ) && *node )
250 free( psz_art_url );
251 psz_art_url = strdup( node );
253 else
254 msg_Dbg( p_demux, "unhandled text in element <%s>",
255 psz_elname );
257 break;
260 // End element
261 case XML_READER_ENDELEM:
263 FREENULL( psz_elname );
265 if( !strcmp( node, "item" ) )
267 if( psz_item_mrl == NULL )
269 if (psz_item_name)
270 msg_Warn( p_demux, "invalid XML item, skipping %s",
271 psz_item_name );
272 else
273 msg_Warn( p_demux, "invalid XML item, skipped" );
274 FREENULL( psz_item_name );
275 FREENULL( psz_item_size );
276 FREENULL( psz_item_type );
277 FREENULL( psz_item_date );
278 FREENULL( psz_item_author );
279 FREENULL( psz_item_category );
280 FREENULL( psz_item_duration );
281 FREENULL( psz_item_keywords );
282 FREENULL( psz_item_subtitle );
283 FREENULL( psz_item_summary );
284 FREENULL( psz_art_url );
285 FREENULL( psz_elname );
286 continue;
289 vlc_xml_decode( psz_item_mrl );
290 vlc_xml_decode( psz_item_name );
291 p_input = input_item_New( psz_item_mrl, psz_item_name );
292 FREENULL( psz_item_mrl );
293 FREENULL( psz_item_name );
295 if( p_input == NULL )
296 break; /* FIXME: meta data memory leaks? */
298 /* Set the duration if available */
299 if( psz_item_duration )
300 p_input->i_duration = strTimeToMTime( psz_item_duration );
302 #define ADD_INFO( info, field ) \
303 if( field ) { \
304 input_item_AddInfo( p_input, _( "Podcast Info" ), (info), "%s", \
305 (field) ); \
306 FREENULL( field ); }
307 ADD_INFO( _("Podcast Publication Date"), psz_item_date );
308 ADD_INFO( _("Podcast Author"), psz_item_author );
309 ADD_INFO( _("Podcast Subcategory"), psz_item_category );
310 ADD_INFO( _("Podcast Duration"), psz_item_duration );
311 ADD_INFO( _("Podcast Keywords"), psz_item_keywords );
312 ADD_INFO( _("Podcast Subtitle"), psz_item_subtitle );
313 ADD_INFO( _("Podcast Summary"), psz_item_summary );
314 ADD_INFO( _("Podcast Type"), psz_item_type );
315 #undef ADD_INFO
317 /* Add the global art url to this item, if any */
318 if( psz_art_url )
320 vlc_xml_decode( psz_art_url );
321 input_item_SetArtURL( p_input, psz_art_url );
324 if( psz_item_size )
326 input_item_AddInfo( p_input,
327 _( "Podcast Info" ),
328 _( "Podcast Size" ),
329 _("%s bytes"),
330 psz_item_size );
331 FREENULL( psz_item_size );
333 input_item_node_AppendItem( p_subitems, p_input );
334 input_item_Release( p_input );
335 b_item = false;
337 else if( !strcmp( node, "image" ) )
339 b_image = false;
341 break;
346 if( i_type < 0 )
348 msg_Warn( p_demux, "error while parsing data" );
351 free( psz_art_url );
352 free( psz_elname );
353 xml_ReaderDelete( p_xml_reader );
355 return VLC_SUCCESS;
357 error:
358 free( psz_item_name );
359 free( psz_item_mrl );
360 free( psz_item_size );
361 free( psz_item_type );
362 free( psz_item_date );
363 free( psz_item_author );
364 free( psz_item_category );
365 free( psz_item_duration );
366 free( psz_item_keywords );
367 free( psz_item_subtitle );
368 free( psz_item_summary );
369 free( psz_art_url );
370 free( psz_elname );
372 if( p_xml_reader )
373 xml_ReaderDelete( p_xml_reader );
375 return VLC_EGENERIC;
378 static mtime_t strTimeToMTime( const char *psz )
380 int h, m, s;
381 switch( sscanf( psz, "%u:%u:%u", &h, &m, &s ) )
383 case 3:
384 return (mtime_t)( ( h*60 + m )*60 + s ) * 1000000;
385 case 2:
386 return (mtime_t)( h*60 + m ) * 1000000;
387 default:
388 return -1;