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