852e49dbb5e2818026efab516197cc6f50db60e7
[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 <vlc_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 const 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_("Speed of feeds")
124 #define SPEED_LONGTEXT N_("Speed of the RSS/Atom feeds (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 const 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 #define CFG_PREFIX "rrs-"
164
165 /*****************************************************************************
166  * Module descriptor
167  *****************************************************************************/
168 vlc_module_begin();
169     set_capability( "sub filter", 0 );
170     set_shortname( "RSS / Atom" );
171     set_callbacks( CreateFilter, DestroyFilter );
172     set_category( CAT_VIDEO );
173     set_subcategory( SUBCAT_VIDEO_SUBPIC );
174     add_string( CFG_PREFIX "urls", "rss", NULL, MSG_TEXT, MSG_LONGTEXT, VLC_FALSE );
175
176     set_section( N_("Position"), NULL );
177     add_integer( CFG_PREFIX "x", -1, NULL, POSX_TEXT, POSX_LONGTEXT, VLC_TRUE );
178     add_integer( CFG_PREFIX "y", 0, NULL, POSY_TEXT, POSY_LONGTEXT, VLC_TRUE );
179     add_integer( CFG_PREFIX "position", 5, NULL, POS_TEXT, POS_LONGTEXT, VLC_FALSE );
180         change_integer_list( pi_pos_values, ppsz_pos_descriptions, 0 );
181
182     set_section( N_("Font"), NULL );
183     /* 5 sets the default to top [1] left [4] */
184     add_integer_with_range( CFG_PREFIX "opacity", 255, 0, 255, NULL,
185         OPACITY_TEXT, OPACITY_LONGTEXT, VLC_FALSE );
186     add_integer( CFG_PREFIX "color", 0xFFFFFF, NULL, COLOR_TEXT, COLOR_LONGTEXT,
187                   VLC_FALSE );
188         change_integer_list( pi_color_values, ppsz_color_descriptions, 0 );
189     add_integer( CFG_PREFIX "size", -1, NULL, SIZE_TEXT, SIZE_LONGTEXT, VLC_FALSE );
190
191     set_section( N_("Misc"), NULL );
192     add_integer( CFG_PREFIX "speed", 100000, NULL, SPEED_TEXT, SPEED_LONGTEXT,
193                  VLC_FALSE );
194     add_integer( CFG_PREFIX "length", 60, NULL, LENGTH_TEXT, LENGTH_LONGTEXT,
195                  VLC_FALSE );
196     add_integer( CFG_PREFIX "ttl", 1800, NULL, TTL_TEXT, TTL_LONGTEXT, VLC_FALSE );
197     add_bool( CFG_PREFIX "images", 1, NULL, IMAGE_TEXT, IMAGE_LONGTEXT, VLC_FALSE );
198
199     set_description( _("RSS and Atom feed display") );
200     add_shortcut( "rss" );
201     add_shortcut( "atom" );
202 vlc_module_end();
203
204 static const char *ppsz_filter_options[] = {
205     "urls", "x", "y", "position", "color", "size", "speed", "length",
206     "ttl", "images", NULL
207 };
208
209 /*****************************************************************************
210  * CreateFilter: allocates RSS video filter
211  *****************************************************************************/
212 static int CreateFilter( vlc_object_t *p_this )
213 {
214     filter_t *p_filter = (filter_t *)p_this;
215     filter_sys_t *p_sys;
216     int i_feed;
217
218     /* Allocate structure */
219     p_sys = p_filter->p_sys = malloc( sizeof( filter_sys_t ) );
220     if( p_sys == NULL )
221     {
222         msg_Err( p_filter, "out of memory" );
223         return VLC_ENOMEM;
224     }
225
226     vlc_mutex_init( p_filter, &p_sys->lock );
227     vlc_mutex_lock( &p_sys->lock );
228
229     config_ChainParse( p_filter, CFG_PREFIX, ppsz_filter_options,
230                        p_filter->p_cfg );
231
232     p_sys->psz_urls = var_CreateGetString( p_filter, "rss-urls" );
233     p_sys->i_cur_feed = 0;
234     p_sys->i_cur_item = 0;
235     p_sys->i_cur_char = 0;
236     p_sys->i_feeds = 0;
237     p_sys->p_feeds = NULL;
238     p_sys->i_speed = var_CreateGetInteger( p_filter, "rss-speed" );
239     p_sys->i_length = var_CreateGetInteger( p_filter, "rss-length" );
240     p_sys->i_ttl = __MAX( 0, var_CreateGetInteger( p_filter, "rss-ttl" ) );
241     p_sys->b_images = var_CreateGetBool( p_filter, "rss-images" );
242     p_sys->psz_marquee = (char *)malloc( p_sys->i_length );
243
244     p_sys->p_style = malloc( sizeof( text_style_t ));
245     memcpy( p_sys->p_style, &default_text_style, sizeof( text_style_t ));
246
247     p_sys->i_xoff = var_CreateGetInteger( p_filter, "rss-x" );
248     p_sys->i_yoff = var_CreateGetInteger( p_filter, "rss-y" );
249     p_sys->i_pos = var_CreateGetInteger( p_filter, "rss-position" );
250     p_sys->p_style->i_font_alpha = 255 - var_CreateGetInteger( p_filter, "rss-opacity" );
251     p_sys->p_style->i_font_color = var_CreateGetInteger( p_filter, "rss-color" );
252     p_sys->p_style->i_font_size = var_CreateGetInteger( p_filter, "rss-size" );
253
254     if( p_sys->b_images == VLC_TRUE && p_sys->p_style->i_font_size == -1 )
255     {
256         msg_Warn( p_filter, "rrs-size wasn't specified. Feed images will thus be displayed without being resized" );
257     }
258
259     if( FetchRSS( p_filter ) )
260     {
261         msg_Err( p_filter, "failed while fetching RSS ... too bad" );
262         vlc_mutex_unlock( &p_sys->lock );
263         return VLC_EGENERIC;
264     }
265     p_sys->t_last_update = time( NULL );
266
267     if( p_sys->i_feeds == 0 )
268     {
269         vlc_mutex_unlock( &p_sys->lock );
270         return VLC_EGENERIC;
271     }
272     for( i_feed=0; i_feed < p_sys->i_feeds; i_feed ++ )
273         if( p_sys->p_feeds[i_feed].i_items == 0 )
274         {
275             vlc_mutex_unlock( &p_sys->lock );
276             return VLC_EGENERIC;
277         }
278
279     /* Misc init */
280     p_filter->pf_sub_filter = Filter;
281     p_sys->last_date = (mtime_t)0;
282
283     vlc_mutex_unlock( &p_sys->lock );
284
285     return VLC_SUCCESS;
286 }
287 /*****************************************************************************
288  * DestroyFilter: destroy RSS video filter
289  *****************************************************************************/
290 static void DestroyFilter( vlc_object_t *p_this )
291 {
292     filter_t *p_filter = (filter_t *)p_this;
293     filter_sys_t *p_sys = p_filter->p_sys;
294
295     vlc_mutex_lock( &p_sys->lock );
296
297     if( p_sys->p_style ) free( p_sys->p_style );
298     if( p_sys->psz_marquee ) free( p_sys->psz_marquee );
299     free( p_sys->psz_urls );
300     FreeRSS( p_filter );
301     vlc_mutex_unlock( &p_sys->lock );
302     vlc_mutex_destroy( &p_sys->lock );
303     free( p_sys );
304
305     /* Delete the RSS variables */
306     var_Destroy( p_filter, "rss-urls" );
307     var_Destroy( p_filter, "rss-speed" );
308     var_Destroy( p_filter, "rss-length" );
309     var_Destroy( p_filter, "rss-ttl" );
310     var_Destroy( p_filter, "rss-images" );
311     var_Destroy( p_filter, "rss-x" );
312     var_Destroy( p_filter, "rss-y" );
313     var_Destroy( p_filter, "rss-position" );
314     var_Destroy( p_filter, "rss-color");
315     var_Destroy( p_filter, "rss-opacity");
316     var_Destroy( p_filter, "rss-size");
317 }
318
319 /****************************************************************************
320  * Filter: the whole thing
321  ****************************************************************************
322  * This function outputs subpictures at regular time intervals.
323  ****************************************************************************/
324 static subpicture_t *Filter( filter_t *p_filter, mtime_t date )
325 {
326     filter_sys_t *p_sys = p_filter->p_sys;
327     subpicture_t *p_spu;
328     video_format_t fmt = {0};
329
330     subpicture_region_t *p_region;
331
332     int i_feed, i_item;
333
334     struct rss_feed_t *p_feed;
335
336     vlc_mutex_lock( &p_sys->lock );
337
338     if( p_sys->last_date
339        + ( p_sys->i_cur_char == 0 && p_sys->i_cur_item == 0 ? 5 : 1 )
340            /* ( ... ? 5 : 1 ) means "wait more for the 1st char" */
341        * p_sys->i_speed > date )
342     {
343         vlc_mutex_unlock( &p_sys->lock );
344         return NULL;
345     }
346
347     /* Do we need to update the feeds ? */
348     if( p_sys->i_ttl
349         && time( NULL ) > p_sys->t_last_update + (time_t)p_sys->i_ttl )
350     {
351         msg_Dbg( p_filter, "Forcing update of all the RSS feeds" );
352         if( FetchRSS( p_filter ) )
353         {
354             msg_Err( p_filter, "failed while fetching RSS ... too bad" );
355             vlc_mutex_unlock( &p_sys->lock );
356             return NULL; /* FIXME : we most likely messed up all the data,
357                           * so we might need to do something about it */
358         }
359         p_sys->t_last_update = time( NULL );
360     }
361
362     p_sys->last_date = date;
363     p_sys->i_cur_char++;
364     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 )
365     {
366         p_sys->i_cur_char = 0;
367         p_sys->i_cur_item++;
368         if( p_sys->i_cur_item >= p_sys->p_feeds[p_sys->i_cur_feed].i_items )
369         {
370             p_sys->i_cur_item = 0;
371             p_sys->i_cur_feed = (p_sys->i_cur_feed + 1)%p_sys->i_feeds;
372         }
373     }
374
375     p_spu = p_filter->pf_sub_buffer_new( p_filter );
376     if( !p_spu )
377     {
378         vlc_mutex_unlock( &p_sys->lock );
379         return NULL;
380     }
381
382     fmt.i_chroma = VLC_FOURCC('T','E','X','T');
383
384     p_spu->p_region = p_spu->pf_create_region( VLC_OBJECT(p_filter), &fmt );
385     if( !p_spu->p_region )
386     {
387         p_filter->pf_sub_buffer_del( p_filter, p_spu );
388         vlc_mutex_unlock( &p_sys->lock );
389         return NULL;
390     }
391
392     /* Generate the string that will be displayed. This string is supposed to
393        be p_sys->i_length characters long. */
394     i_item = p_sys->i_cur_item;
395     i_feed = p_sys->i_cur_feed;
396     p_feed = &p_sys->p_feeds[p_sys->i_cur_feed];
397
398     if( p_feed->p_pic )
399     {
400         /* Don't display the feed's title if we have an image */
401         snprintf( p_sys->psz_marquee, p_sys->i_length, "%s",
402                   p_sys->p_feeds[i_feed].p_items[i_item].psz_title
403                   +p_sys->i_cur_char );
404     }
405     else
406     {
407         snprintf( p_sys->psz_marquee, p_sys->i_length, "%s : %s",
408                   p_sys->p_feeds[i_feed].psz_title,
409                   p_sys->p_feeds[i_feed].p_items[i_item].psz_title
410                   +p_sys->i_cur_char );
411     }
412
413     while( strlen( p_sys->psz_marquee ) < (unsigned int)p_sys->i_length )
414     {
415         i_item++;
416         if( i_item == p_sys->p_feeds[i_feed].i_items ) break;
417         snprintf( strchr( p_sys->psz_marquee, 0 ),
418                   p_sys->i_length - strlen( p_sys->psz_marquee ),
419                   " - %s",
420                   p_sys->p_feeds[i_feed].p_items[i_item].psz_title );
421     }
422
423     /* Calls to snprintf might split multibyte UTF8 chars ...
424      * which freetype doesn't like. */
425     {
426         char *a = strdup( p_sys->psz_marquee );
427         char *a2 = a;
428         char *b = p_sys->psz_marquee;
429         EnsureUTF8( p_sys->psz_marquee );
430         /* we want to use ' ' instead of '?' for erroneous chars */
431         while( *b != '\0' )
432         {
433             if( *b != *a ) *b = ' ';
434             b++;a++;
435         }
436         free( a2 );
437     }
438
439     p_spu->p_region->psz_text = strdup(p_sys->psz_marquee);
440     p_spu->i_start = date;
441     p_spu->i_stop  = 0;
442     p_spu->b_ephemer = VLC_TRUE;
443
444     /*  where to locate the string: */
445     if( p_sys->i_xoff < 0 || p_sys->i_yoff < 0 )
446     {   /* set to one of the 9 relative locations */
447         p_spu->i_flags = p_sys->i_pos;
448         p_spu->i_x = 0;
449         p_spu->i_y = 0;
450         p_spu->b_absolute = VLC_FALSE;
451     }
452     else
453     {   /*  set to an absolute xy, referenced to upper left corner */
454         p_spu->i_flags = OSD_ALIGN_LEFT | OSD_ALIGN_TOP;
455         p_spu->i_x = p_sys->i_xoff;
456         p_spu->i_y = p_sys->i_yoff;
457         p_spu->b_absolute = VLC_TRUE;
458     }
459
460     p_spu->i_height = 1;
461     p_spu->p_region->p_style = p_sys->p_style;
462
463     if( p_feed->p_pic )
464     {
465         /* Display the feed's image */
466         picture_t *p_pic = p_feed->p_pic;
467         video_format_t fmt_out = {0};
468
469         fmt_out.i_chroma = VLC_FOURCC('Y','U','V','A');
470         fmt_out.i_aspect = VOUT_ASPECT_FACTOR;
471         fmt_out.i_sar_num = fmt_out.i_sar_den = 1;
472         fmt_out.i_width =
473             fmt_out.i_visible_width = p_pic->p[Y_PLANE].i_visible_pitch;
474         fmt_out.i_height =
475             fmt_out.i_visible_height = p_pic->p[Y_PLANE].i_visible_lines;
476
477         p_region = p_spu->pf_create_region( VLC_OBJECT( p_filter ), &fmt_out );
478         if( !p_region )
479         {
480             msg_Err( p_filter, "cannot allocate SPU region" );
481         }
482         else
483         {
484             vout_CopyPicture( p_filter, &p_region->picture, p_pic );
485             p_spu->p_region->p_next = p_region;
486         }
487
488         /* Offset text to display right next to the image */
489         p_spu->p_region->i_x = p_pic->p[Y_PLANE].i_visible_pitch;
490     }
491
492     vlc_mutex_unlock( &p_sys->lock );
493     return p_spu;
494 }
495
496 /****************************************************************************
497  * RSS related functions
498  ****************************************************************************
499  * You should always lock the p_filter mutex before using any of these
500  * functions
501  ***************************************************************************/
502
503 #undef LoadImage /* do not conflict with Win32 API */
504
505 /****************************************************************************
506  * download and resize image located at psz_url
507  ***************************************************************************/
508 static picture_t *LoadImage( filter_t *p_filter, const char *psz_url )
509 {
510     filter_sys_t *p_sys = p_filter->p_sys;
511
512     video_format_t fmt_in={0}, fmt_out={0};
513     picture_t *p_orig, *p_pic=NULL;
514     image_handler_t *p_handler = image_HandlerCreate( p_filter );
515
516     fmt_out.i_chroma = VLC_FOURCC('Y','U','V','A');
517     p_orig = image_ReadUrl( p_handler, psz_url, &fmt_in, &fmt_out );
518
519     if( !p_orig )
520     {
521         msg_Warn( p_filter, "Unable to read image %s", psz_url );
522     }
523     else if( p_sys->p_style->i_font_size > 0 )
524     {
525
526         fmt_in.i_chroma = VLC_FOURCC('Y','U','V','A');
527         fmt_in.i_height = p_orig->p[Y_PLANE].i_visible_lines;
528         fmt_in.i_width = p_orig->p[Y_PLANE].i_visible_pitch;
529         fmt_out.i_width = p_orig->p[Y_PLANE].i_visible_pitch
530             *p_sys->p_style->i_font_size/p_orig->p[Y_PLANE].i_visible_lines;
531         fmt_out.i_height = p_sys->p_style->i_font_size;
532
533         p_pic = image_Convert( p_handler, p_orig, &fmt_in, &fmt_out );
534         p_orig->pf_release( p_orig );
535         if( !p_pic )
536         {
537             msg_Warn( p_filter, "Error while converting %s", psz_url );
538         }
539     }
540     else
541     {
542         p_pic = p_orig;
543     }
544
545     image_HandlerDelete( p_handler );
546
547     return p_pic;
548 }
549
550 /****************************************************************************
551  * remove all ' ' '\t' '\n' '\r' characters from the begining and end of the
552  * string.
553  ***************************************************************************/
554 static char *removeWhiteChars( char *psz_src )
555 {
556     char *psz_src2 = strdup( psz_src );
557     char *psz_clean = strdup( psz_src2 );
558     char *psz_clean2;
559     int i;
560     while( ( *psz_clean == ' ' || *psz_clean == '\t'
561            || *psz_clean == '\n' || *psz_clean == '\r' )
562            && *psz_clean != '\0' )
563     {
564         psz_clean++;
565     }
566     i = strlen( psz_clean );
567     while( --i > 0 &&
568          ( psz_clean[i] == ' ' || psz_clean[i] == '\t'
569         || psz_clean[i] == '\n' || psz_clean[i] == '\r' ) );
570     psz_clean[i+1] = '\0';
571     psz_clean2 = strdup( psz_clean );
572     free( psz_src2 );
573     return psz_clean2;
574 }
575
576 /****************************************************************************
577  * FetchRSS (or Atom) feeds
578  ***************************************************************************/
579 static int FetchRSS( filter_t *p_filter)
580 {
581     filter_sys_t *p_sys = p_filter->p_sys;
582
583     stream_t *p_stream = NULL;
584     xml_t *p_xml = NULL;
585     xml_reader_t *p_xml_reader = NULL;
586
587     char *psz_eltname = NULL;
588     char *psz_eltvalue = NULL;
589     char *psz_feed = NULL;
590     char *psz_buffer = NULL;
591     char *psz_buffer_2 = NULL;
592
593     int i_feed;
594     int i_item;
595     vlc_bool_t b_is_item;
596     vlc_bool_t b_is_image;
597     int i_int;
598
599     FreeRSS( p_filter );
600     p_sys->i_feeds = 1;
601     i_int = 0;
602     while( p_sys->psz_urls[i_int] != 0 )
603         if( p_sys->psz_urls[i_int++] == '|' )
604             p_sys->i_feeds++;
605     p_sys->p_feeds = (struct rss_feed_t *)malloc( p_sys->i_feeds
606                                 * sizeof( struct rss_feed_t ) );
607
608     p_xml = xml_Create( p_filter );
609     if( !p_xml )
610     {
611         msg_Err( p_filter, "Failed to open XML parser" );
612         return 1;
613     }
614
615     psz_buffer = strdup( p_sys->psz_urls );
616     psz_buffer_2 = psz_buffer; /* keep track so we can free it */
617     for( i_feed = 0; i_feed < p_sys->i_feeds; i_feed++ )
618     {
619         struct rss_feed_t *p_feed = p_sys->p_feeds+i_feed;
620
621         if( psz_buffer == NULL ) break;
622         if( psz_buffer[0] == 0 ) psz_buffer++;
623         psz_feed = psz_buffer;
624         psz_buffer = strchr( psz_buffer, '|' );
625         if( psz_buffer != NULL ) psz_buffer[0] = 0;
626
627         p_feed->psz_title = NULL;
628         p_feed->psz_description = NULL;
629         p_feed->psz_link = NULL;
630         p_feed->psz_image = NULL;
631         p_feed->p_pic = NULL;
632         p_feed->i_items = 0;
633         p_feed->p_items = NULL;
634
635         msg_Dbg( p_filter, "opening %s RSS/Atom feed ...", psz_feed );
636
637         p_stream = stream_UrlNew( p_filter, psz_feed );
638         if( !p_stream )
639         {
640             msg_Err( p_filter, "Failed to open %s for reading", psz_feed );
641             return 1;
642         }
643
644         p_xml_reader = xml_ReaderCreate( p_xml, p_stream );
645         if( !p_xml_reader )
646         {
647             msg_Err( p_filter, "Failed to open %s for parsing", psz_feed );
648             return 1;
649         }
650
651         i_item = 0;
652         b_is_item = VLC_FALSE;
653         b_is_image = VLC_FALSE;
654
655         while( xml_ReaderRead( p_xml_reader ) == 1 )
656         {
657             switch( xml_ReaderNodeType( p_xml_reader ) )
658             {
659                 // Error
660                 case -1:
661                     return 1;
662
663                 case XML_READER_STARTELEM:
664                     if( psz_eltname )
665                     {
666                         free( psz_eltname );
667                         psz_eltname = NULL;
668                     }
669                     psz_eltname = xml_ReaderName( p_xml_reader );
670                     if( !psz_eltname )
671                     {
672                         return 1;
673                     }
674 #                   ifdef RSS_DEBUG
675                     msg_Dbg( p_filter, "element name: %s", psz_eltname );
676 #                   endif
677                     if( !strcmp( psz_eltname, "item" ) /* rss */
678                      || !strcmp( psz_eltname, "entry" ) ) /* atom */
679                     {
680                         b_is_item = VLC_TRUE;
681                         p_feed->i_items++;
682                         p_feed->p_items = (struct rss_item_t *)realloc( p_feed->p_items, p_feed->i_items * sizeof( struct rss_item_t ) );
683                         p_feed->p_items[p_feed->i_items-1].psz_title = NULL;
684                         p_feed->p_items[p_feed->i_items-1].psz_description
685                                                                      = NULL;
686                         p_feed->p_items[p_feed->i_items-1].psz_link = NULL;
687                     }
688                     else if( !strcmp( psz_eltname, "image" ) ) /* rss */
689                     {
690                         b_is_image = VLC_TRUE;
691                     }
692                     else if( !strcmp( psz_eltname, "link" ) ) /* atom */
693                     {
694                         char *psz_href = NULL;
695                         char *psz_rel = NULL;
696                         while( xml_ReaderNextAttr( p_xml_reader )
697                                == VLC_SUCCESS )
698                         {
699                             char *psz_name = xml_ReaderName( p_xml_reader );
700                             char *psz_value = xml_ReaderValue( p_xml_reader );
701                             if( !strcmp( psz_name, "rel" ) )
702                             {
703                                 psz_rel = psz_value;
704                             }
705                             else if( !strcmp( psz_name, "href" ) )
706                             {
707                                 psz_href = psz_value;
708                             }
709                             else
710                             {
711                                 free( psz_value );
712                             }
713                             free( psz_name );
714                         }
715                         if( psz_rel && psz_href )
716                         {
717                             if( !strcmp( psz_rel, "alternate" )
718                                 && b_is_item == VLC_FALSE
719                                 && b_is_image == VLC_FALSE
720                                 && !p_feed->psz_link )
721                             {
722                                 p_feed->psz_link = psz_href;
723                             }
724                             /* this isn't in the rfc but i found some ... */
725                             else if( ( !strcmp( psz_rel, "logo" )
726                                     || !strcmp( psz_rel, "icon" ) )
727                                     && b_is_item == VLC_FALSE
728                                     && b_is_image == VLC_FALSE
729                                     && !p_feed->psz_image )
730                             {
731                                 p_feed->psz_image = psz_href;
732                             }
733                             else
734                             {
735                                 free( psz_href );
736                             }
737                         }
738                         else
739                         {
740                             if( psz_href ) free( psz_href );
741                         }
742                         if( psz_rel ) free( psz_rel );
743                     }
744                     break;
745
746                 case XML_READER_ENDELEM:
747                     if( psz_eltname )
748                     {
749                         free( psz_eltname );
750                         psz_eltname = NULL;
751                     }
752                     psz_eltname = xml_ReaderName( p_xml_reader );
753                     if( !psz_eltname )
754                     {
755                         return 1;
756                     }
757 #                   ifdef RSS_DEBUG
758                     msg_Dbg( p_filter, "element end : %s", psz_eltname );
759 #                   endif
760                     if( !strcmp( psz_eltname, "item" ) /* rss */
761                      || !strcmp( psz_eltname, "entry" ) ) /* atom */
762                     {
763                         b_is_item = VLC_FALSE;
764                         i_item++;
765                     }
766                     else if( !strcmp( psz_eltname, "image" ) ) /* rss */
767                     {
768                         b_is_image = VLC_FALSE;
769                     }
770                     free( psz_eltname );
771                     psz_eltname = NULL;
772                     break;
773
774                 case XML_READER_TEXT:
775                     if( !psz_eltname ) break;
776                     psz_eltvalue = xml_ReaderValue( p_xml_reader );
777                     if( !psz_eltvalue )
778                     {
779                         return 1;
780                     }
781                     else
782                     {
783                         char *psz_clean;
784                         psz_clean = removeWhiteChars( psz_eltvalue );
785                         free( psz_eltvalue ); psz_eltvalue = psz_clean;
786                     }
787 #                   ifdef RSS_DEBUG
788                     msg_Dbg( p_filter, "  text : <%s>", psz_eltvalue );
789 #                   endif
790                     if( b_is_item == VLC_TRUE )
791                     {
792                         struct rss_item_t *p_item;
793                         p_item = p_feed->p_items+i_item;
794                         if( !strcmp( psz_eltname, "title" ) /* rss/atom */
795                             && !p_item->psz_title )
796                         {
797                             p_item->psz_title = psz_eltvalue;
798                         }
799                         else if( !strcmp( psz_eltname, "link" ) /* rss */
800                                  && !p_item->psz_link )
801                         {
802                             p_item->psz_link = psz_eltvalue;
803                         }
804                         else if((!strcmp( psz_eltname, "description" ) /* rss */
805                               || !strcmp( psz_eltname, "summary" ) ) /* atom */
806                               && !p_item->psz_description )
807                         {
808                             p_item->psz_description = psz_eltvalue;
809                         }
810                         else
811                         {
812                             free( psz_eltvalue );
813                             psz_eltvalue = NULL;
814                         }
815                     }
816                     else if( b_is_image == VLC_TRUE )
817                     {
818                         if( !strcmp( psz_eltname, "url" ) /* rss */
819                             && !p_feed->psz_image )
820                         {
821                             p_feed->psz_image = psz_eltvalue;
822                         }
823                         else
824                         {
825                             free( psz_eltvalue );
826                             psz_eltvalue = NULL;
827                         }
828                     }
829                     else
830                     {
831                         if( !strcmp( psz_eltname, "title" ) /* rss/atom */
832                             && !p_feed->psz_title )
833                         {
834                             p_feed->psz_title = psz_eltvalue;
835                         }
836                         else if( !strcmp( psz_eltname, "link" ) /* rss */
837                                  && !p_feed->psz_link )
838                         {
839                             p_feed->psz_link = psz_eltvalue;
840                         }
841                         else if((!strcmp( psz_eltname, "description" ) /* rss */
842                               || !strcmp( psz_eltname, "subtitle" ) ) /* atom */
843                               && !p_feed->psz_description )
844                         {
845                             p_feed->psz_description = psz_eltvalue;
846                         }
847                         else if( ( !strcmp( psz_eltname, "logo" ) /* atom */
848                               || !strcmp( psz_eltname, "icon" ) ) /* atom */
849                               && !p_feed->psz_image )
850                         {
851                             p_feed->psz_image = psz_eltvalue;
852                         }
853                         else
854                         {
855                             free( psz_eltvalue );
856                             psz_eltvalue = NULL;
857                         }
858                     }
859                     break;
860             }
861         }
862
863         if( p_sys->b_images == VLC_TRUE
864             && p_feed->psz_image && !p_feed->p_pic )
865         {
866             p_feed->p_pic = LoadImage( p_filter, p_feed->psz_image );
867         }
868
869         if( p_xml_reader && p_xml ) xml_ReaderDelete( p_xml, p_xml_reader );
870         if( p_stream ) stream_Delete( p_stream );
871         msg_Dbg( p_filter, "done with %s RSS/Atom feed", psz_feed );
872     }
873     free( psz_buffer_2 );
874     if( p_xml ) xml_Delete( p_xml );
875
876     return 0;
877 }
878
879 /****************************************************************************
880  * FreeRSS
881  ***************************************************************************/
882 static void FreeRSS( filter_t *p_filter)
883 {
884     filter_sys_t *p_sys = p_filter->p_sys;
885
886     struct rss_item_t *p_item;
887     struct rss_feed_t *p_feed;
888
889     int i_feed;
890     int i_item;
891
892     for( i_feed = 0; i_feed < p_sys->i_feeds; i_feed++ )
893     {
894         p_feed = p_sys->p_feeds+i_feed;
895         for( i_item = 0; i_item < p_feed->i_items; i_item++ )
896         {
897             p_item = p_feed->p_items+i_item;
898             free( p_item->psz_title );
899             free( p_item->psz_link );
900             free( p_item->psz_description );
901         }
902         free( p_feed->p_items );
903         free( p_feed->psz_title);
904         free( p_feed->psz_link );
905         free( p_feed->psz_description );
906         free( p_feed->psz_image );
907         if( p_feed->p_pic != NULL )
908             p_feed->p_pic->pf_release( p_feed->p_pic );
909     }
910     free( p_sys->p_feeds );
911     p_sys->i_feeds = 0;
912 }