qt: playlist: use item title if available
[vlc.git] / modules / demux / playlist / podcast.c
blob49a0066c816c80372825870eff5611e295f0167f
1 /*****************************************************************************
2 * podcast.c : podcast playlist imports
3 *****************************************************************************
4 * Copyright (C) 2005-2009 VLC authors and VideoLAN
6 * Authors: Antoine Cellerier <dionoea -at- videolan -dot- org>
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation; either version 2.1 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this program; if not, write to the Free Software Foundation,
20 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21 *****************************************************************************/
23 /*****************************************************************************
24 * Preamble
25 *****************************************************************************/
26 #ifdef HAVE_CONFIG_H
27 # include "config.h"
28 #endif
30 #include <vlc_common.h>
31 #include <vlc_access.h>
33 #include "playlist.h"
34 #include <vlc_xml.h>
35 #include <vlc_strings.h>
37 /*****************************************************************************
38 * Local prototypes
39 *****************************************************************************/
40 static int ReadDir( stream_t *, input_item_node_t * );
41 static vlc_tick_t strTimeToMTime( const char *psz );
43 /*****************************************************************************
44 * Import_podcast: main import function
45 *****************************************************************************/
46 int Import_podcast( vlc_object_t *p_this )
48 stream_t *p_demux = (stream_t *)p_this;
50 if( stream_IsMimeType( p_demux->s, "text/xml" )
51 || stream_IsMimeType( p_demux->s, "application/xml" ) )
53 /* XML: check if the root node is "rss". Use a specific peeked
54 * probestream in order to not modify the source state while probing.
55 * */
56 const uint8_t *p_peek;
57 ssize_t i_peek = vlc_stream_Peek( p_demux->s, &p_peek, 2048 );
58 if( unlikely( i_peek <= 0 ) )
59 return VLC_EGENERIC;
61 stream_t *p_probestream =
62 vlc_stream_MemoryNew( p_demux, (uint8_t *)p_peek, i_peek, true );
63 if( unlikely( !p_probestream ) )
64 return VLC_EGENERIC;
66 xml_reader_t *p_xml_reader = xml_ReaderCreate( p_demux, p_probestream );
67 if( !p_xml_reader )
69 vlc_stream_Delete( p_probestream );
70 return VLC_EGENERIC;
73 const char *node;
74 int ret;
75 if( ( ret = xml_ReaderNextNode( p_xml_reader, &node ) ) != XML_READER_STARTELEM
76 || strcmp( node, "rss" ) )
78 vlc_stream_Delete( p_probestream );
79 xml_ReaderDelete( p_xml_reader );
80 return VLC_EGENERIC;
83 xml_ReaderDelete( p_xml_reader );
84 vlc_stream_Delete( p_probestream );
85 /* SUCCESS: this text/xml is a rss file */
87 else if( !stream_IsMimeType( p_demux->s, "application/rss+xml" ) )
88 return VLC_EGENERIC;
90 p_demux->pf_readdir = ReadDir;
91 p_demux->pf_control = PlaylistControl;
92 msg_Dbg( p_demux, "using podcast reader" );
94 return VLC_SUCCESS;
97 /* "specs" : http://phobos.apple.com/static/iTunesRSS.html */
98 static int ReadDir( stream_t *p_demux, input_item_node_t *p_subitems )
100 bool b_item = false;
101 bool b_image = false;
103 xml_reader_t *p_xml_reader;
104 char *psz_elname = NULL;
105 char *psz_item_mrl = NULL;
106 char *psz_item_size = NULL;
107 char *psz_item_type = NULL;
108 char *psz_item_name = NULL;
109 char *psz_item_date = NULL;
110 char *psz_item_author = NULL;
111 char *psz_item_category = NULL;
112 char *psz_item_duration = NULL;
113 char *psz_item_keywords = NULL;
114 char *psz_item_subtitle = NULL;
115 char *psz_item_summary = NULL;
116 char *psz_art_url = NULL;
117 const char *node;
118 int i_type;
119 input_item_t *p_input;
121 input_item_t *p_current_input = GetCurrentItem(p_demux);
123 p_xml_reader = xml_ReaderCreate( p_demux, p_demux->s );
124 if( !p_xml_reader )
125 goto error;
127 /* xml */
128 /* check root node */
129 if( xml_ReaderNextNode( p_xml_reader, &node ) != XML_READER_STARTELEM )
131 msg_Err( p_demux, "invalid file (no root node)" );
132 goto error;
135 if( strcmp( node, "rss" ) )
137 msg_Err( p_demux, "invalid root node <%s>", node );
138 goto error;
141 while( (i_type = xml_ReaderNextNode( p_xml_reader, &node )) > 0 )
143 switch( i_type )
145 case XML_READER_STARTELEM:
147 free( psz_elname );
148 psz_elname = strdup( node );
149 if( unlikely(!psz_elname) )
150 goto error;
152 if( !strcmp( node, "item" ) )
153 b_item = true;
154 else if( !strcmp( node, "image" ) )
155 b_image = true;
157 // Read the attributes
158 const char *attr, *value;
159 while( (attr = xml_ReaderNextAttr( p_xml_reader, &value )) )
161 if( !strcmp( node, "enclosure" ) )
163 char **p = NULL;
164 if( !strcmp( attr, "url" ) )
165 p = &psz_item_mrl;
166 else if( !strcmp( attr, "length" ) )
167 p = &psz_item_size;
168 else if( !strcmp( attr, "type" ) )
169 p = &psz_item_type;
170 if( p != NULL )
172 free( *p );
173 *p = strdup( value );
175 else
176 msg_Dbg( p_demux,"unhandled attribute %s in <%s>",
177 attr, node );
179 else
180 msg_Dbg( p_demux,"unhandled attribute %s in <%s>",
181 attr, node );
183 break;
186 case XML_READER_TEXT:
188 if(!psz_elname) break;
190 /* item specific meta data */
191 if( b_item )
193 char **p;
195 if( !strcmp( psz_elname, "title" ) )
196 p = &psz_item_name;
197 else if( !strcmp( psz_elname, "itunes:author" ) ||
198 !strcmp( psz_elname, "author" ) )
199 /* <author> isn't standard iTunes podcast stuff */
200 p = &psz_item_author;
201 else if( !strcmp( psz_elname, "itunes:summary" ) ||
202 !strcmp( psz_elname, "description" ) )
203 /* <description> isn't standard iTunes podcast stuff */
204 p = &psz_item_summary;
205 else if( !strcmp( psz_elname, "pubDate" ) )
206 p = &psz_item_date;
207 else if( !strcmp( psz_elname, "itunes:category" ) )
208 p = &psz_item_category;
209 else if( !strcmp( psz_elname, "itunes:duration" ) )
210 p = &psz_item_duration;
211 else if( !strcmp( psz_elname, "itunes:keywords" ) )
212 p = &psz_item_keywords;
213 else if( !strcmp( psz_elname, "itunes:subtitle" ) )
214 p = &psz_item_subtitle;
215 else
216 break;
218 free( *p );
219 *p = strdup( node );
221 /* toplevel meta data */
222 else if( !b_image )
224 if( !strcmp( psz_elname, "title" ) )
225 input_item_SetName( p_current_input, node );
226 #define ADD_GINFO( info, name ) \
227 else if( !strcmp( psz_elname, name ) ) \
228 input_item_AddInfo( p_current_input, _("Podcast Info"), \
229 info, "%s", node );
230 ADD_GINFO( _("Podcast Link"), "link" )
231 ADD_GINFO( _("Podcast Copyright"), "copyright" )
232 ADD_GINFO( _("Podcast Category"), "itunes:category" )
233 ADD_GINFO( _("Podcast Keywords"), "itunes:keywords" )
234 ADD_GINFO( _("Podcast Subtitle"), "itunes:subtitle" )
235 #undef ADD_GINFO
236 else if( !strcmp( psz_elname, "itunes:summary" ) ||
237 !strcmp( psz_elname, "description" ) )
238 { /* <description> isn't standard iTunes podcast stuff */
239 input_item_AddInfo( p_current_input,
240 _( "Podcast Info" ), _( "Podcast Summary" ),
241 "%s", node );
244 else
246 if( !strcmp( psz_elname, "url" ) && *node )
248 free( psz_art_url );
249 psz_art_url = strdup( node );
251 else
252 msg_Dbg( p_demux, "unhandled text in element <%s>",
253 psz_elname );
255 break;
258 // End element
259 case XML_READER_ENDELEM:
261 FREENULL( psz_elname );
263 if( !strcmp( node, "item" ) )
265 if( psz_item_mrl == NULL )
267 if (psz_item_name)
268 msg_Warn( p_demux, "invalid XML item, skipping %s",
269 psz_item_name );
270 else
271 msg_Warn( p_demux, "invalid XML item, skipped" );
272 FREENULL( psz_item_name );
273 FREENULL( psz_item_size );
274 FREENULL( psz_item_type );
275 FREENULL( psz_item_date );
276 FREENULL( psz_item_author );
277 FREENULL( psz_item_category );
278 FREENULL( psz_item_duration );
279 FREENULL( psz_item_keywords );
280 FREENULL( psz_item_subtitle );
281 FREENULL( psz_item_summary );
282 FREENULL( psz_art_url );
283 FREENULL( psz_elname );
284 continue;
287 vlc_xml_decode( psz_item_mrl );
288 if( psz_item_name )
289 vlc_xml_decode( psz_item_name );
290 p_input = input_item_New( psz_item_mrl, psz_item_name );
291 FREENULL( psz_item_mrl );
292 FREENULL( psz_item_name );
294 if( p_input == NULL )
295 break; /* FIXME: meta data memory leaks? */
297 /* Set the duration if available */
298 if( psz_item_duration )
299 p_input->i_duration = strTimeToMTime( psz_item_duration );
301 #define ADD_INFO( info, field ) \
302 if( field ) { \
303 input_item_AddInfo( p_input, _( "Podcast Info" ), (info), "%s", \
304 (field) ); \
305 FREENULL( field ); }
306 ADD_INFO( _("Podcast Publication Date"), psz_item_date );
307 ADD_INFO( _("Podcast Author"), psz_item_author );
308 ADD_INFO( _("Podcast Subcategory"), psz_item_category );
309 ADD_INFO( _("Podcast Duration"), psz_item_duration );
310 ADD_INFO( _("Podcast Keywords"), psz_item_keywords );
311 ADD_INFO( _("Podcast Subtitle"), psz_item_subtitle );
312 ADD_INFO( _("Podcast Summary"), psz_item_summary );
313 ADD_INFO( _("Podcast Type"), psz_item_type );
314 #undef ADD_INFO
316 /* Add the global art url to this item, if any */
317 if( psz_art_url )
319 vlc_xml_decode( psz_art_url );
320 input_item_SetArtURL( p_input, psz_art_url );
323 if( psz_item_size )
325 input_item_AddInfo( p_input,
326 _( "Podcast Info" ),
327 _( "Podcast Size" ),
328 _("%s bytes"),
329 psz_item_size );
330 FREENULL( psz_item_size );
332 input_item_node_AppendItem( p_subitems, p_input );
333 input_item_Release( p_input );
334 b_item = false;
336 else if( !strcmp( node, "image" ) )
338 b_image = false;
340 break;
345 if( i_type < 0 )
347 msg_Warn( p_demux, "error while parsing data" );
350 free( psz_art_url );
351 free( psz_elname );
352 xml_ReaderDelete( p_xml_reader );
354 return VLC_SUCCESS;
356 error:
357 free( psz_item_name );
358 free( psz_item_mrl );
359 free( psz_item_size );
360 free( psz_item_type );
361 free( psz_item_date );
362 free( psz_item_author );
363 free( psz_item_category );
364 free( psz_item_duration );
365 free( psz_item_keywords );
366 free( psz_item_subtitle );
367 free( psz_item_summary );
368 free( psz_art_url );
369 free( psz_elname );
371 if( p_xml_reader )
372 xml_ReaderDelete( p_xml_reader );
374 return VLC_EGENERIC;
377 static vlc_tick_t strTimeToMTime( const char *psz )
379 int h, m, s;
380 switch( sscanf( psz, "%u:%u:%u", &h, &m, &s ) )
382 case 3:
383 return vlc_tick_from_sec( ( h*60 + m )*60 + s );
384 case 2:
385 return vlc_tick_from_sec( h*60 + m );
386 default:
387 return INPUT_DURATION_UNSET;