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