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