]> git.sesse.net Git - vlc/blob - modules/video_filter/rss.c
Fix the help string for rss-speed.
[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 <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     int i_title;
116
117     int i_cur_feed;
118     int i_cur_item;
119     int i_cur_char;
120 };
121
122 #define MSG_TEXT N_("Feed URLs")
123 #define MSG_LONGTEXT N_("RSS/Atom feed '|' (pipe) seperated URLs.")
124 #define SPEED_TEXT N_("Speed of feeds")
125 #define SPEED_LONGTEXT N_("Speed of the RSS/Atom feeds in microseconds (bigger is slower).")
126 #define LENGTH_TEXT N_("Max length")
127 #define LENGTH_LONGTEXT N_("Maximum number of characters displayed on the " \
128                 "screen." )
129 #define TTL_TEXT N_("Refresh time")
130 #define TTL_LONGTEXT N_("Number of seconds between each forced refresh " \
131         "of the feeds. 0 means that the feeds are never updated." )
132 #define IMAGE_TEXT N_("Feed images")
133 #define IMAGE_LONGTEXT N_("Display feed images if available.")
134
135 #define POSX_TEXT N_("X offset")
136 #define POSX_LONGTEXT N_("X offset, from the left screen edge." )
137 #define POSY_TEXT N_("Y offset")
138 #define POSY_LONGTEXT N_("Y offset, down from the top." )
139 #define OPACITY_TEXT N_("Opacity")
140 #define OPACITY_LONGTEXT N_("Opacity (inverse of transparency) of " \
141     "overlay text. 0 = transparent, 255 = totally opaque." )
142
143 #define SIZE_TEXT N_("Font size, pixels")
144 #define SIZE_LONGTEXT N_("Font size, in pixels. Default is -1 (use default " \
145     "font size)." )
146
147 #define COLOR_TEXT N_("Color")
148 #define COLOR_LONGTEXT N_("Color of the text that will be rendered on "\
149     "the video. This must be an hexadecimal (like HTML colors). The first two "\
150     "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
151     " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
152
153 #define POS_TEXT N_("Text position")
154 #define POS_LONGTEXT N_( \
155   "You can enforce the text position on the video " \
156   "(0=center, 1=left, 2=right, 4=top, 8=bottom; you can " \
157   "also use combinations of these values, eg 6 = top-right).")
158
159 #define TITLE_TEXT N_("Title display mode")
160 #define TITLE_LONGTEXT N_("Title display mode. Default is 0 (hidden) if the feed has an image and feed images are enabled, 1 otherwise.")
161
162 static int pi_pos_values[] = { 0, 1, 2, 4, 8, 5, 6, 9, 10 };
163 static const char *ppsz_pos_descriptions[] =
164      { N_("Center"), N_("Left"), N_("Right"), N_("Top"), N_("Bottom"),
165      N_("Top-Left"), N_("Top-Right"), N_("Bottom-Left"), N_("Bottom-Right") };
166
167 enum title_modes {
168     default_title=-1,
169     hide_title,
170     prepend_title,
171     scroll_title };
172
173 static int pi_title_modes[] = { default_title, hide_title, prepend_title, scroll_title };
174 static const char *ppsz_title_modes[] =
175     { N_("Default"), N_("Don't show"), N_("Always visible"), N_("Scroll with feed") };
176
177 #define CFG_PREFIX "rss-"
178
179 /*****************************************************************************
180  * Module descriptor
181  *****************************************************************************/
182 vlc_module_begin();
183     set_capability( "sub filter", 0 );
184     set_shortname( "RSS / Atom" );
185     set_callbacks( CreateFilter, DestroyFilter );
186     set_category( CAT_VIDEO );
187     set_subcategory( SUBCAT_VIDEO_SUBPIC );
188     add_string( CFG_PREFIX "urls", "rss", NULL, MSG_TEXT, MSG_LONGTEXT, VLC_FALSE );
189
190     set_section( N_("Position"), NULL );
191     add_integer( CFG_PREFIX "x", -1, NULL, POSX_TEXT, POSX_LONGTEXT, VLC_TRUE );
192     add_integer( CFG_PREFIX "y", 0, NULL, POSY_TEXT, POSY_LONGTEXT, VLC_TRUE );
193     add_integer( CFG_PREFIX "position", 5, NULL, POS_TEXT, POS_LONGTEXT, VLC_FALSE );
194         change_integer_list( pi_pos_values, ppsz_pos_descriptions, 0 );
195
196     set_section( N_("Font"), NULL );
197     /* 5 sets the default to top [1] left [4] */
198     add_integer_with_range( CFG_PREFIX "opacity", 255, 0, 255, NULL,
199         OPACITY_TEXT, OPACITY_LONGTEXT, VLC_FALSE );
200     add_integer( CFG_PREFIX "color", 0xFFFFFF, NULL, COLOR_TEXT, COLOR_LONGTEXT,
201                   VLC_FALSE );
202         change_integer_list( pi_color_values, ppsz_color_descriptions, 0 );
203     add_integer( CFG_PREFIX "size", -1, NULL, SIZE_TEXT, SIZE_LONGTEXT, VLC_FALSE );
204
205     set_section( N_("Misc"), NULL );
206     add_integer( CFG_PREFIX "speed", 100000, NULL, SPEED_TEXT, SPEED_LONGTEXT,
207                  VLC_FALSE );
208     add_integer( CFG_PREFIX "length", 60, NULL, LENGTH_TEXT, LENGTH_LONGTEXT,
209                  VLC_FALSE );
210     add_integer( CFG_PREFIX "ttl", 1800, NULL, TTL_TEXT, TTL_LONGTEXT, VLC_FALSE );
211     add_bool( CFG_PREFIX "images", 1, NULL, IMAGE_TEXT, IMAGE_LONGTEXT, VLC_FALSE );
212     add_integer( CFG_PREFIX "title", default_title, NULL, TITLE_TEXT, TITLE_LONGTEXT, VLC_FALSE );
213         change_integer_list( pi_title_modes, ppsz_title_modes, 0 );
214
215     set_description( _("RSS and Atom feed display") );
216     add_shortcut( "rss" );
217     add_shortcut( "atom" );
218 vlc_module_end();
219
220 static const char *ppsz_filter_options[] = {
221     "urls", "x", "y", "position", "color", "size", "speed", "length",
222     "ttl", "images", "title", NULL
223 };
224
225 /*****************************************************************************
226  * CreateFilter: allocates RSS video filter
227  *****************************************************************************/
228 static int CreateFilter( vlc_object_t *p_this )
229 {
230     filter_t *p_filter = (filter_t *)p_this;
231     filter_sys_t *p_sys;
232     int i_feed;
233
234     /* Allocate structure */
235     p_sys = p_filter->p_sys = malloc( sizeof( filter_sys_t ) );
236     if( p_sys == NULL )
237     {
238         msg_Err( p_filter, "out of memory" );
239         return VLC_ENOMEM;
240     }
241
242     vlc_mutex_init( p_filter, &p_sys->lock );
243     vlc_mutex_lock( &p_sys->lock );
244
245     config_ChainParse( p_filter, CFG_PREFIX, ppsz_filter_options,
246                        p_filter->p_cfg );
247
248     p_sys->psz_urls = var_CreateGetString( p_filter, CFG_PREFIX "urls" );
249     p_sys->i_title = var_CreateGetInteger( p_filter, CFG_PREFIX "title" );
250     p_sys->i_cur_feed = 0;
251     p_sys->i_cur_item = p_sys->i_title == scroll_title ? -1 : 0;
252     p_sys->i_cur_char = 0;
253     p_sys->i_feeds = 0;
254     p_sys->p_feeds = NULL;
255     p_sys->i_speed = var_CreateGetInteger( p_filter, CFG_PREFIX "speed" );
256     p_sys->i_length = var_CreateGetInteger( p_filter, CFG_PREFIX "length" );
257     p_sys->i_ttl = __MAX( 0, var_CreateGetInteger( p_filter, CFG_PREFIX "ttl" ) );
258     p_sys->b_images = var_CreateGetBool( p_filter, CFG_PREFIX "images" );
259     p_sys->psz_marquee = (char *)malloc( p_sys->i_length );
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 = {0};
347
348     subpicture_region_t *p_region;
349
350     int i_feed, i_item;
351
352     struct rss_feed_t *p_feed;
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->i_flags = 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->i_flags = 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 = {0};
504
505         fmt_out.i_chroma = VLC_FOURCC('Y','U','V','A');
506         fmt_out.i_aspect = VOUT_ASPECT_FACTOR;
507         fmt_out.i_sar_num = fmt_out.i_sar_den = 1;
508         fmt_out.i_width =
509             fmt_out.i_visible_width = p_pic->p[Y_PLANE].i_visible_pitch;
510         fmt_out.i_height =
511             fmt_out.i_visible_height = p_pic->p[Y_PLANE].i_visible_lines;
512
513         p_region = p_spu->pf_create_region( VLC_OBJECT( p_filter ), &fmt_out );
514         if( !p_region )
515         {
516             msg_Err( p_filter, "cannot allocate SPU region" );
517         }
518         else
519         {
520             vout_CopyPicture( p_filter, &p_region->picture, p_pic );
521             p_spu->p_region->p_next = p_region;
522         }
523
524         /* Offset text to display right next to the image */
525         p_spu->p_region->i_x = p_pic->p[Y_PLANE].i_visible_pitch;
526     }
527
528     vlc_mutex_unlock( &p_sys->lock );
529     return p_spu;
530 }
531
532 /****************************************************************************
533  * RSS related functions
534  ****************************************************************************
535  * You should always lock the p_filter mutex before using any of these
536  * functions
537  ***************************************************************************/
538
539 #undef LoadImage /* do not conflict with Win32 API */
540
541 /****************************************************************************
542  * download and resize image located at psz_url
543  ***************************************************************************/
544 static picture_t *LoadImage( filter_t *p_filter, const char *psz_url )
545 {
546     filter_sys_t *p_sys = p_filter->p_sys;
547
548     video_format_t fmt_in={0}, fmt_out={0};
549     picture_t *p_orig, *p_pic=NULL;
550     image_handler_t *p_handler = image_HandlerCreate( p_filter );
551
552     fmt_out.i_chroma = VLC_FOURCC('Y','U','V','A');
553     p_orig = image_ReadUrl( p_handler, psz_url, &fmt_in, &fmt_out );
554
555     if( !p_orig )
556     {
557         msg_Warn( p_filter, "Unable to read image %s", psz_url );
558     }
559     else if( p_sys->p_style->i_font_size > 0 )
560     {
561
562         fmt_in.i_chroma = VLC_FOURCC('Y','U','V','A');
563         fmt_in.i_height = p_orig->p[Y_PLANE].i_visible_lines;
564         fmt_in.i_width = p_orig->p[Y_PLANE].i_visible_pitch;
565         fmt_out.i_width = p_orig->p[Y_PLANE].i_visible_pitch
566             *p_sys->p_style->i_font_size/p_orig->p[Y_PLANE].i_visible_lines;
567         fmt_out.i_height = p_sys->p_style->i_font_size;
568
569         p_pic = image_Convert( p_handler, p_orig, &fmt_in, &fmt_out );
570         p_orig->pf_release( p_orig );
571         if( !p_pic )
572         {
573             msg_Warn( p_filter, "Error while converting %s", psz_url );
574         }
575     }
576     else
577     {
578         p_pic = p_orig;
579     }
580
581     image_HandlerDelete( p_handler );
582
583     return p_pic;
584 }
585
586 /****************************************************************************
587  * remove all ' ' '\t' '\n' '\r' characters from the begining and end of the
588  * string.
589  ***************************************************************************/
590 static char *removeWhiteChars( char *psz_src )
591 {
592     char *psz_src2 = strdup( psz_src );
593     char *psz_clean = strdup( psz_src2 );
594     char *psz_clean2;
595     int i;
596     while( ( *psz_clean == ' ' || *psz_clean == '\t'
597            || *psz_clean == '\n' || *psz_clean == '\r' )
598            && *psz_clean != '\0' )
599     {
600         psz_clean++;
601     }
602     i = strlen( psz_clean );
603     while( --i > 0 &&
604          ( psz_clean[i] == ' ' || psz_clean[i] == '\t'
605         || psz_clean[i] == '\n' || psz_clean[i] == '\r' ) );
606     psz_clean[i+1] = '\0';
607     psz_clean2 = strdup( psz_clean );
608     free( psz_src2 );
609     return psz_clean2;
610 }
611
612 /****************************************************************************
613  * FetchRSS (or Atom) feeds
614  ***************************************************************************/
615 static int FetchRSS( filter_t *p_filter)
616 {
617     filter_sys_t *p_sys = p_filter->p_sys;
618
619     stream_t *p_stream = NULL;
620     xml_t *p_xml = NULL;
621     xml_reader_t *p_xml_reader = NULL;
622
623     char *psz_eltname = NULL;
624     char *psz_eltvalue = NULL;
625     char *psz_feed = NULL;
626     char *psz_buffer = NULL;
627     char *psz_buffer_2 = NULL;
628
629     int i_feed;
630     int i_item;
631     vlc_bool_t b_is_item;
632     vlc_bool_t b_is_image;
633     int i_int;
634
635     FreeRSS( p_filter );
636     p_sys->i_feeds = 1;
637     i_int = 0;
638     while( p_sys->psz_urls[i_int] != 0 )
639         if( p_sys->psz_urls[i_int++] == '|' )
640             p_sys->i_feeds++;
641     p_sys->p_feeds = (struct rss_feed_t *)malloc( p_sys->i_feeds
642                                 * sizeof( struct rss_feed_t ) );
643
644     p_xml = xml_Create( p_filter );
645     if( !p_xml )
646     {
647         msg_Err( p_filter, "Failed to open XML parser" );
648         return 1;
649     }
650
651     psz_buffer = strdup( p_sys->psz_urls );
652     psz_buffer_2 = psz_buffer; /* keep track so we can free it */
653     for( i_feed = 0; i_feed < p_sys->i_feeds; i_feed++ )
654     {
655         struct rss_feed_t *p_feed = p_sys->p_feeds+i_feed;
656
657         if( psz_buffer == NULL ) break;
658         if( psz_buffer[0] == 0 ) psz_buffer++;
659         psz_feed = psz_buffer;
660         psz_buffer = strchr( psz_buffer, '|' );
661         if( psz_buffer != NULL ) psz_buffer[0] = 0;
662
663         p_feed->psz_title = NULL;
664         p_feed->psz_description = NULL;
665         p_feed->psz_link = NULL;
666         p_feed->psz_image = NULL;
667         p_feed->p_pic = NULL;
668         p_feed->i_items = 0;
669         p_feed->p_items = NULL;
670
671         msg_Dbg( p_filter, "opening %s RSS/Atom feed ...", psz_feed );
672
673         p_stream = stream_UrlNew( p_filter, psz_feed );
674         if( !p_stream )
675         {
676             msg_Err( p_filter, "Failed to open %s for reading", psz_feed );
677             return 1;
678         }
679
680         p_xml_reader = xml_ReaderCreate( p_xml, p_stream );
681         if( !p_xml_reader )
682         {
683             msg_Err( p_filter, "Failed to open %s for parsing", psz_feed );
684             return 1;
685         }
686
687         i_item = 0;
688         b_is_item = VLC_FALSE;
689         b_is_image = VLC_FALSE;
690
691         while( xml_ReaderRead( p_xml_reader ) == 1 )
692         {
693             switch( xml_ReaderNodeType( p_xml_reader ) )
694             {
695                 // Error
696                 case -1:
697                     return 1;
698
699                 case XML_READER_STARTELEM:
700                     if( psz_eltname )
701                     {
702                         free( psz_eltname );
703                         psz_eltname = NULL;
704                     }
705                     psz_eltname = xml_ReaderName( p_xml_reader );
706                     if( !psz_eltname )
707                     {
708                         return 1;
709                     }
710 #                   ifdef RSS_DEBUG
711                     msg_Dbg( p_filter, "element name: %s", psz_eltname );
712 #                   endif
713                     if( !strcmp( psz_eltname, "item" ) /* rss */
714                      || !strcmp( psz_eltname, "entry" ) ) /* atom */
715                     {
716                         b_is_item = VLC_TRUE;
717                         p_feed->i_items++;
718                         p_feed->p_items = (struct rss_item_t *)realloc( p_feed->p_items, p_feed->i_items * sizeof( struct rss_item_t ) );
719                         p_feed->p_items[p_feed->i_items-1].psz_title = NULL;
720                         p_feed->p_items[p_feed->i_items-1].psz_description
721                                                                      = NULL;
722                         p_feed->p_items[p_feed->i_items-1].psz_link = NULL;
723                     }
724                     else if( !strcmp( psz_eltname, "image" ) ) /* rss */
725                     {
726                         b_is_image = VLC_TRUE;
727                     }
728                     else if( !strcmp( psz_eltname, "link" ) ) /* atom */
729                     {
730                         char *psz_href = NULL;
731                         char *psz_rel = NULL;
732                         while( xml_ReaderNextAttr( p_xml_reader )
733                                == VLC_SUCCESS )
734                         {
735                             char *psz_name = xml_ReaderName( p_xml_reader );
736                             char *psz_value = xml_ReaderValue( p_xml_reader );
737                             if( !strcmp( psz_name, "rel" ) )
738                             {
739                                 psz_rel = psz_value;
740                             }
741                             else if( !strcmp( psz_name, "href" ) )
742                             {
743                                 psz_href = psz_value;
744                             }
745                             else
746                             {
747                                 free( psz_value );
748                             }
749                             free( psz_name );
750                         }
751                         if( psz_rel && psz_href )
752                         {
753                             if( !strcmp( psz_rel, "alternate" )
754                                 && b_is_item == VLC_FALSE
755                                 && b_is_image == VLC_FALSE
756                                 && !p_feed->psz_link )
757                             {
758                                 p_feed->psz_link = psz_href;
759                             }
760                             /* this isn't in the rfc but i found some ... */
761                             else if( ( !strcmp( psz_rel, "logo" )
762                                     || !strcmp( psz_rel, "icon" ) )
763                                     && b_is_item == VLC_FALSE
764                                     && b_is_image == VLC_FALSE
765                                     && !p_feed->psz_image )
766                             {
767                                 p_feed->psz_image = psz_href;
768                             }
769                             else
770                             {
771                                 free( psz_href );
772                             }
773                         }
774                         else
775                         {
776                             if( psz_href ) free( psz_href );
777                         }
778                         if( psz_rel ) free( psz_rel );
779                     }
780                     break;
781
782                 case XML_READER_ENDELEM:
783                     if( psz_eltname )
784                     {
785                         free( psz_eltname );
786                         psz_eltname = NULL;
787                     }
788                     psz_eltname = xml_ReaderName( p_xml_reader );
789                     if( !psz_eltname )
790                     {
791                         return 1;
792                     }
793 #                   ifdef RSS_DEBUG
794                     msg_Dbg( p_filter, "element end : %s", psz_eltname );
795 #                   endif
796                     if( !strcmp( psz_eltname, "item" ) /* rss */
797                      || !strcmp( psz_eltname, "entry" ) ) /* atom */
798                     {
799                         b_is_item = VLC_FALSE;
800                         i_item++;
801                     }
802                     else if( !strcmp( psz_eltname, "image" ) ) /* rss */
803                     {
804                         b_is_image = VLC_FALSE;
805                     }
806                     free( psz_eltname );
807                     psz_eltname = NULL;
808                     break;
809
810                 case XML_READER_TEXT:
811                     if( !psz_eltname ) break;
812                     psz_eltvalue = xml_ReaderValue( p_xml_reader );
813                     if( !psz_eltvalue )
814                     {
815                         return 1;
816                     }
817                     else
818                     {
819                         char *psz_clean;
820                         psz_clean = removeWhiteChars( psz_eltvalue );
821                         free( psz_eltvalue ); psz_eltvalue = psz_clean;
822                     }
823 #                   ifdef RSS_DEBUG
824                     msg_Dbg( p_filter, "  text : <%s>", psz_eltvalue );
825 #                   endif
826                     if( b_is_item == VLC_TRUE )
827                     {
828                         struct rss_item_t *p_item;
829                         p_item = p_feed->p_items+i_item;
830                         if( !strcmp( psz_eltname, "title" ) /* rss/atom */
831                             && !p_item->psz_title )
832                         {
833                             p_item->psz_title = psz_eltvalue;
834                         }
835                         else if( !strcmp( psz_eltname, "link" ) /* rss */
836                                  && !p_item->psz_link )
837                         {
838                             p_item->psz_link = psz_eltvalue;
839                         }
840                         else if((!strcmp( psz_eltname, "description" ) /* rss */
841                               || !strcmp( psz_eltname, "summary" ) ) /* atom */
842                               && !p_item->psz_description )
843                         {
844                             p_item->psz_description = psz_eltvalue;
845                         }
846                         else
847                         {
848                             free( psz_eltvalue );
849                             psz_eltvalue = NULL;
850                         }
851                     }
852                     else if( b_is_image == VLC_TRUE )
853                     {
854                         if( !strcmp( psz_eltname, "url" ) /* rss */
855                             && !p_feed->psz_image )
856                         {
857                             p_feed->psz_image = psz_eltvalue;
858                         }
859                         else
860                         {
861                             free( psz_eltvalue );
862                             psz_eltvalue = NULL;
863                         }
864                     }
865                     else
866                     {
867                         if( !strcmp( psz_eltname, "title" ) /* rss/atom */
868                             && !p_feed->psz_title )
869                         {
870                             p_feed->psz_title = psz_eltvalue;
871                         }
872                         else if( !strcmp( psz_eltname, "link" ) /* rss */
873                                  && !p_feed->psz_link )
874                         {
875                             p_feed->psz_link = psz_eltvalue;
876                         }
877                         else if((!strcmp( psz_eltname, "description" ) /* rss */
878                               || !strcmp( psz_eltname, "subtitle" ) ) /* atom */
879                               && !p_feed->psz_description )
880                         {
881                             p_feed->psz_description = psz_eltvalue;
882                         }
883                         else if( ( !strcmp( psz_eltname, "logo" ) /* atom */
884                               || !strcmp( psz_eltname, "icon" ) ) /* atom */
885                               && !p_feed->psz_image )
886                         {
887                             p_feed->psz_image = psz_eltvalue;
888                         }
889                         else
890                         {
891                             free( psz_eltvalue );
892                             psz_eltvalue = NULL;
893                         }
894                     }
895                     break;
896             }
897         }
898
899         if( p_sys->b_images == VLC_TRUE
900             && p_feed->psz_image && !p_feed->p_pic )
901         {
902             p_feed->p_pic = LoadImage( p_filter, p_feed->psz_image );
903         }
904
905         if( p_xml_reader && p_xml ) xml_ReaderDelete( p_xml, p_xml_reader );
906         if( p_stream ) stream_Delete( p_stream );
907         msg_Dbg( p_filter, "done with %s RSS/Atom feed", psz_feed );
908     }
909     free( psz_buffer_2 );
910     if( p_xml ) xml_Delete( p_xml );
911
912     return 0;
913 }
914
915 /****************************************************************************
916  * FreeRSS
917  ***************************************************************************/
918 static void FreeRSS( filter_t *p_filter)
919 {
920     filter_sys_t *p_sys = p_filter->p_sys;
921
922     struct rss_item_t *p_item;
923     struct rss_feed_t *p_feed;
924
925     int i_feed;
926     int i_item;
927
928     for( i_feed = 0; i_feed < p_sys->i_feeds; i_feed++ )
929     {
930         p_feed = p_sys->p_feeds+i_feed;
931         for( i_item = 0; i_item < p_feed->i_items; i_item++ )
932         {
933             p_item = p_feed->p_items+i_item;
934             free( p_item->psz_title );
935             free( p_item->psz_link );
936             free( p_item->psz_description );
937         }
938         free( p_feed->p_items );
939         free( p_feed->psz_title);
940         free( p_feed->psz_link );
941         free( p_feed->psz_description );
942         free( p_feed->psz_image );
943         if( p_feed->p_pic != NULL )
944             p_feed->p_pic->pf_release( p_feed->p_pic );
945     }
946     free( p_sys->p_feeds );
947     p_sys->i_feeds = 0;
948 }