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