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