]> git.sesse.net Git - vlc/blob - modules/video_filter/rss.c
video_filter_rss: parse the rss-urls string only one time and store the result.
[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  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Atom : http://www.ietf.org/rfc/rfc4287.txt
26  * RSS : http://www.rssboard.org/rss-specification
27  *****************************************************************************/
28
29 /*****************************************************************************
30  * Preamble
31  *****************************************************************************/
32
33 #ifdef HAVE_CONFIG_H
34 # include "config.h"
35 #endif
36
37 #include <vlc_common.h>
38 #include <vlc_plugin.h>
39
40 #include <vlc_filter.h>
41 #include <vlc_block.h>
42 #include <vlc_osd.h>
43
44 #include <vlc_block.h>
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 int FetchRSS( filter_t * );
61 static void FreeRSS( filter_t * );
62 static int ParseUrls( filter_t *, char * );
63
64 static const int pi_color_values[] = {
65                0xf0000000, 0x00000000, 0x00808080, 0x00C0C0C0,
66                0x00FFFFFF, 0x00800000, 0x00FF0000, 0x00FF00FF, 0x00FFFF00,
67                0x00808000, 0x00008000, 0x00008080, 0x0000FF00, 0x00800080,
68                0x00000080, 0x000000FF, 0x0000FFFF};
69 static const char *const ppsz_color_descriptions[] = {
70                N_("Default"), N_("Black"),
71                N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"), N_("Red"),
72                N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"),
73                N_("Teal"), N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"),
74                N_("Aqua") };
75
76 /*****************************************************************************
77  * filter_sys_t: rss filter descriptor
78  *****************************************************************************/
79
80 struct rss_item_t
81 {
82     char *psz_title;
83     char *psz_description;
84     char *psz_link;
85 };
86
87 typedef struct rss_feed_t
88 {
89     char *psz_url;
90     char *psz_title;
91     char *psz_description;
92     char *psz_link;
93     char *psz_image;
94     picture_t *p_pic;
95
96     int i_items;
97     struct rss_item_t *p_items;
98 } rss_feed_t;
99
100 struct filter_sys_t
101 {
102     vlc_mutex_t lock;
103     vlc_mutex_t *p_lock;
104
105     int i_xoff, i_yoff;  /* offsets for the display string in the video window */
106     int i_pos; /* permit relative positioning (top, bottom, left, right, center) */
107     int i_speed;
108     int i_length;
109
110     char *psz_marquee;    /* marquee string */
111
112     text_style_t *p_style; /* font control */
113
114     mtime_t last_date;
115
116     int i_feeds;
117     struct rss_feed_t *p_feeds;
118
119     int i_ttl;
120     time_t t_last_update;
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 static const int pi_pos_values[] = { 0, 1, 2, 4, 8, 5, 6, 9, 10 };
170 static const char *const ppsz_pos_descriptions[] =
171      { N_("Center"), N_("Left"), N_("Right"), N_("Top"), N_("Bottom"),
172      N_("Top-Left"), N_("Top-Right"), N_("Bottom-Left"), N_("Bottom-Right") };
173
174 enum title_modes {
175     default_title=-1,
176     hide_title,
177     prepend_title,
178     scroll_title };
179
180 static const int pi_title_modes[] = { default_title, hide_title, prepend_title, scroll_title };
181 static const char *const ppsz_title_modes[] =
182     { N_("Default"), N_("Don't show"), N_("Always visible"), N_("Scroll with feed") };
183
184 #define CFG_PREFIX "rss-"
185
186 /*****************************************************************************
187  * Module descriptor
188  *****************************************************************************/
189 vlc_module_begin ()
190     set_capability( "sub filter", 1 )
191     set_shortname( "RSS / Atom" )
192     set_callbacks( CreateFilter, DestroyFilter )
193     set_category( CAT_VIDEO )
194     set_subcategory( SUBCAT_VIDEO_SUBPIC )
195     add_string( CFG_PREFIX "urls", "rss", NULL, MSG_TEXT, MSG_LONGTEXT, false )
196
197     set_section( N_("Position"), NULL )
198     add_integer( CFG_PREFIX "x", 0, NULL, POSX_TEXT, POSX_LONGTEXT, true )
199     add_integer( CFG_PREFIX "y", 0, NULL, POSY_TEXT, POSY_LONGTEXT, true )
200     add_integer( CFG_PREFIX "position", -1, NULL, POS_TEXT, POS_LONGTEXT, false )
201         change_integer_list( pi_pos_values, ppsz_pos_descriptions, NULL )
202
203     set_section( N_("Font"), NULL )
204     /* 5 sets the default to top [1] left [4] */
205     add_integer_with_range( CFG_PREFIX "opacity", 255, 0, 255, NULL,
206         OPACITY_TEXT, OPACITY_LONGTEXT, false )
207     add_integer( CFG_PREFIX "color", 0xFFFFFF, NULL, COLOR_TEXT, COLOR_LONGTEXT,
208                   false )
209         change_integer_list( pi_color_values, ppsz_color_descriptions, NULL )
210     add_integer( CFG_PREFIX "size", -1, NULL, SIZE_TEXT, SIZE_LONGTEXT, false )
211
212     set_section( N_("Misc"), NULL )
213     add_integer( CFG_PREFIX "speed", 100000, NULL, SPEED_TEXT, SPEED_LONGTEXT,
214                  false )
215     add_integer( CFG_PREFIX "length", 60, NULL, LENGTH_TEXT, LENGTH_LONGTEXT,
216                  false )
217     add_integer( CFG_PREFIX "ttl", 1800, NULL, TTL_TEXT, TTL_LONGTEXT, false )
218     add_bool( CFG_PREFIX "images", 1, NULL, IMAGE_TEXT, IMAGE_LONGTEXT, false )
219     add_integer( CFG_PREFIX "title", default_title, NULL, TITLE_TEXT, TITLE_LONGTEXT, false )
220         change_integer_list( pi_title_modes, ppsz_title_modes, NULL )
221
222     set_description( N_("RSS and Atom feed display") )
223     add_shortcut( "rss" )
224     add_shortcut( "atom" )
225 vlc_module_end ()
226
227 static const char *const ppsz_filter_options[] = {
228     "urls", "x", "y", "position", "color", "size", "speed", "length",
229     "ttl", "images", "title", NULL
230 };
231
232 /*****************************************************************************
233  * CreateFilter: allocates RSS video filter
234  *****************************************************************************/
235 static int CreateFilter( vlc_object_t *p_this )
236 {
237     filter_t *p_filter = (filter_t *)p_this;
238     filter_sys_t *p_sys;
239     int i_feed;
240     int i_ret = VLC_ENOMEM;
241     char *psz_urls;
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     psz_urls = var_CreateGetString( p_filter, CFG_PREFIX "urls" );
252     p_sys->i_title = var_CreateGetInteger( p_filter, CFG_PREFIX "title" );
253     p_sys->i_cur_feed = 0;
254     p_sys->i_cur_item = p_sys->i_title == scroll_title ? -1 : 0;
255     p_sys->i_cur_char = 0;
256     p_sys->i_feeds = 0;
257     p_sys->p_feeds = NULL;
258     p_sys->i_speed = var_CreateGetInteger( p_filter, CFG_PREFIX "speed" );
259     p_sys->i_length = var_CreateGetInteger( p_filter, CFG_PREFIX "length" );
260     p_sys->i_ttl = __MAX( 0, var_CreateGetInteger( p_filter, CFG_PREFIX "ttl" ) );
261     p_sys->b_images = var_CreateGetBool( p_filter, CFG_PREFIX "images" );
262
263     p_sys->psz_marquee = malloc( p_sys->i_length + 1 );
264     if( p_sys->psz_marquee == NULL )
265         goto error;
266     p_sys->psz_marquee[p_sys->i_length] = '\0';
267
268     p_sys->p_style = text_style_New();
269     if( p_sys->p_style == NULL )
270         goto error;
271
272     p_sys->i_xoff = var_CreateGetInteger( p_filter, CFG_PREFIX "x" );
273     p_sys->i_yoff = var_CreateGetInteger( p_filter, CFG_PREFIX "y" );
274     p_sys->i_pos = var_CreateGetInteger( p_filter, CFG_PREFIX "position" );
275     p_sys->p_style->i_font_alpha = 255 - var_CreateGetInteger( p_filter, CFG_PREFIX "opacity" );
276     p_sys->p_style->i_font_color = var_CreateGetInteger( p_filter, CFG_PREFIX "color" );
277     p_sys->p_style->i_font_size = var_CreateGetInteger( p_filter, CFG_PREFIX "size" );
278
279     if( p_sys->b_images == true && p_sys->p_style->i_font_size == -1 )
280     {
281         msg_Warn( p_filter, "rss-size wasn't specified. Feed images will thus be displayed without being resized" );
282     }
283
284     /* Parse the urls */
285     ParseUrls( p_filter, psz_urls );
286     free( psz_urls );
287
288     if( FetchRSS( p_filter ) )
289     {
290         msg_Err( p_filter, "failed while fetching RSS ... too bad" );
291         text_style_Delete( p_sys->p_style );
292         i_ret = VLC_EGENERIC;
293         goto error;
294     }
295     p_sys->t_last_update = time( NULL );
296
297     if( p_sys->i_feeds == 0 )
298     {
299         text_style_Delete( p_sys->p_style );
300         i_ret = VLC_EGENERIC;
301         goto error;
302     }
303     for( i_feed=0; i_feed < p_sys->i_feeds; i_feed ++ )
304     {
305         if( p_sys->p_feeds[i_feed].i_items == 0 )
306         {
307             DestroyFilter( p_this );
308             return VLC_EGENERIC;
309         }
310     }
311
312     /* Misc init */
313     vlc_mutex_init( &p_sys->lock );
314     p_filter->pf_sub_filter = Filter;
315     p_sys->last_date = (mtime_t)0;
316
317     return VLC_SUCCESS;
318
319 error:
320     free( p_sys->psz_marquee );
321     free( p_sys );
322     return i_ret;
323 }
324 /*****************************************************************************
325  * DestroyFilter: destroy RSS video filter
326  *****************************************************************************/
327 static void DestroyFilter( vlc_object_t *p_this )
328 {
329     filter_t *p_filter = (filter_t *)p_this;
330     filter_sys_t *p_sys = p_filter->p_sys;
331
332     text_style_Delete( p_sys->p_style );
333     free( p_sys->psz_marquee );
334     FreeRSS( p_filter );
335     free( p_sys );
336 }
337
338 /****************************************************************************
339  * Filter: the whole thing
340  ****************************************************************************
341  * This function outputs subpictures at regular time intervals.
342  ****************************************************************************/
343 static subpicture_t *Filter( filter_t *p_filter, mtime_t date )
344 {
345     filter_sys_t *p_sys = p_filter->p_sys;
346     subpicture_t *p_spu;
347     video_format_t fmt;
348     subpicture_region_t *p_region;
349
350     int i_feed, i_item;
351
352     struct rss_feed_t *p_feed;
353
354     memset( &fmt, 0, sizeof(video_format_t) );
355
356     vlc_mutex_lock( &p_sys->lock );
357
358     if( p_sys->last_date
359        + ( p_sys->i_cur_char == 0 && p_sys->i_cur_item == ( p_sys->i_title == scroll_title ? -1 : 0 ) ? 5 : 1 )
360            /* ( ... ? 5 : 1 ) means "wait 5 times more for the 1st char" */
361        * p_sys->i_speed > date )
362     {
363         vlc_mutex_unlock( &p_sys->lock );
364         return NULL;
365     }
366
367     /* Do we need to update the feeds ? */
368     if( p_sys->i_ttl
369         && time( NULL ) > p_sys->t_last_update + (time_t)p_sys->i_ttl )
370     {
371         msg_Dbg( p_filter, "Forcing update of all the RSS feeds" );
372         if( FetchRSS( p_filter ) )
373         {
374             msg_Err( p_filter, "Failed while fetching RSS ... too bad" );
375             vlc_mutex_unlock( &p_sys->lock );
376             return NULL; /* FIXME : we most likely messed up all the data,
377                           * so we might need to do something about it */
378         }
379         p_sys->t_last_update = time( 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 = OSD_ALIGN_LEFT | OSD_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
494     p_spu->p_region->p_style = text_style_Duplicate( p_sys->p_style );
495
496     if( p_feed->p_pic )
497     {
498         /* Display the feed's image */
499         picture_t *p_pic = p_feed->p_pic;
500         video_format_t fmt_out;
501
502         memset( &fmt_out, 0, sizeof(video_format_t) );
503
504         fmt_out.i_chroma = VLC_CODEC_YUVA;
505         fmt_out.i_aspect = VOUT_ASPECT_FACTOR;
506         fmt_out.i_sar_num = fmt_out.i_sar_den = 1;
507         fmt_out.i_width =
508             fmt_out.i_visible_width = p_pic->p[Y_PLANE].i_visible_pitch;
509         fmt_out.i_height =
510             fmt_out.i_visible_height = p_pic->p[Y_PLANE].i_visible_lines;
511
512         p_region = subpicture_region_New( &fmt_out );
513         if( !p_region )
514         {
515             msg_Err( p_filter, "cannot allocate SPU region" );
516         }
517         else
518         {
519             p_region->i_x = p_sys->i_xoff;
520             p_region->i_y = p_sys->i_yoff;
521             /* FIXME the copy is probably not needed anymore */
522             picture_Copy( p_region->p_picture, p_pic );
523             p_spu->p_region->p_next = p_region;
524         }
525
526         /* Offset text to display right next to the image */
527         p_spu->p_region->i_x = p_pic->p[Y_PLANE].i_visible_pitch;
528     }
529
530     vlc_mutex_unlock( &p_sys->lock );
531     return p_spu;
532 }
533
534 /****************************************************************************
535  * RSS related functions
536  ****************************************************************************
537  * You should always lock the p_filter mutex before using any of these
538  * functions
539  ***************************************************************************/
540
541 #undef LoadImage /* do not conflict with Win32 API */
542
543 /****************************************************************************
544  * download and resize image located at psz_url
545  ***************************************************************************/
546 static picture_t *LoadImage( filter_t *p_filter, const char *psz_url )
547 {
548     filter_sys_t *p_sys = p_filter->p_sys;
549     video_format_t fmt_in;
550     video_format_t fmt_out;
551     picture_t *p_orig;
552     picture_t *p_pic = NULL;
553     image_handler_t *p_handler = image_HandlerCreate( p_filter );
554
555     memset( &fmt_in, 0, sizeof(video_format_t) );
556     memset( &fmt_out, 0, sizeof(video_format_t) );
557
558     fmt_out.i_chroma = VLC_CODEC_YUVA;
559     p_orig = image_ReadUrl( p_handler, psz_url, &fmt_in, &fmt_out );
560
561     if( !p_orig )
562     {
563         msg_Warn( p_filter, "Unable to read image %s", psz_url );
564     }
565     else if( p_sys->p_style->i_font_size > 0 )
566     {
567
568         fmt_in.i_chroma = VLC_CODEC_YUVA;
569         fmt_in.i_height = p_orig->p[Y_PLANE].i_visible_lines;
570         fmt_in.i_width = p_orig->p[Y_PLANE].i_visible_pitch;
571         fmt_out.i_width = p_orig->p[Y_PLANE].i_visible_pitch
572             *p_sys->p_style->i_font_size/p_orig->p[Y_PLANE].i_visible_lines;
573         fmt_out.i_height = p_sys->p_style->i_font_size;
574
575         p_pic = image_Convert( p_handler, p_orig, &fmt_in, &fmt_out );
576         picture_Release( p_orig );
577         if( !p_pic )
578         {
579             msg_Warn( p_filter, "Error while converting %s", psz_url );
580         }
581     }
582     else
583     {
584         p_pic = p_orig;
585     }
586
587     image_HandlerDelete( p_handler );
588
589     return p_pic;
590 }
591
592 /****************************************************************************
593  * remove all ' ' '\t' '\n' '\r' characters from the begining and end of the
594  * string.
595  ***************************************************************************/
596 static char *removeWhiteChars( const char *psz_src )
597 {
598     char *psz_src2,*psz_clean, *psz_clean2;
599     psz_src2 = psz_clean = strdup( psz_src );
600     int i;
601
602     while( ( *psz_clean == ' ' || *psz_clean == '\t'
603            || *psz_clean == '\n' || *psz_clean == '\r' )
604            && *psz_clean != '\0' )
605     {
606         psz_clean++;
607     }
608     i = strlen( psz_clean );
609     while( --i > 0 &&
610          ( psz_clean[i] == ' ' || psz_clean[i] == '\t'
611         || psz_clean[i] == '\n' || psz_clean[i] == '\r' ) );
612     psz_clean[i+1] = '\0';
613     psz_clean2 = strdup( psz_clean );
614     free( psz_src2 );
615     return psz_clean2;
616 }
617
618
619 /****************************************************************************
620  * Parse url list, psz_urls must be non empty
621  ***************************************************************************/
622 static int ParseUrls( filter_t *p_filter, char *psz_urls )
623 {
624     filter_sys_t *p_sys = p_filter->p_sys;
625     char *psz_urls2 = psz_urls;
626
627     p_sys->i_feeds = 1;
628
629     /* Count the number of feeds */
630     while( *psz_urls )
631     {
632         if( *psz_urls == '|' )
633             p_sys->i_feeds++;
634         psz_urls++;
635     }
636
637     /* Allocate the structure */
638     p_sys->p_feeds = malloc( p_sys->i_feeds * sizeof( rss_feed_t ) );
639     if( !p_sys->p_feeds )
640         return VLC_ENOMEM;
641
642     /* Loop on all urls and fill in the struct */
643     psz_urls = psz_urls2;
644     for( int i = 0; i < p_sys->i_feeds; i++ )
645     {
646         rss_feed_t* p_feed = p_sys->p_feeds + i;
647         char *psz_end;
648
649         if( i < p_sys->i_feeds - 1 )
650         {
651             psz_end = strchr( psz_urls, '|' );
652             *psz_end = '\0';
653         }
654         else
655             psz_end = psz_urls;
656
657         p_feed->i_items = 0;
658         p_feed->p_items = NULL;
659         p_feed->psz_title = NULL;
660         p_feed->psz_link = NULL;
661         p_feed->psz_description = NULL;
662         p_feed->psz_image = NULL;
663         p_feed->p_pic = NULL;
664         p_feed->psz_url = strdup( psz_urls );
665
666         psz_urls = psz_end + 1;
667     }
668
669     return VLC_SUCCESS;
670 }
671
672
673 /****************************************************************************
674  * FetchRSS (or Atom) feeds
675  ***************************************************************************/
676 static int FetchRSS( filter_t *p_filter)
677 {
678     filter_sys_t *p_sys = p_filter->p_sys;
679
680     stream_t *p_stream = NULL;
681     xml_t *p_xml = NULL;
682     xml_reader_t *p_xml_reader = NULL;
683
684     char *psz_eltname = NULL;
685     char *psz_eltvalue = NULL;
686
687     int i_feed;
688     int i_item;
689     bool b_is_item;
690     bool b_is_image;
691
692     p_xml = xml_Create( p_filter );
693     if( !p_xml )
694     {
695         msg_Err( p_filter, "Failed to open XML parser" );
696         return 1;
697     }
698
699     for( i_feed = 0; i_feed < p_sys->i_feeds; i_feed++ )
700     {
701         struct rss_feed_t *p_feed = p_sys->p_feeds+i_feed;
702
703         FREENULL( p_feed->psz_title );
704         FREENULL( p_feed->psz_description );
705         FREENULL( p_feed->psz_link );
706         FREENULL( p_feed->psz_image );
707         if( p_feed->p_pic )
708         {
709             picture_Release( p_feed->p_pic );
710             p_feed->p_pic = NULL;
711         }
712         for( int i = 0; i < p_feed->i_items; i++ )
713         {
714             struct rss_item_t *p_item = p_feed->p_items + i;
715             free( p_item->psz_title );
716             free( p_item->psz_link );
717             free( p_item->psz_description );
718         }
719         p_feed->i_items = 0;
720         FREENULL( p_feed->p_items );
721
722         msg_Dbg( p_filter, "opening %s RSS/Atom feed ...", p_feed->psz_url );
723
724         p_stream = stream_UrlNew( p_filter, p_feed->psz_url );
725         if( !p_stream )
726         {
727             msg_Err( p_filter, "Failed to open %s for reading", p_feed->psz_url );
728             xml_Delete( p_xml );
729             return 1;
730         }
731
732         p_xml_reader = xml_ReaderCreate( p_xml, p_stream );
733         if( !p_xml_reader )
734         {
735             msg_Err( p_filter, "Failed to open %s for parsing", p_feed->psz_url );
736             xml_Delete( p_xml );
737             return 1;
738         }
739
740         i_item = 0;
741         b_is_item = false;
742         b_is_image = false;
743
744         while( xml_ReaderRead( p_xml_reader ) == 1 )
745         {
746             switch( xml_ReaderNodeType( p_xml_reader ) )
747             {
748                 // Error
749                 case -1:
750                     return 1;
751
752                 case XML_READER_STARTELEM:
753                     free( psz_eltname );
754                     psz_eltname = xml_ReaderName( p_xml_reader );
755                     if( !psz_eltname )
756                     {
757                         return 1;
758                     }
759 #                   ifdef RSS_DEBUG
760                     msg_Dbg( p_filter, "element name: %s", psz_eltname );
761 #                   endif
762                     if( !strcmp( psz_eltname, "item" ) /* rss */
763                      || !strcmp( psz_eltname, "entry" ) ) /* atom */
764                     {
765                         b_is_item = true;
766                         p_feed->i_items++;
767                         p_feed->p_items = (struct rss_item_t *)realloc( p_feed->p_items, p_feed->i_items * sizeof( struct rss_item_t ) );
768                         p_feed->p_items[p_feed->i_items-1].psz_title = NULL;
769                         p_feed->p_items[p_feed->i_items-1].psz_description
770                                                                      = NULL;
771                         p_feed->p_items[p_feed->i_items-1].psz_link = NULL;
772                     }
773                     else if( !strcmp( psz_eltname, "image" ) ) /* rss */
774                     {
775                         b_is_image = true;
776                     }
777                     else if( !strcmp( psz_eltname, "link" ) ) /* atom */
778                     {
779                         char *psz_href = NULL;
780                         char *psz_rel = NULL;
781                         while( xml_ReaderNextAttr( p_xml_reader )
782                                == VLC_SUCCESS )
783                         {
784                             char *psz_name = xml_ReaderName( p_xml_reader );
785                             char *psz_value = xml_ReaderValue( p_xml_reader );
786                             if( !strcmp( psz_name, "rel" ) )
787                             {
788                                 if( psz_rel )
789                                 {
790                                     msg_Dbg( p_filter, "\"rel\" attribute of link atom duplicated (last value: %s)", psz_value );
791                                     free( psz_rel );
792                                 }
793                                 psz_rel = psz_value;
794                             }
795                             else if( !strcmp( psz_name, "href" ) )
796                             {
797                                 if( psz_href )
798                                 {
799                                     msg_Dbg( p_filter, "\"href\" attribute of link atom duplicated (last value: %s)", psz_href );
800                                     free( psz_href );
801                                 }
802                                 psz_href = psz_value;
803                             }
804                             else
805                             {
806                                 free( psz_value );
807                             }
808                             free( psz_name );
809                         }
810                         if( psz_rel && psz_href )
811                         {
812                             if( !strcmp( psz_rel, "alternate" )
813                                 && b_is_item == false
814                                 && b_is_image == false
815                                 && !p_feed->psz_link )
816                             {
817                                 p_feed->psz_link = psz_href;
818                             }
819                             /* this isn't in the rfc but i found some ... */
820                             else if( ( !strcmp( psz_rel, "logo" )
821                                     || !strcmp( psz_rel, "icon" ) )
822                                     && b_is_item == false
823                                     && b_is_image == false
824                                     && !p_feed->psz_image )
825                             {
826                                 p_feed->psz_image = psz_href;
827                             }
828                             else
829                             {
830                                 free( psz_href );
831                             }
832                         }
833                         else
834                         {
835                             free( psz_href );
836                         }
837                         free( psz_rel );
838                     }
839                     break;
840
841                 case XML_READER_ENDELEM:
842                     free( psz_eltname );
843                     psz_eltname = NULL;
844                     psz_eltname = xml_ReaderName( p_xml_reader );
845                     if( !psz_eltname )
846                     {
847                         return 1;
848                     }
849 #                   ifdef RSS_DEBUG
850                     msg_Dbg( p_filter, "element end : %s", psz_eltname );
851 #                   endif
852                     if( !strcmp( psz_eltname, "item" ) /* rss */
853                      || !strcmp( psz_eltname, "entry" ) ) /* atom */
854                     {
855                         b_is_item = false;
856                         i_item++;
857                     }
858                     else if( !strcmp( psz_eltname, "image" ) ) /* rss */
859                     {
860                         b_is_image = false;
861                     }
862                     free( psz_eltname );
863                     psz_eltname = NULL;
864                     break;
865
866                 case XML_READER_TEXT:
867                     if( !psz_eltname ) break;
868                     psz_eltvalue = xml_ReaderValue( p_xml_reader );
869                     if( !psz_eltvalue )
870                     {
871                         return 1;
872                     }
873                     else
874                     {
875                         char *psz_clean;
876                         psz_clean = removeWhiteChars( psz_eltvalue );
877                         free( psz_eltvalue ); psz_eltvalue = psz_clean;
878                     }
879 #                   ifdef RSS_DEBUG
880                     msg_Dbg( p_filter, "  text : <%s>", psz_eltvalue );
881 #                   endif
882                     if( b_is_item == true )
883                     {
884                         struct rss_item_t *p_item;
885                         p_item = p_feed->p_items+i_item;
886                         if( !strcmp( psz_eltname, "title" ) /* rss/atom */
887                             && !p_item->psz_title )
888                         {
889                             p_item->psz_title = psz_eltvalue;
890                         }
891                         else if( !strcmp( psz_eltname, "link" ) /* rss */
892                                  && !p_item->psz_link )
893                         {
894                             p_item->psz_link = psz_eltvalue;
895                         }
896                         else if((!strcmp( psz_eltname, "description" ) /* rss */
897                               || !strcmp( psz_eltname, "summary" ) ) /* atom */
898                               && !p_item->psz_description )
899                         {
900                             p_item->psz_description = psz_eltvalue;
901                         }
902                         else
903                         {
904                             free( psz_eltvalue );
905                             psz_eltvalue = NULL;
906                         }
907                     }
908                     else if( b_is_image == true )
909                     {
910                         if( !strcmp( psz_eltname, "url" ) /* rss */
911                             && !p_feed->psz_image )
912                         {
913                             p_feed->psz_image = psz_eltvalue;
914                         }
915                         else
916                         {
917                             free( psz_eltvalue );
918                             psz_eltvalue = NULL;
919                         }
920                     }
921                     else
922                     {
923                         if( !strcmp( psz_eltname, "title" ) /* rss/atom */
924                             && !p_feed->psz_title )
925                         {
926                             p_feed->psz_title = psz_eltvalue;
927                         }
928                         else if( !strcmp( psz_eltname, "link" ) /* rss */
929                                  && !p_feed->psz_link )
930                         {
931                             p_feed->psz_link = psz_eltvalue;
932                         }
933                         else if((!strcmp( psz_eltname, "description" ) /* rss */
934                               || !strcmp( psz_eltname, "subtitle" ) ) /* atom */
935                               && !p_feed->psz_description )
936                         {
937                             p_feed->psz_description = psz_eltvalue;
938                         }
939                         else if( ( !strcmp( psz_eltname, "logo" ) /* atom */
940                               || !strcmp( psz_eltname, "icon" ) ) /* atom */
941                               && !p_feed->psz_image )
942                         {
943                             p_feed->psz_image = psz_eltvalue;
944                         }
945                         else
946                         {
947                             free( psz_eltvalue );
948                             psz_eltvalue = NULL;
949                         }
950                     }
951                     break;
952             }
953         }
954
955         if( p_sys->b_images == true
956             && p_feed->psz_image && !p_feed->p_pic )
957         {
958             p_feed->p_pic = LoadImage( p_filter, p_feed->psz_image );
959         }
960
961         if( p_xml_reader && p_xml ) xml_ReaderDelete( p_xml, p_xml_reader );
962         if( p_stream ) stream_Delete( p_stream );
963         msg_Dbg( p_filter, "done with %s RSS/Atom feed", p_feed->psz_url );
964     }
965     if( p_xml ) xml_Delete( p_xml );
966
967     return 0;
968 }
969
970 /****************************************************************************
971  * FreeRSS
972  ***************************************************************************/
973 static void FreeRSS( filter_t *p_filter)
974 {
975     filter_sys_t *p_sys = p_filter->p_sys;
976
977     struct rss_item_t *p_item;
978     struct rss_feed_t *p_feed;
979
980     int i_feed;
981     int i_item;
982
983     for( i_feed = 0; i_feed < p_sys->i_feeds; i_feed++ )
984     {
985         p_feed = p_sys->p_feeds+i_feed;
986         for( i_item = 0; i_item < p_feed->i_items; i_item++ )
987         {
988             p_item = p_feed->p_items+i_item;
989             free( p_item->psz_title );
990             free( p_item->psz_link );
991             free( p_item->psz_description );
992         }
993         free( p_feed->p_items );
994         free( p_feed->psz_title);
995         free( p_feed->psz_link );
996         free( p_feed->psz_description );
997         free( p_feed->psz_image );
998         if( p_feed->p_pic != NULL )
999             picture_Release( p_feed->p_pic );
1000         free( p_feed->psz_url );
1001     }
1002     free( p_sys->p_feeds );
1003     p_sys->i_feeds = 0;
1004 }