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