]> git.sesse.net Git - vlc/blob - modules/video_filter/rss.c
Merge commit 'origin/base'
[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_common.h>
38 #include <vlc_plugin.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 const int pi_color_values[] = {
64                0xf0000000, 0x00000000, 0x00808080, 0x00C0C0C0,
65                0x00FFFFFF, 0x00800000, 0x00FF0000, 0x00FF00FF, 0x00FFFF00,
66                0x00808000, 0x00008000, 0x00008080, 0x0000FF00, 0x00800080,
67                0x00000080, 0x000000FF, 0x0000FFFF};
68 static const char *const ppsz_color_descriptions[] = {
69                N_("Default"), N_("Black"),
70                N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"), N_("Red"),
71                N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"),
72                N_("Teal"), N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"),
73                N_("Aqua") };
74
75 /*****************************************************************************
76  * filter_sys_t: rss filter descriptor
77  *****************************************************************************/
78
79 struct rss_item_t
80 {
81     char *psz_title;
82     char *psz_description;
83     char *psz_link;
84 };
85
86 struct rss_feed_t
87 {
88     char *psz_title;
89     char *psz_description;
90     char *psz_link;
91     char *psz_image;
92     picture_t *p_pic;
93
94     int i_items;
95     struct rss_item_t *p_items;
96 };
97
98 struct filter_sys_t
99 {
100     vlc_mutex_t lock;
101     vlc_mutex_t *p_lock;
102
103     int i_xoff, i_yoff;  /* offsets for the display string in the video window */
104     int i_pos; /* permit relative positioning (top, bottom, left, right, center) */
105     int i_speed;
106     int i_length;
107
108     char *psz_marquee;    /* marquee string */
109
110     text_style_t *p_style; /* font control */
111
112     mtime_t last_date;
113
114     char *psz_urls;
115     int i_feeds;
116     struct rss_feed_t *p_feeds;
117
118     int i_ttl;
119     time_t t_last_update;
120     bool b_images;
121     int i_title;
122
123     int i_cur_feed;
124     int i_cur_item;
125     int i_cur_char;
126 };
127
128 #define MSG_TEXT N_("Feed URLs")
129 #define MSG_LONGTEXT N_("RSS/Atom feed '|' (pipe) seperated URLs.")
130 #define SPEED_TEXT N_("Speed of feeds")
131 #define SPEED_LONGTEXT N_("Speed of the RSS/Atom feeds in microseconds (bigger is slower).")
132 #define LENGTH_TEXT N_("Max length")
133 #define LENGTH_LONGTEXT N_("Maximum number of characters displayed on the " \
134                 "screen." )
135 #define TTL_TEXT N_("Refresh time")
136 #define TTL_LONGTEXT N_("Number of seconds between each forced refresh " \
137         "of the feeds. 0 means that the feeds are never updated." )
138 #define IMAGE_TEXT N_("Feed images")
139 #define IMAGE_LONGTEXT N_("Display feed images if available.")
140
141 #define POSX_TEXT N_("X offset")
142 #define POSX_LONGTEXT N_("X offset, from the left screen edge." )
143 #define POSY_TEXT N_("Y offset")
144 #define POSY_LONGTEXT N_("Y offset, down from the top." )
145 #define OPACITY_TEXT N_("Opacity")
146 #define OPACITY_LONGTEXT N_("Opacity (inverse of transparency) of " \
147     "overlay text. 0 = transparent, 255 = totally opaque." )
148
149 #define SIZE_TEXT N_("Font size, pixels")
150 #define SIZE_LONGTEXT N_("Font size, in pixels. Default is -1 (use default " \
151     "font size)." )
152
153 #define COLOR_TEXT N_("Color")
154 #define COLOR_LONGTEXT N_("Color of the text that will be rendered on "\
155     "the video. This must be an hexadecimal (like HTML colors). The first two "\
156     "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
157     " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
158
159 #define POS_TEXT N_("Text position")
160 #define POS_LONGTEXT N_( \
161   "You can enforce the text position on the video " \
162   "(0=center, 1=left, 2=right, 4=top, 8=bottom; you can " \
163   "also use combinations of these values, eg 6 = top-right).")
164
165 #define TITLE_TEXT N_("Title display mode")
166 #define TITLE_LONGTEXT N_("Title display mode. Default is 0 (hidden) if the feed has an image and feed images are enabled, 1 otherwise.")
167
168 static const int pi_pos_values[] = { 0, 1, 2, 4, 8, 5, 6, 9, 10 };
169 static const char *const ppsz_pos_descriptions[] =
170      { N_("Center"), N_("Left"), N_("Right"), N_("Top"), N_("Bottom"),
171      N_("Top-Left"), N_("Top-Right"), N_("Bottom-Left"), N_("Bottom-Right") };
172
173 enum title_modes {
174     default_title=-1,
175     hide_title,
176     prepend_title,
177     scroll_title };
178
179 static const int pi_title_modes[] = { default_title, hide_title, prepend_title, scroll_title };
180 static const char *const ppsz_title_modes[] =
181     { N_("Default"), N_("Don't show"), N_("Always visible"), N_("Scroll with feed") };
182
183 #define CFG_PREFIX "rss-"
184
185 /*****************************************************************************
186  * Module descriptor
187  *****************************************************************************/
188 vlc_module_begin ()
189     set_capability( "sub filter", 1 )
190     set_shortname( "RSS / Atom" )
191     set_callbacks( CreateFilter, DestroyFilter )
192     set_category( CAT_VIDEO )
193     set_subcategory( SUBCAT_VIDEO_SUBPIC )
194     add_string( CFG_PREFIX "urls", "rss", NULL, MSG_TEXT, MSG_LONGTEXT, false )
195
196     set_section( N_("Position"), NULL )
197     add_integer( CFG_PREFIX "x", 0, NULL, POSX_TEXT, POSX_LONGTEXT, true )
198     add_integer( CFG_PREFIX "y", 0, NULL, POSY_TEXT, POSY_LONGTEXT, true )
199     add_integer( CFG_PREFIX "position", -1, NULL, POS_TEXT, POS_LONGTEXT, false )
200         change_integer_list( pi_pos_values, ppsz_pos_descriptions, NULL )
201
202     set_section( N_("Font"), NULL )
203     /* 5 sets the default to top [1] left [4] */
204     add_integer_with_range( CFG_PREFIX "opacity", 255, 0, 255, NULL,
205         OPACITY_TEXT, OPACITY_LONGTEXT, false )
206     add_integer( CFG_PREFIX "color", 0xFFFFFF, NULL, COLOR_TEXT, COLOR_LONGTEXT,
207                   false )
208         change_integer_list( pi_color_values, ppsz_color_descriptions, NULL )
209     add_integer( CFG_PREFIX "size", -1, NULL, SIZE_TEXT, SIZE_LONGTEXT, false )
210
211     set_section( N_("Misc"), NULL )
212     add_integer( CFG_PREFIX "speed", 100000, NULL, SPEED_TEXT, SPEED_LONGTEXT,
213                  false )
214     add_integer( CFG_PREFIX "length", 60, NULL, LENGTH_TEXT, LENGTH_LONGTEXT,
215                  false )
216     add_integer( CFG_PREFIX "ttl", 1800, NULL, TTL_TEXT, TTL_LONGTEXT, false )
217     add_bool( CFG_PREFIX "images", 1, NULL, IMAGE_TEXT, IMAGE_LONGTEXT, false )
218     add_integer( CFG_PREFIX "title", default_title, NULL, TITLE_TEXT, TITLE_LONGTEXT, false )
219         change_integer_list( pi_title_modes, ppsz_title_modes, NULL )
220
221     set_description( N_("RSS and Atom feed display") )
222     add_shortcut( "rss" )
223     add_shortcut( "atom" )
224 vlc_module_end ()
225
226 static const char *const ppsz_filter_options[] = {
227     "urls", "x", "y", "position", "color", "size", "speed", "length",
228     "ttl", "images", "title", NULL
229 };
230
231 /*****************************************************************************
232  * CreateFilter: allocates RSS video filter
233  *****************************************************************************/
234 static int CreateFilter( vlc_object_t *p_this )
235 {
236     filter_t *p_filter = (filter_t *)p_this;
237     filter_sys_t *p_sys;
238     int i_feed;
239
240     /* Allocate structure */
241     p_sys = p_filter->p_sys = malloc( sizeof( filter_sys_t ) );
242     if( p_sys == NULL )
243         return VLC_ENOMEM;
244
245     vlc_mutex_init( &p_sys->lock );
246     vlc_mutex_lock( &p_sys->lock );
247
248     config_ChainParse( p_filter, CFG_PREFIX, ppsz_filter_options,
249                        p_filter->p_cfg );
250
251     p_sys->psz_urls = var_CreateGetString( p_filter, CFG_PREFIX "urls" );
252     p_sys->i_title = var_CreateGetInteger( p_filter, CFG_PREFIX "title" );
253     p_sys->i_cur_feed = 0;
254     p_sys->i_cur_item = p_sys->i_title == scroll_title ? -1 : 0;
255     p_sys->i_cur_char = 0;
256     p_sys->i_feeds = 0;
257     p_sys->p_feeds = NULL;
258     p_sys->i_speed = var_CreateGetInteger( p_filter, CFG_PREFIX "speed" );
259     p_sys->i_length = var_CreateGetInteger( p_filter, CFG_PREFIX "length" );
260     p_sys->i_ttl = __MAX( 0, var_CreateGetInteger( p_filter, CFG_PREFIX "ttl" ) );
261     p_sys->b_images = var_CreateGetBool( p_filter, CFG_PREFIX "images" );
262
263     p_sys->psz_marquee = (char *)malloc( p_sys->i_length + 1 );
264     if( p_sys->psz_marquee == NULL )
265     {
266         vlc_mutex_unlock( &p_sys->lock );
267         vlc_mutex_destroy( &p_sys->lock );
268         free( p_sys->psz_urls );
269         free( p_sys );
270         return VLC_ENOMEM;
271     }
272     p_sys->psz_marquee[p_sys->i_length] = '\0';
273
274     p_sys->p_style = text_style_New();
275     if( p_sys->p_style == NULL )
276     {
277         free( p_sys->psz_marquee );
278         vlc_mutex_unlock( &p_sys->lock );
279         vlc_mutex_destroy( &p_sys->lock );
280         free( p_sys->psz_urls );
281         free( p_sys );
282         return VLC_ENOMEM;
283     }
284
285     p_sys->i_xoff = var_CreateGetInteger( p_filter, CFG_PREFIX "x" );
286     p_sys->i_yoff = var_CreateGetInteger( p_filter, CFG_PREFIX "y" );
287     p_sys->i_pos = var_CreateGetInteger( p_filter, CFG_PREFIX "position" );
288     p_sys->p_style->i_font_alpha = 255 - var_CreateGetInteger( p_filter, CFG_PREFIX "opacity" );
289     p_sys->p_style->i_font_color = var_CreateGetInteger( p_filter, CFG_PREFIX "color" );
290     p_sys->p_style->i_font_size = var_CreateGetInteger( p_filter, CFG_PREFIX "size" );
291
292     if( p_sys->b_images == true && p_sys->p_style->i_font_size == -1 )
293     {
294         msg_Warn( p_filter, "rss-size wasn't specified. Feed images will thus be displayed without being resized" );
295     }
296
297     if( FetchRSS( p_filter ) )
298     {
299         msg_Err( p_filter, "failed while fetching RSS ... too bad" );
300         text_style_Delete( p_sys->p_style );
301         free( p_sys->psz_marquee );
302         vlc_mutex_unlock( &p_sys->lock );
303         vlc_mutex_destroy( &p_sys->lock );
304         free( p_sys->psz_urls );
305         free( p_sys );
306         return VLC_EGENERIC;
307     }
308     p_sys->t_last_update = time( NULL );
309
310     if( p_sys->i_feeds == 0 )
311     {
312         text_style_Delete( p_sys->p_style );
313         free( p_sys->psz_marquee );
314         vlc_mutex_unlock( &p_sys->lock );
315         vlc_mutex_destroy( &p_sys->lock );
316         free( p_sys->psz_urls );
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             text_style_Delete( 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->psz_urls );
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     text_style_Delete( 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 = filter_NewSubpicture( p_filter );
436     if( !p_spu )
437     {
438         vlc_mutex_unlock( &p_sys->lock );
439         return NULL;
440     }
441
442     fmt.i_chroma = VLC_CODEC_TEXT;
443
444     p_spu->p_region = subpicture_region_New( &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->p_region->p_style = text_style_Duplicate( p_sys->p_style );
532
533     if( p_feed->p_pic )
534     {
535         /* Display the feed's image */
536         picture_t *p_pic = p_feed->p_pic;
537         video_format_t fmt_out;
538
539         memset( &fmt_out, 0, sizeof(video_format_t) );
540
541         fmt_out.i_chroma = VLC_CODEC_YUVA;
542         fmt_out.i_aspect = VOUT_ASPECT_FACTOR;
543         fmt_out.i_sar_num = fmt_out.i_sar_den = 1;
544         fmt_out.i_width =
545             fmt_out.i_visible_width = p_pic->p[Y_PLANE].i_visible_pitch;
546         fmt_out.i_height =
547             fmt_out.i_visible_height = p_pic->p[Y_PLANE].i_visible_lines;
548
549         p_region = subpicture_region_New( &fmt_out );
550         if( !p_region )
551         {
552             msg_Err( p_filter, "cannot allocate SPU region" );
553         }
554         else
555         {
556             p_region->i_x = p_sys->i_xoff;
557             p_region->i_y = p_sys->i_yoff;
558             /* FIXME the copy is probably not needed anymore */
559             picture_Copy( p_region->p_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_CODEC_YUVA;
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_CODEC_YUVA;
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         picture_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     bool b_is_item;
675     bool 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 = false;
732         b_is_image = 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 = xml_ReaderName( p_xml_reader );
745                     if( !psz_eltname )
746                     {
747                         return 1;
748                     }
749 #                   ifdef RSS_DEBUG
750                     msg_Dbg( p_filter, "element name: %s", psz_eltname );
751 #                   endif
752                     if( !strcmp( psz_eltname, "item" ) /* rss */
753                      || !strcmp( psz_eltname, "entry" ) ) /* atom */
754                     {
755                         b_is_item = true;
756                         p_feed->i_items++;
757                         p_feed->p_items = (struct rss_item_t *)realloc( p_feed->p_items, p_feed->i_items * sizeof( struct rss_item_t ) );
758                         p_feed->p_items[p_feed->i_items-1].psz_title = NULL;
759                         p_feed->p_items[p_feed->i_items-1].psz_description
760                                                                      = NULL;
761                         p_feed->p_items[p_feed->i_items-1].psz_link = NULL;
762                     }
763                     else if( !strcmp( psz_eltname, "image" ) ) /* rss */
764                     {
765                         b_is_image = true;
766                     }
767                     else if( !strcmp( psz_eltname, "link" ) ) /* atom */
768                     {
769                         char *psz_href = NULL;
770                         char *psz_rel = NULL;
771                         while( xml_ReaderNextAttr( p_xml_reader )
772                                == VLC_SUCCESS )
773                         {
774                             char *psz_name = xml_ReaderName( p_xml_reader );
775                             char *psz_value = xml_ReaderValue( p_xml_reader );
776                             if( !strcmp( psz_name, "rel" ) )
777                             {
778                                 if( psz_rel )
779                                 {
780                                     msg_Dbg( p_filter, "\"rel\" attribute of link atom duplicated (last value: %s)", psz_value );
781                                     free( psz_rel );
782                                 }
783                                 psz_rel = psz_value;
784                             }
785                             else if( !strcmp( psz_name, "href" ) )
786                             {
787                                 if( psz_href )
788                                 {
789                                     msg_Dbg( p_filter, "\"href\" attribute of link atom duplicated (last value: %s)", psz_href );
790                                     free( psz_href );
791                                 }
792                                 psz_href = psz_value;
793                             }
794                             else
795                             {
796                                 free( psz_value );
797                             }
798                             free( psz_name );
799                         }
800                         if( psz_rel && psz_href )
801                         {
802                             if( !strcmp( psz_rel, "alternate" )
803                                 && b_is_item == false
804                                 && b_is_image == false
805                                 && !p_feed->psz_link )
806                             {
807                                 p_feed->psz_link = psz_href;
808                             }
809                             /* this isn't in the rfc but i found some ... */
810                             else if( ( !strcmp( psz_rel, "logo" )
811                                     || !strcmp( psz_rel, "icon" ) )
812                                     && b_is_item == false
813                                     && b_is_image == false
814                                     && !p_feed->psz_image )
815                             {
816                                 p_feed->psz_image = psz_href;
817                             }
818                             else
819                             {
820                                 free( psz_href );
821                             }
822                         }
823                         else
824                         {
825                             free( psz_href );
826                         }
827                         free( psz_rel );
828                     }
829                     break;
830
831                 case XML_READER_ENDELEM:
832                     free( psz_eltname );
833                     psz_eltname = NULL;
834                     psz_eltname = xml_ReaderName( p_xml_reader );
835                     if( !psz_eltname )
836                     {
837                         return 1;
838                     }
839 #                   ifdef RSS_DEBUG
840                     msg_Dbg( p_filter, "element end : %s", psz_eltname );
841 #                   endif
842                     if( !strcmp( psz_eltname, "item" ) /* rss */
843                      || !strcmp( psz_eltname, "entry" ) ) /* atom */
844                     {
845                         b_is_item = false;
846                         i_item++;
847                     }
848                     else if( !strcmp( psz_eltname, "image" ) ) /* rss */
849                     {
850                         b_is_image = false;
851                     }
852                     free( psz_eltname );
853                     psz_eltname = NULL;
854                     break;
855
856                 case XML_READER_TEXT:
857                     if( !psz_eltname ) break;
858                     psz_eltvalue = xml_ReaderValue( p_xml_reader );
859                     if( !psz_eltvalue )
860                     {
861                         return 1;
862                     }
863                     else
864                     {
865                         char *psz_clean;
866                         psz_clean = removeWhiteChars( psz_eltvalue );
867                         free( psz_eltvalue ); psz_eltvalue = psz_clean;
868                     }
869 #                   ifdef RSS_DEBUG
870                     msg_Dbg( p_filter, "  text : <%s>", psz_eltvalue );
871 #                   endif
872                     if( b_is_item == true )
873                     {
874                         struct rss_item_t *p_item;
875                         p_item = p_feed->p_items+i_item;
876                         if( !strcmp( psz_eltname, "title" ) /* rss/atom */
877                             && !p_item->psz_title )
878                         {
879                             p_item->psz_title = psz_eltvalue;
880                         }
881                         else if( !strcmp( psz_eltname, "link" ) /* rss */
882                                  && !p_item->psz_link )
883                         {
884                             p_item->psz_link = psz_eltvalue;
885                         }
886                         else if((!strcmp( psz_eltname, "description" ) /* rss */
887                               || !strcmp( psz_eltname, "summary" ) ) /* atom */
888                               && !p_item->psz_description )
889                         {
890                             p_item->psz_description = psz_eltvalue;
891                         }
892                         else
893                         {
894                             free( psz_eltvalue );
895                             psz_eltvalue = NULL;
896                         }
897                     }
898                     else if( b_is_image == true )
899                     {
900                         if( !strcmp( psz_eltname, "url" ) /* rss */
901                             && !p_feed->psz_image )
902                         {
903                             p_feed->psz_image = psz_eltvalue;
904                         }
905                         else
906                         {
907                             free( psz_eltvalue );
908                             psz_eltvalue = NULL;
909                         }
910                     }
911                     else
912                     {
913                         if( !strcmp( psz_eltname, "title" ) /* rss/atom */
914                             && !p_feed->psz_title )
915                         {
916                             p_feed->psz_title = psz_eltvalue;
917                         }
918                         else if( !strcmp( psz_eltname, "link" ) /* rss */
919                                  && !p_feed->psz_link )
920                         {
921                             p_feed->psz_link = psz_eltvalue;
922                         }
923                         else if((!strcmp( psz_eltname, "description" ) /* rss */
924                               || !strcmp( psz_eltname, "subtitle" ) ) /* atom */
925                               && !p_feed->psz_description )
926                         {
927                             p_feed->psz_description = psz_eltvalue;
928                         }
929                         else if( ( !strcmp( psz_eltname, "logo" ) /* atom */
930                               || !strcmp( psz_eltname, "icon" ) ) /* atom */
931                               && !p_feed->psz_image )
932                         {
933                             p_feed->psz_image = psz_eltvalue;
934                         }
935                         else
936                         {
937                             free( psz_eltvalue );
938                             psz_eltvalue = NULL;
939                         }
940                     }
941                     break;
942             }
943         }
944
945         if( p_sys->b_images == true
946             && p_feed->psz_image && !p_feed->p_pic )
947         {
948             p_feed->p_pic = LoadImage( p_filter, p_feed->psz_image );
949         }
950
951         if( p_xml_reader && p_xml ) xml_ReaderDelete( p_xml, p_xml_reader );
952         if( p_stream ) stream_Delete( p_stream );
953         msg_Dbg( p_filter, "done with %s RSS/Atom feed", psz_feed );
954     }
955     free( psz_buffer_2 );
956     if( p_xml ) xml_Delete( p_xml );
957
958     return 0;
959 }
960
961 /****************************************************************************
962  * FreeRSS
963  ***************************************************************************/
964 static void FreeRSS( filter_t *p_filter)
965 {
966     filter_sys_t *p_sys = p_filter->p_sys;
967
968     struct rss_item_t *p_item;
969     struct rss_feed_t *p_feed;
970
971     int i_feed;
972     int i_item;
973
974     for( i_feed = 0; i_feed < p_sys->i_feeds; i_feed++ )
975     {
976         p_feed = p_sys->p_feeds+i_feed;
977         for( i_item = 0; i_item < p_feed->i_items; i_item++ )
978         {
979             p_item = p_feed->p_items+i_item;
980             free( p_item->psz_title );
981             free( p_item->psz_link );
982             free( p_item->psz_description );
983         }
984         free( p_feed->p_items );
985         free( p_feed->psz_title);
986         free( p_feed->psz_link );
987         free( p_feed->psz_description );
988         free( p_feed->psz_image );
989         if( p_feed->p_pic != NULL )
990             picture_Release( p_feed->p_pic );
991     }
992     free( p_sys->p_feeds );
993     p_sys->i_feeds = 0;
994 }