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