]> git.sesse.net Git - vlc/blob - modules/video_filter/rss.c
* rss.c : add new rss module (sub filter)
[vlc] / modules / video_filter / rss.c
1 /*****************************************************************************
2  * rss.c : rss feed display video plugin for vlc
3  *****************************************************************************
4  * Copyright (C) 2003-2005 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Antoine Cellerier <dionoea@videolan.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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27 #include <stdlib.h>                                      /* malloc(), free() */
28 #include <string.h>
29
30 #include <vlc/vlc.h>
31 #include <vlc/vout.h>
32
33 #include "vlc_filter.h"
34 #include "vlc_block.h"
35 #include "osd.h"
36
37 #include "vlc_block.h"
38 #include "vlc_stream.h"
39 #include "vlc_xml.h"
40
41 /*****************************************************************************
42  * Local prototypes
43  *****************************************************************************/
44 static int  CreateFilter ( vlc_object_t * );
45 static void DestroyFilter( vlc_object_t * );
46 static subpicture_t *Filter( filter_t *, mtime_t );
47
48 static int FetchRSS( filter_t * );
49
50 static int pi_color_values[] = { 0xf0000000, 0x00000000, 0x00808080, 0x00C0C0C0,
51                0x00FFFFFF, 0x00800000, 0x00FF0000, 0x00FF00FF, 0x00FFFF00,
52                0x00808000, 0x00008000, 0x00008080, 0x0000FF00, 0x00800080,
53                0x00000080, 0x000000FF, 0x0000FFFF};
54 static char *ppsz_color_descriptions[] = { N_("Default"), N_("Black"),
55                N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"), N_("Red"),
56                N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"),
57                N_("Teal"), N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"),
58                N_("Aqua") };
59
60 /*****************************************************************************
61  * filter_sys_t: rss filter descriptor
62  *****************************************************************************/
63
64 struct rss_item_t
65 {
66     char *psz_title;
67     char *psz_description;
68     char *psz_link;
69 };
70
71 struct rss_feed_t
72 {
73     char *psz_title;
74     char *psz_description;
75     char *psz_link;
76
77     int i_items;
78     struct rss_item_t *p_items;
79 };
80
81 struct filter_sys_t
82 {
83     int i_xoff, i_yoff;  /* offsets for the display string in the video window */
84     int i_pos; /* permit relative positioning (top, bottom, left, right, center) */
85     int i_speed;
86     int i_length;
87
88     char *psz_marquee;    /* marquee string */
89
90     int  i_font_color, i_font_opacity, i_font_size; /* font control */
91
92     mtime_t last_date;
93
94     //vlc_bool_t b_need_update;
95
96     char *psz_urls;
97     int i_feeds;
98     struct rss_feed_t *p_feeds;
99
100     int i_cur_feed;
101     int i_cur_item;
102     int i_cur_char;
103 };
104
105 // <TODO>
106 #define MSG_TEXT N_("RSS feed URLs")
107 #define MSG_LONGTEXT N_("RSS feed comma(TODO ?) seperated URLs")
108 #define SPEED_TEXT N_("RSS feed speed")
109 #define SPEED_LONGTEXT N_("RSS feed speed (bigger is slower)")
110 #define LENGTH_TEXT N_("RSS feed max number of chars displayed")
111 #define LENGTH_LONGTEXT N_("RSS feed max number of chars displayed")
112 // </TODO>
113
114 #define POSX_TEXT N_("X offset, from left")
115 #define POSX_LONGTEXT N_("X offset, from the left screen edge" )
116 #define POSY_TEXT N_("Y offset, from the top")
117 #define POSY_LONGTEXT N_("Y offset, down from the top" )
118 #define OPACITY_TEXT N_("Opacity")
119 #define OPACITY_LONGTEXT N_("The opacity (inverse of transparency) of " \
120     "overlay text. 0 = transparent, 255 = totally opaque. " )
121 #define SIZE_TEXT N_("Font size, pixels")
122 #define SIZE_LONGTEXT N_("Specify the font size, in pixels, " \
123     "with -1 = use freetype-fontsize" )
124
125 #define COLOR_TEXT N_("Text Default Color")
126 #define COLOR_LONGTEXT N_("The color of overlay text. 1 byte for each color, hexadecimal. " \
127     "#000000 = all colors off, " \
128     "0xFF0000 = just Red, 0xFFFFFF = all color on [White]" )
129
130 #define POS_TEXT N_("Marquee position")
131 #define POS_LONGTEXT N_( \
132   "You can enforce the marquee position on the video " \
133   "(0=center, 1=left, 2=right, 4=top, 8=bottom, you can " \
134   "also use combinations of these values by adding them).")
135
136 static int pi_pos_values[] = { 0, 1, 2, 4, 8, 5, 6, 9, 10 };
137 static char *ppsz_pos_descriptions[] =
138      { N_("Center"), N_("Left"), N_("Right"), N_("Top"), N_("Bottom"),
139      N_("Top-Left"), N_("Top-Right"), N_("Bottom-Left"), N_("Bottom-Right") };
140
141 /*****************************************************************************
142  * Module descriptor
143  *****************************************************************************/
144 vlc_module_begin();
145     set_capability( "sub filter", 0 );
146     set_shortname( N_("RSS" ));
147     set_callbacks( CreateFilter, DestroyFilter );
148     set_category( CAT_VIDEO );
149     set_subcategory( SUBCAT_VIDEO_SUBPIC );
150     add_string( "rss-urls", "rss", NULL, MSG_TEXT, MSG_LONGTEXT, VLC_FALSE );
151
152     set_section( N_("Position"), NULL );
153     add_integer( "marq-x", -1, NULL, POSX_TEXT, POSX_LONGTEXT, VLC_FALSE );
154     add_integer( "marq-y", 0, NULL, POSY_TEXT, POSY_LONGTEXT, VLC_FALSE );
155     add_integer( "marq-position", 5, NULL, POS_TEXT, POS_LONGTEXT, VLC_TRUE );
156
157     set_section( N_("Font"), NULL );
158     /* 5 sets the default to top [1] left [4] */
159     change_integer_list( pi_pos_values, ppsz_pos_descriptions, 0 );
160     add_integer_with_range( "marq-opacity", 255, 0, 255, NULL,
161         OPACITY_TEXT, OPACITY_LONGTEXT, VLC_FALSE );
162     add_integer( "marq-color", 0xFFFFFF, NULL, COLOR_TEXT, COLOR_LONGTEXT, VLC_TRUE );
163         change_integer_list( pi_color_values, ppsz_color_descriptions, 0 );
164     add_integer( "marq-size", -1, NULL, SIZE_TEXT, SIZE_LONGTEXT, VLC_FALSE );
165
166     set_section( N_("Misc"), NULL );
167     add_integer( "rss-speed", 100000, NULL, SPEED_TEXT, SPEED_LONGTEXT,
168                  VLC_FALSE );
169     add_integer( "rss-length", 60, NULL, LENGTH_TEXT, LENGTH_LONGTEXT,
170                  VLC_FALSE );
171
172     set_description( _("RSS feed display sub filter") );
173     add_shortcut( "rss" );
174 vlc_module_end();
175
176 /*****************************************************************************
177  * CreateFilter: allocates RSS video filter
178  *****************************************************************************/
179 static int CreateFilter( vlc_object_t *p_this )
180 {
181     filter_t *p_filter = (filter_t *)p_this;
182     filter_sys_t *p_sys;
183     int i_feed;
184
185     /* Allocate structure */
186     p_sys = p_filter->p_sys = malloc( sizeof( filter_sys_t ) );
187     if( p_sys == NULL )
188     {
189         msg_Err( p_filter, "out of memory" );
190         return VLC_ENOMEM;
191     }
192
193     p_sys->psz_urls = var_CreateGetString( p_filter, "rss-urls" );
194     p_sys->i_cur_feed = 0;
195     p_sys->i_cur_item = 0;
196     p_sys->i_cur_char = 0;
197     p_sys->i_speed = var_CreateGetInteger( p_filter, "rss-speed" );
198     p_sys->i_length = var_CreateGetInteger( p_filter, "rss-length" );
199     p_sys->psz_marquee = (char *)malloc( p_sys->i_length );
200
201     p_sys->i_xoff = var_CreateGetInteger( p_filter, "marq-x" );
202     p_sys->i_yoff = var_CreateGetInteger( p_filter, "marq-y" );
203     p_sys->i_pos = var_CreateGetInteger( p_filter, "marq-position" );
204     var_Create( p_filter, "marq-opacity", VLC_VAR_INTEGER|VLC_VAR_DOINHERIT );
205     p_sys->i_font_opacity = var_CreateGetInteger( p_filter, "marq-opacity" );
206     p_sys->i_font_color = var_CreateGetInteger( p_filter, "marq-color" );
207     p_sys->i_font_size = var_CreateGetInteger( p_filter, "marq-size" );
208
209     if( FetchRSS( p_filter ) )
210     {
211         msg_Err( p_filter, "failed while fetching RSS ... too bad" );
212         return VLC_EGENERIC;
213     }
214
215     if( p_sys->i_feeds == 0 ) return VLC_EGENERIC;
216     for( i_feed=0; i_feed < p_sys->i_feeds; i_feed ++ )
217         if( p_sys->p_feeds[i_feed].i_items == 0 )
218             return VLC_EGENERIC;
219
220     /* Misc init */
221     p_filter->pf_sub_filter = Filter;
222     p_sys->last_date = (mtime_t)0;
223     //p_sys->b_need_update = VLC_TRUE;
224
225     return VLC_SUCCESS;
226 }
227 /*****************************************************************************
228  * DestroyFilter: destroy RSS video filter
229  *****************************************************************************/
230 static void DestroyFilter( vlc_object_t *p_this )
231 {
232     filter_t *p_filter = (filter_t *)p_this;
233     filter_sys_t *p_sys = p_filter->p_sys;
234
235     if( p_sys->psz_marquee ) free( p_sys->psz_marquee );
236     free( p_sys->psz_urls );
237     /* TODO : free RSS feeds stuff */
238     free( p_sys );
239
240     /* Delete the RSS variables */
241     var_Destroy( p_filter, "rss-urls" );
242     var_Destroy( p_filter, "rss-speed" );
243     var_Destroy( p_filter, "rss-length" );
244     var_Destroy( p_filter, "marq-x" );
245     var_Destroy( p_filter, "marq-y" );
246     var_Destroy( p_filter, "marq-position" );
247     var_Destroy( p_filter, "marq-color");
248     var_Destroy( p_filter, "marq-opacity");
249     var_Destroy( p_filter, "marq-size");
250 }
251
252 /****************************************************************************
253  * Filter: the whole thing
254  ****************************************************************************
255  * This function outputs subpictures at regular time intervals.
256  ****************************************************************************/
257 static subpicture_t *Filter( filter_t *p_filter, mtime_t date )
258 {
259     filter_sys_t *p_sys = p_filter->p_sys;
260     subpicture_t *p_spu;
261     video_format_t fmt;
262
263     int i_feed, i_item;
264
265     /* wait more for the 1st char */
266     if( p_sys->last_date + ( p_sys->i_cur_char == 0 && p_sys->i_cur_item == 0 ? 5 : 1 ) * p_sys->i_speed > date ) return NULL;
267
268     p_sys->last_date = date;
269     p_sys->i_cur_char++;
270     if( p_sys->p_feeds[p_sys->i_cur_feed].p_items[p_sys->i_cur_item].psz_title[p_sys->i_cur_char] == 0 )
271     {
272         p_sys->i_cur_char = 0;
273         p_sys->i_cur_item++;
274         if( p_sys->i_cur_item >= p_sys->p_feeds[p_sys->i_cur_feed].i_items )
275         {
276             p_sys->i_cur_item = 0;
277             p_sys->i_cur_feed = (p_sys->i_cur_feed + 1)%p_sys->i_feeds;
278         }
279     }
280
281
282 /* We always need an update
283     if( p_sys->b_need_update == VLC_FALSE )
284     {
285         return NULL;
286     }*/
287
288     p_spu = p_filter->pf_sub_buffer_new( p_filter );
289     if( !p_spu ) return NULL;
290
291     memset( &fmt, 0, sizeof(video_format_t) );
292     fmt.i_chroma = VLC_FOURCC('T','E','X','T');
293     fmt.i_aspect = 0;
294     fmt.i_width = 0;
295     fmt.i_height = 0;
296     fmt.i_x_offset = 0;
297     fmt.i_y_offset = 0;
298     p_spu->p_region = p_spu->pf_create_region( VLC_OBJECT(p_filter), &fmt );
299     if( !p_spu->p_region )
300     {
301         p_filter->pf_sub_buffer_del( p_filter, p_spu );
302         return NULL;
303     }
304
305     i_item = p_sys->i_cur_item;
306     i_feed = p_sys->i_cur_feed;
307     snprintf( p_sys->psz_marquee, p_sys->i_length, "%s : %s", p_sys->p_feeds[i_feed].psz_title, p_sys->p_feeds[i_feed].p_items[i_item].psz_title+p_sys->i_cur_char );
308     while( strlen( p_sys->psz_marquee ) < (unsigned int)p_sys->i_length )
309     {
310         i_item++;
311         if( i_item == p_sys->p_feeds[i_feed].i_items ) break;
312         snprintf( strchr( p_sys->psz_marquee, 0 ), p_sys->i_length - strlen( p_sys->psz_marquee ), " - %s", p_sys->p_feeds[i_feed].p_items[i_item].psz_title );
313     }
314
315     p_spu->p_region->psz_text = strdup(p_sys->psz_marquee);
316     p_spu->i_start = date;
317     p_spu->i_stop  = 0;
318     p_spu->b_ephemer = VLC_TRUE;
319
320     /*  where to locate the string: */
321     if( p_sys->i_xoff < 0 || p_sys->i_yoff < 0 )
322     {   /* set to one of the 9 relative locations */
323         p_spu->i_flags = p_sys->i_pos;
324         p_spu->i_x = 0;
325         p_spu->i_y = 0;
326         p_spu->b_absolute = VLC_FALSE;
327     }
328     else
329     {   /*  set to an absolute xy, referenced to upper left corner */
330         p_spu->i_flags = OSD_ALIGN_LEFT | OSD_ALIGN_TOP;
331         p_spu->i_x = p_sys->i_xoff;
332         p_spu->i_y = p_sys->i_yoff;
333         p_spu->b_absolute = VLC_TRUE;
334     }
335     p_spu->i_height = 1;
336     p_spu->p_region->i_text_color = p_sys->i_font_color;
337     p_spu->p_region->i_text_alpha = 255 - p_sys->i_font_opacity;
338     p_spu->p_region->i_text_size = p_sys->i_font_size;
339
340
341     //p_sys->b_need_update = VLC_FALSE;
342     return p_spu;
343 }
344
345 static int FetchRSS( filter_t *p_filter)
346 {
347     filter_sys_t *p_sys = p_filter->p_sys;
348
349     stream_t *p_stream = NULL;
350     xml_t *p_xml = NULL;
351     xml_reader_t *p_xml_reader = NULL;
352
353     char *psz_eltname = NULL;
354     char *psz_eltvalue = NULL;
355     char *psz_feed = NULL;
356     char *psz_buffer = NULL;
357
358     int i_feed;
359     int i_item;
360     int i_is_item;
361     int i_int;
362
363     p_sys->i_feeds = 1;
364     i_int = 0;
365     while( p_sys->psz_urls[i_int] != 0 )
366         if( p_sys->psz_urls[i_int++] == ',' )
367             p_sys->i_feeds++;
368     p_sys->p_feeds = (struct rss_feed_t *)malloc( p_sys->i_feeds
369                                 * sizeof( struct rss_feed_t ) );
370
371     p_xml = xml_Create( p_filter );
372     if( !p_xml )
373     {
374         msg_Err( p_filter, "Failed to open XML parser" );
375         return 1;
376     }
377
378     psz_buffer = strdup( p_sys->psz_urls );
379     for( i_feed = 0; i_feed < p_sys->i_feeds; i_feed++ )
380     {
381         struct rss_feed_t *p_feed = p_sys->p_feeds+i_feed;
382
383         if( psz_buffer == NULL ) break;
384         if( psz_buffer[0] == 0 ) psz_buffer++;
385         psz_feed = psz_buffer;
386         psz_buffer = strchr( psz_buffer, ',' );
387         if( psz_buffer != NULL ) psz_buffer[0] = 0;
388
389         p_feed->psz_title = NULL;
390         p_feed->psz_description = NULL;
391         p_feed->psz_link = NULL;
392         p_feed->i_items = 0;
393         p_feed->p_items = NULL;
394
395         msg_Dbg( p_filter, "Opening %s RSS feed ...", psz_feed );
396
397         p_stream = stream_UrlNew( p_filter, psz_feed );
398         if( !p_stream )
399         {
400             msg_Err( p_filter, "Failed to open %s for reading", psz_feed );
401             return 1;
402         }
403
404         p_xml_reader = xml_ReaderCreate( p_xml, p_stream );
405         if( !p_xml_reader )
406         {
407             msg_Err( p_filter, "Failed to open %s for parsing", psz_feed );
408             return 1;
409         }
410
411         i_item = 0;
412         i_is_item = VLC_FALSE;
413
414         while( xml_ReaderRead( p_xml_reader ) == 1 )
415         {
416             switch( xml_ReaderNodeType( p_xml_reader ) )
417             {
418                 // Error
419                 case -1:
420                     return 1;
421
422                 case XML_READER_STARTELEM:
423                     if( psz_eltname ) free( psz_eltname );
424                     psz_eltname = xml_ReaderName( p_xml_reader );
425                     if( !psz_eltname )
426                     {
427                         return 1;
428                     }
429                     msg_Dbg( p_filter, "element name : %s", psz_eltname );
430                     if( !strcmp( psz_eltname, "item" ) )
431                     {
432                         i_is_item = VLC_TRUE;
433                         p_feed->i_items++;
434                         p_feed->p_items = (struct rss_item_t *)realloc( p_feed->p_items, p_feed->i_items * sizeof( struct rss_item_t ) );
435                     }
436                     break;
437
438                 case XML_READER_ENDELEM:
439                     if( psz_eltname ) free( psz_eltname );
440                     psz_eltname = xml_ReaderName( p_xml_reader );
441                     if( !psz_eltname )
442                     {
443                         return 1;
444                     }
445                     msg_Dbg( p_filter, "element end : %s", psz_eltname );
446                     if( !strcmp( psz_eltname, "item" ) )
447                     {
448                         i_is_item = VLC_FALSE;
449                         i_item++;
450                     }
451                     free( psz_eltname ); psz_eltname = NULL;
452                     break;
453
454                 case XML_READER_TEXT:
455                     psz_eltvalue = xml_ReaderValue( p_xml_reader );
456                     if( !psz_eltvalue )
457                     {
458                         return 1;
459                     }
460                     msg_Dbg( p_filter, "  text : %s", psz_eltvalue );
461                     if( i_is_item == VLC_FALSE )
462                     {
463                         if( !strcmp( psz_eltname, "title" ) )
464                         {
465                             p_feed->psz_title = psz_eltvalue;
466                         }
467                         else if( !strcmp( psz_eltname, "link" ) )
468                         {
469                             p_feed->psz_link = psz_eltvalue;
470                         }
471                         else if( !strcmp( psz_eltname, "description" ) )
472                         {
473                             p_feed->psz_description = psz_eltvalue;
474                         }
475                         else
476                         {
477                             free( psz_eltvalue );
478                         }
479                     }
480                     else
481                     {
482                         struct rss_item_t *p_item;
483                         p_item = p_feed->p_items+i_item;
484                         if( !strcmp( psz_eltname, "title" ) )
485                         {
486                             p_item->psz_title = psz_eltvalue;
487                         }
488                         else if( !strcmp( psz_eltname, "link" ) )
489                         {
490                             p_item->psz_link = psz_eltvalue;
491                         }
492                         else if( !strcmp( psz_eltname, "description" ) )
493                         {
494                             p_item->psz_description = psz_eltvalue;
495                         }
496                         else
497                         {
498                             free( psz_eltvalue );
499                         }
500                     }
501                     break;
502             }
503         }
504
505         if( p_xml_reader && p_xml ) xml_ReaderDelete( p_xml, p_xml_reader );
506         if( p_stream ) stream_Delete( p_stream );
507     }
508     if( p_xml ) xml_Delete( p_xml );
509
510     return 0;
511 }