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