1 /*****************************************************************************
2 * podcast.c : podcast playlist imports
3 *****************************************************************************
4 * Copyright (C) 2005-2009 VLC authors and VideoLAN
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 /*****************************************************************************
26 *****************************************************************************/
31 #include <vlc_common.h>
32 #include <vlc_access.h>
36 #include <vlc_strings.h>
38 /*****************************************************************************
40 *****************************************************************************/
41 static int ReadDir( stream_t
*, input_item_node_t
* );
42 static vlc_tick_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
;
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.
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 ) )
63 stream_t
*p_probestream
=
64 vlc_stream_MemoryNew( p_demux
, (uint8_t *)p_peek
, i_peek
, true );
65 if( unlikely( !p_probestream
) )
68 xml_reader_t
*p_xml_reader
= xml_ReaderCreate( p_demux
, p_probestream
);
71 vlc_stream_Delete( p_probestream
);
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
);
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" ) )
92 p_demux
->pf_readdir
= ReadDir
;
93 p_demux
->pf_control
= access_vaDirectoryControlHelper
;
94 msg_Dbg( p_demux
, "using podcast reader" );
99 /* "specs" : http://phobos.apple.com/static/iTunesRSS.html */
100 static int ReadDir( stream_t
*p_demux
, input_item_node_t
*p_subitems
)
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
;
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
);
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)" );
137 if( strcmp( node
, "rss" ) )
139 msg_Err( p_demux
, "invalid root node <%s>", node
);
143 while( (i_type
= xml_ReaderNextNode( p_xml_reader
, &node
)) > 0 )
147 case XML_READER_STARTELEM
:
150 psz_elname
= strdup( node
);
151 if( unlikely(!psz_elname
) )
154 if( !strcmp( node
, "item" ) )
156 else if( !strcmp( node
, "image" ) )
159 // Read the attributes
160 const char *attr
, *value
;
161 while( (attr
= xml_ReaderNextAttr( p_xml_reader
, &value
)) )
163 if( !strcmp( node
, "enclosure" ) )
166 if( !strcmp( attr
, "url" ) )
168 else if( !strcmp( attr
, "length" ) )
170 else if( !strcmp( attr
, "type" ) )
175 *p
= strdup( value
);
178 msg_Dbg( p_demux
,"unhandled attribute %s in <%s>",
182 msg_Dbg( p_demux
,"unhandled attribute %s in <%s>",
188 case XML_READER_TEXT
:
190 if(!psz_elname
) break;
192 /* item specific meta data */
197 if( !strcmp( psz_elname
, "title" ) )
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" ) )
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
;
223 /* toplevel meta data */
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"), \
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" )
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" ),
248 if( !strcmp( psz_elname
, "url" ) && *node
)
251 psz_art_url
= strdup( node
);
254 msg_Dbg( p_demux
, "unhandled text in element <%s>",
261 case XML_READER_ENDELEM
:
263 FREENULL( psz_elname
);
265 if( !strcmp( node
, "item" ) )
267 if( psz_item_mrl
== NULL
)
270 msg_Warn( p_demux
, "invalid XML item, skipping %s",
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
);
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 ) \
304 input_item_AddInfo( p_input, _( "Podcast Info" ), (info), "%s", \
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
);
317 /* Add the global art url to this item, if any */
320 vlc_xml_decode( psz_art_url
);
321 input_item_SetArtURL( p_input
, psz_art_url
);
326 input_item_AddInfo( p_input
,
331 FREENULL( psz_item_size
);
333 input_item_node_AppendItem( p_subitems
, p_input
);
334 input_item_Release( p_input
);
337 else if( !strcmp( node
, "image" ) )
348 msg_Warn( p_demux
, "error while parsing data" );
353 xml_ReaderDelete( p_xml_reader
);
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
);
373 xml_ReaderDelete( p_xml_reader
);
378 static vlc_tick_t
strTimeToMTime( const char *psz
)
381 switch( sscanf( psz
, "%u:%u:%u", &h
, &m
, &s
) )
384 return (vlc_tick_t
)( ( h
*60 + m
)*60 + s
) * CLOCK_FREQ
;
386 return (vlc_tick_t
)( h
*60 + m
) * CLOCK_FREQ
;
388 return INPUT_DURATION_UNKNOWN
;