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