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