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