]> git.sesse.net Git - vlc/blob - modules/video_filter/rss.c
l10n: French update
[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
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
215     set_section( N_("Misc"), NULL )
216     add_integer( CFG_PREFIX "speed", 100000, SPEED_TEXT, SPEED_LONGTEXT,
217                  false )
218     add_integer( CFG_PREFIX "length", 60, LENGTH_TEXT, LENGTH_LONGTEXT,
219                  false )
220     add_integer( CFG_PREFIX "ttl", 1800, TTL_TEXT, TTL_LONGTEXT, false )
221     add_bool( CFG_PREFIX "images", true, IMAGE_TEXT, IMAGE_LONGTEXT, false )
222     add_integer( CFG_PREFIX "title", default_title, TITLE_TEXT, TITLE_LONGTEXT, false )
223         change_integer_list( pi_title_modes, ppsz_title_modes )
224
225     set_description( N_("RSS and Atom feed display") )
226     add_shortcut( "rss", "atom" )
227 vlc_module_end ()
228
229 static const char *const ppsz_filter_options[] = {
230     "urls", "x", "y", "position", "color", "size", "speed", "length",
231     "ttl", "images", "title", NULL
232 };
233
234 /*****************************************************************************
235  * CreateFilter: allocates RSS video filter
236  *****************************************************************************/
237 static int CreateFilter( vlc_object_t *p_this )
238 {
239     filter_t *p_filter = (filter_t *)p_this;
240     filter_sys_t *p_sys;
241     char *psz_urls;
242     int i_ttl;
243
244     /* Allocate structure */
245     p_sys = p_filter->p_sys = malloc( sizeof( filter_sys_t ) );
246     if( p_sys == NULL )
247         return VLC_ENOMEM;
248
249     config_ChainParse( p_filter, CFG_PREFIX, ppsz_filter_options,
250                        p_filter->p_cfg );
251
252     /* Get the urls to parse: must be non empty */
253     psz_urls = var_CreateGetNonEmptyString( p_filter, CFG_PREFIX "urls" );
254     if( !psz_urls )
255     {
256         msg_Err( p_filter, "The list of urls must not be empty" );
257         free( p_sys );
258         return VLC_EGENERIC;
259     }
260
261     /* Fill the p_sys structure with the configuration */
262     p_sys->i_title = var_CreateGetInteger( p_filter, CFG_PREFIX "title" );
263     p_sys->i_cur_feed = 0;
264     p_sys->i_cur_item = p_sys->i_title == scroll_title ? -1 : 0;
265     p_sys->i_cur_char = 0;
266     p_sys->i_feeds = 0;
267     p_sys->p_feeds = NULL;
268     p_sys->i_speed = var_CreateGetInteger( p_filter, CFG_PREFIX "speed" );
269     p_sys->i_length = var_CreateGetInteger( p_filter, CFG_PREFIX "length" );
270     p_sys->b_images = var_CreateGetBool( p_filter, CFG_PREFIX "images" );
271
272     i_ttl = __MAX( 0, var_CreateGetInteger( p_filter, CFG_PREFIX "ttl" ) );
273
274     p_sys->psz_marquee = malloc( p_sys->i_length + 1 );
275     if( p_sys->psz_marquee == NULL )
276     {
277         free( psz_urls );
278         free( p_sys );
279         return VLC_ENOMEM;
280     }
281     p_sys->psz_marquee[p_sys->i_length] = '\0';
282
283     p_sys->p_style = text_style_New();
284     if( p_sys->p_style == NULL )
285         goto error;
286
287     p_sys->i_xoff = var_CreateGetInteger( p_filter, CFG_PREFIX "x" );
288     p_sys->i_yoff = var_CreateGetInteger( p_filter, CFG_PREFIX "y" );
289     p_sys->i_pos = var_CreateGetInteger( p_filter, CFG_PREFIX "position" );
290     p_sys->p_style->i_font_alpha = 255 - var_CreateGetInteger( p_filter, CFG_PREFIX "opacity" );
291     p_sys->p_style->i_font_color = var_CreateGetInteger( p_filter, CFG_PREFIX "color" );
292     p_sys->p_style->i_font_size = var_CreateGetInteger( p_filter, CFG_PREFIX "size" );
293
294     if( p_sys->b_images && p_sys->p_style->i_font_size == -1 )
295     {
296         msg_Warn( p_filter, "rss-size wasn't specified. Feed images will thus be displayed without being resized" );
297     }
298
299     /* Parse the urls */
300     if( ParseUrls( p_filter, psz_urls ) )
301         goto error;
302
303     /* Misc init */
304     vlc_mutex_init( &p_sys->lock );
305     p_filter->pf_sub_source = Filter;
306     p_sys->last_date = (mtime_t)0;
307     p_sys->b_fetched = false;
308
309     /* Create and arm the timer */
310     if( vlc_timer_create( &p_sys->timer, Fetch, p_filter ) )
311     {
312         vlc_mutex_destroy( &p_sys->lock );
313         goto error;
314     }
315     vlc_timer_schedule( p_sys->timer, false, 1,
316                         (mtime_t)(i_ttl)*1000000 );
317
318     free( psz_urls );
319     return VLC_SUCCESS;
320
321 error:
322     if( p_sys->p_style )
323         text_style_Delete( p_sys->p_style );
324     free( p_sys->psz_marquee );
325     free( psz_urls );
326     free( p_sys );
327     return VLC_ENOMEM;
328 }
329 /*****************************************************************************
330  * DestroyFilter: destroy RSS video filter
331  *****************************************************************************/
332 static void DestroyFilter( vlc_object_t *p_this )
333 {
334     filter_t *p_filter = (filter_t *)p_this;
335     filter_sys_t *p_sys = p_filter->p_sys;
336
337     vlc_timer_destroy( p_sys->timer );
338     vlc_mutex_destroy( &p_sys->lock );
339
340     text_style_Delete( p_sys->p_style );
341     free( p_sys->psz_marquee );
342     FreeRSS( p_sys->p_feeds, p_sys->i_feeds );
343     free( p_sys );
344 }
345
346 /****************************************************************************
347  * Filter: the whole thing
348  ****************************************************************************
349  * This function outputs subpictures at regular time intervals.
350  ****************************************************************************/
351 static subpicture_t *Filter( filter_t *p_filter, mtime_t date )
352 {
353     filter_sys_t *p_sys = p_filter->p_sys;
354     subpicture_t *p_spu;
355     video_format_t fmt;
356     subpicture_region_t *p_region;
357
358     int i_feed, i_item;
359     rss_feed_t *p_feed;
360
361     memset( &fmt, 0, sizeof(video_format_t) );
362
363     vlc_mutex_lock( &p_sys->lock );
364
365     /* Check if the feeds have been fetched and that we have some feeds */
366     /* TODO: check that we have items for each feeds */
367     if( !p_sys->b_fetched && p_sys->i_feeds > 0 )
368     {
369         vlc_mutex_unlock( &p_sys->lock );
370         return NULL;
371     }
372
373     if( p_sys->last_date
374        + ( p_sys->i_cur_char == 0 && p_sys->i_cur_item == ( p_sys->i_title == scroll_title ? -1 : 0 ) ? 5 : 1 )
375            /* ( ... ? 5 : 1 ) means "wait 5 times more for the 1st char" */
376        * p_sys->i_speed > date )
377     {
378         vlc_mutex_unlock( &p_sys->lock );
379         return NULL;
380     }
381
382     p_sys->last_date = date;
383     p_sys->i_cur_char++;
384     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 )
385     {
386         p_sys->i_cur_char = 0;
387         p_sys->i_cur_item++;
388         if( p_sys->i_cur_item >= p_sys->p_feeds[p_sys->i_cur_feed].i_items )
389         {
390             if( p_sys->i_title == scroll_title )
391                 p_sys->i_cur_item = -1;
392             else
393                 p_sys->i_cur_item = 0;
394             p_sys->i_cur_feed = (p_sys->i_cur_feed + 1)%p_sys->i_feeds;
395         }
396     }
397
398     p_spu = filter_NewSubpicture( p_filter );
399     if( !p_spu )
400     {
401         vlc_mutex_unlock( &p_sys->lock );
402         return NULL;
403     }
404
405     fmt.i_chroma = VLC_CODEC_TEXT;
406
407     p_spu->p_region = subpicture_region_New( &fmt );
408     if( !p_spu->p_region )
409     {
410         p_filter->pf_sub_buffer_del( p_filter, p_spu );
411         vlc_mutex_unlock( &p_sys->lock );
412         return NULL;
413     }
414
415     /* Generate the string that will be displayed. This string is supposed to
416        be p_sys->i_length characters long. */
417     i_item = p_sys->i_cur_item;
418     i_feed = p_sys->i_cur_feed;
419     p_feed = &p_sys->p_feeds[i_feed];
420
421     if( ( p_feed->p_pic && p_sys->i_title == default_title )
422         || p_sys->i_title == hide_title )
423     {
424         /* Don't display the feed's title if we have an image */
425         snprintf( p_sys->psz_marquee, p_sys->i_length, "%s",
426                   p_sys->p_feeds[i_feed].p_items[i_item].psz_title
427                   +p_sys->i_cur_char );
428     }
429     else if( ( !p_feed->p_pic && p_sys->i_title == default_title )
430              || p_sys->i_title == prepend_title )
431     {
432         snprintf( p_sys->psz_marquee, p_sys->i_length, "%s : %s",
433                   p_sys->p_feeds[i_feed].psz_title,
434                   p_sys->p_feeds[i_feed].p_items[i_item].psz_title
435                   +p_sys->i_cur_char );
436     }
437     else /* scrolling title */
438     {
439         if( i_item == -1 )
440             snprintf( p_sys->psz_marquee, p_sys->i_length, "%s : %s",
441                       p_sys->p_feeds[i_feed].psz_title + p_sys->i_cur_char,
442                       p_sys->p_feeds[i_feed].p_items[i_item+1].psz_title );
443         else
444             snprintf( p_sys->psz_marquee, p_sys->i_length, "%s",
445                       p_sys->p_feeds[i_feed].p_items[i_item].psz_title
446                       +p_sys->i_cur_char );
447     }
448
449     while( strlen( p_sys->psz_marquee ) < (unsigned int)p_sys->i_length )
450     {
451         i_item++;
452         if( i_item == p_sys->p_feeds[i_feed].i_items ) break;
453         snprintf( strchr( p_sys->psz_marquee, 0 ),
454                   p_sys->i_length - strlen( p_sys->psz_marquee ),
455                   " - %s",
456                   p_sys->p_feeds[i_feed].p_items[i_item].psz_title );
457     }
458
459     /* Calls to snprintf might split multibyte UTF8 chars ...
460      * which freetype doesn't like. */
461     {
462         char *a = strdup( p_sys->psz_marquee );
463         char *a2 = a;
464         char *b = p_sys->psz_marquee;
465         EnsureUTF8( p_sys->psz_marquee );
466         /* we want to use ' ' instead of '?' for erroneous chars */
467         while( *b != '\0' )
468         {
469             if( *b != *a ) *b = ' ';
470             b++;a++;
471         }
472         free( a2 );
473     }
474
475     p_spu->p_region->psz_text = strdup(p_sys->psz_marquee);
476     if( p_sys->p_style->i_font_size > 0 )
477         p_spu->p_region->fmt.i_visible_height = p_sys->p_style->i_font_size;
478     p_spu->i_start = date;
479     p_spu->i_stop  = 0;
480     p_spu->b_ephemer = true;
481
482     /*  where to locate the string: */
483     if( p_sys->i_pos < 0 )
484     {   /*  set to an absolute xy */
485         p_spu->p_region->i_align = SUBPICTURE_ALIGN_LEFT | SUBPICTURE_ALIGN_TOP;
486         p_spu->b_absolute = true;
487     }
488     else
489     {   /* set to one of the 9 relative locations */
490         p_spu->p_region->i_align = p_sys->i_pos;
491         p_spu->b_absolute = false;
492     }
493     p_spu->p_region->i_x = p_sys->i_xoff;
494     p_spu->p_region->i_y = p_sys->i_yoff;
495
496     p_spu->p_region->p_style = text_style_Duplicate( p_sys->p_style );
497
498     if( p_feed->p_pic )
499     {
500         /* Display the feed's image */
501         picture_t *p_pic = p_feed->p_pic;
502         video_format_t fmt_out;
503
504         memset( &fmt_out, 0, sizeof(video_format_t) );
505
506         fmt_out.i_chroma = VLC_CODEC_YUVA;
507         fmt_out.i_sar_num = fmt_out.i_sar_den = 1;
508         fmt_out.i_width =
509             fmt_out.i_visible_width = p_pic->p[Y_PLANE].i_visible_pitch;
510         fmt_out.i_height =
511             fmt_out.i_visible_height = p_pic->p[Y_PLANE].i_visible_lines;
512
513         p_region = subpicture_region_New( &fmt_out );
514         if( !p_region )
515         {
516             msg_Err( p_filter, "cannot allocate SPU region" );
517         }
518         else
519         {
520             p_region->i_x = p_spu->p_region->i_x;
521             p_region->i_y = p_spu->p_region->i_y;
522             /* FIXME the copy is probably not needed anymore */
523             picture_Copy( p_region->p_picture, p_pic );
524             p_spu->p_region->p_next = p_region;
525
526             /* Offset text to display right next to the image */
527             p_spu->p_region->i_x += fmt_out.i_visible_width;
528         }
529     }
530
531     vlc_mutex_unlock( &p_sys->lock );
532     return p_spu;
533 }
534
535 /****************************************************************************
536  * RSS related functions
537  ****************************************************************************
538  * You should always lock the p_filter mutex before using any of these
539  * functions
540  ***************************************************************************/
541
542 #undef LoadImage /* do not conflict with Win32 API */
543
544 /****************************************************************************
545  * download and resize image located at psz_url
546  ***************************************************************************/
547 static picture_t *LoadImage( filter_t *p_filter, const char *psz_url )
548 {
549     filter_sys_t *p_sys = p_filter->p_sys;
550     video_format_t fmt_in;
551     video_format_t fmt_out;
552     picture_t *p_orig;
553     picture_t *p_pic = NULL;
554     image_handler_t *p_handler = image_HandlerCreate( p_filter );
555
556     memset( &fmt_in, 0, sizeof(video_format_t) );
557     memset( &fmt_out, 0, sizeof(video_format_t) );
558
559     fmt_out.i_chroma = VLC_CODEC_YUVA;
560     p_orig = image_ReadUrl( p_handler, psz_url, &fmt_in, &fmt_out );
561
562     if( !p_orig )
563     {
564         msg_Warn( p_filter, "Unable to read image %s", psz_url );
565     }
566     else if( p_sys->p_style->i_font_size > 0 )
567     {
568
569         fmt_in.i_chroma = VLC_CODEC_YUVA;
570         fmt_in.i_height = p_orig->p[Y_PLANE].i_visible_lines;
571         fmt_in.i_width = p_orig->p[Y_PLANE].i_visible_pitch;
572         fmt_out.i_width = p_orig->p[Y_PLANE].i_visible_pitch
573             *p_sys->p_style->i_font_size/p_orig->p[Y_PLANE].i_visible_lines;
574         fmt_out.i_height = p_sys->p_style->i_font_size;
575
576         p_pic = image_Convert( p_handler, p_orig, &fmt_in, &fmt_out );
577         picture_Release( p_orig );
578         if( !p_pic )
579         {
580             msg_Warn( p_filter, "Error while converting %s", psz_url );
581         }
582     }
583     else
584     {
585         p_pic = p_orig;
586     }
587
588     image_HandlerDelete( p_handler );
589
590     return p_pic;
591 }
592
593 /****************************************************************************
594  * remove all ' ' '\t' '\n' '\r' characters from the begining and end of the
595  * string.
596  ***************************************************************************/
597 static char *removeWhiteChars( const char *psz_src )
598 {
599     char *psz_src2,*psz_clean, *psz_clean2;
600     psz_src2 = psz_clean = strdup( psz_src );
601     int i;
602
603     while( ( *psz_clean == ' ' || *psz_clean == '\t'
604            || *psz_clean == '\n' || *psz_clean == '\r' )
605            && *psz_clean != '\0' )
606     {
607         psz_clean++;
608     }
609     i = strlen( psz_clean );
610     while( --i > 0 &&
611          ( psz_clean[i] == ' ' || psz_clean[i] == '\t'
612         || psz_clean[i] == '\n' || psz_clean[i] == '\r' ) );
613     psz_clean[i+1] = '\0';
614     psz_clean2 = strdup( psz_clean );
615     free( psz_src2 );
616     return psz_clean2;
617 }
618
619
620 /****************************************************************************
621  * Parse url list, psz_urls must be non empty (TODO: check it !)
622  ***************************************************************************/
623 static int ParseUrls( filter_t *p_filter, char *psz_urls )
624 {
625     filter_sys_t *p_sys = p_filter->p_sys;
626     char *psz_urls2 = psz_urls;
627
628     p_sys->i_feeds = 1;
629
630     /* Count the number of feeds */
631     while( *psz_urls )
632     {
633         if( *psz_urls == '|' )
634             p_sys->i_feeds++;
635         psz_urls++;
636     }
637
638     /* Allocate the structure */
639     p_sys->p_feeds = malloc( p_sys->i_feeds * sizeof( rss_feed_t ) );
640     if( !p_sys->p_feeds )
641         return VLC_ENOMEM;
642
643     /* Loop on all urls and fill in the struct */
644     psz_urls = psz_urls2;
645     for( int i = 0; i < p_sys->i_feeds; i++ )
646     {
647         rss_feed_t* p_feed = p_sys->p_feeds + i;
648         char *psz_end;
649
650         if( i < p_sys->i_feeds - 1 )
651         {
652             psz_end = strchr( psz_urls, '|' );
653             *psz_end = '\0';
654         }
655         else
656             psz_end = psz_urls;
657
658         p_feed->i_items = 0;
659         p_feed->p_items = NULL;
660         p_feed->psz_title = NULL;
661         p_feed->psz_link = NULL;
662         p_feed->psz_description = NULL;
663         p_feed->psz_image = NULL;
664         p_feed->p_pic = NULL;
665         p_feed->psz_url = strdup( psz_urls );
666
667         psz_urls = psz_end + 1;
668     }
669
670     return VLC_SUCCESS;
671 }
672
673
674
675 /****************************************************************************
676  * Parse the rss feed
677  ***************************************************************************/
678 static bool ParseFeed( filter_t *p_filter, xml_reader_t *p_xml_reader,
679                       rss_feed_t *p_feed )
680 {
681     VLC_UNUSED(p_filter);
682     const char *node;
683     char *psz_eltname = NULL;
684
685     bool b_is_item = false;
686     bool b_is_image = false;
687
688     int i_item = 0;
689     int type;
690
691     while( (type = xml_ReaderNextNode( p_xml_reader, &node )) > 0 )
692     {
693         switch( type )
694         {
695         case XML_READER_STARTELEM:
696 #ifdef RSS_DEBUG
697             msg_Dbg( p_filter, "element <%s>", node );
698 #endif
699             psz_eltname = strdup( node );
700             if( unlikely(!psz_eltname) )
701                 goto end;
702
703             /* rss or atom */
704             if( !strcmp( node, "item" ) || !strcmp( node, "entry" ) )
705             {
706                 b_is_item = true;
707                 p_feed->i_items++;
708                 p_feed->p_items = xrealloc( p_feed->p_items,
709                                      p_feed->i_items * sizeof( rss_item_t ) );
710                 p_feed->p_items[p_feed->i_items-1].psz_title = NULL;
711                 p_feed->p_items[p_feed->i_items-1].psz_description = NULL;
712                 p_feed->p_items[p_feed->i_items-1].psz_link = NULL;
713             }
714             /* rss */
715             else if( !strcmp( node, "image" ) )
716             {
717                 b_is_image = true;
718             }
719             /* atom */
720             else if( !strcmp( node, "link" ) )
721             {
722                 const char *name, *value;
723                 char *psz_href = NULL;
724                 char *psz_rel = NULL;
725
726                 while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
727                 {
728                     if( !strcmp( name, "rel" ) )
729                     {
730                         free( psz_rel );
731                         psz_rel = strdup( value );
732                     }
733                     else if( !strcmp( name, "href" ) )
734                     {
735                         free( psz_href );
736                         psz_href = strdup( value );
737                     }
738                 }
739
740                 /* "rel" and "href" must be defined */
741                 if( psz_rel && psz_href )
742                 {
743                     if( !strcmp( psz_rel, "alternate" ) && !b_is_item &&
744                         !b_is_image && !p_feed->psz_link )
745                     {
746                         p_feed->psz_link = psz_href;
747                     }
748                     /* this isn't in the rfc but i found some ... */
749                     else if( ( !strcmp( psz_rel, "logo" ) ||
750                                !strcmp( psz_rel, "icon" ) )
751                              && !b_is_item && !b_is_image
752                              && !p_feed->psz_image )
753                     {
754                         p_feed->psz_image = psz_href;
755                     }
756                     else
757                     {
758                         free( psz_href );
759                     }
760                 }
761                 else
762                 {
763                     free( psz_href );
764                 }
765                 free( psz_rel );
766             }
767             break;
768
769         case XML_READER_ENDELEM:
770             FREENULL( psz_eltname );
771 #ifdef RSS_DEBUG
772             msg_Dbg( p_filter, "element end </%s>", node );
773 #endif
774             /* rss or atom */
775             if( !strcmp( node, "item" ) || !strcmp( node, "entry" ) )
776             {
777                 b_is_item = false;
778                 i_item++;
779             }
780             /* rss */
781             else if( !strcmp( node, "image" ) )
782             {
783                 b_is_image = false;
784             }
785             break;
786
787         case XML_READER_TEXT:
788         {
789             if( !psz_eltname )
790                 break;
791
792             char *psz_eltvalue = removeWhiteChars( node );
793
794 #ifdef RSS_DEBUG
795             msg_Dbg( p_filter, "  text : \"%s\"", psz_eltvalue );
796 #endif
797             /* Is it an item ? */
798             if( b_is_item )
799             {
800                 rss_item_t *p_item = p_feed->p_items+i_item;
801                 /* rss/atom */
802                 if( !strcmp( psz_eltname, "title" ) && !p_item->psz_title )
803                 {
804                     p_item->psz_title = psz_eltvalue;
805                 }
806                 else if( !strcmp( psz_eltname, "link" ) /* rss */
807                          && !p_item->psz_link )
808                 {
809                     p_item->psz_link = psz_eltvalue;
810                 }
811                 /* rss/atom */
812                 else if( ( !strcmp( psz_eltname, "description" ) ||
813                            !strcmp( psz_eltname, "summary" ) )
814                           && !p_item->psz_description )
815                 {
816                     p_item->psz_description = psz_eltvalue;
817                 }
818                 else
819                 {
820                     free( psz_eltvalue );
821                 }
822             }
823             /* Is it an image ? */
824             else if( b_is_image )
825             {
826                 if( !strcmp( psz_eltname, "url" ) && !p_feed->psz_image )
827                     p_feed->psz_image = psz_eltvalue;
828                 else
829                     free( psz_eltvalue );
830             }
831             else
832             {
833                 /* rss/atom */
834                 if( !strcmp( psz_eltname, "title" ) && !p_feed->psz_title )
835                 {
836                     p_feed->psz_title = psz_eltvalue;
837                 }
838                 /* rss */
839                 else if( !strcmp( psz_eltname, "link" ) && !p_feed->psz_link )
840                 {
841                     p_feed->psz_link = psz_eltvalue;
842                 }
843                 /* rss ad atom */
844                 else if( ( !strcmp( psz_eltname, "description" ) ||
845                            !strcmp( psz_eltname, "subtitle" ) )
846                          && !p_feed->psz_description )
847                 {
848                     p_feed->psz_description = psz_eltvalue;
849                 }
850                 /* rss */
851                 else if( ( !strcmp( psz_eltname, "logo" ) ||
852                            !strcmp( psz_eltname, "icon" ) )
853                          && !p_feed->psz_image )
854                 {
855                     p_feed->psz_image = psz_eltvalue;
856                 }
857                 else
858                 {
859                     free( psz_eltvalue );
860                 }
861             }
862             break;
863         }
864         }
865     }
866
867     free( psz_eltname );
868     return true;
869
870 end:
871     free( psz_eltname );
872     return false;
873 }
874
875
876 /****************************************************************************
877  * FetchRSS (or Atom) feeds
878  ***************************************************************************/
879 static rss_feed_t* FetchRSS( filter_t *p_filter )
880 {
881     filter_sys_t *p_sys = p_filter->p_sys;
882
883     stream_t *p_stream;
884     xml_t *p_xml;
885     xml_reader_t *p_xml_reader;
886     int i_feed;
887
888     /* These data are not modified after the creation of the module so we don't
889        need to hold the lock */
890     int i_feeds = p_sys->i_feeds;
891     bool b_images = p_sys->b_images;
892
893     /* Allocate a new structure */
894     rss_feed_t *p_feeds = malloc( i_feeds * sizeof( rss_feed_t ) );
895     if( !p_feeds )
896         return NULL;
897
898     p_xml = xml_Create( p_filter );
899     if( !p_xml )
900     {
901         msg_Err( p_filter, "Failed to open XML parser" );
902         free( p_feeds );
903         return NULL;
904     }
905
906     /* Fetch all feeds and parse them */
907     for( i_feed = 0; i_feed < i_feeds; i_feed++ )
908     {
909         rss_feed_t *p_feed = p_feeds + i_feed;
910         rss_feed_t *p_old_feed = p_sys->p_feeds + i_feed;
911
912         /* Initialize the structure */
913         p_feed->psz_title = NULL;
914         p_feed->psz_description = NULL;
915         p_feed->psz_link = NULL;
916         p_feed->psz_image = NULL;
917         p_feed->p_pic = NULL;
918         p_feed->i_items = 0;
919         p_feed->p_items = NULL;
920
921         p_feed->psz_url = strdup( p_old_feed->psz_url );
922
923         /* Fetch the feed */
924         msg_Dbg( p_filter, "opening %s RSS/Atom feed ...", p_feed->psz_url );
925
926         p_stream = stream_UrlNew( p_filter, p_feed->psz_url );
927         if( !p_stream )
928         {
929             msg_Err( p_filter, "Failed to open %s for reading", p_feed->psz_url );
930             p_xml_reader = NULL;
931             goto error;
932         }
933
934         p_xml_reader = xml_ReaderCreate( p_xml, p_stream );
935         if( !p_xml_reader )
936         {
937             msg_Err( p_filter, "Failed to open %s for parsing", p_feed->psz_url );
938             goto error;
939         }
940
941         /* Parse the feed */
942         if( !ParseFeed( p_filter, p_xml_reader, p_feed ) )
943             goto error;
944
945         /* If we have a image: load it if requiere */
946         if( b_images && p_feed->psz_image && !p_feed->p_pic )
947         {
948             p_feed->p_pic = LoadImage( p_filter, p_feed->psz_image );
949         }
950
951         msg_Dbg( p_filter, "done with %s RSS/Atom feed", p_feed->psz_url );
952         xml_ReaderDelete( p_xml_reader );
953         stream_Delete( p_stream );
954     }
955
956     xml_Delete( p_xml );
957     return p_feeds;
958
959 error:
960     FreeRSS( p_feeds, i_feed + 1 );
961     if( p_xml_reader )
962         xml_ReaderDelete( p_xml_reader );
963     if( p_stream )
964         stream_Delete( p_stream );
965     if( p_xml )
966         xml_Delete( p_xml );
967
968     return NULL;
969 }
970
971 /****************************************************************************
972  * FreeRSS
973  ***************************************************************************/
974 static void FreeRSS( rss_feed_t *p_feeds, int i_feeds )
975 {
976     for( int i_feed = 0; i_feed < i_feeds; i_feed++ )
977     {
978         rss_feed_t *p_feed = p_feeds+i_feed;
979         for( int i_item = 0; i_item < p_feed->i_items; i_item++ )
980         {
981             rss_item_t *p_item = p_feed->p_items+i_item;
982             free( p_item->psz_title );
983             free( p_item->psz_link );
984             free( p_item->psz_description );
985         }
986         free( p_feed->p_items );
987         free( p_feed->psz_title);
988         free( p_feed->psz_link );
989         free( p_feed->psz_description );
990         free( p_feed->psz_image );
991         if( p_feed->p_pic != NULL )
992             picture_Release( p_feed->p_pic );
993         free( p_feed->psz_url );
994     }
995     free( p_feeds );
996 }
997
998 static void Fetch( void *p_data )
999 {
1000     filter_t *p_filter = p_data;
1001     filter_sys_t *p_sys = p_filter->p_sys;
1002
1003     msg_Dbg( p_filter, "Updating the rss feeds" );
1004     rss_feed_t *p_feeds = FetchRSS( p_filter );
1005     if( !p_feeds )
1006     {
1007         msg_Err( p_filter, "Unable to fetch the feeds" );
1008         return;
1009     }
1010
1011     rss_feed_t *p_old_feeds = p_sys->p_feeds;
1012
1013     vlc_mutex_lock( &p_sys->lock );
1014     /* Update the feeds */
1015     p_sys->p_feeds = p_feeds;
1016     p_sys->b_fetched = true;
1017     /* Set all current info to the original values */
1018     p_sys->i_cur_feed = 0;
1019     p_sys->i_cur_item = p_sys->i_title == scroll_title ? -1 : 0;
1020     p_sys->i_cur_char = 0;
1021     vlc_mutex_unlock( &p_sys->lock );
1022
1023     if( p_old_feeds )
1024         FreeRSS( p_old_feeds, p_sys->i_feeds );
1025 }