]> git.sesse.net Git - vlc/blob - modules/video_filter/rss.c
f00eed1e36c12a7883fcc5ab4c3dff35475f6d95
[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_sar_num = fmt_out.i_sar_den = 1;
505         fmt_out.i_width =
506             fmt_out.i_visible_width = p_pic->p[Y_PLANE].i_visible_pitch;
507         fmt_out.i_height =
508             fmt_out.i_visible_height = p_pic->p[Y_PLANE].i_visible_lines;
509
510         p_region = subpicture_region_New( &fmt_out );
511         if( !p_region )
512         {
513             msg_Err( p_filter, "cannot allocate SPU region" );
514         }
515         else
516         {
517             p_region->i_x = p_sys->i_xoff;
518             p_region->i_y = p_sys->i_yoff;
519             /* FIXME the copy is probably not needed anymore */
520             picture_Copy( p_region->p_picture, p_pic );
521             p_spu->p_region->p_next = p_region;
522         }
523
524         /* Offset text to display right next to the image */
525         p_spu->p_region->i_x = p_pic->p[Y_PLANE].i_visible_pitch;
526     }
527
528     vlc_mutex_unlock( &p_sys->lock );
529     return p_spu;
530 }
531
532 /****************************************************************************
533  * RSS related functions
534  ****************************************************************************
535  * You should always lock the p_filter mutex before using any of these
536  * functions
537  ***************************************************************************/
538
539 #undef LoadImage /* do not conflict with Win32 API */
540
541 /****************************************************************************
542  * download and resize image located at psz_url
543  ***************************************************************************/
544 static picture_t *LoadImage( filter_t *p_filter, const char *psz_url )
545 {
546     filter_sys_t *p_sys = p_filter->p_sys;
547     video_format_t fmt_in;
548     video_format_t fmt_out;
549     picture_t *p_orig;
550     picture_t *p_pic = NULL;
551     image_handler_t *p_handler = image_HandlerCreate( p_filter );
552
553     memset( &fmt_in, 0, sizeof(video_format_t) );
554     memset( &fmt_out, 0, sizeof(video_format_t) );
555
556     fmt_out.i_chroma = VLC_CODEC_YUVA;
557     p_orig = image_ReadUrl( p_handler, psz_url, &fmt_in, &fmt_out );
558
559     if( !p_orig )
560     {
561         msg_Warn( p_filter, "Unable to read image %s", psz_url );
562     }
563     else if( p_sys->p_style->i_font_size > 0 )
564     {
565
566         fmt_in.i_chroma = VLC_CODEC_YUVA;
567         fmt_in.i_height = p_orig->p[Y_PLANE].i_visible_lines;
568         fmt_in.i_width = p_orig->p[Y_PLANE].i_visible_pitch;
569         fmt_out.i_width = p_orig->p[Y_PLANE].i_visible_pitch
570             *p_sys->p_style->i_font_size/p_orig->p[Y_PLANE].i_visible_lines;
571         fmt_out.i_height = p_sys->p_style->i_font_size;
572
573         p_pic = image_Convert( p_handler, p_orig, &fmt_in, &fmt_out );
574         picture_Release( p_orig );
575         if( !p_pic )
576         {
577             msg_Warn( p_filter, "Error while converting %s", psz_url );
578         }
579     }
580     else
581     {
582         p_pic = p_orig;
583     }
584
585     image_HandlerDelete( p_handler );
586
587     return p_pic;
588 }
589
590 /****************************************************************************
591  * remove all ' ' '\t' '\n' '\r' characters from the begining and end of the
592  * string.
593  ***************************************************************************/
594 static char *removeWhiteChars( const char *psz_src )
595 {
596     char *psz_src2,*psz_clean, *psz_clean2;
597     psz_src2 = psz_clean = strdup( psz_src );
598     int i;
599
600     while( ( *psz_clean == ' ' || *psz_clean == '\t'
601            || *psz_clean == '\n' || *psz_clean == '\r' )
602            && *psz_clean != '\0' )
603     {
604         psz_clean++;
605     }
606     i = strlen( psz_clean );
607     while( --i > 0 &&
608          ( psz_clean[i] == ' ' || psz_clean[i] == '\t'
609         || psz_clean[i] == '\n' || psz_clean[i] == '\r' ) );
610     psz_clean[i+1] = '\0';
611     psz_clean2 = strdup( psz_clean );
612     free( psz_src2 );
613     return psz_clean2;
614 }
615
616
617 /****************************************************************************
618  * Parse url list, psz_urls must be non empty (TODO: check it !)
619  ***************************************************************************/
620 static int ParseUrls( filter_t *p_filter, char *psz_urls )
621 {
622     filter_sys_t *p_sys = p_filter->p_sys;
623     char *psz_urls2 = psz_urls;
624
625     p_sys->i_feeds = 1;
626
627     /* Count the number of feeds */
628     while( *psz_urls )
629     {
630         if( *psz_urls == '|' )
631             p_sys->i_feeds++;
632         psz_urls++;
633     }
634
635     /* Allocate the structure */
636     p_sys->p_feeds = malloc( p_sys->i_feeds * sizeof( rss_feed_t ) );
637     if( !p_sys->p_feeds )
638         return VLC_ENOMEM;
639
640     /* Loop on all urls and fill in the struct */
641     psz_urls = psz_urls2;
642     for( int i = 0; i < p_sys->i_feeds; i++ )
643     {
644         rss_feed_t* p_feed = p_sys->p_feeds + i;
645         char *psz_end;
646
647         if( i < p_sys->i_feeds - 1 )
648         {
649             psz_end = strchr( psz_urls, '|' );
650             *psz_end = '\0';
651         }
652         else
653             psz_end = psz_urls;
654
655         p_feed->i_items = 0;
656         p_feed->p_items = NULL;
657         p_feed->psz_title = NULL;
658         p_feed->psz_link = NULL;
659         p_feed->psz_description = NULL;
660         p_feed->psz_image = NULL;
661         p_feed->p_pic = NULL;
662         p_feed->psz_url = strdup( psz_urls );
663
664         psz_urls = psz_end + 1;
665     }
666
667     return VLC_SUCCESS;
668 }
669
670
671
672 /****************************************************************************
673  * Parse the rss feed
674  ***************************************************************************/
675 static bool ParseFeed( filter_t *p_filter, xml_reader_t *p_xml_reader,
676                       rss_feed_t *p_feed )
677 {
678     VLC_UNUSED(p_filter);
679     char *psz_eltname = NULL;
680
681     bool b_is_item = false;
682     bool b_is_image = false;
683
684     int i_item = 0;
685
686     while( xml_ReaderRead( p_xml_reader ) == 1 )
687     {
688         switch( xml_ReaderNodeType( p_xml_reader ) )
689         {
690         // Error
691         case -1:
692             goto end;
693
694         case XML_READER_STARTELEM:
695             free( psz_eltname );
696             psz_eltname = xml_ReaderName( p_xml_reader );
697             if( !psz_eltname )
698                 goto end;
699
700 #ifdef RSS_DEBUG
701             msg_Dbg( p_filter, "element name: %s", psz_eltname );
702 #endif
703             /* rss or atom */
704             if( !strcmp( psz_eltname, "item" ) || !strcmp( psz_eltname, "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( psz_eltname, "image" ) )
716             {
717                 b_is_image = true;
718             }
719             /* atom */
720             else if( !strcmp( psz_eltname, "link" ) )
721             {
722                 char *psz_href = NULL;
723                 char *psz_rel = NULL;
724                 while( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
725                 {
726                     char *psz_name = xml_ReaderName( p_xml_reader );
727                     char *psz_value = xml_ReaderValue( p_xml_reader );
728                     if( !strcmp( psz_name, "rel" ) )
729                     {
730                         free( psz_rel );
731                         psz_rel = psz_value;
732                     }
733                     else if( !strcmp( psz_name, "href" ) )
734                     {
735                         free( psz_href );
736                         psz_href = psz_value;
737                     }
738                     else
739                     {
740                         free( psz_value );
741                     }
742                     free( psz_name );
743                 }
744
745                 /* "rel" and "href" must be defined */
746                 if( psz_rel && psz_href )
747                 {
748                     if( !strcmp( psz_rel, "alternate" ) && !b_is_item &&
749                         !b_is_image && !p_feed->psz_link )
750                     {
751                         p_feed->psz_link = psz_href;
752                     }
753                     /* this isn't in the rfc but i found some ... */
754                     else if( ( !strcmp( psz_rel, "logo" ) ||
755                                !strcmp( psz_rel, "icon" ) )
756                              && !b_is_item && !b_is_image
757                              && !p_feed->psz_image )
758                     {
759                         p_feed->psz_image = psz_href;
760                     }
761                     else
762                     {
763                         free( psz_href );
764                     }
765                 }
766                 else
767                 {
768                     free( psz_href );
769                 }
770                 free( psz_rel );
771             }
772             break;
773
774         case XML_READER_ENDELEM:
775             free( psz_eltname );
776             psz_eltname = xml_ReaderName( p_xml_reader );
777             if( !psz_eltname )
778                 goto end;
779
780 #ifdef RSS_DEBUG
781             msg_Dbg( p_filter, "element end : %s", psz_eltname );
782 #endif
783             /* rss or atom */
784             if( !strcmp( psz_eltname, "item" ) || !strcmp( psz_eltname, "entry" ) )
785             {
786                 b_is_item = false;
787                 i_item++;
788             }
789             /* rss */
790             else if( !strcmp( psz_eltname, "image" ) )
791             {
792                 b_is_image = false;
793             }
794             FREENULL( psz_eltname );
795             break;
796
797         case XML_READER_TEXT:
798         {
799             if( !psz_eltname )
800                 break;
801             char *psz_eltvalue = xml_ReaderValue( p_xml_reader );
802             if( !psz_eltvalue )
803                 goto end;
804
805             char *psz_clean = removeWhiteChars( psz_eltvalue );
806             free( psz_eltvalue );
807             psz_eltvalue = psz_clean;
808
809 #ifdef RSS_DEBUG
810             msg_Dbg( p_filter, "  text : <%s>", psz_eltvalue );
811 #endif
812             /* Is it an item ? */
813             if( b_is_item )
814             {
815                 rss_item_t *p_item = p_feed->p_items+i_item;
816                 /* rss/atom */
817                 if( !strcmp( psz_eltname, "title" ) && !p_item->psz_title )
818                 {
819                     p_item->psz_title = psz_eltvalue;
820                 }
821                 else if( !strcmp( psz_eltname, "link" ) /* rss */
822                          && !p_item->psz_link )
823                 {
824                     p_item->psz_link = psz_eltvalue;
825                 }
826                 /* rss/atom */
827                 else if( ( !strcmp( psz_eltname, "description" ) ||
828                            !strcmp( psz_eltname, "summary" ) )
829                           && !p_item->psz_description )
830                 {
831                     p_item->psz_description = psz_eltvalue;
832                 }
833                 else
834                 {
835                     free( psz_eltvalue );
836                 }
837             }
838             /* Is it an image ? */
839             else if( b_is_image )
840             {
841                 if( !strcmp( psz_eltname, "url" ) && !p_feed->psz_image )
842                     p_feed->psz_image = psz_eltvalue;
843                 else
844                     free( psz_eltvalue );
845             }
846             else
847             {
848                 /* rss/atom */
849                 if( !strcmp( psz_eltname, "title" ) && !p_feed->psz_title )
850                 {
851                     p_feed->psz_title = psz_eltvalue;
852                 }
853                 /* rss */
854                 else if( !strcmp( psz_eltname, "link" ) && !p_feed->psz_link )
855                 {
856                     p_feed->psz_link = psz_eltvalue;
857                 }
858                 /* rss ad atom */
859                 else if( ( !strcmp( psz_eltname, "description" ) ||
860                            !strcmp( psz_eltname, "subtitle" ) )
861                          && !p_feed->psz_description )
862                 {
863                     p_feed->psz_description = psz_eltvalue;
864                 }
865                 /* rss */
866                 else if( ( !strcmp( psz_eltname, "logo" ) ||
867                            !strcmp( psz_eltname, "icon" ) )
868                          && !p_feed->psz_image )
869                 {
870                     p_feed->psz_image = psz_eltvalue;
871                 }
872                 else
873                 {
874                     free( psz_eltvalue );
875                 }
876             }
877             break;
878         }
879         }
880     }
881
882     free( psz_eltname );
883     return true;
884
885 end:
886     free( psz_eltname );
887     return false;
888 }
889
890
891 /****************************************************************************
892  * FetchRSS (or Atom) feeds
893  ***************************************************************************/
894 static rss_feed_t* FetchRSS( filter_t *p_filter )
895 {
896     filter_sys_t *p_sys = p_filter->p_sys;
897
898     stream_t *p_stream;
899     xml_t *p_xml;
900     xml_reader_t *p_xml_reader;
901     int i_feed;
902
903     /* These data are not modified after the creation of the module so we don't
904        need to hold the lock */
905     int i_feeds = p_sys->i_feeds;
906     bool b_images = p_sys->b_images;
907
908     /* Allocate a new structure */
909     rss_feed_t *p_feeds = malloc( i_feeds * sizeof( rss_feed_t ) );
910     if( !p_feeds )
911         return NULL;
912
913     p_xml = xml_Create( p_filter );
914     if( !p_xml )
915     {
916         msg_Err( p_filter, "Failed to open XML parser" );
917         free( p_feeds );
918         return NULL;
919     }
920
921     /* Fetch all feeds and parse them */
922     for( i_feed = 0; i_feed < i_feeds; i_feed++ )
923     {
924         rss_feed_t *p_feed = p_feeds + i_feed;
925         rss_feed_t *p_old_feed = p_sys->p_feeds + i_feed;
926
927         /* Initialize the structure */
928         p_feed->psz_title = NULL;
929         p_feed->psz_description = NULL;
930         p_feed->psz_link = NULL;
931         p_feed->psz_image = NULL;
932         p_feed->p_pic = NULL;
933         p_feed->i_items = 0;
934         p_feed->p_items = NULL;
935
936         p_feed->psz_url = strdup( p_old_feed->psz_url );
937
938         /* Fetch the feed */
939         msg_Dbg( p_filter, "opening %s RSS/Atom feed ...", p_feed->psz_url );
940
941         p_stream = stream_UrlNew( p_filter, p_feed->psz_url );
942         if( !p_stream )
943         {
944             msg_Err( p_filter, "Failed to open %s for reading", p_feed->psz_url );
945             p_xml_reader = NULL;
946             goto error;
947         }
948
949         p_xml_reader = xml_ReaderCreate( p_xml, p_stream );
950         if( !p_xml_reader )
951         {
952             msg_Err( p_filter, "Failed to open %s for parsing", p_feed->psz_url );
953             goto error;
954         }
955
956         /* Parse the feed */
957         if( !ParseFeed( p_filter, p_xml_reader, p_feed ) )
958             goto error;
959
960         /* If we have a image: load it if requiere */
961         if( b_images && p_feed->psz_image && !p_feed->p_pic )
962         {
963             p_feed->p_pic = LoadImage( p_filter, p_feed->psz_image );
964         }
965
966         msg_Dbg( p_filter, "done with %s RSS/Atom feed", p_feed->psz_url );
967         xml_ReaderDelete( p_xml, p_xml_reader );
968         stream_Delete( p_stream );
969     }
970
971     xml_Delete( p_xml );
972     return p_feeds;
973
974 error:
975     FreeRSS( p_feeds, i_feed + 1 );
976     if( p_xml_reader )
977         xml_ReaderDelete( p_xml, p_xml_reader );
978     if( p_stream )
979         stream_Delete( p_stream );
980     if( p_xml )
981         xml_Delete( p_xml );
982
983     return NULL;
984 }
985
986 /****************************************************************************
987  * FreeRSS
988  ***************************************************************************/
989 static void FreeRSS( rss_feed_t *p_feeds, int i_feeds )
990 {
991     for( int i_feed = 0; i_feed < i_feeds; i_feed++ )
992     {
993         rss_feed_t *p_feed = p_feeds+i_feed;
994         for( int i_item = 0; i_item < p_feed->i_items; i_item++ )
995         {
996             rss_item_t *p_item = p_feed->p_items+i_item;
997             free( p_item->psz_title );
998             free( p_item->psz_link );
999             free( p_item->psz_description );
1000         }
1001         free( p_feed->p_items );
1002         free( p_feed->psz_title);
1003         free( p_feed->psz_link );
1004         free( p_feed->psz_description );
1005         free( p_feed->psz_image );
1006         if( p_feed->p_pic != NULL )
1007             picture_Release( p_feed->p_pic );
1008         free( p_feed->psz_url );
1009     }
1010     free( p_feeds );
1011 }
1012
1013 static void Fetch( void *p_data )
1014 {
1015     filter_t *p_filter = p_data;
1016     filter_sys_t *p_sys = p_filter->p_sys;
1017
1018     msg_Dbg( p_filter, "Updating the rss feeds" );
1019     rss_feed_t *p_feeds = FetchRSS( p_filter );
1020     if( !p_feeds )
1021     {
1022         msg_Err( p_filter, "Unable to fetch the feeds" );
1023         return;
1024     }
1025
1026     rss_feed_t *p_old_feeds = p_sys->p_feeds;
1027
1028     vlc_mutex_lock( &p_sys->lock );
1029     /* Update the feeds */
1030     p_sys->p_feeds = p_feeds;
1031     p_sys->b_fetched = true;
1032     /* Set all current info to the original values */
1033     p_sys->i_cur_feed = 0;
1034     p_sys->i_cur_item = p_sys->i_title == scroll_title ? -1 : 0;
1035     p_sys->i_cur_char = 0;
1036     vlc_mutex_unlock( &p_sys->lock );
1037
1038     if( p_old_feeds )
1039         FreeRSS( p_old_feeds, p_sys->i_feeds );
1040 }