d20978de64d6ac8f8becdd92e5647fc87697b398
[vlc.git] / modules / video_filter / rss.c
1 /*****************************************************************************
2  * rss.c : rss/atom feed display video plugin for vlc
3  *****************************************************************************
4  * Copyright (C) 2003-2006 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  * Atom : http://www.ietf.org/rfc/rfc4287.txt
26  * RSS : http://www.rssboard.org/rss-specification
27  *****************************************************************************/
28
29 /*****************************************************************************
30  * Preamble
31  *****************************************************************************/
32 #include <stdlib.h>                                      /* malloc(), free() */
33 #include <string.h>
34
35 #include <vlc/vlc.h>
36 #include <vlc/vout.h>
37
38 #include "vlc_filter.h"
39 #include "vlc_block.h"
40 #include "vlc_osd.h"
41
42 #include "vlc_block.h"
43 #include "vlc_stream.h"
44 #include "vlc_xml.h"
45 #include "charset.h"
46
47 #include "vlc_image.h"
48
49 /*****************************************************************************
50  * Local prototypes
51  *****************************************************************************/
52 static int  CreateFilter ( vlc_object_t * );
53 static void DestroyFilter( vlc_object_t * );
54 static subpicture_t *Filter( filter_t *, mtime_t );
55
56 static int FetchRSS( filter_t * );
57 static void FreeRSS( filter_t * );
58
59 static int pi_color_values[] = { 0xf0000000, 0x00000000, 0x00808080, 0x00C0C0C0,
60                0x00FFFFFF, 0x00800000, 0x00FF0000, 0x00FF00FF, 0x00FFFF00,
61                0x00808000, 0x00008000, 0x00008080, 0x0000FF00, 0x00800080,
62                0x00000080, 0x000000FF, 0x0000FFFF};
63 static char *ppsz_color_descriptions[] = { N_("Default"), N_("Black"),
64                N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"), N_("Red"),
65                N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"),
66                N_("Teal"), N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"),
67                N_("Aqua") };
68
69 /*****************************************************************************
70  * filter_sys_t: rss filter descriptor
71  *****************************************************************************/
72
73 struct rss_item_t
74 {
75     char *psz_title;
76     char *psz_description;
77     char *psz_link;
78 };
79
80 struct rss_feed_t
81 {
82     char *psz_title;
83     char *psz_description;
84     char *psz_link;
85     char *psz_image;
86     picture_t *p_pic;
87
88     int i_items;
89     struct rss_item_t *p_items;
90 };
91
92 struct filter_sys_t
93 {
94     vlc_mutex_t lock;
95     vlc_mutex_t *p_lock;
96
97     int i_xoff, i_yoff;  /* offsets for the display string in the video window */
98     int i_pos; /* permit relative positioning (top, bottom, left, right, center) */
99     int i_speed;
100     int i_length;
101
102     char *psz_marquee;    /* marquee string */
103
104     text_style_t *p_style; /* font control */
105
106     mtime_t last_date;
107
108     char *psz_urls;
109     int i_feeds;
110     struct rss_feed_t *p_feeds;
111
112     int i_ttl;
113     time_t t_last_update;
114     vlc_bool_t b_images;
115
116     int i_cur_feed;
117     int i_cur_item;
118     int i_cur_char;
119 };
120
121 #define MSG_TEXT N_("Feed URLs")
122 #define MSG_LONGTEXT N_("RSS/Atom feed '|' (pipe) seperated URLs.")
123 #define SPEED_TEXT N_("Feeds speed")
124 #define SPEED_LONGTEXT N_("RSS/Atom feeds speed (bigger is slower).")
125 #define LENGTH_TEXT N_("Max length")
126 #define LENGTH_LONGTEXT N_("Maximum number of characters displayed on the " \
127                 "screen." )
128 #define TTL_TEXT N_("Refresh time")
129 #define TTL_LONGTEXT N_("Number of seconds between each forced refresh " \
130         "of the feeds. 0 means that the feeds are never updated." )
131 #define IMAGE_TEXT N_("Feed images")
132 #define IMAGE_LONGTEXT N_("Display feed images if available.")
133
134 #define POSX_TEXT N_("X offset")
135 #define POSX_LONGTEXT N_("X offset, from the left screen edge." )
136 #define POSY_TEXT N_("Y offset")
137 #define POSY_LONGTEXT N_("Y offset, down from the top." )
138 #define OPACITY_TEXT N_("Opacity")
139 #define OPACITY_LONGTEXT N_("Opacity (inverse of transparency) of " \
140     "overlay text. 0 = transparent, 255 = totally opaque. " )
141
142 #define SIZE_TEXT N_("Font size, pixels")
143 #define SIZE_LONGTEXT N_("Font size, in pixels. Default is -1 (use default " \
144     "font size)." )
145
146 #define COLOR_TEXT N_("Color")
147 #define COLOR_LONGTEXT N_("Color of the text that will be rendered on "\
148     "the video. This must be an hexadecimal (like HTML colors). The first two "\
149     "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
150     " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
151
152 #define POS_TEXT N_("Text position")
153 #define POS_LONGTEXT N_( \
154   "You can enforce the text position on the video " \
155   "(0=center, 1=left, 2=right, 4=top, 8=bottom, you can " \
156   "also use combinations of these values, eg 6 = top-right).")
157
158 static int pi_pos_values[] = { 0, 1, 2, 4, 8, 5, 6, 9, 10 };
159 static char *ppsz_pos_descriptions[] =
160      { N_("Center"), N_("Left"), N_("Right"), N_("Top"), N_("Bottom"),
161      N_("Top-Left"), N_("Top-Right"), N_("Bottom-Left"), N_("Bottom-Right") };
162
163 /*****************************************************************************
164  * Module descriptor
165  *****************************************************************************/
166 vlc_module_begin();
167     set_capability( "sub filter", 0 );
168     set_shortname( "RSS / Atom" );
169     set_callbacks( CreateFilter, DestroyFilter );
170     set_category( CAT_VIDEO );
171     set_subcategory( SUBCAT_VIDEO_SUBPIC );
172     add_string( "rss-urls", "rss", NULL, MSG_TEXT, MSG_LONGTEXT, VLC_FALSE );
173
174     set_section( N_("Position"), NULL );
175     add_integer( "rss-x", -1, NULL, POSX_TEXT, POSX_LONGTEXT, VLC_TRUE );
176     add_integer( "rss-y", 0, NULL, POSY_TEXT, POSY_LONGTEXT, VLC_TRUE );
177     add_integer( "rss-position", 5, NULL, POS_TEXT, POS_LONGTEXT, VLC_FALSE );
178         change_integer_list( pi_pos_values, ppsz_pos_descriptions, 0 );
179
180     set_section( N_("Font"), NULL );
181     /* 5 sets the default to top [1] left [4] */
182     add_integer_with_range( "rss-opacity", 255, 0, 255, NULL,
183         OPACITY_TEXT, OPACITY_LONGTEXT, VLC_FALSE );
184     add_integer( "rss-color", 0xFFFFFF, NULL, COLOR_TEXT, COLOR_LONGTEXT,
185                   VLC_FALSE );
186         change_integer_list( pi_color_values, ppsz_color_descriptions, 0 );
187     add_integer( "rss-size", -1, NULL, SIZE_TEXT, SIZE_LONGTEXT, VLC_FALSE );
188
189     set_section( N_("Misc"), NULL );
190     add_integer( "rss-speed", 100000, NULL, SPEED_TEXT, SPEED_LONGTEXT,
191                  VLC_FALSE );
192     add_integer( "rss-length", 60, NULL, LENGTH_TEXT, LENGTH_LONGTEXT,
193                  VLC_FALSE );
194     add_integer( "rss-ttl", 1800, NULL, TTL_TEXT, TTL_LONGTEXT, VLC_FALSE );
195     add_bool( "rss-images", 1, NULL, IMAGE_TEXT, IMAGE_LONGTEXT, VLC_FALSE );
196
197     set_description( _("RSS and Atom feed display") );
198     add_shortcut( "rss" );
199     add_shortcut( "atom" );
200 vlc_module_end();
201
202 /*****************************************************************************
203  * CreateFilter: allocates RSS video filter
204  *****************************************************************************/
205 static int CreateFilter( vlc_object_t *p_this )
206 {
207     filter_t *p_filter = (filter_t *)p_this;
208     filter_sys_t *p_sys;
209     int i_feed;
210
211     /* Allocate structure */
212     p_sys = p_filter->p_sys = malloc( sizeof( filter_sys_t ) );
213     if( p_sys == NULL )
214     {
215         msg_Err( p_filter, "out of memory" );
216         return VLC_ENOMEM;
217     }
218
219     vlc_mutex_init( p_filter, &p_sys->lock );
220     vlc_mutex_lock( &p_sys->lock );
221
222     p_sys->psz_urls = var_CreateGetString( p_filter, "rss-urls" );
223     p_sys->i_cur_feed = 0;
224     p_sys->i_cur_item = 0;
225     p_sys->i_cur_char = 0;
226     p_sys->i_feeds = 0;
227     p_sys->p_feeds = NULL;
228     p_sys->i_speed = var_CreateGetInteger( p_filter, "rss-speed" );
229     p_sys->i_length = var_CreateGetInteger( p_filter, "rss-length" );
230     p_sys->i_ttl = __MAX( 0, var_CreateGetInteger( p_filter, "rss-ttl" ) );
231     p_sys->b_images = var_CreateGetBool( p_filter, "rss-images" );
232     p_sys->psz_marquee = (char *)malloc( p_sys->i_length );
233
234     p_sys->p_style = malloc( sizeof( text_style_t ));
235     memcpy( p_sys->p_style, &default_text_style, sizeof( text_style_t ));
236
237     p_sys->i_xoff = var_CreateGetInteger( p_filter, "rss-x" );
238     p_sys->i_yoff = var_CreateGetInteger( p_filter, "rss-y" );
239     p_sys->i_pos = var_CreateGetInteger( p_filter, "rss-position" );
240     p_sys->p_style->i_font_alpha = 255 - var_CreateGetInteger( p_filter, "rss-opacity" );
241     p_sys->p_style->i_font_color = var_CreateGetInteger( p_filter, "rss-color" );
242     p_sys->p_style->i_font_size = var_CreateGetInteger( p_filter, "rss-size" );
243
244     if( p_sys->b_images == VLC_TRUE && p_sys->p_style->i_font_size == -1 )
245     {
246         msg_Warn( p_filter, "rrs-size wasn't specified. Feed images will thus be displayed without being resized" );
247     }
248
249     if( FetchRSS( p_filter ) )
250     {
251         msg_Err( p_filter, "failed while fetching RSS ... too bad" );
252         vlc_mutex_unlock( &p_sys->lock );
253         return VLC_EGENERIC;
254     }
255     p_sys->t_last_update = time( NULL );
256
257     if( p_sys->i_feeds == 0 )
258     {
259         vlc_mutex_unlock( &p_sys->lock );
260         return VLC_EGENERIC;
261     }
262     for( i_feed=0; i_feed < p_sys->i_feeds; i_feed ++ )
263         if( p_sys->p_feeds[i_feed].i_items == 0 )
264         {
265             vlc_mutex_unlock( &p_sys->lock );
266             return VLC_EGENERIC;
267         }
268
269     /* Misc init */
270     p_filter->pf_sub_filter = Filter;
271     p_sys->last_date = (mtime_t)0;
272
273     vlc_mutex_unlock( &p_sys->lock );
274
275     return VLC_SUCCESS;
276 }
277 /*****************************************************************************
278  * DestroyFilter: destroy RSS video filter
279  *****************************************************************************/
280 static void DestroyFilter( vlc_object_t *p_this )
281 {
282     filter_t *p_filter = (filter_t *)p_this;
283     filter_sys_t *p_sys = p_filter->p_sys;
284
285     vlc_mutex_lock( &p_sys->lock );
286
287     if( p_sys->p_style ) free( p_sys->p_style );
288     if( p_sys->psz_marquee ) free( p_sys->psz_marquee );
289     free( p_sys->psz_urls );
290     FreeRSS( p_filter );
291     vlc_mutex_unlock( &p_sys->lock );
292     vlc_mutex_destroy( &p_sys->lock );
293     free( p_sys );
294
295     /* Delete the RSS variables */
296     var_Destroy( p_filter, "rss-urls" );
297     var_Destroy( p_filter, "rss-speed" );
298     var_Destroy( p_filter, "rss-length" );
299     var_Destroy( p_filter, "rss-ttl" );
300     var_Destroy( p_filter, "rss-images" );
301     var_Destroy( p_filter, "rss-x" );
302     var_Destroy( p_filter, "rss-y" );
303     var_Destroy( p_filter, "rss-position" );
304     var_Destroy( p_filter, "rss-color");
305     var_Destroy( p_filter, "rss-opacity");
306     var_Destroy( p_filter, "rss-size");
307 }
308
309 /****************************************************************************
310  * Filter: the whole thing
311  ****************************************************************************
312  * This function outputs subpictures at regular time intervals.
313  ****************************************************************************/
314 static subpicture_t *Filter( filter_t *p_filter, mtime_t date )
315 {
316     filter_sys_t *p_sys = p_filter->p_sys;
317     subpicture_t *p_spu;
318     video_format_t fmt = {0};
319
320     subpicture_region_t *p_region;
321
322     int i_feed, i_item;
323
324     struct rss_feed_t *p_feed;
325
326     vlc_mutex_lock( &p_sys->lock );
327
328     if( p_sys->last_date
329        + ( p_sys->i_cur_char == 0 && p_sys->i_cur_item == 0 ? 5 : 1 )
330            /* ( ... ? 5 : 1 ) means "wait more for the 1st char" */
331        * p_sys->i_speed > date )
332     {
333         vlc_mutex_unlock( &p_sys->lock );
334         return NULL;
335     }
336
337     /* Do we need to update the feeds ? */
338     if( p_sys->i_ttl
339         && time( NULL ) > p_sys->t_last_update + (time_t)p_sys->i_ttl )
340     {
341         msg_Dbg( p_filter, "Forcing update of all the RSS feeds" );
342         if( FetchRSS( p_filter ) )
343         {
344             msg_Err( p_filter, "failed while fetching RSS ... too bad" );
345             vlc_mutex_unlock( &p_sys->lock );
346             return NULL; /* FIXME : we most likely messed up all the data,
347                           * so we might need to do something about it */
348         }
349         p_sys->t_last_update = time( NULL );
350     }
351
352     p_sys->last_date = date;
353     p_sys->i_cur_char++;
354     if( p_sys->p_feeds[p_sys->i_cur_feed].p_items[p_sys->i_cur_item].psz_title[p_sys->i_cur_char] == 0 )
355     {
356         p_sys->i_cur_char = 0;
357         p_sys->i_cur_item++;
358         if( p_sys->i_cur_item >= p_sys->p_feeds[p_sys->i_cur_feed].i_items )
359         {
360             p_sys->i_cur_item = 0;
361             p_sys->i_cur_feed = (p_sys->i_cur_feed + 1)%p_sys->i_feeds;
362         }
363     }
364
365     p_spu = p_filter->pf_sub_buffer_new( p_filter );
366     if( !p_spu )
367     {
368         vlc_mutex_unlock( &p_sys->lock );
369         return NULL;
370     }
371
372     fmt.i_chroma = VLC_FOURCC('T','E','X','T');
373
374     p_spu->p_region = p_spu->pf_create_region( VLC_OBJECT(p_filter), &fmt );
375     if( !p_spu->p_region )
376     {
377         p_filter->pf_sub_buffer_del( p_filter, p_spu );
378         vlc_mutex_unlock( &p_sys->lock );
379         return NULL;
380     }
381
382     /* Generate the string that will be displayed. This string is supposed to
383        be p_sys->i_length characters long. */
384     i_item = p_sys->i_cur_item;
385     i_feed = p_sys->i_cur_feed;
386     p_feed = &p_sys->p_feeds[p_sys->i_cur_feed];
387
388     if( p_feed->p_pic )
389     {
390         /* Don't display the feed's title if we have an image */
391         snprintf( p_sys->psz_marquee, p_sys->i_length, "%s",
392                   p_sys->p_feeds[i_feed].p_items[i_item].psz_title
393                   +p_sys->i_cur_char );
394     }
395     else
396     {
397         snprintf( p_sys->psz_marquee, p_sys->i_length, "%s : %s",
398                   p_sys->p_feeds[i_feed].psz_title,
399                   p_sys->p_feeds[i_feed].p_items[i_item].psz_title
400                   +p_sys->i_cur_char );
401     }
402
403     while( strlen( p_sys->psz_marquee ) < (unsigned int)p_sys->i_length )
404     {
405         i_item++;
406         if( i_item == p_sys->p_feeds[i_feed].i_items ) break;
407         snprintf( strchr( p_sys->psz_marquee, 0 ),
408                   p_sys->i_length - strlen( p_sys->psz_marquee ),
409                   " - %s",
410                   p_sys->p_feeds[i_feed].p_items[i_item].psz_title );
411     }
412
413     /* Calls to snprintf might split multibyte UTF8 chars ...
414      * which freetype doesn't like. */
415     {
416         char *a = strdup( p_sys->psz_marquee );
417         char *a2 = a;
418         char *b = p_sys->psz_marquee;
419         EnsureUTF8( p_sys->psz_marquee );
420         /* we want to use ' ' instead of '?' for erroneous chars */
421         while( *b != '\0' )
422         {
423             if( *b != *a ) *b = ' ';
424             b++;a++;
425         }
426         free( a2 );
427     }
428
429     p_spu->p_region->psz_text = strdup(p_sys->psz_marquee);
430     p_spu->i_start = date;
431     p_spu->i_stop  = 0;
432     p_spu->b_ephemer = VLC_TRUE;
433
434     /*  where to locate the string: */
435     if( p_sys->i_xoff < 0 || p_sys->i_yoff < 0 )
436     {   /* set to one of the 9 relative locations */
437         p_spu->i_flags = p_sys->i_pos;
438         p_spu->i_x = 0;
439         p_spu->i_y = 0;
440         p_spu->b_absolute = VLC_FALSE;
441     }
442     else
443     {   /*  set to an absolute xy, referenced to upper left corner */
444         p_spu->i_flags = OSD_ALIGN_LEFT | OSD_ALIGN_TOP;
445         p_spu->i_x = p_sys->i_xoff;
446         p_spu->i_y = p_sys->i_yoff;
447         p_spu->b_absolute = VLC_TRUE;
448     }
449
450     p_spu->i_height = 1;
451     p_spu->p_region->p_style = p_sys->p_style;
452
453     if( p_feed->p_pic )
454     {
455         /* Display the feed's image */
456         picture_t *p_pic = p_feed->p_pic;
457         video_format_t fmt_out = {0};
458
459         fmt_out.i_chroma = VLC_FOURCC('Y','U','V','A');
460         fmt_out.i_aspect = VOUT_ASPECT_FACTOR;
461         fmt_out.i_sar_num = fmt_out.i_sar_den = 1;
462         fmt_out.i_width =
463             fmt_out.i_visible_width = p_pic->p[Y_PLANE].i_visible_pitch;
464         fmt_out.i_height =
465             fmt_out.i_visible_height = p_pic->p[Y_PLANE].i_visible_lines;
466
467         p_region = p_spu->pf_create_region( VLC_OBJECT( p_filter ), &fmt_out );
468         if( !p_region )
469         {
470             msg_Err( p_filter, "cannot allocate SPU region" );
471         }
472         else
473         {
474             vout_CopyPicture( p_filter, &p_region->picture, p_pic );
475             p_spu->p_region->p_next = p_region;
476         }
477
478         /* Offset text to display right next to the image */
479         p_spu->p_region->i_x = p_pic->p[Y_PLANE].i_visible_pitch;
480     }
481
482     vlc_mutex_unlock( &p_sys->lock );
483     return p_spu;
484 }
485
486 /****************************************************************************
487  * RSS related functions
488  ****************************************************************************
489  * You should always lock the p_filter mutex before using any of these
490  * functions
491  ***************************************************************************/
492
493 #undef LoadImage /* do not conflict with Win32 API */
494
495 /****************************************************************************
496  * download and resize image located at psz_url
497  ***************************************************************************/
498 picture_t *LoadImage( filter_t *p_filter, const char *psz_url )
499 {
500     filter_sys_t *p_sys = p_filter->p_sys;
501
502     video_format_t fmt_in={0}, fmt_out={0};
503     picture_t *p_orig, *p_pic=NULL;
504     image_handler_t *p_handler = image_HandlerCreate( p_filter );
505
506     char *psz_local;
507
508     psz_local = ToLocale( psz_url );
509     fmt_out.i_chroma = VLC_FOURCC('Y','U','V','A');
510     p_orig = image_ReadUrl( p_handler, psz_local, &fmt_in, &fmt_out );
511     LocaleFree( psz_local );
512
513     if( !p_orig )
514     {
515         msg_Warn( p_filter, "Unable to read image %s", psz_url );
516     }
517     else if( p_sys->p_style->i_font_size > 0 )
518     {
519
520         fmt_in.i_chroma = VLC_FOURCC('Y','U','V','A');
521         fmt_in.i_height = p_orig->p[Y_PLANE].i_visible_lines;
522         fmt_in.i_width = p_orig->p[Y_PLANE].i_visible_pitch;
523         fmt_out.i_width = p_orig->p[Y_PLANE].i_visible_pitch
524             *p_sys->p_style->i_font_size/p_orig->p[Y_PLANE].i_visible_lines;
525         fmt_out.i_height = p_sys->p_style->i_font_size;
526
527         p_pic = image_Convert( p_handler, p_orig, &fmt_in, &fmt_out );
528         p_orig->pf_release( p_orig );
529         if( !p_pic )
530         {
531             msg_Warn( p_filter, "Error while converting %s", psz_url );
532         }
533     }
534     else
535     {
536         p_pic = p_orig;
537     }
538
539     image_HandlerDelete( p_handler );
540
541     return p_pic;
542 }
543
544 /****************************************************************************
545  * remove all ' ' '\t' '\n' '\r' characters from the begining and end of the
546  * string.
547  ***************************************************************************/
548 char *removeWhiteChars( char *psz_src )
549 {
550     char *psz_src2 = strdup( psz_src );
551     char *psz_clean = strdup( psz_src2 );
552     char *psz_clean2;
553     int i;
554     while( ( *psz_clean == ' ' || *psz_clean == '\t'
555            || *psz_clean == '\n' || *psz_clean == '\r' )
556            && *psz_clean != '\0' )
557     {
558         psz_clean++;
559     }
560     i = strlen( psz_clean );
561     while( --i > 0 &&
562          ( psz_clean[i] == ' ' || psz_clean[i] == '\t'
563         || psz_clean[i] == '\n' || psz_clean[i] == '\r' ) );
564     psz_clean[i+1] = '\0';
565     psz_clean2 = strdup( psz_clean );
566     free( psz_src2 );
567     return psz_clean2;
568 }
569
570 /****************************************************************************
571  * FetchRSS (or Atom) feeds
572  ***************************************************************************/
573 static int FetchRSS( filter_t *p_filter)
574 {
575     filter_sys_t *p_sys = p_filter->p_sys;
576
577     stream_t *p_stream = NULL;
578     xml_t *p_xml = NULL;
579     xml_reader_t *p_xml_reader = NULL;
580
581     char *psz_eltname = NULL;
582     char *psz_eltvalue = NULL;
583     char *psz_feed = NULL;
584     char *psz_buffer = NULL;
585     char *psz_buffer_2 = NULL;
586
587     int i_feed;
588     int i_item;
589     vlc_bool_t b_is_item;
590     vlc_bool_t b_is_image;
591     int i_int;
592
593     FreeRSS( p_filter );
594     p_sys->i_feeds = 1;
595     i_int = 0;
596     while( p_sys->psz_urls[i_int] != 0 )
597         if( p_sys->psz_urls[i_int++] == '|' )
598             p_sys->i_feeds++;
599     p_sys->p_feeds = (struct rss_feed_t *)malloc( p_sys->i_feeds
600                                 * sizeof( struct rss_feed_t ) );
601
602     p_xml = xml_Create( p_filter );
603     if( !p_xml )
604     {
605         msg_Err( p_filter, "Failed to open XML parser" );
606         return 1;
607     }
608
609     psz_buffer = strdup( p_sys->psz_urls );
610     psz_buffer_2 = psz_buffer; /* keep track so we can free it */
611     for( i_feed = 0; i_feed < p_sys->i_feeds; i_feed++ )
612     {
613         struct rss_feed_t *p_feed = p_sys->p_feeds+i_feed;
614
615         if( psz_buffer == NULL ) break;
616         if( psz_buffer[0] == 0 ) psz_buffer++;
617         psz_feed = psz_buffer;
618         psz_buffer = strchr( psz_buffer, '|' );
619         if( psz_buffer != NULL ) psz_buffer[0] = 0;
620
621         p_feed->psz_title = NULL;
622         p_feed->psz_description = NULL;
623         p_feed->psz_link = NULL;
624         p_feed->psz_image = NULL;
625         p_feed->p_pic = NULL;
626         p_feed->i_items = 0;
627         p_feed->p_items = NULL;
628
629         msg_Dbg( p_filter, "Opening %s RSS/Atom feed ...", psz_feed );
630
631         p_stream = stream_UrlNew( p_filter, psz_feed );
632         if( !p_stream )
633         {
634             msg_Err( p_filter, "Failed to open %s for reading", psz_feed );
635             return 1;
636         }
637
638         p_xml_reader = xml_ReaderCreate( p_xml, p_stream );
639         if( !p_xml_reader )
640         {
641             msg_Err( p_filter, "Failed to open %s for parsing", psz_feed );
642             return 1;
643         }
644
645         i_item = 0;
646         b_is_item = VLC_FALSE;
647         b_is_image = VLC_FALSE;
648
649         while( xml_ReaderRead( p_xml_reader ) == 1 )
650         {
651             switch( xml_ReaderNodeType( p_xml_reader ) )
652             {
653                 // Error
654                 case -1:
655                     return 1;
656
657                 case XML_READER_STARTELEM:
658                     if( psz_eltname )
659                     {
660                         free( psz_eltname );
661                         psz_eltname = NULL;
662                     }
663                     psz_eltname = xml_ReaderName( p_xml_reader );
664                     if( !psz_eltname )
665                     {
666                         return 1;
667                     }
668 #                   ifdef RSS_DEBUG
669                     msg_Dbg( p_filter, "element name : %s", psz_eltname );
670 #                   endif
671                     if( !strcmp( psz_eltname, "item" ) /* rss */
672                      || !strcmp( psz_eltname, "entry" ) ) /* atom */
673                     {
674                         b_is_item = VLC_TRUE;
675                         p_feed->i_items++;
676                         p_feed->p_items = (struct rss_item_t *)realloc( p_feed->p_items, p_feed->i_items * sizeof( struct rss_item_t ) );
677                         p_feed->p_items[p_feed->i_items-1].psz_title = NULL;
678                         p_feed->p_items[p_feed->i_items-1].psz_description
679                                                                      = NULL;
680                         p_feed->p_items[p_feed->i_items-1].psz_link = NULL;
681                     }
682                     else if( !strcmp( psz_eltname, "image" ) ) /* rss */
683                     {
684                         b_is_image = VLC_TRUE;
685                     }
686                     else if( !strcmp( psz_eltname, "link" ) ) /* atom */
687                     {
688                         char *psz_href = NULL;
689                         char *psz_rel = NULL;
690                         while( xml_ReaderNextAttr( p_xml_reader )
691                                == VLC_SUCCESS )
692                         {
693                             char *psz_name = xml_ReaderName( p_xml_reader );
694                             char *psz_value = xml_ReaderValue( p_xml_reader );
695                             if( !strcmp( psz_name, "rel" ) )
696                             {
697                                 psz_rel = psz_value;
698                             }
699                             else if( !strcmp( psz_name, "href" ) )
700                             {
701                                 psz_href = psz_value;
702                             }
703                             else
704                             {
705                                 free( psz_value );
706                             }
707                             free( psz_name );
708                         }
709                         if( psz_rel && psz_href )
710                         {
711                             if( !strcmp( psz_rel, "alternate" )
712                                 && b_is_item == VLC_FALSE
713                                 && b_is_image == VLC_FALSE
714                                 && !p_feed->psz_link )
715                             {
716                                 p_feed->psz_link = psz_href;
717                             }
718                             /* this isn't in the rfc but i found some ... */
719                             else if( ( !strcmp( psz_rel, "logo" )
720                                     || !strcmp( psz_rel, "icon" ) )
721                                     && b_is_item == VLC_FALSE
722                                     && b_is_image == VLC_FALSE
723                                     && !p_feed->psz_image )
724                             {
725                                 p_feed->psz_image = psz_href;
726                             }
727                             else
728                             {
729                                 free( psz_href );
730                             }
731                         }
732                         else
733                         {
734                             if( psz_href ) free( psz_href );
735                         }
736                         if( psz_rel ) free( psz_rel );
737                     }
738                     break;
739
740                 case XML_READER_ENDELEM:
741                     if( psz_eltname )
742                     {
743                         free( psz_eltname );
744                         psz_eltname = NULL;
745                     }
746                     psz_eltname = xml_ReaderName( p_xml_reader );
747                     if( !psz_eltname )
748                     {
749                         return 1;
750                     }
751 #                   ifdef RSS_DEBUG
752                     msg_Dbg( p_filter, "element end : %s", psz_eltname );
753 #                   endif
754                     if( !strcmp( psz_eltname, "item" ) /* rss */
755                      || !strcmp( psz_eltname, "entry" ) ) /* atom */
756                     {
757                         b_is_item = VLC_FALSE;
758                         i_item++;
759                     }
760                     else if( !strcmp( psz_eltname, "image" ) ) /* rss */
761                     {
762                         b_is_image = VLC_FALSE;
763                     }
764                     free( psz_eltname );
765                     psz_eltname = NULL;
766                     break;
767
768                 case XML_READER_TEXT:
769                     if( !psz_eltname ) break;
770                     psz_eltvalue = xml_ReaderValue( p_xml_reader );
771                     if( !psz_eltvalue )
772                     {
773                         return 1;
774                     }
775                     else
776                     {
777                         char *psz_clean;
778                         psz_clean = removeWhiteChars( psz_eltvalue );
779                         free( psz_eltvalue ); psz_eltvalue = psz_clean;
780                     }
781 #                   ifdef RSS_DEBUG
782                     msg_Dbg( p_filter, "  text : <%s>", psz_eltvalue );
783 #                   endif
784                     if( b_is_item == VLC_TRUE )
785                     {
786                         struct rss_item_t *p_item;
787                         p_item = p_feed->p_items+i_item;
788                         if( !strcmp( psz_eltname, "title" ) /* rss/atom */
789                             && !p_item->psz_title )
790                         {
791                             p_item->psz_title = psz_eltvalue;
792                         }
793                         else if( !strcmp( psz_eltname, "link" ) /* rss */
794                                  && !p_item->psz_link )
795                         {
796                             p_item->psz_link = psz_eltvalue;
797                         }
798                         else if((!strcmp( psz_eltname, "description" ) /* rss */
799                               || !strcmp( psz_eltname, "summary" ) ) /* atom */
800                               && !p_item->psz_description )
801                         {
802                             p_item->psz_description = psz_eltvalue;
803                         }
804                         else
805                         {
806                             free( psz_eltvalue );
807                             psz_eltvalue = NULL;
808                         }
809                     }
810                     else if( b_is_image == VLC_TRUE )
811                     {
812                         if( !strcmp( psz_eltname, "url" ) /* rss */
813                             && !p_feed->psz_image )
814                         {
815                             p_feed->psz_image = psz_eltvalue;
816                         }
817                         else
818                         {
819                             free( psz_eltvalue );
820                             psz_eltvalue = NULL;
821                         }
822                     }
823                     else
824                     {
825                         if( !strcmp( psz_eltname, "title" ) /* rss/atom */
826                             && !p_feed->psz_title )
827                         {
828                             p_feed->psz_title = psz_eltvalue;
829                         }
830                         else if( !strcmp( psz_eltname, "link" ) /* rss */
831                                  && !p_feed->psz_link )
832                         {
833                             p_feed->psz_link = psz_eltvalue;
834                         }
835                         else if((!strcmp( psz_eltname, "description" ) /* rss */
836                               || !strcmp( psz_eltname, "subtitle" ) ) /* atom */
837                               && !p_feed->psz_description )
838                         {
839                             p_feed->psz_description = psz_eltvalue;
840                         }
841                         else if( ( !strcmp( psz_eltname, "logo" ) /* atom */
842                               || !strcmp( psz_eltname, "icon" ) ) /* atom */
843                               && !p_feed->psz_image )
844                         {
845                             p_feed->psz_image = psz_eltvalue;
846                         }
847                         else
848                         {
849                             free( psz_eltvalue );
850                             psz_eltvalue = NULL;
851                         }
852                     }
853                     break;
854             }
855         }
856
857         if( p_sys->b_images == VLC_TRUE
858             && p_feed->psz_image && !p_feed->p_pic )
859         {
860             p_feed->p_pic = LoadImage( p_filter, p_feed->psz_image );
861         }
862
863         if( p_xml_reader && p_xml ) xml_ReaderDelete( p_xml, p_xml_reader );
864         if( p_stream ) stream_Delete( p_stream );
865         msg_Dbg( p_filter, "Done with %s RSS/Atom feed.", psz_feed );
866     }
867     free( psz_buffer_2 );
868     if( p_xml ) xml_Delete( p_xml );
869
870     return 0;
871 }
872
873 /****************************************************************************
874  * FreeRSS
875  ***************************************************************************/
876 static void FreeRSS( filter_t *p_filter)
877 {
878     filter_sys_t *p_sys = p_filter->p_sys;
879
880     struct rss_item_t *p_item;
881     struct rss_feed_t *p_feed;
882
883     int i_feed;
884     int i_item;
885
886     for( i_feed = 0; i_feed < p_sys->i_feeds; i_feed++ )
887     {
888         p_feed = p_sys->p_feeds+i_feed;
889         for( i_item = 0; i_item < p_feed->i_items; i_item++ )
890         {
891             p_item = p_feed->p_items+i_item;
892             free( p_item->psz_title );
893             free( p_item->psz_link );
894             free( p_item->psz_description );
895         }
896         free( p_feed->p_items );
897         free( p_feed->psz_title);
898         free( p_feed->psz_link );
899         free( p_feed->psz_description );
900         free( p_feed->psz_image );
901         if( p_feed->p_pic != NULL )
902             p_feed->p_pic->pf_release( p_feed->p_pic );
903     }
904     free( p_sys->p_feeds );
905     p_sys->i_feeds = 0;
906 }