]> git.sesse.net Git - vlc/blob - modules/video_filter/rss.c
2636741c1539c4c632a7bba9e5a87083e035063b
[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
188     set_section( N_("Position"), NULL );
189     add_integer( CFG_PREFIX "x", 0, NULL, POSX_TEXT, POSX_LONGTEXT, VLC_TRUE );
190     add_integer( CFG_PREFIX "y", 0, NULL, POSY_TEXT, POSY_LONGTEXT, VLC_TRUE );
191     add_integer( CFG_PREFIX "position", -1, NULL, POS_TEXT, POS_LONGTEXT, VLC_FALSE );
192         change_integer_list( pi_pos_values, ppsz_pos_descriptions, 0 );
193
194     set_section( N_("Font"), NULL );
195     /* 5 sets the default to top [1] left [4] */
196     add_integer_with_range( CFG_PREFIX "opacity", 255, 0, 255, NULL,
197         OPACITY_TEXT, OPACITY_LONGTEXT, VLC_FALSE );
198     add_integer( CFG_PREFIX "color", 0xFFFFFF, NULL, COLOR_TEXT, COLOR_LONGTEXT,
199                   VLC_FALSE );
200         change_integer_list( pi_color_values, ppsz_color_descriptions, 0 );
201     add_integer( CFG_PREFIX "size", -1, NULL, SIZE_TEXT, SIZE_LONGTEXT, VLC_FALSE );
202
203     set_section( N_("Misc"), NULL );
204     add_integer( CFG_PREFIX "speed", 100000, NULL, SPEED_TEXT, SPEED_LONGTEXT,
205                  VLC_FALSE );
206     add_integer( CFG_PREFIX "length", 60, NULL, LENGTH_TEXT, LENGTH_LONGTEXT,
207                  VLC_FALSE );
208     add_integer( CFG_PREFIX "ttl", 1800, NULL, TTL_TEXT, TTL_LONGTEXT, VLC_FALSE );
209     add_bool( CFG_PREFIX "images", 1, NULL, IMAGE_TEXT, IMAGE_LONGTEXT, VLC_FALSE );
210     add_integer( CFG_PREFIX "title", default_title, NULL, TITLE_TEXT, TITLE_LONGTEXT, VLC_FALSE );
211         change_integer_list( pi_title_modes, ppsz_title_modes, 0 );
212
213     set_description( _("RSS and Atom feed display") );
214     add_shortcut( "rss" );
215     add_shortcut( "atom" );
216 vlc_module_end();
217
218 static const char *ppsz_filter_options[] = {
219     "urls", "x", "y", "position", "color", "size", "speed", "length",
220     "ttl", "images", "title", NULL
221 };
222
223 /*****************************************************************************
224  * CreateFilter: allocates RSS video filter
225  *****************************************************************************/
226 static int CreateFilter( vlc_object_t *p_this )
227 {
228     filter_t *p_filter = (filter_t *)p_this;
229     filter_sys_t *p_sys;
230     int i_feed;
231
232     /* Allocate structure */
233     p_sys = p_filter->p_sys = malloc( sizeof( filter_sys_t ) );
234     if( p_sys == NULL )
235     {
236         msg_Err( p_filter, "out of memory" );
237         return VLC_ENOMEM;
238     }
239
240     vlc_mutex_init( p_filter, &p_sys->lock );
241     vlc_mutex_lock( &p_sys->lock );
242
243     config_ChainParse( p_filter, CFG_PREFIX, ppsz_filter_options,
244                        p_filter->p_cfg );
245
246     p_sys->psz_urls = var_CreateGetString( p_filter, CFG_PREFIX "urls" );
247     p_sys->i_title = var_CreateGetInteger( p_filter, CFG_PREFIX "title" );
248     p_sys->i_cur_feed = 0;
249     p_sys->i_cur_item = p_sys->i_title == scroll_title ? -1 : 0;
250     p_sys->i_cur_char = 0;
251     p_sys->i_feeds = 0;
252     p_sys->p_feeds = NULL;
253     p_sys->i_speed = var_CreateGetInteger( p_filter, CFG_PREFIX "speed" );
254     p_sys->i_length = var_CreateGetInteger( p_filter, CFG_PREFIX "length" );
255     p_sys->i_ttl = __MAX( 0, var_CreateGetInteger( p_filter, CFG_PREFIX "ttl" ) );
256     p_sys->b_images = var_CreateGetBool( p_filter, CFG_PREFIX "images" );
257
258     p_sys->psz_marquee = (char *)malloc( p_sys->i_length + 1 );
259     if( p_sys->psz_marquee == NULL )
260     {
261         msg_Err( p_filter, "out of memory" );
262         vlc_mutex_unlock( &p_sys->lock );
263         vlc_mutex_destroy( &p_sys->lock );
264         free( p_sys );
265         return VLC_ENOMEM;
266     }
267     p_sys->psz_marquee[p_sys->i_length] = '\0';
268
269     p_sys->p_style = malloc( sizeof( text_style_t ));
270     if( p_sys->p_style == NULL )
271     {
272         msg_Err( p_filter, "out of memory" );
273         free( p_sys->psz_marquee );
274         vlc_mutex_unlock( &p_sys->lock );
275         vlc_mutex_destroy( &p_sys->lock );
276         free( p_sys );
277         return VLC_ENOMEM;
278     }
279     memcpy( p_sys->p_style, &default_text_style, sizeof( text_style_t ));
280
281     p_sys->i_xoff = var_CreateGetInteger( p_filter, CFG_PREFIX "x" );
282     p_sys->i_yoff = var_CreateGetInteger( p_filter, CFG_PREFIX "y" );
283     p_sys->i_pos = var_CreateGetInteger( p_filter, CFG_PREFIX "position" );
284     p_sys->p_style->i_font_alpha = 255 - var_CreateGetInteger( p_filter, CFG_PREFIX "opacity" );
285     p_sys->p_style->i_font_color = var_CreateGetInteger( p_filter, CFG_PREFIX "color" );
286     p_sys->p_style->i_font_size = var_CreateGetInteger( p_filter, CFG_PREFIX "size" );
287
288     if( p_sys->b_images == VLC_TRUE && p_sys->p_style->i_font_size == -1 )
289     {
290         msg_Warn( p_filter, "rrs-size wasn't specified. Feed images will thus be displayed without being resized" );
291     }
292
293     if( FetchRSS( p_filter ) )
294     {
295         msg_Err( p_filter, "failed while fetching RSS ... too bad" );
296         free( p_sys->p_style );
297         free( p_sys->psz_marquee );
298         vlc_mutex_unlock( &p_sys->lock );
299         vlc_mutex_destroy( &p_sys->lock );
300         free( p_sys );
301         return VLC_EGENERIC;
302     }
303     p_sys->t_last_update = time( NULL );
304
305     if( p_sys->i_feeds == 0 )
306     {
307         free( p_sys->p_style );
308         free( p_sys->psz_marquee );
309         vlc_mutex_unlock( &p_sys->lock );
310         vlc_mutex_destroy( &p_sys->lock );
311         free( p_sys );
312         return VLC_EGENERIC;
313     }
314     for( i_feed=0; i_feed < p_sys->i_feeds; i_feed ++ )
315     {
316         if( p_sys->p_feeds[i_feed].i_items == 0 )
317         {
318             free( p_sys->p_style );
319             free( p_sys->psz_marquee );
320             FreeRSS( p_filter );
321             vlc_mutex_unlock( &p_sys->lock );
322             vlc_mutex_destroy( &p_sys->lock );
323             free( p_sys );
324             return VLC_EGENERIC;
325         }
326     }
327     /* Misc init */
328     p_filter->pf_sub_filter = Filter;
329     p_sys->last_date = (mtime_t)0;
330
331     vlc_mutex_unlock( &p_sys->lock );
332
333     return VLC_SUCCESS;
334 }
335 /*****************************************************************************
336  * DestroyFilter: destroy RSS video filter
337  *****************************************************************************/
338 static void DestroyFilter( vlc_object_t *p_this )
339 {
340     filter_t *p_filter = (filter_t *)p_this;
341     filter_sys_t *p_sys = p_filter->p_sys;
342
343     vlc_mutex_lock( &p_sys->lock );
344
345     if( p_sys->p_style ) free( p_sys->p_style );
346     if( p_sys->psz_marquee ) free( p_sys->psz_marquee );
347     free( p_sys->psz_urls );
348     FreeRSS( p_filter );
349     vlc_mutex_unlock( &p_sys->lock );
350     vlc_mutex_destroy( &p_sys->lock );
351     free( p_sys );
352
353     /* Delete the RSS variables */
354     var_Destroy( p_filter, CFG_PREFIX "urls" );
355     var_Destroy( p_filter, CFG_PREFIX "speed" );
356     var_Destroy( p_filter, CFG_PREFIX "length" );
357     var_Destroy( p_filter, CFG_PREFIX "ttl" );
358     var_Destroy( p_filter, CFG_PREFIX "images" );
359     var_Destroy( p_filter, CFG_PREFIX "x" );
360     var_Destroy( p_filter, CFG_PREFIX "y" );
361     var_Destroy( p_filter, CFG_PREFIX "position" );
362     var_Destroy( p_filter, CFG_PREFIX "color");
363     var_Destroy( p_filter, CFG_PREFIX "opacity");
364     var_Destroy( p_filter, CFG_PREFIX "size");
365     var_Destroy( p_filter, CFG_PREFIX "title" );
366 }
367
368 /****************************************************************************
369  * Filter: the whole thing
370  ****************************************************************************
371  * This function outputs subpictures at regular time intervals.
372  ****************************************************************************/
373 static subpicture_t *Filter( filter_t *p_filter, mtime_t date )
374 {
375     filter_sys_t *p_sys = p_filter->p_sys;
376     subpicture_t *p_spu;
377     video_format_t fmt;
378     subpicture_region_t *p_region;
379
380     int i_feed, i_item;
381
382     struct rss_feed_t *p_feed;
383
384     memset( &fmt, 0, sizeof(video_format_t) );
385
386     vlc_mutex_lock( &p_sys->lock );
387
388     if( p_sys->last_date
389        + ( p_sys->i_cur_char == 0 && p_sys->i_cur_item == ( p_sys->i_title == scroll_title ? -1 : 0 ) ? 5 : 1 )
390            /* ( ... ? 5 : 1 ) means "wait 5 times more for the 1st char" */
391        * p_sys->i_speed > date )
392     {
393         vlc_mutex_unlock( &p_sys->lock );
394         return NULL;
395     }
396
397     /* Do we need to update the feeds ? */
398     if( p_sys->i_ttl
399         && time( NULL ) > p_sys->t_last_update + (time_t)p_sys->i_ttl )
400     {
401         msg_Dbg( p_filter, "Forcing update of all the RSS feeds" );
402         if( FetchRSS( p_filter ) )
403         {
404             msg_Err( p_filter, "Failed while fetching RSS ... too bad" );
405             vlc_mutex_unlock( &p_sys->lock );
406             return NULL; /* FIXME : we most likely messed up all the data,
407                           * so we might need to do something about it */
408         }
409         p_sys->t_last_update = time( NULL );
410     }
411
412     p_sys->last_date = date;
413     p_sys->i_cur_char++;
414     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 )
415     {
416         p_sys->i_cur_char = 0;
417         p_sys->i_cur_item++;
418         if( p_sys->i_cur_item >= p_sys->p_feeds[p_sys->i_cur_feed].i_items )
419         {
420             if( p_sys->i_title == scroll_title )
421                 p_sys->i_cur_item = -1;
422             else
423                 p_sys->i_cur_item = 0;
424             p_sys->i_cur_feed = (p_sys->i_cur_feed + 1)%p_sys->i_feeds;
425         }
426     }
427
428     p_spu = p_filter->pf_sub_buffer_new( p_filter );
429     if( !p_spu )
430     {
431         vlc_mutex_unlock( &p_sys->lock );
432         return NULL;
433     }
434
435     fmt.i_chroma = VLC_FOURCC('T','E','X','T');
436
437     p_spu->p_region = p_spu->pf_create_region( VLC_OBJECT(p_filter), &fmt );
438     if( !p_spu->p_region )
439     {
440         p_filter->pf_sub_buffer_del( p_filter, p_spu );
441         vlc_mutex_unlock( &p_sys->lock );
442         return NULL;
443     }
444
445     /* Generate the string that will be displayed. This string is supposed to
446        be p_sys->i_length characters long. */
447     i_item = p_sys->i_cur_item;
448     i_feed = p_sys->i_cur_feed;
449     p_feed = &p_sys->p_feeds[i_feed];
450
451     if( ( p_feed->p_pic && p_sys->i_title == default_title )
452         || p_sys->i_title == hide_title )
453     {
454         /* Don't display the feed's title if we have an image */
455         snprintf( p_sys->psz_marquee, p_sys->i_length, "%s",
456                   p_sys->p_feeds[i_feed].p_items[i_item].psz_title
457                   +p_sys->i_cur_char );
458     }
459     else if( ( !p_feed->p_pic && p_sys->i_title == default_title )
460              || p_sys->i_title == prepend_title )
461     {
462         snprintf( p_sys->psz_marquee, p_sys->i_length, "%s : %s",
463                   p_sys->p_feeds[i_feed].psz_title,
464                   p_sys->p_feeds[i_feed].p_items[i_item].psz_title
465                   +p_sys->i_cur_char );
466     }
467     else /* scrolling title */
468     {
469         if( i_item == -1 )
470             snprintf( p_sys->psz_marquee, p_sys->i_length, "%s : %s",
471                       p_sys->p_feeds[i_feed].psz_title + p_sys->i_cur_char,
472                       p_sys->p_feeds[i_feed].p_items[i_item+1].psz_title );
473         else
474             snprintf( p_sys->psz_marquee, p_sys->i_length, "%s",
475                       p_sys->p_feeds[i_feed].p_items[i_item].psz_title
476                       +p_sys->i_cur_char );
477     }
478
479     while( strlen( p_sys->psz_marquee ) < (unsigned int)p_sys->i_length )
480     {
481         i_item++;
482         if( i_item == p_sys->p_feeds[i_feed].i_items ) break;
483         snprintf( strchr( p_sys->psz_marquee, 0 ),
484                   p_sys->i_length - strlen( p_sys->psz_marquee ),
485                   " - %s",
486                   p_sys->p_feeds[i_feed].p_items[i_item].psz_title );
487     }
488
489     /* Calls to snprintf might split multibyte UTF8 chars ...
490      * which freetype doesn't like. */
491     {
492         char *a = strdup( p_sys->psz_marquee );
493         char *a2 = a;
494         char *b = p_sys->psz_marquee;
495         EnsureUTF8( p_sys->psz_marquee );
496         /* we want to use ' ' instead of '?' for erroneous chars */
497         while( *b != '\0' )
498         {
499             if( *b != *a ) *b = ' ';
500             b++;a++;
501         }
502         free( a2 );
503     }
504
505     p_spu->p_region->psz_text = strdup(p_sys->psz_marquee);
506     if( p_sys->p_style->i_font_size > 0 )
507         p_spu->p_region->fmt.i_visible_height = p_sys->p_style->i_font_size;
508     p_spu->i_start = date;
509     p_spu->i_stop  = 0;
510     p_spu->b_ephemer = VLC_TRUE;
511
512     /*  where to locate the string: */
513     if( p_sys->i_pos < 0 )
514     {   /*  set to an absolute xy */
515         p_spu->p_region->i_align = OSD_ALIGN_LEFT | OSD_ALIGN_TOP;
516         p_spu->b_absolute = VLC_TRUE;
517     }
518     else
519     {   /* set to one of the 9 relative locations */
520         p_spu->p_region->i_align = p_sys->i_pos;
521         p_spu->b_absolute = VLC_FALSE;
522     }
523
524     p_spu->i_x = p_sys->i_xoff;
525     p_spu->i_y = p_sys->i_yoff;
526
527     p_spu->i_height = 1;
528     p_spu->p_region->p_style = p_sys->p_style;
529
530     if( p_feed->p_pic )
531     {
532         /* Display the feed's image */
533         picture_t *p_pic = p_feed->p_pic;
534         video_format_t fmt_out;
535
536         memset( &fmt_out, 0, sizeof(video_format_t) );
537
538         fmt_out.i_chroma = VLC_FOURCC('Y','U','V','A');
539         fmt_out.i_aspect = VOUT_ASPECT_FACTOR;
540         fmt_out.i_sar_num = fmt_out.i_sar_den = 1;
541         fmt_out.i_width =
542             fmt_out.i_visible_width = p_pic->p[Y_PLANE].i_visible_pitch;
543         fmt_out.i_height =
544             fmt_out.i_visible_height = p_pic->p[Y_PLANE].i_visible_lines;
545
546         p_region = p_spu->pf_create_region( VLC_OBJECT( p_filter ), &fmt_out );
547         if( !p_region )
548         {
549             msg_Err( p_filter, "cannot allocate SPU region" );
550         }
551         else
552         {
553             vout_CopyPicture( p_filter, &p_region->picture, p_pic );
554             p_spu->p_region->p_next = p_region;
555         }
556
557         /* Offset text to display right next to the image */
558         p_spu->p_region->i_x = p_pic->p[Y_PLANE].i_visible_pitch;
559     }
560
561     vlc_mutex_unlock( &p_sys->lock );
562     return p_spu;
563 }
564
565 /****************************************************************************
566  * RSS related functions
567  ****************************************************************************
568  * You should always lock the p_filter mutex before using any of these
569  * functions
570  ***************************************************************************/
571
572 #undef LoadImage /* do not conflict with Win32 API */
573
574 /****************************************************************************
575  * download and resize image located at psz_url
576  ***************************************************************************/
577 static picture_t *LoadImage( filter_t *p_filter, const char *psz_url )
578 {
579     filter_sys_t *p_sys = p_filter->p_sys;
580     video_format_t fmt_in;
581     video_format_t fmt_out;
582     picture_t *p_orig;
583     picture_t *p_pic = NULL;
584     image_handler_t *p_handler = image_HandlerCreate( p_filter );
585
586     memset( &fmt_in, 0, sizeof(video_format_t) );
587     memset( &fmt_out, 0, sizeof(video_format_t) );
588
589     fmt_out.i_chroma = VLC_FOURCC('Y','U','V','A');
590     p_orig = image_ReadUrl( p_handler, psz_url, &fmt_in, &fmt_out );
591
592     if( !p_orig )
593     {
594         msg_Warn( p_filter, "Unable to read image %s", psz_url );
595     }
596     else if( p_sys->p_style->i_font_size > 0 )
597     {
598
599         fmt_in.i_chroma = VLC_FOURCC('Y','U','V','A');
600         fmt_in.i_height = p_orig->p[Y_PLANE].i_visible_lines;
601         fmt_in.i_width = p_orig->p[Y_PLANE].i_visible_pitch;
602         fmt_out.i_width = p_orig->p[Y_PLANE].i_visible_pitch
603             *p_sys->p_style->i_font_size/p_orig->p[Y_PLANE].i_visible_lines;
604         fmt_out.i_height = p_sys->p_style->i_font_size;
605
606         p_pic = image_Convert( p_handler, p_orig, &fmt_in, &fmt_out );
607         p_orig->pf_release( p_orig );
608         if( !p_pic )
609         {
610             msg_Warn( p_filter, "Error while converting %s", psz_url );
611         }
612     }
613     else
614     {
615         p_pic = p_orig;
616     }
617
618     image_HandlerDelete( p_handler );
619
620     return p_pic;
621 }
622
623 /****************************************************************************
624  * remove all ' ' '\t' '\n' '\r' characters from the begining and end of the
625  * string.
626  ***************************************************************************/
627 static char *removeWhiteChars( char *psz_src )
628 {
629     char *psz_src2 = strdup( psz_src );
630     char *psz_clean = strdup( psz_src2 );
631     char *psz_clean2;
632     int i;
633     while( ( *psz_clean == ' ' || *psz_clean == '\t'
634            || *psz_clean == '\n' || *psz_clean == '\r' )
635            && *psz_clean != '\0' )
636     {
637         psz_clean++;
638     }
639     i = strlen( psz_clean );
640     while( --i > 0 &&
641          ( psz_clean[i] == ' ' || psz_clean[i] == '\t'
642         || psz_clean[i] == '\n' || psz_clean[i] == '\r' ) );
643     psz_clean[i+1] = '\0';
644     psz_clean2 = strdup( psz_clean );
645     free( psz_src2 );
646     return psz_clean2;
647 }
648
649 /****************************************************************************
650  * FetchRSS (or Atom) feeds
651  ***************************************************************************/
652 static int FetchRSS( filter_t *p_filter)
653 {
654     filter_sys_t *p_sys = p_filter->p_sys;
655
656     stream_t *p_stream = NULL;
657     xml_t *p_xml = NULL;
658     xml_reader_t *p_xml_reader = NULL;
659
660     char *psz_eltname = NULL;
661     char *psz_eltvalue = NULL;
662     char *psz_feed = NULL;
663     char *psz_buffer = NULL;
664     char *psz_buffer_2 = NULL;
665
666     int i_feed;
667     int i_item;
668     vlc_bool_t b_is_item;
669     vlc_bool_t b_is_image;
670     int i_int;
671
672     FreeRSS( p_filter );
673     p_sys->i_feeds = 1;
674     i_int = 0;
675     while( p_sys->psz_urls[i_int] != 0 )
676         if( p_sys->psz_urls[i_int++] == '|' )
677             p_sys->i_feeds++;
678     p_sys->p_feeds = (struct rss_feed_t *)malloc( p_sys->i_feeds
679                                 * sizeof( struct rss_feed_t ) );
680
681     p_xml = xml_Create( p_filter );
682     if( !p_xml )
683     {
684         msg_Err( p_filter, "Failed to open XML parser" );
685         return 1;
686     }
687
688     psz_buffer = strdup( p_sys->psz_urls );
689     psz_buffer_2 = psz_buffer; /* keep track so we can free it */
690     for( i_feed = 0; i_feed < p_sys->i_feeds; i_feed++ )
691     {
692         struct rss_feed_t *p_feed = p_sys->p_feeds+i_feed;
693
694         if( psz_buffer == NULL ) break;
695         if( psz_buffer[0] == 0 ) psz_buffer++;
696         psz_feed = psz_buffer;
697         psz_buffer = strchr( psz_buffer, '|' );
698         if( psz_buffer != NULL ) psz_buffer[0] = 0;
699
700         p_feed->psz_title = NULL;
701         p_feed->psz_description = NULL;
702         p_feed->psz_link = NULL;
703         p_feed->psz_image = NULL;
704         p_feed->p_pic = NULL;
705         p_feed->i_items = 0;
706         p_feed->p_items = NULL;
707
708         msg_Dbg( p_filter, "opening %s RSS/Atom feed ...", psz_feed );
709
710         p_stream = stream_UrlNew( p_filter, psz_feed );
711         if( !p_stream )
712         {
713             msg_Err( p_filter, "Failed to open %s for reading", psz_feed );
714             return 1;
715         }
716
717         p_xml_reader = xml_ReaderCreate( p_xml, p_stream );
718         if( !p_xml_reader )
719         {
720             msg_Err( p_filter, "Failed to open %s for parsing", psz_feed );
721             return 1;
722         }
723
724         i_item = 0;
725         b_is_item = VLC_FALSE;
726         b_is_image = VLC_FALSE;
727
728         while( xml_ReaderRead( p_xml_reader ) == 1 )
729         {
730             switch( xml_ReaderNodeType( p_xml_reader ) )
731             {
732                 // Error
733                 case -1:
734                     return 1;
735
736                 case XML_READER_STARTELEM:
737                     if( psz_eltname )
738                     {
739                         free( psz_eltname );
740                         psz_eltname = NULL;
741                     }
742                     psz_eltname = xml_ReaderName( p_xml_reader );
743                     if( !psz_eltname )
744                     {
745                         return 1;
746                     }
747 #                   ifdef RSS_DEBUG
748                     msg_Dbg( p_filter, "element name: %s", psz_eltname );
749 #                   endif
750                     if( !strcmp( psz_eltname, "item" ) /* rss */
751                      || !strcmp( psz_eltname, "entry" ) ) /* atom */
752                     {
753                         b_is_item = VLC_TRUE;
754                         p_feed->i_items++;
755                         p_feed->p_items = (struct rss_item_t *)realloc( p_feed->p_items, p_feed->i_items * sizeof( struct rss_item_t ) );
756                         p_feed->p_items[p_feed->i_items-1].psz_title = NULL;
757                         p_feed->p_items[p_feed->i_items-1].psz_description
758                                                                      = NULL;
759                         p_feed->p_items[p_feed->i_items-1].psz_link = NULL;
760                     }
761                     else if( !strcmp( psz_eltname, "image" ) ) /* rss */
762                     {
763                         b_is_image = VLC_TRUE;
764                     }
765                     else if( !strcmp( psz_eltname, "link" ) ) /* atom */
766                     {
767                         char *psz_href = NULL;
768                         char *psz_rel = NULL;
769                         while( xml_ReaderNextAttr( p_xml_reader )
770                                == VLC_SUCCESS )
771                         {
772                             char *psz_name = xml_ReaderName( p_xml_reader );
773                             char *psz_value = xml_ReaderValue( p_xml_reader );
774                             if( !strcmp( psz_name, "rel" ) )
775                             {
776                                 psz_rel = psz_value;
777                             }
778                             else if( !strcmp( psz_name, "href" ) )
779                             {
780                                 psz_href = psz_value;
781                             }
782                             else
783                             {
784                                 free( psz_value );
785                             }
786                             free( psz_name );
787                         }
788                         if( psz_rel && psz_href )
789                         {
790                             if( !strcmp( psz_rel, "alternate" )
791                                 && b_is_item == VLC_FALSE
792                                 && b_is_image == VLC_FALSE
793                                 && !p_feed->psz_link )
794                             {
795                                 p_feed->psz_link = psz_href;
796                             }
797                             /* this isn't in the rfc but i found some ... */
798                             else if( ( !strcmp( psz_rel, "logo" )
799                                     || !strcmp( psz_rel, "icon" ) )
800                                     && b_is_item == VLC_FALSE
801                                     && b_is_image == VLC_FALSE
802                                     && !p_feed->psz_image )
803                             {
804                                 p_feed->psz_image = psz_href;
805                             }
806                             else
807                             {
808                                 free( psz_href );
809                             }
810                         }
811                         else
812                         {
813                             if( psz_href ) free( psz_href );
814                         }
815                         if( psz_rel ) free( psz_rel );
816                     }
817                     break;
818
819                 case XML_READER_ENDELEM:
820                     if( psz_eltname )
821                     {
822                         free( psz_eltname );
823                         psz_eltname = NULL;
824                     }
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 }