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