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