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