rss: fix object leak.
[vlc.git] / 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
240     /* Allocate structure */
241     p_sys = p_filter->p_sys = malloc( sizeof( filter_sys_t ) );
242     if( p_sys == NULL )
243         return VLC_ENOMEM;
244
245     vlc_mutex_init( &p_sys->lock );
246     vlc_mutex_lock( &p_sys->lock );
247
248     config_ChainParse( p_filter, CFG_PREFIX, ppsz_filter_options,
249                        p_filter->p_cfg );
250
251     p_sys->psz_urls = var_CreateGetString( p_filter, CFG_PREFIX "urls" );
252     p_sys->i_title = var_CreateGetInteger( p_filter, CFG_PREFIX "title" );
253     p_sys->i_cur_feed = 0;
254     p_sys->i_cur_item = p_sys->i_title == scroll_title ? -1 : 0;
255     p_sys->i_cur_char = 0;
256     p_sys->i_feeds = 0;
257     p_sys->p_feeds = NULL;
258     p_sys->i_speed = var_CreateGetInteger( p_filter, CFG_PREFIX "speed" );
259     p_sys->i_length = var_CreateGetInteger( p_filter, CFG_PREFIX "length" );
260     p_sys->i_ttl = __MAX( 0, var_CreateGetInteger( p_filter, CFG_PREFIX "ttl" ) );
261     p_sys->b_images = var_CreateGetBool( p_filter, CFG_PREFIX "images" );
262
263     p_sys->psz_marquee = (char *)malloc( p_sys->i_length + 1 );
264     if( p_sys->psz_marquee == NULL )
265     {
266         vlc_mutex_unlock( &p_sys->lock );
267         vlc_mutex_destroy( &p_sys->lock );
268         free( p_sys->psz_urls );
269         free( p_sys );
270         return VLC_ENOMEM;
271     }
272     p_sys->psz_marquee[p_sys->i_length] = '\0';
273
274     p_sys->p_style = text_style_New();
275     if( p_sys->p_style == NULL )
276     {
277         free( p_sys->psz_marquee );
278         vlc_mutex_unlock( &p_sys->lock );
279         vlc_mutex_destroy( &p_sys->lock );
280         free( p_sys->psz_urls );
281         free( p_sys );
282         return VLC_ENOMEM;
283     }
284
285     p_sys->i_xoff = var_CreateGetInteger( p_filter, CFG_PREFIX "x" );
286     p_sys->i_yoff = var_CreateGetInteger( p_filter, CFG_PREFIX "y" );
287     p_sys->i_pos = var_CreateGetInteger( p_filter, CFG_PREFIX "position" );
288     p_sys->p_style->i_font_alpha = 255 - var_CreateGetInteger( p_filter, CFG_PREFIX "opacity" );
289     p_sys->p_style->i_font_color = var_CreateGetInteger( p_filter, CFG_PREFIX "color" );
290     p_sys->p_style->i_font_size = var_CreateGetInteger( p_filter, CFG_PREFIX "size" );
291
292     if( p_sys->b_images == true && p_sys->p_style->i_font_size == -1 )
293     {
294         msg_Warn( p_filter, "rss-size wasn't specified. Feed images will thus be displayed without being resized" );
295     }
296
297     if( FetchRSS( p_filter ) )
298     {
299         msg_Err( p_filter, "failed while fetching RSS ... too bad" );
300         text_style_Delete( p_sys->p_style );
301         free( p_sys->psz_marquee );
302         vlc_mutex_unlock( &p_sys->lock );
303         vlc_mutex_destroy( &p_sys->lock );
304         free( p_sys->psz_urls );
305         free( p_sys );
306         return VLC_EGENERIC;
307     }
308     p_sys->t_last_update = time( NULL );
309
310     if( p_sys->i_feeds == 0 )
311     {
312         text_style_Delete( p_sys->p_style );
313         free( p_sys->psz_marquee );
314         vlc_mutex_unlock( &p_sys->lock );
315         vlc_mutex_destroy( &p_sys->lock );
316         free( p_sys->psz_urls );
317         free( p_sys );
318         return VLC_EGENERIC;
319     }
320     for( i_feed=0; i_feed < p_sys->i_feeds; i_feed ++ )
321     {
322         if( p_sys->p_feeds[i_feed].i_items == 0 )
323         {
324             text_style_Delete( p_sys->p_style );
325             free( p_sys->psz_marquee );
326             FreeRSS( p_filter );
327             vlc_mutex_unlock( &p_sys->lock );
328             vlc_mutex_destroy( &p_sys->lock );
329             free( p_sys->psz_urls );
330             free( p_sys );
331             return VLC_EGENERIC;
332         }
333     }
334     /* Misc init */
335     p_filter->pf_sub_filter = Filter;
336     p_sys->last_date = (mtime_t)0;
337
338     vlc_mutex_unlock( &p_sys->lock );
339
340     return VLC_SUCCESS;
341 }
342 /*****************************************************************************
343  * DestroyFilter: destroy RSS video filter
344  *****************************************************************************/
345 static void DestroyFilter( vlc_object_t *p_this )
346 {
347     filter_t *p_filter = (filter_t *)p_this;
348     filter_sys_t *p_sys = p_filter->p_sys;
349
350     vlc_mutex_lock( &p_sys->lock );
351
352     text_style_Delete( p_sys->p_style );
353     free( p_sys->psz_marquee );
354     free( p_sys->psz_urls );
355     FreeRSS( p_filter );
356     vlc_mutex_unlock( &p_sys->lock );
357     vlc_mutex_destroy( &p_sys->lock );
358     free( p_sys );
359
360     /* Delete the RSS variables */
361     var_Destroy( p_filter, CFG_PREFIX "urls" );
362     var_Destroy( p_filter, CFG_PREFIX "speed" );
363     var_Destroy( p_filter, CFG_PREFIX "length" );
364     var_Destroy( p_filter, CFG_PREFIX "ttl" );
365     var_Destroy( p_filter, CFG_PREFIX "images" );
366     var_Destroy( p_filter, CFG_PREFIX "x" );
367     var_Destroy( p_filter, CFG_PREFIX "y" );
368     var_Destroy( p_filter, CFG_PREFIX "position" );
369     var_Destroy( p_filter, CFG_PREFIX "color");
370     var_Destroy( p_filter, CFG_PREFIX "opacity");
371     var_Destroy( p_filter, CFG_PREFIX "size");
372     var_Destroy( p_filter, CFG_PREFIX "title" );
373 }
374
375 /****************************************************************************
376  * Filter: the whole thing
377  ****************************************************************************
378  * This function outputs subpictures at regular time intervals.
379  ****************************************************************************/
380 static subpicture_t *Filter( filter_t *p_filter, mtime_t date )
381 {
382     filter_sys_t *p_sys = p_filter->p_sys;
383     subpicture_t *p_spu;
384     video_format_t fmt;
385     subpicture_region_t *p_region;
386
387     int i_feed, i_item;
388
389     struct rss_feed_t *p_feed;
390
391     memset( &fmt, 0, sizeof(video_format_t) );
392
393     vlc_mutex_lock( &p_sys->lock );
394
395     if( p_sys->last_date
396        + ( p_sys->i_cur_char == 0 && p_sys->i_cur_item == ( p_sys->i_title == scroll_title ? -1 : 0 ) ? 5 : 1 )
397            /* ( ... ? 5 : 1 ) means "wait 5 times more for the 1st char" */
398        * p_sys->i_speed > date )
399     {
400         vlc_mutex_unlock( &p_sys->lock );
401         return NULL;
402     }
403
404     /* Do we need to update the feeds ? */
405     if( p_sys->i_ttl
406         && time( NULL ) > p_sys->t_last_update + (time_t)p_sys->i_ttl )
407     {
408         msg_Dbg( p_filter, "Forcing update of all the RSS feeds" );
409         if( FetchRSS( p_filter ) )
410         {
411             msg_Err( p_filter, "Failed while fetching RSS ... too bad" );
412             vlc_mutex_unlock( &p_sys->lock );
413             return NULL; /* FIXME : we most likely messed up all the data,
414                           * so we might need to do something about it */
415         }
416         p_sys->t_last_update = time( NULL );
417     }
418
419     p_sys->last_date = date;
420     p_sys->i_cur_char++;
421     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 )
422     {
423         p_sys->i_cur_char = 0;
424         p_sys->i_cur_item++;
425         if( p_sys->i_cur_item >= p_sys->p_feeds[p_sys->i_cur_feed].i_items )
426         {
427             if( p_sys->i_title == scroll_title )
428                 p_sys->i_cur_item = -1;
429             else
430                 p_sys->i_cur_item = 0;
431             p_sys->i_cur_feed = (p_sys->i_cur_feed + 1)%p_sys->i_feeds;
432         }
433     }
434
435     p_spu = filter_NewSubpicture( p_filter );
436     if( !p_spu )
437     {
438         vlc_mutex_unlock( &p_sys->lock );
439         return NULL;
440     }
441
442     fmt.i_chroma = VLC_CODEC_TEXT;
443
444     p_spu->p_region = subpicture_region_New( &fmt );
445     if( !p_spu->p_region )
446     {
447         p_filter->pf_sub_buffer_del( p_filter, p_spu );
448         vlc_mutex_unlock( &p_sys->lock );
449         return NULL;
450     }
451
452     /* Generate the string that will be displayed. This string is supposed to
453        be p_sys->i_length characters long. */
454     i_item = p_sys->i_cur_item;
455     i_feed = p_sys->i_cur_feed;
456     p_feed = &p_sys->p_feeds[i_feed];
457
458     if( ( p_feed->p_pic && p_sys->i_title == default_title )
459         || p_sys->i_title == hide_title )
460     {
461         /* Don't display the feed's title if we have an image */
462         snprintf( p_sys->psz_marquee, p_sys->i_length, "%s",
463                   p_sys->p_feeds[i_feed].p_items[i_item].psz_title
464                   +p_sys->i_cur_char );
465     }
466     else if( ( !p_feed->p_pic && p_sys->i_title == default_title )
467              || p_sys->i_title == prepend_title )
468     {
469         snprintf( p_sys->psz_marquee, p_sys->i_length, "%s : %s",
470                   p_sys->p_feeds[i_feed].psz_title,
471                   p_sys->p_feeds[i_feed].p_items[i_item].psz_title
472                   +p_sys->i_cur_char );
473     }
474     else /* scrolling title */
475     {
476         if( i_item == -1 )
477             snprintf( p_sys->psz_marquee, p_sys->i_length, "%s : %s",
478                       p_sys->p_feeds[i_feed].psz_title + p_sys->i_cur_char,
479                       p_sys->p_feeds[i_feed].p_items[i_item+1].psz_title );
480         else
481             snprintf( p_sys->psz_marquee, p_sys->i_length, "%s",
482                       p_sys->p_feeds[i_feed].p_items[i_item].psz_title
483                       +p_sys->i_cur_char );
484     }
485
486     while( strlen( p_sys->psz_marquee ) < (unsigned int)p_sys->i_length )
487     {
488         i_item++;
489         if( i_item == p_sys->p_feeds[i_feed].i_items ) break;
490         snprintf( strchr( p_sys->psz_marquee, 0 ),
491                   p_sys->i_length - strlen( p_sys->psz_marquee ),
492                   " - %s",
493                   p_sys->p_feeds[i_feed].p_items[i_item].psz_title );
494     }
495
496     /* Calls to snprintf might split multibyte UTF8 chars ...
497      * which freetype doesn't like. */
498     {
499         char *a = strdup( p_sys->psz_marquee );
500         char *a2 = a;
501         char *b = p_sys->psz_marquee;
502         EnsureUTF8( p_sys->psz_marquee );
503         /* we want to use ' ' instead of '?' for erroneous chars */
504         while( *b != '\0' )
505         {
506             if( *b != *a ) *b = ' ';
507             b++;a++;
508         }
509         free( a2 );
510     }
511
512     p_spu->p_region->psz_text = strdup(p_sys->psz_marquee);
513     if( p_sys->p_style->i_font_size > 0 )
514         p_spu->p_region->fmt.i_visible_height = p_sys->p_style->i_font_size;
515     p_spu->i_start = date;
516     p_spu->i_stop  = 0;
517     p_spu->b_ephemer = true;
518
519     /*  where to locate the string: */
520     if( p_sys->i_pos < 0 )
521     {   /*  set to an absolute xy */
522         p_spu->p_region->i_align = OSD_ALIGN_LEFT | OSD_ALIGN_TOP;
523         p_spu->b_absolute = true;
524     }
525     else
526     {   /* set to one of the 9 relative locations */
527         p_spu->p_region->i_align = p_sys->i_pos;
528         p_spu->b_absolute = false;
529     }
530
531     p_spu->p_region->p_style = text_style_Duplicate( p_sys->p_style );
532
533     if( p_feed->p_pic )
534     {
535         /* Display the feed's image */
536         picture_t *p_pic = p_feed->p_pic;
537         video_format_t fmt_out;
538
539         memset( &fmt_out, 0, sizeof(video_format_t) );
540
541         fmt_out.i_chroma = VLC_CODEC_YUVA;
542         fmt_out.i_aspect = VOUT_ASPECT_FACTOR;
543         fmt_out.i_sar_num = fmt_out.i_sar_den = 1;
544         fmt_out.i_width =
545             fmt_out.i_visible_width = p_pic->p[Y_PLANE].i_visible_pitch;
546         fmt_out.i_height =
547             fmt_out.i_visible_height = p_pic->p[Y_PLANE].i_visible_lines;
548
549         p_region = subpicture_region_New( &fmt_out );
550         if( !p_region )
551         {
552             msg_Err( p_filter, "cannot allocate SPU region" );
553         }
554         else
555         {
556             p_region->i_x = p_sys->i_xoff;
557             p_region->i_y = p_sys->i_yoff;
558             /* FIXME the copy is probably not needed anymore */
559             picture_Copy( p_region->p_picture, p_pic );
560             p_spu->p_region->p_next = p_region;
561         }
562
563         /* Offset text to display right next to the image */
564         p_spu->p_region->i_x = p_pic->p[Y_PLANE].i_visible_pitch;
565     }
566
567     vlc_mutex_unlock( &p_sys->lock );
568     return p_spu;
569 }
570
571 /****************************************************************************
572  * RSS related functions
573  ****************************************************************************
574  * You should always lock the p_filter mutex before using any of these
575  * functions
576  ***************************************************************************/
577
578 #undef LoadImage /* do not conflict with Win32 API */
579
580 /****************************************************************************
581  * download and resize image located at psz_url
582  ***************************************************************************/
583 static picture_t *LoadImage( filter_t *p_filter, const char *psz_url )
584 {
585     filter_sys_t *p_sys = p_filter->p_sys;
586     video_format_t fmt_in;
587     video_format_t fmt_out;
588     picture_t *p_orig;
589     picture_t *p_pic = NULL;
590     image_handler_t *p_handler = image_HandlerCreate( p_filter );
591
592     memset( &fmt_in, 0, sizeof(video_format_t) );
593     memset( &fmt_out, 0, sizeof(video_format_t) );
594
595     fmt_out.i_chroma = VLC_CODEC_YUVA;
596     p_orig = image_ReadUrl( p_handler, psz_url, &fmt_in, &fmt_out );
597
598     if( !p_orig )
599     {
600         msg_Warn( p_filter, "Unable to read image %s", psz_url );
601     }
602     else if( p_sys->p_style->i_font_size > 0 )
603     {
604
605         fmt_in.i_chroma = VLC_CODEC_YUVA;
606         fmt_in.i_height = p_orig->p[Y_PLANE].i_visible_lines;
607         fmt_in.i_width = p_orig->p[Y_PLANE].i_visible_pitch;
608         fmt_out.i_width = p_orig->p[Y_PLANE].i_visible_pitch
609             *p_sys->p_style->i_font_size/p_orig->p[Y_PLANE].i_visible_lines;
610         fmt_out.i_height = p_sys->p_style->i_font_size;
611
612         p_pic = image_Convert( p_handler, p_orig, &fmt_in, &fmt_out );
613         picture_Release( p_orig );
614         if( !p_pic )
615         {
616             msg_Warn( p_filter, "Error while converting %s", psz_url );
617         }
618     }
619     else
620     {
621         p_pic = p_orig;
622     }
623
624     image_HandlerDelete( p_handler );
625
626     return p_pic;
627 }
628
629 /****************************************************************************
630  * remove all ' ' '\t' '\n' '\r' characters from the begining and end of the
631  * string.
632  ***************************************************************************/
633 static char *removeWhiteChars( char *psz_src )
634 {
635     char *psz_src2 = strdup( psz_src );
636     char *psz_clean = strdup( psz_src2 );
637     char *psz_clean2;
638     int i;
639     while( ( *psz_clean == ' ' || *psz_clean == '\t'
640            || *psz_clean == '\n' || *psz_clean == '\r' )
641            && *psz_clean != '\0' )
642     {
643         psz_clean++;
644     }
645     i = strlen( psz_clean );
646     while( --i > 0 &&
647          ( psz_clean[i] == ' ' || psz_clean[i] == '\t'
648         || psz_clean[i] == '\n' || psz_clean[i] == '\r' ) );
649     psz_clean[i+1] = '\0';
650     psz_clean2 = strdup( psz_clean );
651     free( psz_src2 );
652     return psz_clean2;
653 }
654
655 /****************************************************************************
656  * FetchRSS (or Atom) feeds
657  ***************************************************************************/
658 static int FetchRSS( filter_t *p_filter)
659 {
660     filter_sys_t *p_sys = p_filter->p_sys;
661
662     stream_t *p_stream = NULL;
663     xml_t *p_xml = NULL;
664     xml_reader_t *p_xml_reader = NULL;
665
666     char *psz_eltname = NULL;
667     char *psz_eltvalue = NULL;
668     char *psz_feed = NULL;
669     char *psz_buffer = NULL;
670     char *psz_buffer_2 = NULL;
671
672     int i_feed;
673     int i_item;
674     bool b_is_item;
675     bool b_is_image;
676     int i_int;
677
678     FreeRSS( p_filter );
679     p_sys->i_feeds = 1;
680     i_int = 0;
681     while( p_sys->psz_urls[i_int] != 0 )
682         if( p_sys->psz_urls[i_int++] == '|' )
683             p_sys->i_feeds++;
684     p_sys->p_feeds = (struct rss_feed_t *)malloc( p_sys->i_feeds
685                                 * sizeof( struct rss_feed_t ) );
686
687     p_xml = xml_Create( p_filter );
688     if( !p_xml )
689     {
690         msg_Err( p_filter, "Failed to open XML parser" );
691         return 1;
692     }
693
694     psz_buffer = strdup( p_sys->psz_urls );
695     psz_buffer_2 = psz_buffer; /* keep track so we can free it */
696     for( i_feed = 0; i_feed < p_sys->i_feeds; i_feed++ )
697     {
698         struct rss_feed_t *p_feed = p_sys->p_feeds+i_feed;
699
700         if( psz_buffer == NULL ) break;
701         if( psz_buffer[0] == 0 ) psz_buffer++;
702         psz_feed = psz_buffer;
703         psz_buffer = strchr( psz_buffer, '|' );
704         if( psz_buffer != NULL ) psz_buffer[0] = 0;
705
706         p_feed->psz_title = NULL;
707         p_feed->psz_description = NULL;
708         p_feed->psz_link = NULL;
709         p_feed->psz_image = NULL;
710         p_feed->p_pic = NULL;
711         p_feed->i_items = 0;
712         p_feed->p_items = NULL;
713
714         msg_Dbg( p_filter, "opening %s RSS/Atom feed ...", psz_feed );
715
716         p_stream = stream_UrlNew( p_filter, psz_feed );
717         if( !p_stream )
718         {
719             msg_Err( p_filter, "Failed to open %s for reading", psz_feed );
720             xml_Delete( p_xml );
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             xml_Delete( p_xml );
729             return 1;
730         }
731
732         i_item = 0;
733         b_is_item = false;
734         b_is_image = false;
735
736         while( xml_ReaderRead( p_xml_reader ) == 1 )
737         {
738             switch( xml_ReaderNodeType( p_xml_reader ) )
739             {
740                 // Error
741                 case -1:
742                     return 1;
743
744                 case XML_READER_STARTELEM:
745                     free( psz_eltname );
746                     psz_eltname = xml_ReaderName( p_xml_reader );
747                     if( !psz_eltname )
748                     {
749                         return 1;
750                     }
751 #                   ifdef RSS_DEBUG
752                     msg_Dbg( p_filter, "element name: %s", psz_eltname );
753 #                   endif
754                     if( !strcmp( psz_eltname, "item" ) /* rss */
755                      || !strcmp( psz_eltname, "entry" ) ) /* atom */
756                     {
757                         b_is_item = true;
758                         p_feed->i_items++;
759                         p_feed->p_items = (struct rss_item_t *)realloc( p_feed->p_items, p_feed->i_items * sizeof( struct rss_item_t ) );
760                         p_feed->p_items[p_feed->i_items-1].psz_title = NULL;
761                         p_feed->p_items[p_feed->i_items-1].psz_description
762                                                                      = NULL;
763                         p_feed->p_items[p_feed->i_items-1].psz_link = NULL;
764                     }
765                     else if( !strcmp( psz_eltname, "image" ) ) /* rss */
766                     {
767                         b_is_image = true;
768                     }
769                     else if( !strcmp( psz_eltname, "link" ) ) /* atom */
770                     {
771                         char *psz_href = NULL;
772                         char *psz_rel = NULL;
773                         while( xml_ReaderNextAttr( p_xml_reader )
774                                == VLC_SUCCESS )
775                         {
776                             char *psz_name = xml_ReaderName( p_xml_reader );
777                             char *psz_value = xml_ReaderValue( p_xml_reader );
778                             if( !strcmp( psz_name, "rel" ) )
779                             {
780                                 if( psz_rel )
781                                 {
782                                     msg_Dbg( p_filter, "\"rel\" attribute of link atom duplicated (last value: %s)", psz_value );
783                                     free( psz_rel );
784                                 }
785                                 psz_rel = psz_value;
786                             }
787                             else if( !strcmp( psz_name, "href" ) )
788                             {
789                                 if( psz_href )
790                                 {
791                                     msg_Dbg( p_filter, "\"href\" attribute of link atom duplicated (last value: %s)", psz_href );
792                                     free( psz_href );
793                                 }
794                                 psz_href = psz_value;
795                             }
796                             else
797                             {
798                                 free( psz_value );
799                             }
800                             free( psz_name );
801                         }
802                         if( psz_rel && psz_href )
803                         {
804                             if( !strcmp( psz_rel, "alternate" )
805                                 && b_is_item == false
806                                 && b_is_image == false
807                                 && !p_feed->psz_link )
808                             {
809                                 p_feed->psz_link = psz_href;
810                             }
811                             /* this isn't in the rfc but i found some ... */
812                             else if( ( !strcmp( psz_rel, "logo" )
813                                     || !strcmp( psz_rel, "icon" ) )
814                                     && b_is_item == false
815                                     && b_is_image == false
816                                     && !p_feed->psz_image )
817                             {
818                                 p_feed->psz_image = psz_href;
819                             }
820                             else
821                             {
822                                 free( psz_href );
823                             }
824                         }
825                         else
826                         {
827                             free( psz_href );
828                         }
829                         free( psz_rel );
830                     }
831                     break;
832
833                 case XML_READER_ENDELEM:
834                     free( psz_eltname );
835                     psz_eltname = NULL;
836                     psz_eltname = xml_ReaderName( p_xml_reader );
837                     if( !psz_eltname )
838                     {
839                         return 1;
840                     }
841 #                   ifdef RSS_DEBUG
842                     msg_Dbg( p_filter, "element end : %s", psz_eltname );
843 #                   endif
844                     if( !strcmp( psz_eltname, "item" ) /* rss */
845                      || !strcmp( psz_eltname, "entry" ) ) /* atom */
846                     {
847                         b_is_item = false;
848                         i_item++;
849                     }
850                     else if( !strcmp( psz_eltname, "image" ) ) /* rss */
851                     {
852                         b_is_image = false;
853                     }
854                     free( psz_eltname );
855                     psz_eltname = NULL;
856                     break;
857
858                 case XML_READER_TEXT:
859                     if( !psz_eltname ) break;
860                     psz_eltvalue = xml_ReaderValue( p_xml_reader );
861                     if( !psz_eltvalue )
862                     {
863                         return 1;
864                     }
865                     else
866                     {
867                         char *psz_clean;
868                         psz_clean = removeWhiteChars( psz_eltvalue );
869                         free( psz_eltvalue ); psz_eltvalue = psz_clean;
870                     }
871 #                   ifdef RSS_DEBUG
872                     msg_Dbg( p_filter, "  text : <%s>", psz_eltvalue );
873 #                   endif
874                     if( b_is_item == true )
875                     {
876                         struct rss_item_t *p_item;
877                         p_item = p_feed->p_items+i_item;
878                         if( !strcmp( psz_eltname, "title" ) /* rss/atom */
879                             && !p_item->psz_title )
880                         {
881                             p_item->psz_title = psz_eltvalue;
882                         }
883                         else if( !strcmp( psz_eltname, "link" ) /* rss */
884                                  && !p_item->psz_link )
885                         {
886                             p_item->psz_link = psz_eltvalue;
887                         }
888                         else if((!strcmp( psz_eltname, "description" ) /* rss */
889                               || !strcmp( psz_eltname, "summary" ) ) /* atom */
890                               && !p_item->psz_description )
891                         {
892                             p_item->psz_description = psz_eltvalue;
893                         }
894                         else
895                         {
896                             free( psz_eltvalue );
897                             psz_eltvalue = NULL;
898                         }
899                     }
900                     else if( b_is_image == true )
901                     {
902                         if( !strcmp( psz_eltname, "url" ) /* rss */
903                             && !p_feed->psz_image )
904                         {
905                             p_feed->psz_image = psz_eltvalue;
906                         }
907                         else
908                         {
909                             free( psz_eltvalue );
910                             psz_eltvalue = NULL;
911                         }
912                     }
913                     else
914                     {
915                         if( !strcmp( psz_eltname, "title" ) /* rss/atom */
916                             && !p_feed->psz_title )
917                         {
918                             p_feed->psz_title = psz_eltvalue;
919                         }
920                         else if( !strcmp( psz_eltname, "link" ) /* rss */
921                                  && !p_feed->psz_link )
922                         {
923                             p_feed->psz_link = psz_eltvalue;
924                         }
925                         else if((!strcmp( psz_eltname, "description" ) /* rss */
926                               || !strcmp( psz_eltname, "subtitle" ) ) /* atom */
927                               && !p_feed->psz_description )
928                         {
929                             p_feed->psz_description = psz_eltvalue;
930                         }
931                         else if( ( !strcmp( psz_eltname, "logo" ) /* atom */
932                               || !strcmp( psz_eltname, "icon" ) ) /* atom */
933                               && !p_feed->psz_image )
934                         {
935                             p_feed->psz_image = psz_eltvalue;
936                         }
937                         else
938                         {
939                             free( psz_eltvalue );
940                             psz_eltvalue = NULL;
941                         }
942                     }
943                     break;
944             }
945         }
946
947         if( p_sys->b_images == true
948             && p_feed->psz_image && !p_feed->p_pic )
949         {
950             p_feed->p_pic = LoadImage( p_filter, p_feed->psz_image );
951         }
952
953         if( p_xml_reader && p_xml ) xml_ReaderDelete( p_xml, p_xml_reader );
954         if( p_stream ) stream_Delete( p_stream );
955         msg_Dbg( p_filter, "done with %s RSS/Atom feed", psz_feed );
956     }
957     free( psz_buffer_2 );
958     if( p_xml ) xml_Delete( p_xml );
959
960     return 0;
961 }
962
963 /****************************************************************************
964  * FreeRSS
965  ***************************************************************************/
966 static void FreeRSS( filter_t *p_filter)
967 {
968     filter_sys_t *p_sys = p_filter->p_sys;
969
970     struct rss_item_t *p_item;
971     struct rss_feed_t *p_feed;
972
973     int i_feed;
974     int i_item;
975
976     for( i_feed = 0; i_feed < p_sys->i_feeds; i_feed++ )
977     {
978         p_feed = p_sys->p_feeds+i_feed;
979         for( i_item = 0; i_item < p_feed->i_items; i_item++ )
980         {
981             p_item = p_feed->p_items+i_item;
982             free( p_item->psz_title );
983             free( p_item->psz_link );
984             free( p_item->psz_description );
985         }
986         free( p_feed->p_items );
987         free( p_feed->psz_title);
988         free( p_feed->psz_link );
989         free( p_feed->psz_description );
990         free( p_feed->psz_image );
991         if( p_feed->p_pic != NULL )
992             picture_Release( p_feed->p_pic );
993     }
994     free( p_sys->p_feeds );
995     p_sys->i_feeds = 0;
996 }