]> git.sesse.net Git - vlc/blob - modules/video_filter/rss.c
RSS: remove no-op
[vlc] / modules / video_filter / rss.c
1 /*****************************************************************************
2  * rss.c : rss/atom feed display video plugin for vlc
3  *****************************************************************************
4  * Copyright (C) 2003-2006 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Antoine Cellerier <dionoea -at- videolan -dot- org>
8  *          RĂ©mi Duraffort <ivoire -at- videolan -dot- org>
9  *
10  * This program is free software; you can redistribute it and/or modify it
11  * under the terms of the GNU Lesser General Public License as published by
12  * the Free Software Foundation; either version 2.1 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public License
21  * along with this program; if not, write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 /*****************************************************************************
26  * Atom : http://www.ietf.org/rfc/rfc4287.txt
27  * RSS : http://www.rssboard.org/rss-specification
28  *****************************************************************************/
29
30 /*****************************************************************************
31  * Preamble
32  *****************************************************************************/
33
34 #ifdef HAVE_CONFIG_H
35 # include "config.h"
36 #endif
37
38 #include <vlc_common.h>
39 #include <vlc_plugin.h>
40
41 #include <vlc_filter.h>
42 #include <vlc_block.h>
43
44 #include <vlc_stream.h>
45 #include <vlc_xml.h>
46 #include <vlc_charset.h>
47
48 #include <vlc_image.h>
49
50 #include <time.h>
51
52 /*****************************************************************************
53  * Local prototypes
54  *****************************************************************************/
55 static int  CreateFilter ( vlc_object_t * );
56 static void DestroyFilter( vlc_object_t * );
57 static subpicture_t *Filter( filter_t *, mtime_t );
58
59 static struct rss_feed_t *FetchRSS( filter_t * );
60 static void FreeRSS( struct rss_feed_t *, int );
61 static int ParseUrls( filter_t *, char * );
62
63 static void Fetch( void * );
64
65 static const int pi_color_values[] = {
66                0xf0000000, 0x00000000, 0x00808080, 0x00C0C0C0,
67                0x00FFFFFF, 0x00800000, 0x00FF0000, 0x00FF00FF, 0x00FFFF00,
68                0x00808000, 0x00008000, 0x00008080, 0x0000FF00, 0x00800080,
69                0x00000080, 0x000000FF, 0x0000FFFF};
70 static const char *const ppsz_color_descriptions[] = {
71                N_("Default"), N_("Black"),
72                N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"), N_("Red"),
73                N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"),
74                N_("Teal"), N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"),
75                N_("Aqua") };
76
77 /*****************************************************************************
78  * filter_sys_t: rss filter descriptor
79  *****************************************************************************/
80
81 typedef struct rss_item_t
82 {
83     char *psz_title;
84     char *psz_description;
85     char *psz_link;
86 } rss_item_t;
87
88 typedef struct rss_feed_t
89 {
90     char *psz_url;
91     char *psz_title;
92     char *psz_description;
93     char *psz_link;
94     char *psz_image;
95     picture_t *p_pic;
96
97     int i_items;
98     rss_item_t *p_items;
99 } rss_feed_t;
100
101 struct filter_sys_t
102 {
103     vlc_mutex_t lock;
104     vlc_timer_t timer;  /* Timer to refresh the rss feeds */
105     bool b_fetched;
106
107     int i_xoff, i_yoff;  /* offsets for the display string in the video window */
108     int i_pos; /* permit relative positioning (top, bottom, left, right, center) */
109     int i_speed;
110     int i_length;
111
112     char *psz_marquee;    /* marquee string */
113
114     text_style_t *p_style; /* font control */
115
116     mtime_t last_date;
117
118     int i_feeds;
119     rss_feed_t *p_feeds;
120
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 #define RSS_HELP N_("Display a RSS or ATOM Feed on your video")
170
171 static const int pi_pos_values[] = { 0, 1, 2, 4, 8, 5, 6, 9, 10 };
172 static const char *const ppsz_pos_descriptions[] =
173      { N_("Center"), N_("Left"), N_("Right"), N_("Top"), N_("Bottom"),
174      N_("Top-Left"), N_("Top-Right"), N_("Bottom-Left"), N_("Bottom-Right") };
175
176 enum title_modes {
177     default_title=-1,
178     hide_title,
179     prepend_title,
180     scroll_title };
181
182 static const int pi_title_modes[] = { default_title, hide_title, prepend_title, scroll_title };
183 static const char *const ppsz_title_modes[] =
184     { N_("Default"), N_("Don't show"), N_("Always visible"), N_("Scroll with feed") };
185
186 #define CFG_PREFIX "rss-"
187
188 /*****************************************************************************
189  * Module descriptor
190  *****************************************************************************/
191 vlc_module_begin ()
192     set_capability( "sub source", 1 )
193     set_shortname( N_("RSS / Atom") )
194     set_help(RSS_HELP)
195     set_callbacks( CreateFilter, DestroyFilter )
196     set_category( CAT_VIDEO )
197     set_subcategory( SUBCAT_VIDEO_SUBPIC )
198     add_string( CFG_PREFIX "urls", NULL, MSG_TEXT, MSG_LONGTEXT, false )
199
200     set_section( N_("Position"), NULL )
201     add_integer( CFG_PREFIX "x", 0, POSX_TEXT, POSX_LONGTEXT, true )
202     add_integer( CFG_PREFIX "y", 0, POSY_TEXT, POSY_LONGTEXT, true )
203     add_integer( CFG_PREFIX "position", -1, POS_TEXT, POS_LONGTEXT, false )
204         change_integer_list( pi_pos_values, ppsz_pos_descriptions )
205
206     set_section( N_("Font"), NULL )
207     /* 5 sets the default to top [1] left [4] */
208     add_integer_with_range( CFG_PREFIX "opacity", 255, 0, 255,
209         OPACITY_TEXT, OPACITY_LONGTEXT, false )
210     add_rgb( CFG_PREFIX "color", 0xFFFFFF, COLOR_TEXT, COLOR_LONGTEXT,
211                   false )
212         change_integer_list( pi_color_values, ppsz_color_descriptions )
213     add_integer( CFG_PREFIX "size", -1, SIZE_TEXT, SIZE_LONGTEXT, false )
214         change_integer_range( -1, 4096)
215
216     set_section( N_("Misc"), NULL )
217     add_integer( CFG_PREFIX "speed", 100000, SPEED_TEXT, SPEED_LONGTEXT,
218                  false )
219     add_integer( CFG_PREFIX "length", 60, LENGTH_TEXT, LENGTH_LONGTEXT,
220                  false )
221     add_integer( CFG_PREFIX "ttl", 1800, TTL_TEXT, TTL_LONGTEXT, false )
222     add_bool( CFG_PREFIX "images", true, IMAGE_TEXT, IMAGE_LONGTEXT, false )
223     add_integer( CFG_PREFIX "title", default_title, TITLE_TEXT, TITLE_LONGTEXT,
224                  false )
225         change_integer_list( pi_title_modes, ppsz_title_modes )
226
227     set_description( N_("RSS and Atom feed display") )
228     add_shortcut( "rss", "atom" )
229 vlc_module_end ()
230
231 static const char *const ppsz_filter_options[] = {
232     "urls", "x", "y", "position", "opacity", "color", "size", "speed", "length",
233     "ttl", "images", "title", NULL
234 };
235
236 /*****************************************************************************
237  * CreateFilter: allocates RSS video filter
238  *****************************************************************************/
239 static int CreateFilter( vlc_object_t *p_this )
240 {
241     filter_t *p_filter = (filter_t *)p_this;
242     filter_sys_t *p_sys;
243     char *psz_urls;
244     int i_ttl;
245
246     /* Allocate structure */
247     p_sys = p_filter->p_sys = malloc( sizeof( filter_sys_t ) );
248     if( p_sys == NULL )
249         return VLC_ENOMEM;
250
251     config_ChainParse( p_filter, CFG_PREFIX, ppsz_filter_options,
252                        p_filter->p_cfg );
253
254     /* Get the urls to parse: must be non empty */
255     psz_urls = var_CreateGetNonEmptyString( p_filter, CFG_PREFIX "urls" );
256     if( !psz_urls )
257     {
258         msg_Err( p_filter, "The list of urls must not be empty" );
259         free( p_sys );
260         return VLC_EGENERIC;
261     }
262
263     /* Fill the p_sys structure with the configuration */
264     p_sys->i_title = var_CreateGetInteger( p_filter, CFG_PREFIX "title" );
265     p_sys->i_cur_feed = 0;
266     p_sys->i_cur_item = p_sys->i_title == scroll_title ? -1 : 0;
267     p_sys->i_cur_char = 0;
268     p_sys->i_feeds = 0;
269     p_sys->p_feeds = NULL;
270     p_sys->i_speed = var_CreateGetInteger( p_filter, CFG_PREFIX "speed" );
271     p_sys->i_length = var_CreateGetInteger( p_filter, CFG_PREFIX "length" );
272     p_sys->b_images = var_CreateGetBool( p_filter, CFG_PREFIX "images" );
273
274     i_ttl = __MAX( 0, var_CreateGetInteger( p_filter, CFG_PREFIX "ttl" ) );
275
276     p_sys->psz_marquee = malloc( p_sys->i_length + 1 );
277     if( p_sys->psz_marquee == NULL )
278     {
279         free( psz_urls );
280         free( p_sys );
281         return VLC_ENOMEM;
282     }
283     p_sys->psz_marquee[p_sys->i_length] = '\0';
284
285     p_sys->p_style = text_style_New();
286     if( p_sys->p_style == NULL )
287         goto error;
288
289     p_sys->i_xoff = var_CreateGetInteger( p_filter, CFG_PREFIX "x" );
290     p_sys->i_yoff = var_CreateGetInteger( p_filter, CFG_PREFIX "y" );
291     p_sys->i_pos = var_CreateGetInteger( p_filter, CFG_PREFIX "position" );
292     p_sys->p_style->i_font_alpha = var_CreateGetInteger( p_filter, CFG_PREFIX "opacity" );
293     p_sys->p_style->i_font_color = var_CreateGetInteger( p_filter, CFG_PREFIX "color" );
294     p_sys->p_style->i_font_size = var_CreateGetInteger( p_filter, CFG_PREFIX "size" );
295
296     if( p_sys->b_images && p_sys->p_style->i_font_size == -1 )
297     {
298         msg_Warn( p_filter, "rss-size wasn't specified. Feed images will thus "
299                             "be displayed without being resized" );
300     }
301
302     /* Parse the urls */
303     if( ParseUrls( p_filter, psz_urls ) )
304         goto error;
305
306     /* Misc init */
307     vlc_mutex_init( &p_sys->lock );
308     p_filter->pf_sub_source = Filter;
309     p_sys->last_date = (mtime_t)0;
310     p_sys->b_fetched = false;
311
312     /* Create and arm the timer */
313     if( vlc_timer_create( &p_sys->timer, Fetch, p_filter ) )
314     {
315         vlc_mutex_destroy( &p_sys->lock );
316         goto error;
317     }
318     vlc_timer_schedule( p_sys->timer, false, 1,
319                         (mtime_t)(i_ttl)*1000000 );
320
321     free( psz_urls );
322     return VLC_SUCCESS;
323
324 error:
325     if( p_sys->p_style )
326         text_style_Delete( p_sys->p_style );
327     free( p_sys->psz_marquee );
328     free( psz_urls );
329     free( p_sys );
330     return VLC_ENOMEM;
331 }
332 /*****************************************************************************
333  * DestroyFilter: destroy RSS video filter
334  *****************************************************************************/
335 static void DestroyFilter( vlc_object_t *p_this )
336 {
337     filter_t *p_filter = (filter_t *)p_this;
338     filter_sys_t *p_sys = p_filter->p_sys;
339
340     vlc_timer_destroy( p_sys->timer );
341     vlc_mutex_destroy( &p_sys->lock );
342
343     text_style_Delete( p_sys->p_style );
344     free( p_sys->psz_marquee );
345     FreeRSS( p_sys->p_feeds, p_sys->i_feeds );
346     free( p_sys );
347 }
348
349 /****************************************************************************
350  * Filter: the whole thing
351  ****************************************************************************
352  * This function outputs subpictures at regular time intervals.
353  ****************************************************************************/
354 static subpicture_t *Filter( filter_t *p_filter, mtime_t date )
355 {
356     filter_sys_t *p_sys = p_filter->p_sys;
357     subpicture_t *p_spu;
358     video_format_t fmt;
359     subpicture_region_t *p_region;
360
361     int i_feed, i_item;
362     rss_feed_t *p_feed;
363
364     memset( &fmt, 0, sizeof(video_format_t) );
365
366     vlc_mutex_lock( &p_sys->lock );
367
368     /* Check if the feeds have been fetched and that we have some feeds */
369     /* TODO: check that we have items for each feeds */
370     if( !p_sys->b_fetched && p_sys->i_feeds > 0 )
371     {
372         vlc_mutex_unlock( &p_sys->lock );
373         return NULL;
374     }
375
376     if( p_sys->last_date
377        + ( p_sys->i_cur_char == 0 &&
378            p_sys->i_cur_item == ( p_sys->i_title == scroll_title ? -1 : 0 ) ? 5 : 1 )
379            /* ( ... ? 5 : 1 ) means "wait 5 times more for the 1st char" */
380        * p_sys->i_speed > date )
381     {
382         vlc_mutex_unlock( &p_sys->lock );
383         return NULL;
384     }
385
386     p_sys->last_date = date;
387     p_sys->i_cur_char++;
388
389     if( p_sys->i_cur_item == -1 ?
390             p_sys->p_feeds[p_sys->i_cur_feed].psz_title[p_sys->i_cur_char] == 0 :
391             p_sys->p_feeds[p_sys->i_cur_feed].p_items[p_sys->i_cur_item].psz_title[p_sys->i_cur_char] == 0 )
392     {
393         p_sys->i_cur_char = 0;
394         p_sys->i_cur_item++;
395         if( p_sys->i_cur_item >= p_sys->p_feeds[p_sys->i_cur_feed].i_items )
396         {
397             if( p_sys->i_title == scroll_title )
398                 p_sys->i_cur_item = -1;
399             else
400                 p_sys->i_cur_item = 0;
401             p_sys->i_cur_feed = (p_sys->i_cur_feed + 1)%p_sys->i_feeds;
402         }
403     }
404
405     p_spu = filter_NewSubpicture( p_filter );
406     if( !p_spu )
407     {
408         vlc_mutex_unlock( &p_sys->lock );
409         return NULL;
410     }
411
412     fmt.i_chroma = VLC_CODEC_TEXT;
413
414     p_spu->p_region = subpicture_region_New( &fmt );
415     if( !p_spu->p_region )
416     {
417         subpicture_Delete( p_spu );
418         vlc_mutex_unlock( &p_sys->lock );
419         return NULL;
420     }
421
422     /* Generate the string that will be displayed. This string is supposed to
423        be p_sys->i_length characters long. */
424     i_item = p_sys->i_cur_item;
425     i_feed = p_sys->i_cur_feed;
426     p_feed = &p_sys->p_feeds[i_feed];
427
428     if( ( p_feed->p_pic && p_sys->i_title == default_title )
429         || p_sys->i_title == hide_title )
430     {
431         /* Don't display the feed's title if we have an image */
432         snprintf( p_sys->psz_marquee, p_sys->i_length, "%s",
433                   p_sys->p_feeds[i_feed].p_items[i_item].psz_title
434                   +p_sys->i_cur_char );
435     }
436     else if( ( !p_feed->p_pic && p_sys->i_title == default_title )
437              || p_sys->i_title == prepend_title )
438     {
439         snprintf( p_sys->psz_marquee, p_sys->i_length, "%s : %s",
440                   p_sys->p_feeds[i_feed].psz_title,
441                   p_sys->p_feeds[i_feed].p_items[i_item].psz_title
442                   +p_sys->i_cur_char );
443     }
444     else /* scrolling title */
445     {
446         if( i_item == -1 )
447             snprintf( p_sys->psz_marquee, p_sys->i_length, "%s : %s",
448                       p_sys->p_feeds[i_feed].psz_title + p_sys->i_cur_char,
449                       p_sys->p_feeds[i_feed].p_items[i_item+1].psz_title );
450         else
451             snprintf( p_sys->psz_marquee, p_sys->i_length, "%s",
452                       p_sys->p_feeds[i_feed].p_items[i_item].psz_title
453                       +p_sys->i_cur_char );
454     }
455
456     while( strlen( p_sys->psz_marquee ) < (unsigned int)p_sys->i_length )
457     {
458         i_item++;
459         if( i_item == p_sys->p_feeds[i_feed].i_items ) break;
460         snprintf( strchr( p_sys->psz_marquee, 0 ),
461                   p_sys->i_length - strlen( p_sys->psz_marquee ),
462                   " - %s",
463                   p_sys->p_feeds[i_feed].p_items[i_item].psz_title );
464     }
465
466     /* Calls to snprintf might split multibyte UTF8 chars ...
467      * which freetype doesn't like. */
468     {
469         char *a = strdup( p_sys->psz_marquee );
470         char *a2 = a;
471         char *b = p_sys->psz_marquee;
472         EnsureUTF8( p_sys->psz_marquee );
473         /* we want to use ' ' instead of '?' for erroneous chars */
474         while( *b != '\0' )
475         {
476             if( *b != *a ) *b = ' ';
477             b++;a++;
478         }
479         free( a2 );
480     }
481
482     p_spu->p_region->psz_text = strdup(p_sys->psz_marquee);
483     if( p_sys->p_style->i_font_size > 0 )
484         p_spu->p_region->fmt.i_visible_height = p_sys->p_style->i_font_size;
485     p_spu->i_start = date;
486     p_spu->i_stop  = 0;
487     p_spu->b_ephemer = true;
488
489     /*  where to locate the string: */
490     if( p_sys->i_pos < 0 )
491     {   /*  set to an absolute xy */
492         p_spu->p_region->i_align = SUBPICTURE_ALIGN_LEFT | SUBPICTURE_ALIGN_TOP;
493         p_spu->b_absolute = true;
494     }
495     else
496     {   /* set to one of the 9 relative locations */
497         p_spu->p_region->i_align = p_sys->i_pos;
498         p_spu->b_absolute = false;
499     }
500     p_spu->p_region->i_x = p_sys->i_xoff;
501     p_spu->p_region->i_y = p_sys->i_yoff;
502
503     p_spu->p_region->p_style = text_style_Duplicate( p_sys->p_style );
504
505     if( p_feed->p_pic )
506     {
507         /* Display the feed's image */
508         picture_t *p_pic = p_feed->p_pic;
509         video_format_t fmt_out;
510
511         memset( &fmt_out, 0, sizeof(video_format_t) );
512
513         fmt_out.i_chroma = VLC_CODEC_YUVA;
514         fmt_out.i_sar_num = fmt_out.i_sar_den = 1;
515         fmt_out.i_width =
516             fmt_out.i_visible_width = p_pic->p[Y_PLANE].i_visible_pitch;
517         fmt_out.i_height =
518             fmt_out.i_visible_height = p_pic->p[Y_PLANE].i_visible_lines;
519
520         p_region = subpicture_region_New( &fmt_out );
521         if( !p_region )
522         {
523             msg_Err( p_filter, "cannot allocate SPU region" );
524         }
525         else
526         {
527             p_region->i_x = p_spu->p_region->i_x;
528             p_region->i_y = p_spu->p_region->i_y;
529             /* FIXME the copy is probably not needed anymore */
530             picture_Copy( p_region->p_picture, p_pic );
531             p_spu->p_region->p_next = p_region;
532
533             /* Offset text to display right next to the image */
534             p_spu->p_region->i_x += fmt_out.i_visible_width;
535         }
536     }
537
538     vlc_mutex_unlock( &p_sys->lock );
539     return p_spu;
540 }
541
542 /****************************************************************************
543  * RSS related functions
544  ****************************************************************************
545  * You should always lock the p_filter mutex before using any of these
546  * functions
547  ***************************************************************************/
548
549 #undef LoadImage /* do not conflict with Win32 API */
550
551 /****************************************************************************
552  * download and resize image located at psz_url
553  ***************************************************************************/
554 static picture_t *LoadImage( filter_t *p_filter, const char *psz_url )
555 {
556     filter_sys_t *p_sys = p_filter->p_sys;
557     video_format_t fmt_in;
558     video_format_t fmt_out;
559     picture_t *p_orig;
560     picture_t *p_pic = NULL;
561     image_handler_t *p_handler = image_HandlerCreate( p_filter );
562
563     memset( &fmt_in, 0, sizeof(video_format_t) );
564     memset( &fmt_out, 0, sizeof(video_format_t) );
565
566     fmt_out.i_chroma = VLC_CODEC_YUVA;
567     p_orig = image_ReadUrl( p_handler, psz_url, &fmt_in, &fmt_out );
568
569     if( !p_orig )
570     {
571         msg_Warn( p_filter, "Unable to read image %s", psz_url );
572     }
573     else if( p_sys->p_style->i_font_size > 0 )
574     {
575
576         fmt_in.i_chroma = VLC_CODEC_YUVA;
577         fmt_in.i_height = p_orig->p[Y_PLANE].i_visible_lines;
578         fmt_in.i_width = p_orig->p[Y_PLANE].i_visible_pitch;
579         fmt_out.i_width = p_orig->p[Y_PLANE].i_visible_pitch
580             *p_sys->p_style->i_font_size/p_orig->p[Y_PLANE].i_visible_lines;
581         fmt_out.i_height = p_sys->p_style->i_font_size;
582
583         p_pic = image_Convert( p_handler, p_orig, &fmt_in, &fmt_out );
584         picture_Release( p_orig );
585         if( !p_pic )
586         {
587             msg_Warn( p_filter, "Error while converting %s", psz_url );
588         }
589     }
590     else
591     {
592         p_pic = p_orig;
593     }
594
595     image_HandlerDelete( p_handler );
596
597     return p_pic;
598 }
599
600 /****************************************************************************
601  * remove all ' ' '\t' '\n' '\r' characters from the begining and end of the
602  * string.
603  ***************************************************************************/
604 static char *removeWhiteChars( const char *psz_src )
605 {
606     char *psz_src2,*psz_clean, *psz_clean2;
607     psz_src2 = psz_clean = strdup( psz_src );
608     int i;
609
610     while( ( *psz_clean == ' ' || *psz_clean == '\t'
611            || *psz_clean == '\n' || *psz_clean == '\r' )
612            && *psz_clean != '\0' )
613     {
614         psz_clean++;
615     }
616     i = strlen( psz_clean );
617     while( --i > 0 &&
618          ( psz_clean[i] == ' ' || psz_clean[i] == '\t'
619         || psz_clean[i] == '\n' || psz_clean[i] == '\r' ) );
620     psz_clean[i+1] = '\0';
621     psz_clean2 = strdup( psz_clean );
622     free( psz_src2 );
623     return psz_clean2;
624 }
625
626
627 /****************************************************************************
628  * Parse url list, psz_urls must be non empty (TODO: check it !)
629  ***************************************************************************/
630 static int ParseUrls( filter_t *p_filter, char *psz_urls )
631 {
632     filter_sys_t *p_sys = p_filter->p_sys;
633     char *psz_urls2 = psz_urls;
634
635     p_sys->i_feeds = 1;
636
637     /* Count the number of feeds */
638     while( *psz_urls )
639     {
640         if( *psz_urls == '|' )
641             p_sys->i_feeds++;
642         psz_urls++;
643     }
644
645     /* Allocate the structure */
646     p_sys->p_feeds = malloc( p_sys->i_feeds * sizeof( rss_feed_t ) );
647     if( !p_sys->p_feeds )
648         return VLC_ENOMEM;
649
650     /* Loop on all urls and fill in the struct */
651     psz_urls = psz_urls2;
652     for( int i = 0; i < p_sys->i_feeds; i++ )
653     {
654         rss_feed_t* p_feed = p_sys->p_feeds + i;
655         char *psz_end;
656
657         if( i < p_sys->i_feeds - 1 )
658         {
659             psz_end = strchr( psz_urls, '|' );
660             *psz_end = '\0';
661         }
662         else
663             psz_end = psz_urls;
664
665         p_feed->i_items = 0;
666         p_feed->p_items = NULL;
667         p_feed->psz_title = NULL;
668         p_feed->psz_link = NULL;
669         p_feed->psz_description = NULL;
670         p_feed->psz_image = NULL;
671         p_feed->p_pic = NULL;
672         p_feed->psz_url = strdup( psz_urls );
673
674         psz_urls = psz_end + 1;
675     }
676
677     return VLC_SUCCESS;
678 }
679
680
681
682 /****************************************************************************
683  * Parse the rss feed
684  ***************************************************************************/
685 static bool ParseFeed( filter_t *p_filter, xml_reader_t *p_xml_reader,
686                       rss_feed_t *p_feed )
687 {
688     VLC_UNUSED(p_filter);
689     const char *node;
690     char *psz_eltname = NULL;
691
692     bool b_is_item = false;
693     bool b_is_image = false;
694
695     int i_item = 0;
696     int type;
697
698     while( (type = xml_ReaderNextNode( p_xml_reader, &node )) > 0 )
699     {
700         switch( type )
701         {
702         case XML_READER_STARTELEM:
703 #ifdef RSS_DEBUG
704             msg_Dbg( p_filter, "element <%s>", node );
705 #endif
706             psz_eltname = strdup( node );
707             if( unlikely(!psz_eltname) )
708                 goto end;
709
710             /* rss or atom */
711             if( !strcmp( node, "item" ) || !strcmp( node, "entry" ) )
712             {
713                 b_is_item = true;
714                 p_feed->i_items++;
715                 p_feed->p_items = xrealloc( p_feed->p_items,
716                                      p_feed->i_items * sizeof( rss_item_t ) );
717                 p_feed->p_items[p_feed->i_items-1].psz_title = NULL;
718                 p_feed->p_items[p_feed->i_items-1].psz_description = NULL;
719                 p_feed->p_items[p_feed->i_items-1].psz_link = NULL;
720             }
721             /* rss */
722             else if( !strcmp( node, "image" ) )
723             {
724                 b_is_image = true;
725             }
726             /* atom */
727             else if( !strcmp( node, "link" ) )
728             {
729                 const char *name, *value;
730                 char *psz_href = NULL;
731                 char *psz_rel = NULL;
732
733                 while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
734                 {
735                     if( !strcmp( name, "rel" ) )
736                     {
737                         free( psz_rel );
738                         psz_rel = strdup( value );
739                     }
740                     else if( !strcmp( name, "href" ) )
741                     {
742                         free( psz_href );
743                         psz_href = strdup( value );
744                     }
745                 }
746
747                 /* "rel" and "href" must be defined */
748                 if( psz_rel && psz_href )
749                 {
750                     if( !strcmp( psz_rel, "alternate" ) && !b_is_item &&
751                         !b_is_image && !p_feed->psz_link )
752                     {
753                         p_feed->psz_link = psz_href;
754                     }
755                     /* this isn't in the rfc but i found some ... */
756                     else if( ( !strcmp( psz_rel, "logo" ) ||
757                                !strcmp( psz_rel, "icon" ) )
758                              && !b_is_item && !b_is_image
759                              && !p_feed->psz_image )
760                     {
761                         p_feed->psz_image = psz_href;
762                     }
763                     else
764                     {
765                         free( psz_href );
766                     }
767                 }
768                 else
769                 {
770                     free( psz_href );
771                 }
772                 free( psz_rel );
773             }
774             break;
775
776         case XML_READER_ENDELEM:
777             FREENULL( psz_eltname );
778 #ifdef RSS_DEBUG
779             msg_Dbg( p_filter, "element end </%s>", node );
780 #endif
781             /* rss or atom */
782             if( !strcmp( node, "item" ) || !strcmp( node, "entry" ) )
783             {
784                 b_is_item = false;
785                 i_item++;
786             }
787             /* rss */
788             else if( !strcmp( node, "image" ) )
789             {
790                 b_is_image = false;
791             }
792             break;
793
794         case XML_READER_TEXT:
795         {
796             if( !psz_eltname )
797                 break;
798
799             char *psz_eltvalue = removeWhiteChars( node );
800
801 #ifdef RSS_DEBUG
802             msg_Dbg( p_filter, "  text : \"%s\"", psz_eltvalue );
803 #endif
804             /* Is it an item ? */
805             if( b_is_item )
806             {
807                 rss_item_t *p_item = p_feed->p_items+i_item;
808                 /* rss/atom */
809                 if( !strcmp( psz_eltname, "title" ) && !p_item->psz_title )
810                 {
811                     p_item->psz_title = psz_eltvalue;
812                 }
813                 else if( !strcmp( psz_eltname, "link" ) /* rss */
814                          && !p_item->psz_link )
815                 {
816                     p_item->psz_link = psz_eltvalue;
817                 }
818                 /* rss/atom */
819                 else if( ( !strcmp( psz_eltname, "description" ) ||
820                            !strcmp( psz_eltname, "summary" ) )
821                           && !p_item->psz_description )
822                 {
823                     p_item->psz_description = psz_eltvalue;
824                 }
825                 else
826                 {
827                     free( psz_eltvalue );
828                 }
829             }
830             /* Is it an image ? */
831             else if( b_is_image )
832             {
833                 if( !strcmp( psz_eltname, "url" ) && !p_feed->psz_image )
834                     p_feed->psz_image = psz_eltvalue;
835                 else
836                     free( psz_eltvalue );
837             }
838             else
839             {
840                 /* rss/atom */
841                 if( !strcmp( psz_eltname, "title" ) && !p_feed->psz_title )
842                 {
843                     p_feed->psz_title = psz_eltvalue;
844                 }
845                 /* rss */
846                 else if( !strcmp( psz_eltname, "link" ) && !p_feed->psz_link )
847                 {
848                     p_feed->psz_link = psz_eltvalue;
849                 }
850                 /* rss ad atom */
851                 else if( ( !strcmp( psz_eltname, "description" ) ||
852                            !strcmp( psz_eltname, "subtitle" ) )
853                          && !p_feed->psz_description )
854                 {
855                     p_feed->psz_description = psz_eltvalue;
856                 }
857                 /* rss */
858                 else if( ( !strcmp( psz_eltname, "logo" ) ||
859                            !strcmp( psz_eltname, "icon" ) )
860                          && !p_feed->psz_image )
861                 {
862                     p_feed->psz_image = psz_eltvalue;
863                 }
864                 else
865                 {
866                     free( psz_eltvalue );
867                 }
868             }
869             break;
870         }
871         }
872     }
873
874     free( psz_eltname );
875     return true;
876
877 end:
878     return false;
879 }
880
881
882 /****************************************************************************
883  * FetchRSS (or Atom) feeds
884  ***************************************************************************/
885 static rss_feed_t* FetchRSS( filter_t *p_filter )
886 {
887     filter_sys_t *p_sys = p_filter->p_sys;
888
889     stream_t *p_stream;
890     xml_t *p_xml;
891     xml_reader_t *p_xml_reader;
892     int i_feed;
893
894     /* These data are not modified after the creation of the module so we don't
895        need to hold the lock */
896     int i_feeds = p_sys->i_feeds;
897     bool b_images = p_sys->b_images;
898
899     /* Allocate a new structure */
900     rss_feed_t *p_feeds = malloc( i_feeds * sizeof( rss_feed_t ) );
901     if( !p_feeds )
902         return NULL;
903
904     p_xml = xml_Create( p_filter );
905     if( !p_xml )
906     {
907         msg_Err( p_filter, "Failed to open XML parser" );
908         free( p_feeds );
909         return NULL;
910     }
911
912     /* Fetch all feeds and parse them */
913     for( i_feed = 0; i_feed < i_feeds; i_feed++ )
914     {
915         rss_feed_t *p_feed = p_feeds + i_feed;
916         rss_feed_t *p_old_feed = p_sys->p_feeds + i_feed;
917
918         /* Initialize the structure */
919         p_feed->psz_title = NULL;
920         p_feed->psz_description = NULL;
921         p_feed->psz_link = NULL;
922         p_feed->psz_image = NULL;
923         p_feed->p_pic = NULL;
924         p_feed->i_items = 0;
925         p_feed->p_items = NULL;
926
927         p_feed->psz_url = strdup( p_old_feed->psz_url );
928
929         /* Fetch the feed */
930         msg_Dbg( p_filter, "opening %s RSS/Atom feed ...", p_feed->psz_url );
931
932         p_stream = stream_UrlNew( p_filter, p_feed->psz_url );
933         if( !p_stream )
934         {
935             msg_Err( p_filter, "Failed to open %s for reading", p_feed->psz_url );
936             p_xml_reader = NULL;
937             goto error;
938         }
939
940         p_xml_reader = xml_ReaderCreate( p_xml, p_stream );
941         if( !p_xml_reader )
942         {
943             msg_Err( p_filter, "Failed to open %s for parsing", p_feed->psz_url );
944             goto error;
945         }
946
947         /* Parse the feed */
948         if( !ParseFeed( p_filter, p_xml_reader, p_feed ) )
949             goto error;
950
951         /* If we have a image: load it if requiere */
952         if( b_images && p_feed->psz_image && !p_feed->p_pic )
953         {
954             p_feed->p_pic = LoadImage( p_filter, p_feed->psz_image );
955         }
956
957         msg_Dbg( p_filter, "done with %s RSS/Atom feed", p_feed->psz_url );
958         xml_ReaderDelete( p_xml_reader );
959         stream_Delete( p_stream );
960     }
961
962     xml_Delete( p_xml );
963     return p_feeds;
964
965 error:
966     FreeRSS( p_feeds, i_feed + 1 );
967     if( p_xml_reader )
968         xml_ReaderDelete( p_xml_reader );
969     if( p_stream )
970         stream_Delete( p_stream );
971     if( p_xml )
972         xml_Delete( p_xml );
973
974     return NULL;
975 }
976
977 /****************************************************************************
978  * FreeRSS
979  ***************************************************************************/
980 static void FreeRSS( rss_feed_t *p_feeds, int i_feeds )
981 {
982     for( int i_feed = 0; i_feed < i_feeds; i_feed++ )
983     {
984         rss_feed_t *p_feed = p_feeds+i_feed;
985         for( int i_item = 0; i_item < p_feed->i_items; i_item++ )
986         {
987             rss_item_t *p_item = p_feed->p_items+i_item;
988             free( p_item->psz_title );
989             free( p_item->psz_link );
990             free( p_item->psz_description );
991         }
992         free( p_feed->p_items );
993         free( p_feed->psz_title);
994         free( p_feed->psz_link );
995         free( p_feed->psz_description );
996         free( p_feed->psz_image );
997         if( p_feed->p_pic != NULL )
998             picture_Release( p_feed->p_pic );
999         free( p_feed->psz_url );
1000     }
1001     free( p_feeds );
1002 }
1003
1004 static void Fetch( void *p_data )
1005 {
1006     filter_t *p_filter = p_data;
1007     filter_sys_t *p_sys = p_filter->p_sys;
1008
1009     msg_Dbg( p_filter, "Updating the rss feeds" );
1010     rss_feed_t *p_feeds = FetchRSS( p_filter );
1011     if( !p_feeds )
1012     {
1013         msg_Err( p_filter, "Unable to fetch the feeds" );
1014         return;
1015     }
1016
1017     rss_feed_t *p_old_feeds = p_sys->p_feeds;
1018
1019     vlc_mutex_lock( &p_sys->lock );
1020     /* Update the feeds */
1021     p_sys->p_feeds = p_feeds;
1022     p_sys->b_fetched = true;
1023     /* Set all current info to the original values */
1024     p_sys->i_cur_feed = 0;
1025     p_sys->i_cur_item = p_sys->i_title == scroll_title ? -1 : 0;
1026     p_sys->i_cur_char = 0;
1027     vlc_mutex_unlock( &p_sys->lock );
1028
1029     if( p_old_feeds )
1030         FreeRSS( p_old_feeds, p_sys->i_feeds );
1031 }