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