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