]> git.sesse.net Git - vlc/blob - modules/video_filter/rss.c
update module LIST file.
[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     free( p_sys->p_style );
352     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                     free( psz_eltname );
744                     psz_eltname = NULL;
745                     psz_eltname = xml_ReaderName( p_xml_reader );
746                     if( !psz_eltname )
747                     {
748                         return 1;
749                     }
750 #                   ifdef RSS_DEBUG
751                     msg_Dbg( p_filter, "element name: %s", psz_eltname );
752 #                   endif
753                     if( !strcmp( psz_eltname, "item" ) /* rss */
754                      || !strcmp( psz_eltname, "entry" ) ) /* atom */
755                     {
756                         b_is_item = VLC_TRUE;
757                         p_feed->i_items++;
758                         p_feed->p_items = (struct rss_item_t *)realloc( p_feed->p_items, p_feed->i_items * sizeof( struct rss_item_t ) );
759                         p_feed->p_items[p_feed->i_items-1].psz_title = NULL;
760                         p_feed->p_items[p_feed->i_items-1].psz_description
761                                                                      = NULL;
762                         p_feed->p_items[p_feed->i_items-1].psz_link = NULL;
763                     }
764                     else if( !strcmp( psz_eltname, "image" ) ) /* rss */
765                     {
766                         b_is_image = VLC_TRUE;
767                     }
768                     else if( !strcmp( psz_eltname, "link" ) ) /* atom */
769                     {
770                         char *psz_href = NULL;
771                         char *psz_rel = NULL;
772                         while( xml_ReaderNextAttr( p_xml_reader )
773                                == VLC_SUCCESS )
774                         {
775                             char *psz_name = xml_ReaderName( p_xml_reader );
776                             char *psz_value = xml_ReaderValue( p_xml_reader );
777                             if( !strcmp( psz_name, "rel" ) )
778                             {
779                                 psz_rel = psz_value;
780                             }
781                             else if( !strcmp( psz_name, "href" ) )
782                             {
783                                 psz_href = psz_value;
784                             }
785                             else
786                             {
787                                 free( psz_value );
788                             }
789                             free( psz_name );
790                         }
791                         if( psz_rel && psz_href )
792                         {
793                             if( !strcmp( psz_rel, "alternate" )
794                                 && b_is_item == VLC_FALSE
795                                 && b_is_image == VLC_FALSE
796                                 && !p_feed->psz_link )
797                             {
798                                 p_feed->psz_link = psz_href;
799                             }
800                             /* this isn't in the rfc but i found some ... */
801                             else if( ( !strcmp( psz_rel, "logo" )
802                                     || !strcmp( psz_rel, "icon" ) )
803                                     && b_is_item == VLC_FALSE
804                                     && b_is_image == VLC_FALSE
805                                     && !p_feed->psz_image )
806                             {
807                                 p_feed->psz_image = psz_href;
808                             }
809                             else
810                             {
811                                 free( psz_href );
812                             }
813                         }
814                         else
815                         {
816                             free( psz_href );
817                         }
818                         free( psz_rel );
819                     }
820                     break;
821
822                 case XML_READER_ENDELEM:
823                     free( psz_eltname );
824                     psz_eltname = NULL;
825                     psz_eltname = xml_ReaderName( p_xml_reader );
826                     if( !psz_eltname )
827                     {
828                         return 1;
829                     }
830 #                   ifdef RSS_DEBUG
831                     msg_Dbg( p_filter, "element end : %s", psz_eltname );
832 #                   endif
833                     if( !strcmp( psz_eltname, "item" ) /* rss */
834                      || !strcmp( psz_eltname, "entry" ) ) /* atom */
835                     {
836                         b_is_item = VLC_FALSE;
837                         i_item++;
838                     }
839                     else if( !strcmp( psz_eltname, "image" ) ) /* rss */
840                     {
841                         b_is_image = VLC_FALSE;
842                     }
843                     free( psz_eltname );
844                     psz_eltname = NULL;
845                     break;
846
847                 case XML_READER_TEXT:
848                     if( !psz_eltname ) break;
849                     psz_eltvalue = xml_ReaderValue( p_xml_reader );
850                     if( !psz_eltvalue )
851                     {
852                         return 1;
853                     }
854                     else
855                     {
856                         char *psz_clean;
857                         psz_clean = removeWhiteChars( psz_eltvalue );
858                         free( psz_eltvalue ); psz_eltvalue = psz_clean;
859                     }
860 #                   ifdef RSS_DEBUG
861                     msg_Dbg( p_filter, "  text : <%s>", psz_eltvalue );
862 #                   endif
863                     if( b_is_item == VLC_TRUE )
864                     {
865                         struct rss_item_t *p_item;
866                         p_item = p_feed->p_items+i_item;
867                         if( !strcmp( psz_eltname, "title" ) /* rss/atom */
868                             && !p_item->psz_title )
869                         {
870                             p_item->psz_title = psz_eltvalue;
871                         }
872                         else if( !strcmp( psz_eltname, "link" ) /* rss */
873                                  && !p_item->psz_link )
874                         {
875                             p_item->psz_link = psz_eltvalue;
876                         }
877                         else if((!strcmp( psz_eltname, "description" ) /* rss */
878                               || !strcmp( psz_eltname, "summary" ) ) /* atom */
879                               && !p_item->psz_description )
880                         {
881                             p_item->psz_description = psz_eltvalue;
882                         }
883                         else
884                         {
885                             free( psz_eltvalue );
886                             psz_eltvalue = NULL;
887                         }
888                     }
889                     else if( b_is_image == VLC_TRUE )
890                     {
891                         if( !strcmp( psz_eltname, "url" ) /* rss */
892                             && !p_feed->psz_image )
893                         {
894                             p_feed->psz_image = psz_eltvalue;
895                         }
896                         else
897                         {
898                             free( psz_eltvalue );
899                             psz_eltvalue = NULL;
900                         }
901                     }
902                     else
903                     {
904                         if( !strcmp( psz_eltname, "title" ) /* rss/atom */
905                             && !p_feed->psz_title )
906                         {
907                             p_feed->psz_title = psz_eltvalue;
908                         }
909                         else if( !strcmp( psz_eltname, "link" ) /* rss */
910                                  && !p_feed->psz_link )
911                         {
912                             p_feed->psz_link = psz_eltvalue;
913                         }
914                         else if((!strcmp( psz_eltname, "description" ) /* rss */
915                               || !strcmp( psz_eltname, "subtitle" ) ) /* atom */
916                               && !p_feed->psz_description )
917                         {
918                             p_feed->psz_description = psz_eltvalue;
919                         }
920                         else if( ( !strcmp( psz_eltname, "logo" ) /* atom */
921                               || !strcmp( psz_eltname, "icon" ) ) /* atom */
922                               && !p_feed->psz_image )
923                         {
924                             p_feed->psz_image = psz_eltvalue;
925                         }
926                         else
927                         {
928                             free( psz_eltvalue );
929                             psz_eltvalue = NULL;
930                         }
931                     }
932                     break;
933             }
934         }
935
936         if( p_sys->b_images == VLC_TRUE
937             && p_feed->psz_image && !p_feed->p_pic )
938         {
939             p_feed->p_pic = LoadImage( p_filter, p_feed->psz_image );
940         }
941
942         if( p_xml_reader && p_xml ) xml_ReaderDelete( p_xml, p_xml_reader );
943         if( p_stream ) stream_Delete( p_stream );
944         msg_Dbg( p_filter, "done with %s RSS/Atom feed", psz_feed );
945     }
946     free( psz_buffer_2 );
947     if( p_xml ) xml_Delete( p_xml );
948
949     return 0;
950 }
951
952 /****************************************************************************
953  * FreeRSS
954  ***************************************************************************/
955 static void FreeRSS( filter_t *p_filter)
956 {
957     filter_sys_t *p_sys = p_filter->p_sys;
958
959     struct rss_item_t *p_item;
960     struct rss_feed_t *p_feed;
961
962     int i_feed;
963     int i_item;
964
965     for( i_feed = 0; i_feed < p_sys->i_feeds; i_feed++ )
966     {
967         p_feed = p_sys->p_feeds+i_feed;
968         for( i_item = 0; i_item < p_feed->i_items; i_item++ )
969         {
970             p_item = p_feed->p_items+i_item;
971             free( p_item->psz_title );
972             free( p_item->psz_link );
973             free( p_item->psz_description );
974         }
975         free( p_feed->p_items );
976         free( p_feed->psz_title);
977         free( p_feed->psz_link );
978         free( p_feed->psz_description );
979         free( p_feed->psz_image );
980         if( p_feed->p_pic != NULL )
981             p_feed->p_pic->pf_release( p_feed->p_pic );
982     }
983     free( p_sys->p_feeds );
984     p_sys->i_feeds = 0;
985 }