* Add new rss-title option to configure how/when/if you want to display the feed...
[vlc.git] / modules / video_filter / rss.c
1 /*****************************************************************************
2  * rss.c : rss/atom feed display video plugin for vlc
3  *****************************************************************************
4  * Copyright (C) 2003-2006 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Antoine Cellerier <dionoea -at- videolan -dot- org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Atom : http://www.ietf.org/rfc/rfc4287.txt
26  * RSS : http://www.rssboard.org/rss-specification
27  *****************************************************************************/
28
29 /*****************************************************************************
30  * Preamble
31  *****************************************************************************/
32 #include <stdlib.h>                                      /* malloc(), free() */
33 #include <string.h>
34
35 #include <vlc/vlc.h>
36 #include <vlc_vout.h>
37
38 #include "vlc_filter.h"
39 #include "vlc_block.h"
40 #include "vlc_osd.h"
41
42 #include "vlc_block.h"
43 #include "vlc_stream.h"
44 #include "vlc_xml.h"
45 #include <vlc_charset.h>
46
47 #include "vlc_image.h"
48
49 /*****************************************************************************
50  * Local prototypes
51  *****************************************************************************/
52 static int  CreateFilter ( vlc_object_t * );
53 static void DestroyFilter( vlc_object_t * );
54 static subpicture_t *Filter( filter_t *, mtime_t );
55
56 static int FetchRSS( filter_t * );
57 static void FreeRSS( filter_t * );
58
59 static int pi_color_values[] = { 0xf0000000, 0x00000000, 0x00808080, 0x00C0C0C0,
60                0x00FFFFFF, 0x00800000, 0x00FF0000, 0x00FF00FF, 0x00FFFF00,
61                0x00808000, 0x00008000, 0x00008080, 0x0000FF00, 0x00800080,
62                0x00000080, 0x000000FF, 0x0000FFFF};
63 static const char *ppsz_color_descriptions[] = { N_("Default"), N_("Black"),
64                N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"), N_("Red"),
65                N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"),
66                N_("Teal"), N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"),
67                N_("Aqua") };
68
69 /*****************************************************************************
70  * filter_sys_t: rss filter descriptor
71  *****************************************************************************/
72
73 struct rss_item_t
74 {
75     char *psz_title;
76     char *psz_description;
77     char *psz_link;
78 };
79
80 struct rss_feed_t
81 {
82     char *psz_title;
83     char *psz_description;
84     char *psz_link;
85     char *psz_image;
86     picture_t *p_pic;
87
88     int i_items;
89     struct rss_item_t *p_items;
90 };
91
92 struct filter_sys_t
93 {
94     vlc_mutex_t lock;
95     vlc_mutex_t *p_lock;
96
97     int i_xoff, i_yoff;  /* offsets for the display string in the video window */
98     int i_pos; /* permit relative positioning (top, bottom, left, right, center) */
99     int i_speed;
100     int i_length;
101
102     char *psz_marquee;    /* marquee string */
103
104     text_style_t *p_style; /* font control */
105
106     mtime_t last_date;
107
108     char *psz_urls;
109     int i_feeds;
110     struct rss_feed_t *p_feeds;
111
112     int i_ttl;
113     time_t t_last_update;
114     vlc_bool_t b_images;
115     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 milliseconds (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     p_spu->i_start = date;
475     p_spu->i_stop  = 0;
476     p_spu->b_ephemer = VLC_TRUE;
477
478     /*  where to locate the string: */
479     if( p_sys->i_xoff < 0 || p_sys->i_yoff < 0 )
480     {   /* set to one of the 9 relative locations */
481         p_spu->i_flags = p_sys->i_pos;
482         p_spu->i_x = 0;
483         p_spu->i_y = 0;
484         p_spu->b_absolute = VLC_FALSE;
485     }
486     else
487     {   /*  set to an absolute xy, referenced to upper left corner */
488         p_spu->i_flags = OSD_ALIGN_LEFT | OSD_ALIGN_TOP;
489         p_spu->i_x = p_sys->i_xoff;
490         p_spu->i_y = p_sys->i_yoff;
491         p_spu->b_absolute = VLC_TRUE;
492     }
493
494     p_spu->i_height = 1;
495     p_spu->p_region->p_style = p_sys->p_style;
496
497     if( p_feed->p_pic )
498     {
499         /* Display the feed's image */
500         picture_t *p_pic = p_feed->p_pic;
501         video_format_t fmt_out = {0};
502
503         fmt_out.i_chroma = VLC_FOURCC('Y','U','V','A');
504         fmt_out.i_aspect = VOUT_ASPECT_FACTOR;
505         fmt_out.i_sar_num = fmt_out.i_sar_den = 1;
506         fmt_out.i_width =
507             fmt_out.i_visible_width = p_pic->p[Y_PLANE].i_visible_pitch;
508         fmt_out.i_height =
509             fmt_out.i_visible_height = p_pic->p[Y_PLANE].i_visible_lines;
510
511         p_region = p_spu->pf_create_region( VLC_OBJECT( p_filter ), &fmt_out );
512         if( !p_region )
513         {
514             msg_Err( p_filter, "cannot allocate SPU region" );
515         }
516         else
517         {
518             vout_CopyPicture( p_filter, &p_region->picture, p_pic );
519             p_spu->p_region->p_next = p_region;
520         }
521
522         /* Offset text to display right next to the image */
523         p_spu->p_region->i_x = p_pic->p[Y_PLANE].i_visible_pitch;
524     }
525
526     vlc_mutex_unlock( &p_sys->lock );
527     return p_spu;
528 }
529
530 /****************************************************************************
531  * RSS related functions
532  ****************************************************************************
533  * You should always lock the p_filter mutex before using any of these
534  * functions
535  ***************************************************************************/
536
537 #undef LoadImage /* do not conflict with Win32 API */
538
539 /****************************************************************************
540  * download and resize image located at psz_url
541  ***************************************************************************/
542 static picture_t *LoadImage( filter_t *p_filter, const char *psz_url )
543 {
544     filter_sys_t *p_sys = p_filter->p_sys;
545
546     video_format_t fmt_in={0}, fmt_out={0};
547     picture_t *p_orig, *p_pic=NULL;
548     image_handler_t *p_handler = image_HandlerCreate( p_filter );
549
550     fmt_out.i_chroma = VLC_FOURCC('Y','U','V','A');
551     p_orig = image_ReadUrl( p_handler, psz_url, &fmt_in, &fmt_out );
552
553     if( !p_orig )
554     {
555         msg_Warn( p_filter, "Unable to read image %s", psz_url );
556     }
557     else if( p_sys->p_style->i_font_size > 0 )
558     {
559
560         fmt_in.i_chroma = VLC_FOURCC('Y','U','V','A');
561         fmt_in.i_height = p_orig->p[Y_PLANE].i_visible_lines;
562         fmt_in.i_width = p_orig->p[Y_PLANE].i_visible_pitch;
563         fmt_out.i_width = p_orig->p[Y_PLANE].i_visible_pitch
564             *p_sys->p_style->i_font_size/p_orig->p[Y_PLANE].i_visible_lines;
565         fmt_out.i_height = p_sys->p_style->i_font_size;
566
567         p_pic = image_Convert( p_handler, p_orig, &fmt_in, &fmt_out );
568         p_orig->pf_release( p_orig );
569         if( !p_pic )
570         {
571             msg_Warn( p_filter, "Error while converting %s", psz_url );
572         }
573     }
574     else
575     {
576         p_pic = p_orig;
577     }
578
579     image_HandlerDelete( p_handler );
580
581     return p_pic;
582 }
583
584 /****************************************************************************
585  * remove all ' ' '\t' '\n' '\r' characters from the begining and end of the
586  * string.
587  ***************************************************************************/
588 static char *removeWhiteChars( char *psz_src )
589 {
590     char *psz_src2 = strdup( psz_src );
591     char *psz_clean = strdup( psz_src2 );
592     char *psz_clean2;
593     int i;
594     while( ( *psz_clean == ' ' || *psz_clean == '\t'
595            || *psz_clean == '\n' || *psz_clean == '\r' )
596            && *psz_clean != '\0' )
597     {
598         psz_clean++;
599     }
600     i = strlen( psz_clean );
601     while( --i > 0 &&
602          ( psz_clean[i] == ' ' || psz_clean[i] == '\t'
603         || psz_clean[i] == '\n' || psz_clean[i] == '\r' ) );
604     psz_clean[i+1] = '\0';
605     psz_clean2 = strdup( psz_clean );
606     free( psz_src2 );
607     return psz_clean2;
608 }
609
610 /****************************************************************************
611  * FetchRSS (or Atom) feeds
612  ***************************************************************************/
613 static int FetchRSS( filter_t *p_filter)
614 {
615     filter_sys_t *p_sys = p_filter->p_sys;
616
617     stream_t *p_stream = NULL;
618     xml_t *p_xml = NULL;
619     xml_reader_t *p_xml_reader = NULL;
620
621     char *psz_eltname = NULL;
622     char *psz_eltvalue = NULL;
623     char *psz_feed = NULL;
624     char *psz_buffer = NULL;
625     char *psz_buffer_2 = NULL;
626
627     int i_feed;
628     int i_item;
629     vlc_bool_t b_is_item;
630     vlc_bool_t b_is_image;
631     int i_int;
632
633     FreeRSS( p_filter );
634     p_sys->i_feeds = 1;
635     i_int = 0;
636     while( p_sys->psz_urls[i_int] != 0 )
637         if( p_sys->psz_urls[i_int++] == '|' )
638             p_sys->i_feeds++;
639     p_sys->p_feeds = (struct rss_feed_t *)malloc( p_sys->i_feeds
640                                 * sizeof( struct rss_feed_t ) );
641
642     p_xml = xml_Create( p_filter );
643     if( !p_xml )
644     {
645         msg_Err( p_filter, "Failed to open XML parser" );
646         return 1;
647     }
648
649     psz_buffer = strdup( p_sys->psz_urls );
650     psz_buffer_2 = psz_buffer; /* keep track so we can free it */
651     for( i_feed = 0; i_feed < p_sys->i_feeds; i_feed++ )
652     {
653         struct rss_feed_t *p_feed = p_sys->p_feeds+i_feed;
654
655         if( psz_buffer == NULL ) break;
656         if( psz_buffer[0] == 0 ) psz_buffer++;
657         psz_feed = psz_buffer;
658         psz_buffer = strchr( psz_buffer, '|' );
659         if( psz_buffer != NULL ) psz_buffer[0] = 0;
660
661         p_feed->psz_title = NULL;
662         p_feed->psz_description = NULL;
663         p_feed->psz_link = NULL;
664         p_feed->psz_image = NULL;
665         p_feed->p_pic = NULL;
666         p_feed->i_items = 0;
667         p_feed->p_items = NULL;
668
669         msg_Dbg( p_filter, "opening %s RSS/Atom feed ...", psz_feed );
670
671         p_stream = stream_UrlNew( p_filter, psz_feed );
672         if( !p_stream )
673         {
674             msg_Err( p_filter, "Failed to open %s for reading", psz_feed );
675             return 1;
676         }
677
678         p_xml_reader = xml_ReaderCreate( p_xml, p_stream );
679         if( !p_xml_reader )
680         {
681             msg_Err( p_filter, "Failed to open %s for parsing", psz_feed );
682             return 1;
683         }
684
685         i_item = 0;
686         b_is_item = VLC_FALSE;
687         b_is_image = VLC_FALSE;
688
689         while( xml_ReaderRead( p_xml_reader ) == 1 )
690         {
691             switch( xml_ReaderNodeType( p_xml_reader ) )
692             {
693                 // Error
694                 case -1:
695                     return 1;
696
697                 case XML_READER_STARTELEM:
698                     if( psz_eltname )
699                     {
700                         free( psz_eltname );
701                         psz_eltname = NULL;
702                     }
703                     psz_eltname = xml_ReaderName( p_xml_reader );
704                     if( !psz_eltname )
705                     {
706                         return 1;
707                     }
708 #                   ifdef RSS_DEBUG
709                     msg_Dbg( p_filter, "element name: %s", psz_eltname );
710 #                   endif
711                     if( !strcmp( psz_eltname, "item" ) /* rss */
712                      || !strcmp( psz_eltname, "entry" ) ) /* atom */
713                     {
714                         b_is_item = VLC_TRUE;
715                         p_feed->i_items++;
716                         p_feed->p_items = (struct rss_item_t *)realloc( p_feed->p_items, p_feed->i_items * sizeof( struct rss_item_t ) );
717                         p_feed->p_items[p_feed->i_items-1].psz_title = NULL;
718                         p_feed->p_items[p_feed->i_items-1].psz_description
719                                                                      = NULL;
720                         p_feed->p_items[p_feed->i_items-1].psz_link = NULL;
721                     }
722                     else if( !strcmp( psz_eltname, "image" ) ) /* rss */
723                     {
724                         b_is_image = VLC_TRUE;
725                     }
726                     else if( !strcmp( psz_eltname, "link" ) ) /* atom */
727                     {
728                         char *psz_href = NULL;
729                         char *psz_rel = NULL;
730                         while( xml_ReaderNextAttr( p_xml_reader )
731                                == VLC_SUCCESS )
732                         {
733                             char *psz_name = xml_ReaderName( p_xml_reader );
734                             char *psz_value = xml_ReaderValue( p_xml_reader );
735                             if( !strcmp( psz_name, "rel" ) )
736                             {
737                                 psz_rel = psz_value;
738                             }
739                             else if( !strcmp( psz_name, "href" ) )
740                             {
741                                 psz_href = psz_value;
742                             }
743                             else
744                             {
745                                 free( psz_value );
746                             }
747                             free( psz_name );
748                         }
749                         if( psz_rel && psz_href )
750                         {
751                             if( !strcmp( psz_rel, "alternate" )
752                                 && b_is_item == VLC_FALSE
753                                 && b_is_image == VLC_FALSE
754                                 && !p_feed->psz_link )
755                             {
756                                 p_feed->psz_link = psz_href;
757                             }
758                             /* this isn't in the rfc but i found some ... */
759                             else if( ( !strcmp( psz_rel, "logo" )
760                                     || !strcmp( psz_rel, "icon" ) )
761                                     && b_is_item == VLC_FALSE
762                                     && b_is_image == VLC_FALSE
763                                     && !p_feed->psz_image )
764                             {
765                                 p_feed->psz_image = psz_href;
766                             }
767                             else
768                             {
769                                 free( psz_href );
770                             }
771                         }
772                         else
773                         {
774                             if( psz_href ) free( psz_href );
775                         }
776                         if( psz_rel ) free( psz_rel );
777                     }
778                     break;
779
780                 case XML_READER_ENDELEM:
781                     if( psz_eltname )
782                     {
783                         free( psz_eltname );
784                         psz_eltname = NULL;
785                     }
786                     psz_eltname = xml_ReaderName( p_xml_reader );
787                     if( !psz_eltname )
788                     {
789                         return 1;
790                     }
791 #                   ifdef RSS_DEBUG
792                     msg_Dbg( p_filter, "element end : %s", psz_eltname );
793 #                   endif
794                     if( !strcmp( psz_eltname, "item" ) /* rss */
795                      || !strcmp( psz_eltname, "entry" ) ) /* atom */
796                     {
797                         b_is_item = VLC_FALSE;
798                         i_item++;
799                     }
800                     else if( !strcmp( psz_eltname, "image" ) ) /* rss */
801                     {
802                         b_is_image = VLC_FALSE;
803                     }
804                     free( psz_eltname );
805                     psz_eltname = NULL;
806                     break;
807
808                 case XML_READER_TEXT:
809                     if( !psz_eltname ) break;
810                     psz_eltvalue = xml_ReaderValue( p_xml_reader );
811                     if( !psz_eltvalue )
812                     {
813                         return 1;
814                     }
815                     else
816                     {
817                         char *psz_clean;
818                         psz_clean = removeWhiteChars( psz_eltvalue );
819                         free( psz_eltvalue ); psz_eltvalue = psz_clean;
820                     }
821 #                   ifdef RSS_DEBUG
822                     msg_Dbg( p_filter, "  text : <%s>", psz_eltvalue );
823 #                   endif
824                     if( b_is_item == VLC_TRUE )
825                     {
826                         struct rss_item_t *p_item;
827                         p_item = p_feed->p_items+i_item;
828                         if( !strcmp( psz_eltname, "title" ) /* rss/atom */
829                             && !p_item->psz_title )
830                         {
831                             p_item->psz_title = psz_eltvalue;
832                         }
833                         else if( !strcmp( psz_eltname, "link" ) /* rss */
834                                  && !p_item->psz_link )
835                         {
836                             p_item->psz_link = psz_eltvalue;
837                         }
838                         else if((!strcmp( psz_eltname, "description" ) /* rss */
839                               || !strcmp( psz_eltname, "summary" ) ) /* atom */
840                               && !p_item->psz_description )
841                         {
842                             p_item->psz_description = psz_eltvalue;
843                         }
844                         else
845                         {
846                             free( psz_eltvalue );
847                             psz_eltvalue = NULL;
848                         }
849                     }
850                     else if( b_is_image == VLC_TRUE )
851                     {
852                         if( !strcmp( psz_eltname, "url" ) /* rss */
853                             && !p_feed->psz_image )
854                         {
855                             p_feed->psz_image = psz_eltvalue;
856                         }
857                         else
858                         {
859                             free( psz_eltvalue );
860                             psz_eltvalue = NULL;
861                         }
862                     }
863                     else
864                     {
865                         if( !strcmp( psz_eltname, "title" ) /* rss/atom */
866                             && !p_feed->psz_title )
867                         {
868                             p_feed->psz_title = psz_eltvalue;
869                         }
870                         else if( !strcmp( psz_eltname, "link" ) /* rss */
871                                  && !p_feed->psz_link )
872                         {
873                             p_feed->psz_link = psz_eltvalue;
874                         }
875                         else if((!strcmp( psz_eltname, "description" ) /* rss */
876                               || !strcmp( psz_eltname, "subtitle" ) ) /* atom */
877                               && !p_feed->psz_description )
878                         {
879                             p_feed->psz_description = psz_eltvalue;
880                         }
881                         else if( ( !strcmp( psz_eltname, "logo" ) /* atom */
882                               || !strcmp( psz_eltname, "icon" ) ) /* atom */
883                               && !p_feed->psz_image )
884                         {
885                             p_feed->psz_image = psz_eltvalue;
886                         }
887                         else
888                         {
889                             free( psz_eltvalue );
890                             psz_eltvalue = NULL;
891                         }
892                     }
893                     break;
894             }
895         }
896
897         if( p_sys->b_images == VLC_TRUE
898             && p_feed->psz_image && !p_feed->p_pic )
899         {
900             p_feed->p_pic = LoadImage( p_filter, p_feed->psz_image );
901         }
902
903         if( p_xml_reader && p_xml ) xml_ReaderDelete( p_xml, p_xml_reader );
904         if( p_stream ) stream_Delete( p_stream );
905         msg_Dbg( p_filter, "done with %s RSS/Atom feed", psz_feed );
906     }
907     free( psz_buffer_2 );
908     if( p_xml ) xml_Delete( p_xml );
909
910     return 0;
911 }
912
913 /****************************************************************************
914  * FreeRSS
915  ***************************************************************************/
916 static void FreeRSS( filter_t *p_filter)
917 {
918     filter_sys_t *p_sys = p_filter->p_sys;
919
920     struct rss_item_t *p_item;
921     struct rss_feed_t *p_feed;
922
923     int i_feed;
924     int i_item;
925
926     for( i_feed = 0; i_feed < p_sys->i_feeds; i_feed++ )
927     {
928         p_feed = p_sys->p_feeds+i_feed;
929         for( i_item = 0; i_item < p_feed->i_items; i_item++ )
930         {
931             p_item = p_feed->p_items+i_item;
932             free( p_item->psz_title );
933             free( p_item->psz_link );
934             free( p_item->psz_description );
935         }
936         free( p_feed->p_items );
937         free( p_feed->psz_title);
938         free( p_feed->psz_link );
939         free( p_feed->psz_description );
940         free( p_feed->psz_image );
941         if( p_feed->p_pic != NULL )
942             p_feed->p_pic->pf_release( p_feed->p_pic );
943     }
944     free( p_sys->p_feeds );
945     p_sys->i_feeds = 0;
946 }