]> git.sesse.net Git - vlc/blob - modules/video_filter/rss.c
video_filter_rss: use the return value and fix mutex leak (the mutex was never
[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
104     int i_xoff, i_yoff;  /* offsets for the display string in the video window */
105     int i_pos; /* permit relative positioning (top, bottom, left, right, center) */
106     int i_speed;
107     int i_length;
108
109     char *psz_marquee;    /* marquee string */
110
111     text_style_t *p_style; /* font control */
112
113     mtime_t last_date;
114
115     int i_feeds;
116     rss_feed_t *p_feeds;
117
118     int i_ttl;
119     time_t t_last_update;
120     bool b_images;
121     int i_title;
122
123     int i_cur_feed;
124     int i_cur_item;
125     int i_cur_char;
126 };
127
128 #define MSG_TEXT N_("Feed URLs")
129 #define MSG_LONGTEXT N_("RSS/Atom feed '|' (pipe) separated URLs.")
130 #define SPEED_TEXT N_("Speed of feeds")
131 #define SPEED_LONGTEXT N_("Speed of the RSS/Atom feeds in microseconds (bigger is slower).")
132 #define LENGTH_TEXT N_("Max length")
133 #define LENGTH_LONGTEXT N_("Maximum number of characters displayed on the " \
134                 "screen." )
135 #define TTL_TEXT N_("Refresh time")
136 #define TTL_LONGTEXT N_("Number of seconds between each forced refresh " \
137         "of the feeds. 0 means that the feeds are never updated." )
138 #define IMAGE_TEXT N_("Feed images")
139 #define IMAGE_LONGTEXT N_("Display feed images if available.")
140
141 #define POSX_TEXT N_("X offset")
142 #define POSX_LONGTEXT N_("X offset, from the left screen edge." )
143 #define POSY_TEXT N_("Y offset")
144 #define POSY_LONGTEXT N_("Y offset, down from the top." )
145 #define OPACITY_TEXT N_("Opacity")
146 #define OPACITY_LONGTEXT N_("Opacity (inverse of transparency) of " \
147     "overlay text. 0 = transparent, 255 = totally opaque." )
148
149 #define SIZE_TEXT N_("Font size, pixels")
150 #define SIZE_LONGTEXT N_("Font size, in pixels. Default is -1 (use default " \
151     "font size)." )
152
153 #define COLOR_TEXT N_("Color")
154 #define COLOR_LONGTEXT N_("Color of the text that will be rendered on "\
155     "the video. This must be an hexadecimal (like HTML colors). The first two "\
156     "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
157     " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
158
159 #define POS_TEXT N_("Text position")
160 #define POS_LONGTEXT N_( \
161   "You can enforce the text position on the video " \
162   "(0=center, 1=left, 2=right, 4=top, 8=bottom; you can " \
163   "also use combinations of these values, eg 6 = top-right).")
164
165 #define TITLE_TEXT N_("Title display mode")
166 #define TITLE_LONGTEXT N_("Title display mode. Default is 0 (hidden) if the feed has an image and feed images are enabled, 1 otherwise.")
167
168 static const int pi_pos_values[] = { 0, 1, 2, 4, 8, 5, 6, 9, 10 };
169 static const char *const ppsz_pos_descriptions[] =
170      { N_("Center"), N_("Left"), N_("Right"), N_("Top"), N_("Bottom"),
171      N_("Top-Left"), N_("Top-Right"), N_("Bottom-Left"), N_("Bottom-Right") };
172
173 enum title_modes {
174     default_title=-1,
175     hide_title,
176     prepend_title,
177     scroll_title };
178
179 static const int pi_title_modes[] = { default_title, hide_title, prepend_title, scroll_title };
180 static const char *const ppsz_title_modes[] =
181     { N_("Default"), N_("Don't show"), N_("Always visible"), N_("Scroll with feed") };
182
183 #define CFG_PREFIX "rss-"
184
185 /*****************************************************************************
186  * Module descriptor
187  *****************************************************************************/
188 vlc_module_begin ()
189     set_capability( "sub filter", 1 )
190     set_shortname( "RSS / Atom" )
191     set_callbacks( CreateFilter, DestroyFilter )
192     set_category( CAT_VIDEO )
193     set_subcategory( SUBCAT_VIDEO_SUBPIC )
194     add_string( CFG_PREFIX "urls", "rss", NULL, MSG_TEXT, MSG_LONGTEXT, false )
195
196     set_section( N_("Position"), NULL )
197     add_integer( CFG_PREFIX "x", 0, NULL, POSX_TEXT, POSX_LONGTEXT, true )
198     add_integer( CFG_PREFIX "y", 0, NULL, POSY_TEXT, POSY_LONGTEXT, true )
199     add_integer( CFG_PREFIX "position", -1, NULL, POS_TEXT, POS_LONGTEXT, false )
200         change_integer_list( pi_pos_values, ppsz_pos_descriptions, NULL )
201
202     set_section( N_("Font"), NULL )
203     /* 5 sets the default to top [1] left [4] */
204     add_integer_with_range( CFG_PREFIX "opacity", 255, 0, 255, NULL,
205         OPACITY_TEXT, OPACITY_LONGTEXT, false )
206     add_integer( CFG_PREFIX "color", 0xFFFFFF, NULL, COLOR_TEXT, COLOR_LONGTEXT,
207                   false )
208         change_integer_list( pi_color_values, ppsz_color_descriptions, NULL )
209     add_integer( CFG_PREFIX "size", -1, NULL, SIZE_TEXT, SIZE_LONGTEXT, false )
210
211     set_section( N_("Misc"), NULL )
212     add_integer( CFG_PREFIX "speed", 100000, NULL, SPEED_TEXT, SPEED_LONGTEXT,
213                  false )
214     add_integer( CFG_PREFIX "length", 60, NULL, LENGTH_TEXT, LENGTH_LONGTEXT,
215                  false )
216     add_integer( CFG_PREFIX "ttl", 1800, NULL, TTL_TEXT, TTL_LONGTEXT, false )
217     add_bool( CFG_PREFIX "images", 1, NULL, IMAGE_TEXT, IMAGE_LONGTEXT, false )
218     add_integer( CFG_PREFIX "title", default_title, NULL, TITLE_TEXT, TITLE_LONGTEXT, false )
219         change_integer_list( pi_title_modes, ppsz_title_modes, NULL )
220
221     set_description( N_("RSS and Atom feed display") )
222     add_shortcut( "rss" )
223     add_shortcut( "atom" )
224 vlc_module_end ()
225
226 static const char *const ppsz_filter_options[] = {
227     "urls", "x", "y", "position", "color", "size", "speed", "length",
228     "ttl", "images", "title", NULL
229 };
230
231 /*****************************************************************************
232  * CreateFilter: allocates RSS video filter
233  *****************************************************************************/
234 static int CreateFilter( vlc_object_t *p_this )
235 {
236     filter_t *p_filter = (filter_t *)p_this;
237     filter_sys_t *p_sys;
238     int i_feed;
239     int i_ret = VLC_ENOMEM;
240     char *psz_urls;
241
242     /* Allocate structure */
243     p_sys = p_filter->p_sys = malloc( sizeof( filter_sys_t ) );
244     if( p_sys == NULL )
245         return VLC_ENOMEM;
246
247     config_ChainParse( p_filter, CFG_PREFIX, ppsz_filter_options,
248                        p_filter->p_cfg );
249
250     psz_urls = var_CreateGetString( p_filter, CFG_PREFIX "urls" );
251     p_sys->i_title = var_CreateGetInteger( p_filter, CFG_PREFIX "title" );
252     p_sys->i_cur_feed = 0;
253     p_sys->i_cur_item = p_sys->i_title == scroll_title ? -1 : 0;
254     p_sys->i_cur_char = 0;
255     p_sys->i_feeds = 0;
256     p_sys->p_feeds = NULL;
257     p_sys->i_speed = var_CreateGetInteger( p_filter, CFG_PREFIX "speed" );
258     p_sys->i_length = var_CreateGetInteger( p_filter, CFG_PREFIX "length" );
259     p_sys->i_ttl = __MAX( 0, var_CreateGetInteger( p_filter, CFG_PREFIX "ttl" ) );
260     p_sys->b_images = var_CreateGetBool( p_filter, CFG_PREFIX "images" );
261
262     p_sys->psz_marquee = malloc( p_sys->i_length + 1 );
263     if( p_sys->psz_marquee == NULL )
264         goto error;
265     p_sys->psz_marquee[p_sys->i_length] = '\0';
266
267     p_sys->p_style = text_style_New();
268     if( p_sys->p_style == NULL )
269         goto error;
270
271     p_sys->i_xoff = var_CreateGetInteger( p_filter, CFG_PREFIX "x" );
272     p_sys->i_yoff = var_CreateGetInteger( p_filter, CFG_PREFIX "y" );
273     p_sys->i_pos = var_CreateGetInteger( p_filter, CFG_PREFIX "position" );
274     p_sys->p_style->i_font_alpha = 255 - var_CreateGetInteger( p_filter, CFG_PREFIX "opacity" );
275     p_sys->p_style->i_font_color = var_CreateGetInteger( p_filter, CFG_PREFIX "color" );
276     p_sys->p_style->i_font_size = var_CreateGetInteger( p_filter, CFG_PREFIX "size" );
277
278     if( p_sys->b_images == true && p_sys->p_style->i_font_size == -1 )
279     {
280         msg_Warn( p_filter, "rss-size wasn't specified. Feed images will thus be displayed without being resized" );
281     }
282
283     /* Parse the urls */
284     if( ParseUrls( p_filter, psz_urls ) )
285     {
286         free( psz_urls );
287         goto error;
288     }
289     free( psz_urls );
290
291     if( FetchRSS( p_filter ) )
292     {
293         msg_Err( p_filter, "failed while fetching RSS ... too bad" );
294         text_style_Delete( p_sys->p_style );
295         i_ret = VLC_EGENERIC;
296         goto error;
297     }
298     p_sys->t_last_update = time( NULL );
299
300     if( p_sys->i_feeds == 0 )
301     {
302         text_style_Delete( p_sys->p_style );
303         i_ret = VLC_EGENERIC;
304         goto error;
305     }
306     for( i_feed=0; i_feed < p_sys->i_feeds; i_feed ++ )
307     {
308         if( p_sys->p_feeds[i_feed].i_items == 0 )
309         {
310             DestroyFilter( p_this );
311             return VLC_EGENERIC;
312         }
313     }
314
315     /* Misc init */
316     vlc_mutex_init( &p_sys->lock );
317     p_filter->pf_sub_filter = Filter;
318     p_sys->last_date = (mtime_t)0;
319
320     return VLC_SUCCESS;
321
322 error:
323     free( p_sys->psz_marquee );
324     free( p_sys );
325     return i_ret;
326 }
327 /*****************************************************************************
328  * DestroyFilter: destroy RSS video filter
329  *****************************************************************************/
330 static void DestroyFilter( vlc_object_t *p_this )
331 {
332     filter_t *p_filter = (filter_t *)p_this;
333     filter_sys_t *p_sys = p_filter->p_sys;
334
335     vlc_mutex_destroy( &p_sys->lock );
336     text_style_Delete( p_sys->p_style );
337     free( p_sys->psz_marquee );
338     FreeRSS( p_filter );
339     free( p_sys );
340 }
341
342 /****************************************************************************
343  * Filter: the whole thing
344  ****************************************************************************
345  * This function outputs subpictures at regular time intervals.
346  ****************************************************************************/
347 static subpicture_t *Filter( filter_t *p_filter, mtime_t date )
348 {
349     filter_sys_t *p_sys = p_filter->p_sys;
350     subpicture_t *p_spu;
351     video_format_t fmt;
352     subpicture_region_t *p_region;
353
354     int i_feed, i_item;
355     rss_feed_t *p_feed;
356
357     memset( &fmt, 0, sizeof(video_format_t) );
358
359     vlc_mutex_lock( &p_sys->lock );
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     /* Do we need to update the feeds ? */
371     if( p_sys->i_ttl
372         && time( NULL ) > p_sys->t_last_update + (time_t)p_sys->i_ttl )
373     {
374         msg_Dbg( p_filter, "Forcing update of all the RSS feeds" );
375         if( FetchRSS( p_filter ) )
376         {
377             msg_Err( p_filter, "Failed while fetching RSS ... too bad" );
378             vlc_mutex_unlock( &p_sys->lock );
379             return NULL; /* FIXME : we most likely messed up all the data,
380                           * so we might need to do something about it */
381         }
382         p_sys->t_last_update = time( NULL );
383     }
384
385     p_sys->last_date = date;
386     p_sys->i_cur_char++;
387     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 )
388     {
389         p_sys->i_cur_char = 0;
390         p_sys->i_cur_item++;
391         if( p_sys->i_cur_item >= p_sys->p_feeds[p_sys->i_cur_feed].i_items )
392         {
393             if( p_sys->i_title == scroll_title )
394                 p_sys->i_cur_item = -1;
395             else
396                 p_sys->i_cur_item = 0;
397             p_sys->i_cur_feed = (p_sys->i_cur_feed + 1)%p_sys->i_feeds;
398         }
399     }
400
401     p_spu = filter_NewSubpicture( p_filter );
402     if( !p_spu )
403     {
404         vlc_mutex_unlock( &p_sys->lock );
405         return NULL;
406     }
407
408     fmt.i_chroma = VLC_CODEC_TEXT;
409
410     p_spu->p_region = subpicture_region_New( &fmt );
411     if( !p_spu->p_region )
412     {
413         p_filter->pf_sub_buffer_del( p_filter, p_spu );
414         vlc_mutex_unlock( &p_sys->lock );
415         return NULL;
416     }
417
418     /* Generate the string that will be displayed. This string is supposed to
419        be p_sys->i_length characters long. */
420     i_item = p_sys->i_cur_item;
421     i_feed = p_sys->i_cur_feed;
422     p_feed = &p_sys->p_feeds[i_feed];
423
424     if( ( p_feed->p_pic && p_sys->i_title == default_title )
425         || p_sys->i_title == hide_title )
426     {
427         /* Don't display the feed's title if we have an image */
428         snprintf( p_sys->psz_marquee, p_sys->i_length, "%s",
429                   p_sys->p_feeds[i_feed].p_items[i_item].psz_title
430                   +p_sys->i_cur_char );
431     }
432     else if( ( !p_feed->p_pic && p_sys->i_title == default_title )
433              || p_sys->i_title == prepend_title )
434     {
435         snprintf( p_sys->psz_marquee, p_sys->i_length, "%s : %s",
436                   p_sys->p_feeds[i_feed].psz_title,
437                   p_sys->p_feeds[i_feed].p_items[i_item].psz_title
438                   +p_sys->i_cur_char );
439     }
440     else /* scrolling title */
441     {
442         if( i_item == -1 )
443             snprintf( p_sys->psz_marquee, p_sys->i_length, "%s : %s",
444                       p_sys->p_feeds[i_feed].psz_title + p_sys->i_cur_char,
445                       p_sys->p_feeds[i_feed].p_items[i_item+1].psz_title );
446         else
447             snprintf( p_sys->psz_marquee, p_sys->i_length, "%s",
448                       p_sys->p_feeds[i_feed].p_items[i_item].psz_title
449                       +p_sys->i_cur_char );
450     }
451
452     while( strlen( p_sys->psz_marquee ) < (unsigned int)p_sys->i_length )
453     {
454         i_item++;
455         if( i_item == p_sys->p_feeds[i_feed].i_items ) break;
456         snprintf( strchr( p_sys->psz_marquee, 0 ),
457                   p_sys->i_length - strlen( p_sys->psz_marquee ),
458                   " - %s",
459                   p_sys->p_feeds[i_feed].p_items[i_item].psz_title );
460     }
461
462     /* Calls to snprintf might split multibyte UTF8 chars ...
463      * which freetype doesn't like. */
464     {
465         char *a = strdup( p_sys->psz_marquee );
466         char *a2 = a;
467         char *b = p_sys->psz_marquee;
468         EnsureUTF8( p_sys->psz_marquee );
469         /* we want to use ' ' instead of '?' for erroneous chars */
470         while( *b != '\0' )
471         {
472             if( *b != *a ) *b = ' ';
473             b++;a++;
474         }
475         free( a2 );
476     }
477
478     p_spu->p_region->psz_text = strdup(p_sys->psz_marquee);
479     if( p_sys->p_style->i_font_size > 0 )
480         p_spu->p_region->fmt.i_visible_height = p_sys->p_style->i_font_size;
481     p_spu->i_start = date;
482     p_spu->i_stop  = 0;
483     p_spu->b_ephemer = true;
484
485     /*  where to locate the string: */
486     if( p_sys->i_pos < 0 )
487     {   /*  set to an absolute xy */
488         p_spu->p_region->i_align = OSD_ALIGN_LEFT | OSD_ALIGN_TOP;
489         p_spu->b_absolute = true;
490     }
491     else
492     {   /* set to one of the 9 relative locations */
493         p_spu->p_region->i_align = p_sys->i_pos;
494         p_spu->b_absolute = false;
495     }
496
497     p_spu->p_region->p_style = text_style_Duplicate( p_sys->p_style );
498
499     if( p_feed->p_pic )
500     {
501         /* Display the feed's image */
502         picture_t *p_pic = p_feed->p_pic;
503         video_format_t fmt_out;
504
505         memset( &fmt_out, 0, sizeof(video_format_t) );
506
507         fmt_out.i_chroma = VLC_CODEC_YUVA;
508         fmt_out.i_aspect = VOUT_ASPECT_FACTOR;
509         fmt_out.i_sar_num = fmt_out.i_sar_den = 1;
510         fmt_out.i_width =
511             fmt_out.i_visible_width = p_pic->p[Y_PLANE].i_visible_pitch;
512         fmt_out.i_height =
513             fmt_out.i_visible_height = p_pic->p[Y_PLANE].i_visible_lines;
514
515         p_region = subpicture_region_New( &fmt_out );
516         if( !p_region )
517         {
518             msg_Err( p_filter, "cannot allocate SPU region" );
519         }
520         else
521         {
522             p_region->i_x = p_sys->i_xoff;
523             p_region->i_y = p_sys->i_yoff;
524             /* FIXME the copy is probably not needed anymore */
525             picture_Copy( p_region->p_picture, p_pic );
526             p_spu->p_region->p_next = p_region;
527         }
528
529         /* Offset text to display right next to the image */
530         p_spu->p_region->i_x = p_pic->p[Y_PLANE].i_visible_pitch;
531     }
532
533     vlc_mutex_unlock( &p_sys->lock );
534     return p_spu;
535 }
536
537 /****************************************************************************
538  * RSS related functions
539  ****************************************************************************
540  * You should always lock the p_filter mutex before using any of these
541  * functions
542  ***************************************************************************/
543
544 #undef LoadImage /* do not conflict with Win32 API */
545
546 /****************************************************************************
547  * download and resize image located at psz_url
548  ***************************************************************************/
549 static picture_t *LoadImage( filter_t *p_filter, const char *psz_url )
550 {
551     filter_sys_t *p_sys = p_filter->p_sys;
552     video_format_t fmt_in;
553     video_format_t fmt_out;
554     picture_t *p_orig;
555     picture_t *p_pic = NULL;
556     image_handler_t *p_handler = image_HandlerCreate( p_filter );
557
558     memset( &fmt_in, 0, sizeof(video_format_t) );
559     memset( &fmt_out, 0, sizeof(video_format_t) );
560
561     fmt_out.i_chroma = VLC_CODEC_YUVA;
562     p_orig = image_ReadUrl( p_handler, psz_url, &fmt_in, &fmt_out );
563
564     if( !p_orig )
565     {
566         msg_Warn( p_filter, "Unable to read image %s", psz_url );
567     }
568     else if( p_sys->p_style->i_font_size > 0 )
569     {
570
571         fmt_in.i_chroma = VLC_CODEC_YUVA;
572         fmt_in.i_height = p_orig->p[Y_PLANE].i_visible_lines;
573         fmt_in.i_width = p_orig->p[Y_PLANE].i_visible_pitch;
574         fmt_out.i_width = p_orig->p[Y_PLANE].i_visible_pitch
575             *p_sys->p_style->i_font_size/p_orig->p[Y_PLANE].i_visible_lines;
576         fmt_out.i_height = p_sys->p_style->i_font_size;
577
578         p_pic = image_Convert( p_handler, p_orig, &fmt_in, &fmt_out );
579         picture_Release( p_orig );
580         if( !p_pic )
581         {
582             msg_Warn( p_filter, "Error while converting %s", psz_url );
583         }
584     }
585     else
586     {
587         p_pic = p_orig;
588     }
589
590     image_HandlerDelete( p_handler );
591
592     return p_pic;
593 }
594
595 /****************************************************************************
596  * remove all ' ' '\t' '\n' '\r' characters from the begining and end of the
597  * string.
598  ***************************************************************************/
599 static char *removeWhiteChars( const char *psz_src )
600 {
601     char *psz_src2,*psz_clean, *psz_clean2;
602     psz_src2 = psz_clean = strdup( psz_src );
603     int i;
604
605     while( ( *psz_clean == ' ' || *psz_clean == '\t'
606            || *psz_clean == '\n' || *psz_clean == '\r' )
607            && *psz_clean != '\0' )
608     {
609         psz_clean++;
610     }
611     i = strlen( psz_clean );
612     while( --i > 0 &&
613          ( psz_clean[i] == ' ' || psz_clean[i] == '\t'
614         || psz_clean[i] == '\n' || psz_clean[i] == '\r' ) );
615     psz_clean[i+1] = '\0';
616     psz_clean2 = strdup( psz_clean );
617     free( psz_src2 );
618     return psz_clean2;
619 }
620
621
622 /****************************************************************************
623  * Parse url list, psz_urls must be non empty
624  ***************************************************************************/
625 static int ParseUrls( filter_t *p_filter, char *psz_urls )
626 {
627     filter_sys_t *p_sys = p_filter->p_sys;
628     char *psz_urls2 = psz_urls;
629
630     p_sys->i_feeds = 1;
631
632     /* Count the number of feeds */
633     while( *psz_urls )
634     {
635         if( *psz_urls == '|' )
636             p_sys->i_feeds++;
637         psz_urls++;
638     }
639
640     /* Allocate the structure */
641     p_sys->p_feeds = malloc( p_sys->i_feeds * sizeof( rss_feed_t ) );
642     if( !p_sys->p_feeds )
643         return VLC_ENOMEM;
644
645     /* Loop on all urls and fill in the struct */
646     psz_urls = psz_urls2;
647     for( int i = 0; i < p_sys->i_feeds; i++ )
648     {
649         rss_feed_t* p_feed = p_sys->p_feeds + i;
650         char *psz_end;
651
652         if( i < p_sys->i_feeds - 1 )
653         {
654             psz_end = strchr( psz_urls, '|' );
655             *psz_end = '\0';
656         }
657         else
658             psz_end = psz_urls;
659
660         p_feed->i_items = 0;
661         p_feed->p_items = NULL;
662         p_feed->psz_title = NULL;
663         p_feed->psz_link = NULL;
664         p_feed->psz_description = NULL;
665         p_feed->psz_image = NULL;
666         p_feed->p_pic = NULL;
667         p_feed->psz_url = strdup( psz_urls );
668
669         psz_urls = psz_end + 1;
670     }
671
672     return VLC_SUCCESS;
673 }
674
675
676
677 /****************************************************************************
678  * Parse the rss feed
679  ***************************************************************************/
680 static bool ParseFeed( filter_t *p_filter, xml_reader_t *p_xml_reader,
681                       rss_feed_t *p_feed )
682 {
683     VLC_UNUSED(p_filter);
684     char *psz_eltname = NULL;
685
686     bool b_is_item = false;
687     bool b_is_image = false;
688
689     int i_item = 0;
690
691     while( xml_ReaderRead( p_xml_reader ) == 1 )
692     {
693         switch( xml_ReaderNodeType( p_xml_reader ) )
694         {
695         // Error
696         case -1:
697             goto end;
698
699         case XML_READER_STARTELEM:
700             free( psz_eltname );
701             psz_eltname = xml_ReaderName( p_xml_reader );
702             if( !psz_eltname )
703                 goto end;
704
705 #ifdef RSS_DEBUG
706             msg_Dbg( p_filter, "element name: %s", psz_eltname );
707 #endif
708             /* rss or atom */
709             if( !strcmp( psz_eltname, "item" ) || !strcmp( psz_eltname, "entry" ) )
710             {
711                 b_is_item = true;
712                 p_feed->i_items++;
713                 p_feed->p_items = realloc( p_feed->p_items,
714                                            p_feed->i_items * sizeof( rss_item_t ) );
715                 p_feed->p_items[p_feed->i_items-1].psz_title = NULL;
716                 p_feed->p_items[p_feed->i_items-1].psz_description = NULL;
717                 p_feed->p_items[p_feed->i_items-1].psz_link = NULL;
718             }
719             /* rss */
720             else if( !strcmp( psz_eltname, "image" ) )
721             {
722                 b_is_image = true;
723             }
724             /* atom */
725             else if( !strcmp( psz_eltname, "link" ) )
726             {
727                 char *psz_href = NULL;
728                 char *psz_rel = NULL;
729                 while( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
730                 {
731                     char *psz_name = xml_ReaderName( p_xml_reader );
732                     char *psz_value = xml_ReaderValue( p_xml_reader );
733                     if( !strcmp( psz_name, "rel" ) )
734                     {
735                         free( psz_rel );
736                         psz_rel = psz_value;
737                     }
738                     else if( !strcmp( psz_name, "href" ) )
739                     {
740                         free( psz_href );
741                         psz_href = psz_value;
742                     }
743                     else
744                     {
745                         free( psz_value );
746                     }
747                     free( psz_name );
748                 }
749
750                 /* "rel" and "href" must be defined */
751                 if( psz_rel && psz_href )
752                 {
753                     if( !strcmp( psz_rel, "alternate" ) && !b_is_item &&
754                         !b_is_image && !p_feed->psz_link )
755                     {
756                         p_feed->psz_link = psz_href;
757                     }
758                     /* this isn't in the rfc but i found some ... */
759                     else if( ( !strcmp( psz_rel, "logo" ) ||
760                                !strcmp( psz_rel, "icon" ) )
761                              && !b_is_item && !b_is_image
762                              && !p_feed->psz_image )
763                     {
764                         p_feed->psz_image = psz_href;
765                     }
766                     else
767                     {
768                         free( psz_href );
769                     }
770                 }
771                 else
772                 {
773                     free( psz_href );
774                 }
775                 free( psz_rel );
776             }
777             break;
778
779         case XML_READER_ENDELEM:
780             free( psz_eltname );
781             psz_eltname = xml_ReaderName( p_xml_reader );
782             if( !psz_eltname )
783                 goto end;
784
785 #ifdef RSS_DEBUG
786             msg_Dbg( p_filter, "element end : %s", psz_eltname );
787 #endif
788             /* rss or atom */
789             if( !strcmp( psz_eltname, "item" ) || !strcmp( psz_eltname, "entry" ) )
790             {
791                 b_is_item = false;
792                 i_item++;
793             }
794             /* rss */
795             else if( !strcmp( psz_eltname, "image" ) )
796             {
797                 b_is_image = false;
798             }
799             FREENULL( psz_eltname );
800             break;
801
802         case XML_READER_TEXT:
803         {
804             if( !psz_eltname )
805                 break;
806             char *psz_eltvalue = xml_ReaderValue( p_xml_reader );
807             if( !psz_eltvalue )
808                 goto end;
809
810             char *psz_clean = removeWhiteChars( psz_eltvalue );
811             free( psz_eltvalue );
812             psz_eltvalue = psz_clean;
813
814 #ifdef RSS_DEBUG
815             msg_Dbg( p_filter, "  text : <%s>", psz_eltvalue );
816 #endif
817             /* Is it an item ? */
818             if( b_is_item )
819             {
820                 rss_item_t *p_item = p_feed->p_items+i_item;
821                 /* rss/atom */
822                 if( !strcmp( psz_eltname, "title" ) && !p_item->psz_title )
823                 {
824                     p_item->psz_title = psz_eltvalue;
825                 }
826                 else if( !strcmp( psz_eltname, "link" ) /* rss */
827                          && !p_item->psz_link )
828                 {
829                     p_item->psz_link = psz_eltvalue;
830                 }
831                 /* rss/atom */
832                 else if( ( !strcmp( psz_eltname, "description" ) ||
833                            !strcmp( psz_eltname, "summary" ) )
834                           && !p_item->psz_description )
835                 {
836                     p_item->psz_description = psz_eltvalue;
837                 }
838                 else
839                 {
840                     free( psz_eltvalue );
841                 }
842             }
843             /* Is it an image ? */
844             else if( b_is_image )
845             {
846                 if( !strcmp( psz_eltname, "url" ) && !p_feed->psz_image )
847                     p_feed->psz_image = psz_eltvalue;
848                 else
849                     free( psz_eltvalue );
850             }
851             else
852             {
853                 /* rss/atom */
854                 if( !strcmp( psz_eltname, "title" ) && !p_feed->psz_title )
855                 {
856                     p_feed->psz_title = psz_eltvalue;
857                 }
858                 /* rss */
859                 else if( !strcmp( psz_eltname, "link" ) && !p_feed->psz_link )
860                 {
861                     p_feed->psz_link = psz_eltvalue;
862                 }
863                 /* rss ad atom */
864                 else if( ( !strcmp( psz_eltname, "description" ) ||
865                            !strcmp( psz_eltname, "subtitle" ) )
866                          && !p_feed->psz_description )
867                 {
868                     p_feed->psz_description = psz_eltvalue;
869                 }
870                 /* rss */
871                 else if( ( !strcmp( psz_eltname, "logo" ) ||
872                            !strcmp( psz_eltname, "icon" ) )
873                          && !p_feed->psz_image )
874                 {
875                     p_feed->psz_image = psz_eltvalue;
876                 }
877                 else
878                 {
879                     free( psz_eltvalue );
880                 }
881             }
882             break;
883         }
884         }
885     }
886
887     free( psz_eltname );
888     return true;
889
890 end:
891     free( psz_eltname );
892     return false;
893 }
894
895
896 /****************************************************************************
897  * FetchRSS (or Atom) feeds
898  ***************************************************************************/
899 static int FetchRSS( filter_t *p_filter )
900 {
901     filter_sys_t *p_sys = p_filter->p_sys;
902
903     stream_t *p_stream;
904     xml_t *p_xml;
905     xml_reader_t *p_xml_reader;
906
907     p_xml = xml_Create( p_filter );
908     if( !p_xml )
909     {
910         msg_Err( p_filter, "Failed to open XML parser" );
911         return 1;
912     }
913
914     /* Fetch all feeds and parse them */
915     for( int i_feed = 0; i_feed < p_sys->i_feeds; i_feed++ )
916     {
917         rss_feed_t *p_feed = p_sys->p_feeds+i_feed;
918
919         /* Free the ressources */
920         FREENULL( p_feed->psz_title );
921         FREENULL( p_feed->psz_description );
922         FREENULL( p_feed->psz_link );
923         FREENULL( p_feed->psz_image );
924         if( p_feed->p_pic )
925         {
926             picture_Release( p_feed->p_pic );
927             p_feed->p_pic = NULL;
928         }
929         for( int i = 0; i < p_feed->i_items; i++ )
930         {
931             rss_item_t *p_item = p_feed->p_items + i;
932             free( p_item->psz_title );
933             free( p_item->psz_link );
934             free( p_item->psz_description );
935         }
936         p_feed->i_items = 0;
937         FREENULL( p_feed->p_items );
938
939         /* Fetch the feed */
940         msg_Dbg( p_filter, "opening %s RSS/Atom feed ...", p_feed->psz_url );
941
942         p_stream = stream_UrlNew( p_filter, p_feed->psz_url );
943         if( !p_stream )
944         {
945             msg_Err( p_filter, "Failed to open %s for reading", p_feed->psz_url );
946             p_xml_reader = NULL;
947             goto error;
948         }
949
950         p_xml_reader = xml_ReaderCreate( p_xml, p_stream );
951         if( !p_xml_reader )
952         {
953             msg_Err( p_filter, "Failed to open %s for parsing", p_feed->psz_url );
954             goto error;
955         }
956
957         /* Parse the feed */
958         if( !ParseFeed( p_filter, p_xml_reader, p_feed ) )
959             goto error;
960
961         /* If we have a image: load it if requiere */
962         if( p_sys->b_images == true
963             && p_feed->psz_image && !p_feed->p_pic )
964         {
965             p_feed->p_pic = LoadImage( p_filter, p_feed->psz_image );
966         }
967
968         msg_Dbg( p_filter, "done with %s RSS/Atom feed", p_feed->psz_url );
969         xml_ReaderDelete( p_xml, p_xml_reader );
970         stream_Delete( p_stream );
971     }
972
973     xml_Delete( p_xml );
974     return 0;
975
976 error:
977
978     if( p_xml_reader )
979         xml_ReaderDelete( p_xml, p_xml_reader );
980     if( p_stream )
981         stream_Delete( p_stream );
982     if( p_xml )
983         xml_Delete( p_xml );
984
985     return 1;
986 }
987
988 /****************************************************************************
989  * FreeRSS
990  ***************************************************************************/
991 static void FreeRSS( filter_t *p_filter)
992 {
993     filter_sys_t *p_sys = p_filter->p_sys;
994
995     for( int i_feed = 0; i_feed < p_sys->i_feeds; i_feed++ )
996     {
997         rss_feed_t *p_feed = p_sys->p_feeds+i_feed;
998         for( int i_item = 0; i_item < p_feed->i_items; i_item++ )
999         {
1000             rss_item_t *p_item = p_feed->p_items+i_item;
1001             free( p_item->psz_title );
1002             free( p_item->psz_link );
1003             free( p_item->psz_description );
1004         }
1005         free( p_feed->p_items );
1006         free( p_feed->psz_title);
1007         free( p_feed->psz_link );
1008         free( p_feed->psz_description );
1009         free( p_feed->psz_image );
1010         if( p_feed->p_pic != NULL )
1011             picture_Release( p_feed->p_pic );
1012         free( p_feed->psz_url );
1013     }
1014     free( p_sys->p_feeds );
1015     p_sys->i_feeds = 0;
1016 }