]> git.sesse.net Git - vlc/blob - modules/video_filter/rss.c
video_filter_rss: cosmetics.
[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 = NULL;
680     xml_t *p_xml = NULL;
681     xml_reader_t *p_xml_reader = NULL;
682
683     char *psz_eltname = NULL;
684     char *psz_eltvalue = NULL;
685
686     int i_feed;
687     int i_item;
688     bool b_is_item;
689     bool b_is_image;
690
691     p_xml = xml_Create( p_filter );
692     if( !p_xml )
693     {
694         msg_Err( p_filter, "Failed to open XML parser" );
695         return 1;
696     }
697
698     for( i_feed = 0; i_feed < p_sys->i_feeds; i_feed++ )
699     {
700         rss_feed_t *p_feed = p_sys->p_feeds+i_feed;
701
702         FREENULL( p_feed->psz_title );
703         FREENULL( p_feed->psz_description );
704         FREENULL( p_feed->psz_link );
705         FREENULL( p_feed->psz_image );
706         if( p_feed->p_pic )
707         {
708             picture_Release( p_feed->p_pic );
709             p_feed->p_pic = NULL;
710         }
711         for( int i = 0; i < p_feed->i_items; i++ )
712         {
713             rss_item_t *p_item = p_feed->p_items + i;
714             free( p_item->psz_title );
715             free( p_item->psz_link );
716             free( p_item->psz_description );
717         }
718         p_feed->i_items = 0;
719         FREENULL( p_feed->p_items );
720
721         msg_Dbg( p_filter, "opening %s RSS/Atom feed ...", p_feed->psz_url );
722
723         p_stream = stream_UrlNew( p_filter, p_feed->psz_url );
724         if( !p_stream )
725         {
726             msg_Err( p_filter, "Failed to open %s for reading", p_feed->psz_url );
727             xml_Delete( p_xml );
728             return 1;
729         }
730
731         p_xml_reader = xml_ReaderCreate( p_xml, p_stream );
732         if( !p_xml_reader )
733         {
734             msg_Err( p_filter, "Failed to open %s for parsing", p_feed->psz_url );
735             xml_Delete( p_xml );
736             return 1;
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                     return 1;
750
751                 case XML_READER_STARTELEM:
752                     free( psz_eltname );
753                     psz_eltname = xml_ReaderName( p_xml_reader );
754                     if( !psz_eltname )
755                     {
756                         return 1;
757                     }
758 #                   ifdef RSS_DEBUG
759                     msg_Dbg( p_filter, "element name: %s", psz_eltname );
760 #                   endif
761                     if( !strcmp( psz_eltname, "item" ) /* rss */
762                      || !strcmp( psz_eltname, "entry" ) ) /* atom */
763                     {
764                         b_is_item = true;
765                         p_feed->i_items++;
766                         p_feed->p_items = realloc( p_feed->p_items, p_feed->i_items * sizeof( rss_item_t ) );
767                         p_feed->p_items[p_feed->i_items-1].psz_title = NULL;
768                         p_feed->p_items[p_feed->i_items-1].psz_description
769                                                                      = NULL;
770                         p_feed->p_items[p_feed->i_items-1].psz_link = NULL;
771                     }
772                     else if( !strcmp( psz_eltname, "image" ) ) /* rss */
773                     {
774                         b_is_image = true;
775                     }
776                     else if( !strcmp( psz_eltname, "link" ) ) /* atom */
777                     {
778                         char *psz_href = NULL;
779                         char *psz_rel = NULL;
780                         while( xml_ReaderNextAttr( p_xml_reader )
781                                == VLC_SUCCESS )
782                         {
783                             char *psz_name = xml_ReaderName( p_xml_reader );
784                             char *psz_value = xml_ReaderValue( p_xml_reader );
785                             if( !strcmp( psz_name, "rel" ) )
786                             {
787                                 if( psz_rel )
788                                 {
789                                     msg_Dbg( p_filter, "\"rel\" attribute of link atom duplicated (last value: %s)", psz_value );
790                                     free( psz_rel );
791                                 }
792                                 psz_rel = psz_value;
793                             }
794                             else if( !strcmp( psz_name, "href" ) )
795                             {
796                                 if( psz_href )
797                                 {
798                                     msg_Dbg( p_filter, "\"href\" attribute of link atom duplicated (last value: %s)", psz_href );
799                                     free( psz_href );
800                                 }
801                                 psz_href = psz_value;
802                             }
803                             else
804                             {
805                                 free( psz_value );
806                             }
807                             free( psz_name );
808                         }
809                         if( psz_rel && psz_href )
810                         {
811                             if( !strcmp( psz_rel, "alternate" )
812                                 && b_is_item == false
813                                 && b_is_image == false
814                                 && !p_feed->psz_link )
815                             {
816                                 p_feed->psz_link = psz_href;
817                             }
818                             /* this isn't in the rfc but i found some ... */
819                             else if( ( !strcmp( psz_rel, "logo" )
820                                     || !strcmp( psz_rel, "icon" ) )
821                                     && b_is_item == false
822                                     && b_is_image == false
823                                     && !p_feed->psz_image )
824                             {
825                                 p_feed->psz_image = psz_href;
826                             }
827                             else
828                             {
829                                 free( psz_href );
830                             }
831                         }
832                         else
833                         {
834                             free( psz_href );
835                         }
836                         free( psz_rel );
837                     }
838                     break;
839
840                 case XML_READER_ENDELEM:
841                     free( psz_eltname );
842                     psz_eltname = NULL;
843                     psz_eltname = xml_ReaderName( p_xml_reader );
844                     if( !psz_eltname )
845                     {
846                         return 1;
847                     }
848 #                   ifdef RSS_DEBUG
849                     msg_Dbg( p_filter, "element end : %s", psz_eltname );
850 #                   endif
851                     if( !strcmp( psz_eltname, "item" ) /* rss */
852                      || !strcmp( psz_eltname, "entry" ) ) /* atom */
853                     {
854                         b_is_item = false;
855                         i_item++;
856                     }
857                     else if( !strcmp( psz_eltname, "image" ) ) /* rss */
858                     {
859                         b_is_image = false;
860                     }
861                     free( psz_eltname );
862                     psz_eltname = NULL;
863                     break;
864
865                 case XML_READER_TEXT:
866                     if( !psz_eltname ) break;
867                     psz_eltvalue = xml_ReaderValue( p_xml_reader );
868                     if( !psz_eltvalue )
869                     {
870                         return 1;
871                     }
872                     else
873                     {
874                         char *psz_clean;
875                         psz_clean = removeWhiteChars( psz_eltvalue );
876                         free( psz_eltvalue ); psz_eltvalue = psz_clean;
877                     }
878 #                   ifdef RSS_DEBUG
879                     msg_Dbg( p_filter, "  text : <%s>", psz_eltvalue );
880 #                   endif
881                     if( b_is_item == true )
882                     {
883                         rss_item_t *p_item = p_feed->p_items+i_item;
884                         if( !strcmp( psz_eltname, "title" ) /* rss/atom */
885                             && !p_item->psz_title )
886                         {
887                             p_item->psz_title = psz_eltvalue;
888                         }
889                         else if( !strcmp( psz_eltname, "link" ) /* rss */
890                                  && !p_item->psz_link )
891                         {
892                             p_item->psz_link = psz_eltvalue;
893                         }
894                         else if((!strcmp( psz_eltname, "description" ) /* rss */
895                               || !strcmp( psz_eltname, "summary" ) ) /* atom */
896                               && !p_item->psz_description )
897                         {
898                             p_item->psz_description = psz_eltvalue;
899                         }
900                         else
901                         {
902                             free( psz_eltvalue );
903                             psz_eltvalue = NULL;
904                         }
905                     }
906                     else if( b_is_image == true )
907                     {
908                         if( !strcmp( psz_eltname, "url" ) /* rss */
909                             && !p_feed->psz_image )
910                         {
911                             p_feed->psz_image = psz_eltvalue;
912                         }
913                         else
914                         {
915                             free( psz_eltvalue );
916                             psz_eltvalue = NULL;
917                         }
918                     }
919                     else
920                     {
921                         if( !strcmp( psz_eltname, "title" ) /* rss/atom */
922                             && !p_feed->psz_title )
923                         {
924                             p_feed->psz_title = psz_eltvalue;
925                         }
926                         else if( !strcmp( psz_eltname, "link" ) /* rss */
927                                  && !p_feed->psz_link )
928                         {
929                             p_feed->psz_link = psz_eltvalue;
930                         }
931                         else if((!strcmp( psz_eltname, "description" ) /* rss */
932                               || !strcmp( psz_eltname, "subtitle" ) ) /* atom */
933                               && !p_feed->psz_description )
934                         {
935                             p_feed->psz_description = psz_eltvalue;
936                         }
937                         else if( ( !strcmp( psz_eltname, "logo" ) /* atom */
938                               || !strcmp( psz_eltname, "icon" ) ) /* atom */
939                               && !p_feed->psz_image )
940                         {
941                             p_feed->psz_image = psz_eltvalue;
942                         }
943                         else
944                         {
945                             free( psz_eltvalue );
946                             psz_eltvalue = NULL;
947                         }
948                     }
949                     break;
950             }
951         }
952
953         if( p_sys->b_images == true
954             && p_feed->psz_image && !p_feed->p_pic )
955         {
956             p_feed->p_pic = LoadImage( p_filter, p_feed->psz_image );
957         }
958
959         if( p_xml_reader && p_xml ) xml_ReaderDelete( p_xml, p_xml_reader );
960         if( p_stream ) stream_Delete( p_stream );
961         msg_Dbg( p_filter, "done with %s RSS/Atom feed", p_feed->psz_url );
962     }
963     if( p_xml ) xml_Delete( p_xml );
964
965     return 0;
966 }
967
968 /****************************************************************************
969  * FreeRSS
970  ***************************************************************************/
971 static void FreeRSS( filter_t *p_filter)
972 {
973     filter_sys_t *p_sys = p_filter->p_sys;
974
975     for( int i_feed = 0; i_feed < p_sys->i_feeds; i_feed++ )
976     {
977         rss_feed_t *p_feed = p_sys->p_feeds+i_feed;
978         for( int i_item = 0; i_item < p_feed->i_items; i_item++ )
979         {
980             rss_item_t *p_item = p_feed->p_items+i_item;
981             free( p_item->psz_title );
982             free( p_item->psz_link );
983             free( p_item->psz_description );
984         }
985         free( p_feed->p_items );
986         free( p_feed->psz_title);
987         free( p_feed->psz_link );
988         free( p_feed->psz_description );
989         free( p_feed->psz_image );
990         if( p_feed->p_pic != NULL )
991             picture_Release( p_feed->p_pic );
992         free( p_feed->psz_url );
993     }
994     free( p_sys->p_feeds );
995     p_sys->i_feeds = 0;
996 }