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 /*****************************************************************************
25 *****************************************************************************/
30 #include <vlc_common.h>
31 #include <vlc_access.h>
35 #include <vlc_strings.h>
37 /*****************************************************************************
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.
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 ) )
61 stream_t
*p_probestream
=
62 vlc_stream_MemoryNew( p_demux
, (uint8_t *)p_peek
, i_peek
, true );
63 if( unlikely( !p_probestream
) )
66 xml_reader_t
*p_xml_reader
= xml_ReaderCreate( p_demux
, p_probestream
);
69 vlc_stream_Delete( p_probestream
);
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
);
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" ) )
90 p_demux
->pf_readdir
= ReadDir
;
91 p_demux
->pf_control
= PlaylistControl
;
92 msg_Dbg( p_demux
, "using podcast reader" );
97 /* "specs" : http://phobos.apple.com/static/iTunesRSS.html */
98 static int ReadDir( stream_t
*p_demux
, input_item_node_t
*p_subitems
)
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
;
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
);
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)" );
135 if( strcmp( node
, "rss" ) )
137 msg_Err( p_demux
, "invalid root node <%s>", node
);
141 while( (i_type
= xml_ReaderNextNode( p_xml_reader
, &node
)) > 0 )
145 case XML_READER_STARTELEM
:
148 psz_elname
= strdup( node
);
149 if( unlikely(!psz_elname
) )
152 if( !strcmp( node
, "item" ) )
154 else if( !strcmp( node
, "image" ) )
157 // Read the attributes
158 const char *attr
, *value
;
159 while( (attr
= xml_ReaderNextAttr( p_xml_reader
, &value
)) )
161 if( !strcmp( node
, "enclosure" ) )
164 if( !strcmp( attr
, "url" ) )
166 else if( !strcmp( attr
, "length" ) )
168 else if( !strcmp( attr
, "type" ) )
173 *p
= strdup( value
);
176 msg_Dbg( p_demux
,"unhandled attribute %s in <%s>",
180 msg_Dbg( p_demux
,"unhandled attribute %s in <%s>",
186 case XML_READER_TEXT
:
188 if(!psz_elname
) break;
190 /* item specific meta data */
195 if( !strcmp( psz_elname
, "title" ) )
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" ) )
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
;
221 /* toplevel meta data */
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"), \
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" )
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" ),
246 if( !strcmp( psz_elname
, "url" ) && *node
)
249 psz_art_url
= strdup( node
);
252 msg_Dbg( p_demux
, "unhandled text in element <%s>",
259 case XML_READER_ENDELEM
:
261 FREENULL( psz_elname
);
263 if( !strcmp( node
, "item" ) )
265 if( psz_item_mrl
== NULL
)
268 msg_Warn( p_demux
, "invalid XML item, skipping %s",
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
);
287 vlc_xml_decode( psz_item_mrl
);
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 ) \
303 input_item_AddInfo( p_input, _( "Podcast Info" ), (info), "%s", \
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
);
316 /* Add the global art url to this item, if any */
319 vlc_xml_decode( psz_art_url
);
320 input_item_SetArtURL( p_input
, psz_art_url
);
325 input_item_AddInfo( p_input
,
330 FREENULL( psz_item_size
);
332 input_item_node_AppendItem( p_subitems
, p_input
);
333 input_item_Release( p_input
);
336 else if( !strcmp( node
, "image" ) )
347 msg_Warn( p_demux
, "error while parsing data" );
352 xml_ReaderDelete( p_xml_reader
);
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
);
372 xml_ReaderDelete( p_xml_reader
);
377 static vlc_tick_t
strTimeToMTime( const char *psz
)
380 switch( sscanf( psz
, "%u:%u:%u", &h
, &m
, &s
) )
383 return vlc_tick_from_sec( ( h
*60 + m
)*60 + s
);
385 return vlc_tick_from_sec( h
*60 + m
);
387 return INPUT_DURATION_UNSET
;