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