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