fetcher: fix a typo (thanks to fenrir).
[vlc.git] / modules / demux / playlist / podcast.c
1 /*****************************************************************************
2  * podcast.c : podcast playlist imports
3  *****************************************************************************
4  * Copyright (C) 2005-2009 the VideoLAN team
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
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 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 General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, 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 int Control( demux_t *p_demux, int i_query, va_list args );
42 static mtime_t strTimeToMTime( const char *psz );
43
44 /*****************************************************************************
45  * Import_podcast: main import function
46  *****************************************************************************/
47 int Import_podcast( vlc_object_t *p_this )
48 {
49     demux_t *p_demux = (demux_t *)p_this;
50
51     if( !demux_IsForced( p_demux, "podcast" ) )
52         return VLC_EGENERIC;
53
54     p_demux->pf_demux = Demux;
55     p_demux->pf_control = Control;
56     msg_Dbg( p_demux, "using podcast reader" );
57
58     return VLC_SUCCESS;
59 }
60
61 /*****************************************************************************
62  * Deactivate: frees unused data
63  *****************************************************************************/
64 void Close_podcast( vlc_object_t *p_this )
65 {
66     (void)p_this;
67 }
68
69 /* "specs" : http://phobos.apple.com/static/iTunesRSS.html */
70 static int Demux( demux_t *p_demux )
71 {
72     bool b_item = false;
73     bool b_image = false;
74     int i_ret;
75
76     xml_t *p_xml;
77     xml_reader_t *p_xml_reader;
78     char *psz_elname = NULL;
79     char *psz_item_mrl = NULL;
80     char *psz_item_size = NULL;
81     char *psz_item_type = NULL;
82     char *psz_item_name = NULL;
83     char *psz_item_date = NULL;
84     char *psz_item_author = NULL;
85     char *psz_item_category = NULL;
86     char *psz_item_duration = NULL;
87     char *psz_item_keywords = NULL;
88     char *psz_item_subtitle = NULL;
89     char *psz_item_summary = NULL;
90     int i_type;
91     input_item_t *p_input;
92
93     INIT_PLAYLIST_STUFF;
94
95     p_xml = xml_Create( p_demux );
96     if( !p_xml )
97         goto error;
98
99     p_xml_reader = xml_ReaderCreate( p_xml, p_demux->s );
100     if( !p_xml_reader )
101         goto error;
102
103     /* xml */
104     /* check root node */
105     if( xml_ReaderRead( p_xml_reader ) != 1 )
106     {
107         msg_Err( p_demux, "invalid file (no root node)" );
108         goto error;
109     }
110
111     while( xml_ReaderNodeType( p_xml_reader ) == XML_READER_NONE )
112     {
113         if( xml_ReaderRead( p_xml_reader ) != 1 )
114         {
115             msg_Err( p_demux, "invalid file (no root node)" );
116             goto error;
117         }
118     }
119
120     if( xml_ReaderNodeType( p_xml_reader ) != XML_READER_STARTELEM ||
121         ( psz_elname = xml_ReaderName( p_xml_reader ) ) == NULL ||
122         strcmp( psz_elname, "rss" ) )
123     {
124         msg_Err( p_demux, "invalid root node %i, %s",
125                  xml_ReaderNodeType( p_xml_reader ), psz_elname );
126         goto error;
127     }
128     FREENULL( psz_elname );
129
130     while( (i_ret = xml_ReaderRead( p_xml_reader )) == 1 )
131     {
132         // Get the node type
133         i_type = xml_ReaderNodeType( p_xml_reader );
134         switch( i_type )
135         {
136             // Error
137             case -1:
138                 goto error;
139
140             case XML_READER_STARTELEM:
141             {
142                 // Read the element name
143                 free( psz_elname );
144                 psz_elname = xml_ReaderName( p_xml_reader );
145                 if( !psz_elname )
146                     goto error;
147
148                 if( !strcmp( psz_elname, "item" ) )
149                 {
150                     b_item = true;
151                 }
152                 else if( !strcmp( psz_elname, "image" ) )
153                 {
154                     b_image = true;
155                 }
156
157                 // Read the attributes
158                 while( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
159                 {
160                     char *psz_name = xml_ReaderName( p_xml_reader );
161                     char *psz_value = xml_ReaderValue( p_xml_reader );
162                     if( !psz_name || !psz_value )
163                     {
164                         free( psz_name );
165                         free( psz_value );
166                         goto error;
167                     }
168
169                     if( !strcmp( psz_elname, "enclosure" ) )
170                     {
171                         if( !strcmp( psz_name, "url" ) )
172                         {
173                             free( psz_item_mrl );
174                             psz_item_mrl = psz_value;
175                         }
176                         else if( !strcmp( psz_name, "length" ) )
177                         {
178                             free( psz_item_size );
179                             psz_item_size = psz_value;
180                         }
181                         else if( !strcmp( psz_name, "type" ) )
182                         {
183                             free( psz_item_type );
184                             psz_item_type = psz_value;
185                         }
186                         else
187                         {
188                             msg_Dbg( p_demux,"unhandled attribure %s in element %s",
189                                      psz_name, psz_elname );
190                             free( psz_value );
191                         }
192                     }
193                     else
194                     {
195                         msg_Dbg( p_demux,"unhandled attribure %s in element %s",
196                                   psz_name, psz_elname );
197                         free( psz_value );
198                     }
199                     free( psz_name );
200                 }
201                 break;
202             }
203             case XML_READER_TEXT:
204             {
205                 char *psz_text = xml_ReaderValue( p_xml_reader );
206
207 #define SET_DATA( field, name )                 \
208     else if( !strcmp( psz_elname, name ) )      \
209     {                                           \
210         field = psz_text;                       \
211     }
212                 /* item specific meta data */
213                 if( b_item == true )
214                 {
215                     if( !strcmp( psz_elname, "title" ) )
216                     {
217                         psz_item_name = psz_text;
218                     }
219                     else if( !strcmp( psz_elname, "itunes:author" ) ||
220                              !strcmp( psz_elname, "author" ) )
221                     { /* <author> isn't standard iTunes podcast stuff */
222                         psz_item_author = psz_text;
223                     }
224                     else if( !strcmp( psz_elname, "itunes:summary" ) ||
225                              !strcmp( psz_elname, "description" ) )
226                     { /* <description> isn't standard iTunes podcast stuff */
227                         psz_item_summary = psz_text;
228                     }
229                     SET_DATA( psz_item_date, "pubDate" )
230                     SET_DATA( psz_item_category, "itunes:category" )
231                     SET_DATA( psz_item_duration, "itunes:duration" )
232                     SET_DATA( psz_item_keywords, "itunes:keywords" )
233                     SET_DATA( psz_item_subtitle, "itunes:subtitle" )
234                     else
235                         free( psz_text );
236                 }
237 #undef SET_DATA
238
239                 /* toplevel meta data */
240                 else if( b_image == false )
241                 {
242                     if( !strcmp( psz_elname, "title" ) )
243                     {
244                         input_item_SetName( p_current_input, psz_text );
245                     }
246 #define ADD_GINFO( info, name ) \
247     else if( !strcmp( psz_elname, name ) ) \
248     { \
249         input_item_AddInfo( p_current_input, _("Podcast Info"), \
250                                 _( info ), "%s", psz_text ); \
251     }
252                     ADD_GINFO( "Podcast Link", "link" )
253                     ADD_GINFO( "Podcast Copyright", "copyright" )
254                     ADD_GINFO( "Podcast Category", "itunes:category" )
255                     ADD_GINFO( "Podcast Keywords", "itunes:keywords" )
256                     ADD_GINFO( "Podcast Subtitle", "itunes:subtitle" )
257 #undef ADD_GINFO
258                     else if( !strcmp( psz_elname, "itunes:summary" ) ||
259                              !strcmp( psz_elname, "description" ) )
260                     { /* <description> isn't standard iTunes podcast stuff */
261                         input_item_AddInfo( p_current_input,
262                             _( "Podcast Info" ), _( "Podcast Summary" ),
263                             "%s", psz_text );
264                     }
265                     free( psz_text );
266                 }
267                 else
268                 {
269                     msg_Dbg( p_demux, "unhandled text in element '%s'",
270                               psz_elname );
271                     free( psz_text );
272                 }
273                 break;
274             }
275             // End element
276             case XML_READER_ENDELEM:
277             {
278                 // Read the element name
279                 free( psz_elname );
280                 psz_elname = xml_ReaderName( p_xml_reader );
281                 if( !psz_elname )
282                     goto error;
283                 if( !strcmp( psz_elname, "item" ) )
284                 {
285                     if( psz_item_mrl == NULL )
286                     {
287                         msg_Err( p_demux, "invalid XML (no enclosure markup)" );
288                         goto error;
289                     }
290                     p_input = input_item_New( p_demux, psz_item_mrl, psz_item_name );
291                     if( p_input == NULL ) break;
292 #define ADD_INFO( info, field ) \
293     if( field ) { input_item_AddInfo( p_input, \
294                             _( "Podcast Info" ),  _( info ), "%s", field ); }
295                     ADD_INFO( "Podcast Publication Date", psz_item_date  );
296                     ADD_INFO( "Podcast Author", psz_item_author );
297                     ADD_INFO( "Podcast Subcategory", psz_item_category );
298                     ADD_INFO( "Podcast Duration", psz_item_duration );
299                     ADD_INFO( "Podcast Keywords", psz_item_keywords );
300                     ADD_INFO( "Podcast Subtitle", psz_item_subtitle );
301                     ADD_INFO( "Podcast Summary", psz_item_summary );
302                     ADD_INFO( "Podcast Type", psz_item_type );
303 #undef ADD_INFO
304
305                     /* Set the duration if available */
306                     if( psz_item_duration )
307                         input_item_SetDuration( p_input, strTimeToMTime( psz_item_duration ) );
308
309                     if( psz_item_size )
310                     {
311                         input_item_AddInfo( p_input,
312                                                 _( "Podcast Info" ),
313                                                 _( "Podcast Size" ),
314                                                 "%s bytes",
315                                                 psz_item_size );
316                     }
317                     input_item_AddSubItem( p_current_input, p_input );
318                     vlc_gc_decref( p_input );
319                     FREENULL( psz_item_name );
320                     FREENULL( psz_item_mrl );
321                     FREENULL( psz_item_size );
322                     FREENULL( psz_item_type );
323                     FREENULL( psz_item_date );
324                     FREENULL( psz_item_author );
325                     FREENULL( psz_item_category );
326                     FREENULL( psz_item_duration );
327                     FREENULL( psz_item_keywords );
328                     FREENULL( psz_item_subtitle );
329                     FREENULL( psz_item_summary );
330                     b_item = false;
331                 }
332                 else if( !strcmp( psz_elname, "image" ) )
333                 {
334                     b_image = false;
335                 }
336                 free( psz_elname );
337                 psz_elname = strdup( "" );
338
339                 break;
340             }
341         }
342     }
343
344     if( i_ret != 0 )
345     {
346         msg_Warn( p_demux, "error while parsing data" );
347     }
348
349     free( psz_elname );
350     xml_ReaderDelete( p_xml, p_xml_reader );
351     xml_Delete( p_xml );
352
353     HANDLE_PLAY_AND_RELEASE;
354     return 0; /* Needed for correct operation of go back */
355
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_elname );
369
370     if( p_xml_reader )
371         xml_ReaderDelete( p_xml, p_xml_reader );
372     xml_Delete( p_xml );
373
374     HANDLE_PLAY_AND_RELEASE;
375     return -1;
376 }
377
378 static int Control( demux_t *p_demux, int i_query, va_list args )
379 {
380     VLC_UNUSED(p_demux); VLC_UNUSED(i_query); VLC_UNUSED(args);
381     return VLC_EGENERIC;
382 }
383
384 static mtime_t strTimeToMTime( const char *psz )
385 {
386     int h, m, s;
387     switch( sscanf( psz, "%u:%u:%u", &h, &m, &s ) )
388     {
389     case 3:
390         return (mtime_t)( ( h*60 + m )*60 + s ) * 1000000;
391     case 2:
392         return (mtime_t)( h*60 + m ) * 1000000;
393         break;
394     default:
395         return -1;
396     }
397 }