]> git.sesse.net Git - vlc/blob - modules/video_filter/rss.c
video_filter_rss: factorize and fix object leaks and memleaks.
[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 typedef struct rss_item_t
81 {
82     char *psz_title;
83     char *psz_description;
84     char *psz_link;
85 } rss_item_t;
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     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     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     rss_feed_t *p_feed;
352
353     memset( &fmt, 0, sizeof(video_format_t) );
354
355     vlc_mutex_lock( &p_sys->lock );
356
357     if( p_sys->last_date
358        + ( p_sys->i_cur_char == 0 && p_sys->i_cur_item == ( p_sys->i_title == scroll_title ? -1 : 0 ) ? 5 : 1 )
359            /* ( ... ? 5 : 1 ) means "wait 5 times more for the 1st char" */
360        * p_sys->i_speed > date )
361     {
362         vlc_mutex_unlock( &p_sys->lock );
363         return NULL;
364     }
365
366     /* Do we need to update the feeds ? */
367     if( p_sys->i_ttl
368         && time( NULL ) > p_sys->t_last_update + (time_t)p_sys->i_ttl )
369     {
370         msg_Dbg( p_filter, "Forcing update of all the RSS feeds" );
371         if( FetchRSS( p_filter ) )
372         {
373             msg_Err( p_filter, "Failed while fetching RSS ... too bad" );
374             vlc_mutex_unlock( &p_sys->lock );
375             return NULL; /* FIXME : we most likely messed up all the data,
376                           * so we might need to do something about it */
377         }
378         p_sys->t_last_update = time( 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_aspect = VOUT_ASPECT_FACTOR;
505         fmt_out.i_sar_num = fmt_out.i_sar_den = 1;
506         fmt_out.i_width =
507             fmt_out.i_visible_width = p_pic->p[Y_PLANE].i_visible_pitch;
508         fmt_out.i_height =
509             fmt_out.i_visible_height = p_pic->p[Y_PLANE].i_visible_lines;
510
511         p_region = subpicture_region_New( &fmt_out );
512         if( !p_region )
513         {
514             msg_Err( p_filter, "cannot allocate SPU region" );
515         }
516         else
517         {
518             p_region->i_x = p_sys->i_xoff;
519             p_region->i_y = p_sys->i_yoff;
520             /* FIXME the copy is probably not needed anymore */
521             picture_Copy( p_region->p_picture, p_pic );
522             p_spu->p_region->p_next = p_region;
523         }
524
525         /* Offset text to display right next to the image */
526         p_spu->p_region->i_x = p_pic->p[Y_PLANE].i_visible_pitch;
527     }
528
529     vlc_mutex_unlock( &p_sys->lock );
530     return p_spu;
531 }
532
533 /****************************************************************************
534  * RSS related functions
535  ****************************************************************************
536  * You should always lock the p_filter mutex before using any of these
537  * functions
538  ***************************************************************************/
539
540 #undef LoadImage /* do not conflict with Win32 API */
541
542 /****************************************************************************
543  * download and resize image located at psz_url
544  ***************************************************************************/
545 static picture_t *LoadImage( filter_t *p_filter, const char *psz_url )
546 {
547     filter_sys_t *p_sys = p_filter->p_sys;
548     video_format_t fmt_in;
549     video_format_t fmt_out;
550     picture_t *p_orig;
551     picture_t *p_pic = NULL;
552     image_handler_t *p_handler = image_HandlerCreate( p_filter );
553
554     memset( &fmt_in, 0, sizeof(video_format_t) );
555     memset( &fmt_out, 0, sizeof(video_format_t) );
556
557     fmt_out.i_chroma = VLC_CODEC_YUVA;
558     p_orig = image_ReadUrl( p_handler, psz_url, &fmt_in, &fmt_out );
559
560     if( !p_orig )
561     {
562         msg_Warn( p_filter, "Unable to read image %s", psz_url );
563     }
564     else if( p_sys->p_style->i_font_size > 0 )
565     {
566
567         fmt_in.i_chroma = VLC_CODEC_YUVA;
568         fmt_in.i_height = p_orig->p[Y_PLANE].i_visible_lines;
569         fmt_in.i_width = p_orig->p[Y_PLANE].i_visible_pitch;
570         fmt_out.i_width = p_orig->p[Y_PLANE].i_visible_pitch
571             *p_sys->p_style->i_font_size/p_orig->p[Y_PLANE].i_visible_lines;
572         fmt_out.i_height = p_sys->p_style->i_font_size;
573
574         p_pic = image_Convert( p_handler, p_orig, &fmt_in, &fmt_out );
575         picture_Release( p_orig );
576         if( !p_pic )
577         {
578             msg_Warn( p_filter, "Error while converting %s", psz_url );
579         }
580     }
581     else
582     {
583         p_pic = p_orig;
584     }
585
586     image_HandlerDelete( p_handler );
587
588     return p_pic;
589 }
590
591 /****************************************************************************
592  * remove all ' ' '\t' '\n' '\r' characters from the begining and end of the
593  * string.
594  ***************************************************************************/
595 static char *removeWhiteChars( const char *psz_src )
596 {
597     char *psz_src2,*psz_clean, *psz_clean2;
598     psz_src2 = psz_clean = strdup( psz_src );
599     int i;
600
601     while( ( *psz_clean == ' ' || *psz_clean == '\t'
602            || *psz_clean == '\n' || *psz_clean == '\r' )
603            && *psz_clean != '\0' )
604     {
605         psz_clean++;
606     }
607     i = strlen( psz_clean );
608     while( --i > 0 &&
609          ( psz_clean[i] == ' ' || psz_clean[i] == '\t'
610         || psz_clean[i] == '\n' || psz_clean[i] == '\r' ) );
611     psz_clean[i+1] = '\0';
612     psz_clean2 = strdup( psz_clean );
613     free( psz_src2 );
614     return psz_clean2;
615 }
616
617
618 /****************************************************************************
619  * Parse url list, psz_urls must be non empty
620  ***************************************************************************/
621 static int ParseUrls( filter_t *p_filter, char *psz_urls )
622 {
623     filter_sys_t *p_sys = p_filter->p_sys;
624     char *psz_urls2 = psz_urls;
625
626     p_sys->i_feeds = 1;
627
628     /* Count the number of feeds */
629     while( *psz_urls )
630     {
631         if( *psz_urls == '|' )
632             p_sys->i_feeds++;
633         psz_urls++;
634     }
635
636     /* Allocate the structure */
637     p_sys->p_feeds = malloc( p_sys->i_feeds * sizeof( rss_feed_t ) );
638     if( !p_sys->p_feeds )
639         return VLC_ENOMEM;
640
641     /* Loop on all urls and fill in the struct */
642     psz_urls = psz_urls2;
643     for( int i = 0; i < p_sys->i_feeds; i++ )
644     {
645         rss_feed_t* p_feed = p_sys->p_feeds + i;
646         char *psz_end;
647
648         if( i < p_sys->i_feeds - 1 )
649         {
650             psz_end = strchr( psz_urls, '|' );
651             *psz_end = '\0';
652         }
653         else
654             psz_end = psz_urls;
655
656         p_feed->i_items = 0;
657         p_feed->p_items = NULL;
658         p_feed->psz_title = NULL;
659         p_feed->psz_link = NULL;
660         p_feed->psz_description = NULL;
661         p_feed->psz_image = NULL;
662         p_feed->p_pic = NULL;
663         p_feed->psz_url = strdup( psz_urls );
664
665         psz_urls = psz_end + 1;
666     }
667
668     return VLC_SUCCESS;
669 }
670
671
672 /****************************************************************************
673  * FetchRSS (or Atom) feeds
674  ***************************************************************************/
675 static int FetchRSS( filter_t *p_filter)
676 {
677     filter_sys_t *p_sys = p_filter->p_sys;
678
679     stream_t *p_stream;
680     xml_t *p_xml;
681     xml_reader_t *p_xml_reader;
682     int i_ret = 1;
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         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             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             p_xml_reader = NULL;
729             goto error;
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             goto error;
737         }
738
739         i_item = 0;
740         b_is_item = false;
741         b_is_image = false;
742
743         while( xml_ReaderRead( p_xml_reader ) == 1 )
744         {
745             switch( xml_ReaderNodeType( p_xml_reader ) )
746             {
747                 // Error
748                 case -1:
749                     goto error;
750
751                 case XML_READER_STARTELEM:
752                     free( psz_eltname );
753                     psz_eltname = xml_ReaderName( p_xml_reader );
754                     if( !psz_eltname )
755                         goto error;
756
757 #                   ifdef RSS_DEBUG
758                     msg_Dbg( p_filter, "element name: %s", psz_eltname );
759 #                   endif
760                     if( !strcmp( psz_eltname, "item" ) /* rss */
761                      || !strcmp( psz_eltname, "entry" ) ) /* atom */
762                     {
763                         b_is_item = true;
764                         p_feed->i_items++;
765                         p_feed->p_items = realloc( p_feed->p_items, p_feed->i_items * sizeof( rss_item_t ) );
766                         p_feed->p_items[p_feed->i_items-1].psz_title = NULL;
767                         p_feed->p_items[p_feed->i_items-1].psz_description
768                                                                      = NULL;
769                         p_feed->p_items[p_feed->i_items-1].psz_link = NULL;
770                     }
771                     else if( !strcmp( psz_eltname, "image" ) ) /* rss */
772                     {
773                         b_is_image = true;
774                     }
775                     else if( !strcmp( psz_eltname, "link" ) ) /* atom */
776                     {
777                         char *psz_href = NULL;
778                         char *psz_rel = NULL;
779                         while( xml_ReaderNextAttr( p_xml_reader )
780                                == VLC_SUCCESS )
781                         {
782                             char *psz_name = xml_ReaderName( p_xml_reader );
783                             char *psz_value = xml_ReaderValue( p_xml_reader );
784                             if( !strcmp( psz_name, "rel" ) )
785                             {
786                                 if( psz_rel )
787                                 {
788                                     msg_Dbg( p_filter, "\"rel\" attribute of link atom duplicated (last value: %s)", psz_value );
789                                     free( psz_rel );
790                                 }
791                                 psz_rel = psz_value;
792                             }
793                             else if( !strcmp( psz_name, "href" ) )
794                             {
795                                 if( psz_href )
796                                 {
797                                     msg_Dbg( p_filter, "\"href\" attribute of link atom duplicated (last value: %s)", psz_href );
798                                     free( psz_href );
799                                 }
800                                 psz_href = psz_value;
801                             }
802                             else
803                             {
804                                 free( psz_value );
805                             }
806                             free( psz_name );
807                         }
808                         if( psz_rel && psz_href )
809                         {
810                             if( !strcmp( psz_rel, "alternate" )
811                                 && b_is_item == false
812                                 && b_is_image == false
813                                 && !p_feed->psz_link )
814                             {
815                                 p_feed->psz_link = psz_href;
816                             }
817                             /* this isn't in the rfc but i found some ... */
818                             else if( ( !strcmp( psz_rel, "logo" )
819                                     || !strcmp( psz_rel, "icon" ) )
820                                     && b_is_item == false
821                                     && b_is_image == false
822                                     && !p_feed->psz_image )
823                             {
824                                 p_feed->psz_image = psz_href;
825                             }
826                             else
827                             {
828                                 free( psz_href );
829                             }
830                         }
831                         else
832                         {
833                             free( psz_href );
834                         }
835                         free( psz_rel );
836                     }
837                     break;
838
839                 case XML_READER_ENDELEM:
840                     free( psz_eltname );
841                     psz_eltname = xml_ReaderName( p_xml_reader );
842                     if( !psz_eltname )
843                         goto error;
844
845 #                   ifdef RSS_DEBUG
846                     msg_Dbg( p_filter, "element end : %s", psz_eltname );
847 #                   endif
848                     if( !strcmp( psz_eltname, "item" ) /* rss */
849                      || !strcmp( psz_eltname, "entry" ) ) /* atom */
850                     {
851                         b_is_item = false;
852                         i_item++;
853                     }
854                     else if( !strcmp( psz_eltname, "image" ) ) /* rss */
855                     {
856                         b_is_image = false;
857                     }
858                     FREENULL( psz_eltname );
859                     break;
860
861                 case XML_READER_TEXT:
862                     if( !psz_eltname ) break;
863                     psz_eltvalue = xml_ReaderValue( p_xml_reader );
864                     if( !psz_eltvalue )
865                     {
866                         goto error;
867                     }
868                     else
869                     {
870                         char *psz_clean = removeWhiteChars( psz_eltvalue );
871                         free( psz_eltvalue );
872                         psz_eltvalue = psz_clean;
873                     }
874 #                   ifdef RSS_DEBUG
875                     msg_Dbg( p_filter, "  text : <%s>", psz_eltvalue );
876 #                   endif
877                     if( b_is_item == true )
878                     {
879                         rss_item_t *p_item = p_feed->p_items+i_item;
880                         if( !strcmp( psz_eltname, "title" ) /* rss/atom */
881                             && !p_item->psz_title )
882                         {
883                             p_item->psz_title = psz_eltvalue;
884                         }
885                         else if( !strcmp( psz_eltname, "link" ) /* rss */
886                                  && !p_item->psz_link )
887                         {
888                             p_item->psz_link = psz_eltvalue;
889                         }
890                         else if((!strcmp( psz_eltname, "description" ) /* rss */
891                               || !strcmp( psz_eltname, "summary" ) ) /* atom */
892                               && !p_item->psz_description )
893                         {
894                             p_item->psz_description = psz_eltvalue;
895                         }
896                         else
897                         {
898                             FREENULL( psz_eltvalue );
899                         }
900                     }
901                     else if( b_is_image == true )
902                     {
903                         if( !strcmp( psz_eltname, "url" ) /* rss */
904                             && !p_feed->psz_image )
905                         {
906                             p_feed->psz_image = psz_eltvalue;
907                         }
908                         else
909                         {
910                             FREENULL( psz_eltvalue );
911                         }
912                     }
913                     else
914                     {
915                         if( !strcmp( psz_eltname, "title" ) /* rss/atom */
916                             && !p_feed->psz_title )
917                         {
918                             p_feed->psz_title = psz_eltvalue;
919                         }
920                         else if( !strcmp( psz_eltname, "link" ) /* rss */
921                                  && !p_feed->psz_link )
922                         {
923                             p_feed->psz_link = psz_eltvalue;
924                         }
925                         else if((!strcmp( psz_eltname, "description" ) /* rss */
926                               || !strcmp( psz_eltname, "subtitle" ) ) /* atom */
927                               && !p_feed->psz_description )
928                         {
929                             p_feed->psz_description = psz_eltvalue;
930                         }
931                         else if( ( !strcmp( psz_eltname, "logo" ) /* atom */
932                               || !strcmp( psz_eltname, "icon" ) ) /* atom */
933                               && !p_feed->psz_image )
934                         {
935                             p_feed->psz_image = psz_eltvalue;
936                         }
937                         else
938                         {
939                             FREENULL( psz_eltvalue );
940                         }
941                     }
942                     break;
943             }
944         }
945
946         if( p_sys->b_images == true
947             && p_feed->psz_image && !p_feed->p_pic )
948         {
949             p_feed->p_pic = LoadImage( p_filter, p_feed->psz_image );
950         }
951
952         xml_ReaderDelete( p_xml, p_xml_reader );
953         stream_Delete( p_stream );
954         msg_Dbg( p_filter, "done with %s RSS/Atom feed", p_feed->psz_url );
955     }
956
957     free( psz_eltname );
958     free( psz_eltvalue );
959     xml_Delete( p_xml );
960     return 0;
961
962 error:
963     free( psz_eltname );
964     free( psz_eltvalue );
965
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 i_ret;
974 }
975
976 /****************************************************************************
977  * FreeRSS
978  ***************************************************************************/
979 static void FreeRSS( filter_t *p_filter)
980 {
981     filter_sys_t *p_sys = p_filter->p_sys;
982
983     for( int i_feed = 0; i_feed < p_sys->i_feeds; i_feed++ )
984     {
985         rss_feed_t *p_feed = p_sys->p_feeds+i_feed;
986         for( int i_item = 0; i_item < p_feed->i_items; i_item++ )
987         {
988             rss_item_t *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 }