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