95baafcc7a7e40497c1eb403022db623c19bfbc9
[vlc.git] / modules / spu / marq.c
1 /*****************************************************************************
2  * marq.c : marquee display video plugin for vlc
3  *****************************************************************************
4  * Copyright (C) 2003-2008 VLC authors and VideoLAN
5  *
6  * Authors: Mark Moriarty
7  *          Sigmund Augdal Helberg <dnumgis@videolan.org>
8  *          Antoine Cellerier <dionoea . videolan \ org>
9  *
10  * This program is free software; you can redistribute it and/or modify it
11  * under the terms of the GNU Lesser General Public License as published by
12  * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public License
21  * along with this program; if not, write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28
29 #ifdef HAVE_CONFIG_H
30 # include "config.h"
31 #endif
32
33 #include <errno.h>
34
35 #include <vlc_common.h>
36 #include <vlc_plugin.h>
37 #include <vlc_filter.h>
38 #include <vlc_block.h>
39 #include <vlc_fs.h>
40 #include <vlc_strings.h>
41 #include <vlc_subpicture.h>
42
43 /*****************************************************************************
44  * Local prototypes
45  *****************************************************************************/
46 static int  CreateFilter ( filter_t * );
47 static void DestroyFilter( filter_t * );
48 static subpicture_t *Filter( filter_t *, vlc_tick_t );
49
50 static char *MarqueeReadFile( filter_t *, const char * );
51 static int MarqueeCallback( vlc_object_t *p_this, char const *psz_var,
52                             vlc_value_t oldval, vlc_value_t newval,
53                             void *p_data );
54 static const int pi_color_values[] = {
55                0xf0000000, 0x00000000, 0x00808080, 0x00C0C0C0,
56                0x00FFFFFF, 0x00800000, 0x00FF0000, 0x00FF00FF, 0x00FFFF00,
57                0x00808000, 0x00008000, 0x00008080, 0x0000FF00, 0x00800080,
58                0x00000080, 0x000000FF, 0x0000FFFF};
59 static const char *const ppsz_color_descriptions[] = {
60                N_("Default"), N_("Black"), N_("Gray"),
61                N_("Silver"), N_("White"), N_("Maroon"), N_("Red"),
62                N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"),
63                N_("Teal"), N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"),
64                N_("Aqua") };
65
66 /*****************************************************************************
67  * filter_sys_t: marquee filter descriptor
68  *****************************************************************************/
69 typedef struct
70 {
71     vlc_mutex_t lock;
72
73     int i_xoff, i_yoff;  /* offsets for the display string in the video window */
74     int i_pos; /* permit relative positioning (top, bottom, left, right, center) */
75     vlc_tick_t i_timeout;
76
77     char *format; /**< marquee text format */
78     char *filepath; /**< marquee file path */
79     char *message; /**< marquee plain text */
80
81     text_style_t *p_style; /* font control */
82
83     vlc_tick_t last_time;
84     vlc_tick_t i_refresh;
85 } filter_sys_t;
86
87 #define MSG_TEXT N_("Text")
88 #define MSG_LONGTEXT N_( \
89     "Marquee text to display. " \
90     "(Available format strings: " \
91     "%Y = year, %m = month, %d = day, %H = hour, " \
92     "%M = minute, %S = second, ...)" )
93 #define FILE_TEXT N_("Text file")
94 #define FILE_LONGTEXT N_("File to read the marquee text from.")
95 #define POSX_TEXT N_("X offset")
96 #define POSX_LONGTEXT N_("X offset, from the left screen edge." )
97 #define POSY_TEXT N_("Y offset")
98 #define POSY_LONGTEXT N_("Y offset, down from the top." )
99 #define TIMEOUT_TEXT N_("Timeout")
100 #define TIMEOUT_LONGTEXT N_("Number of milliseconds the marquee must remain " \
101                             "displayed. Default value is " \
102                             "0 (remains forever).")
103 #define REFRESH_TEXT N_("Refresh period in ms")
104 #define REFRESH_LONGTEXT N_("Number of milliseconds between string updates. " \
105                             "This is mainly useful when using meta data " \
106                             "or time format string sequences.")
107 #define OPACITY_TEXT N_("Opacity")
108 #define OPACITY_LONGTEXT N_("Opacity (inverse of transparency) of " \
109     "overlayed text. 0 = transparent, 255 = totally opaque." )
110 #define SIZE_TEXT N_("Font size, pixels")
111 #define SIZE_LONGTEXT N_("Font size, in pixels. Default is 0 (use default " \
112     "font size)." )
113
114 #define COLOR_TEXT N_("Color")
115 #define COLOR_LONGTEXT N_("Color of the text that will be rendered on "\
116     "the video. This must be an hexadecimal (like HTML colors). The first two "\
117     "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
118     " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
119
120 #define POS_TEXT N_("Marquee position")
121 #define POS_LONGTEXT N_( \
122   "You can enforce the marquee position on the video " \
123   "(0=center, 1=left, 2=right, 4=top, 8=bottom, you can " \
124   "also use combinations of these values, eg 6 = top-right).")
125
126 static const int pi_pos_values[] = { 0, 1, 2, 4, 8, 5, 6, 9, 10 };
127 static const char *const ppsz_pos_descriptions[] =
128      { N_("Center"), N_("Left"), N_("Right"), N_("Top"), N_("Bottom"),
129      N_("Top-Left"), N_("Top-Right"), N_("Bottom-Left"), N_("Bottom-Right") };
130
131 #define CFG_PREFIX "marq-"
132
133 #define MARQUEE_HELP N_("Display text above the video")
134
135 /*****************************************************************************
136  * Module descriptor
137  *****************************************************************************/
138 vlc_module_begin ()
139     set_shortname( N_("Marquee" ))
140     set_description( N_("Marquee display") )
141     set_help(MARQUEE_HELP)
142     set_callback_sub_source( CreateFilter, 0 )
143     set_category( CAT_VIDEO )
144     set_subcategory( SUBCAT_VIDEO_SUBPIC )
145     add_string( CFG_PREFIX "marquee", "VLC", MSG_TEXT, MSG_LONGTEXT,
146                 false )
147     add_loadfile(CFG_PREFIX "file", NULL, FILE_TEXT, FILE_LONGTEXT)
148
149     set_section( N_("Position"), NULL )
150     add_integer( CFG_PREFIX "x", 0, POSX_TEXT, POSX_LONGTEXT, true )
151     add_integer( CFG_PREFIX "y", 0, POSY_TEXT, POSY_LONGTEXT, true )
152     add_integer( CFG_PREFIX "position", -1, POS_TEXT, POS_LONGTEXT, false )
153         change_integer_list( pi_pos_values, ppsz_pos_descriptions )
154
155     set_section( N_("Font"), NULL )
156     /* 5 sets the default to top [1] left [4] */
157     add_integer_with_range( CFG_PREFIX "opacity", 255, 0, 255,
158         OPACITY_TEXT, OPACITY_LONGTEXT, false )
159     add_rgb(CFG_PREFIX "color", 0xFFFFFF, COLOR_TEXT, COLOR_LONGTEXT)
160         change_integer_list( pi_color_values, ppsz_color_descriptions )
161     add_integer( CFG_PREFIX "size", 0, SIZE_TEXT, SIZE_LONGTEXT,
162                  false )
163         change_integer_range( 0, 4096)
164
165     set_section( N_("Misc"), NULL )
166     add_integer( CFG_PREFIX "timeout", 0, TIMEOUT_TEXT, TIMEOUT_LONGTEXT,
167                  false )
168     add_integer( CFG_PREFIX "refresh", 1000, REFRESH_TEXT,
169                  REFRESH_LONGTEXT, false )
170
171     add_shortcut( "time" )
172 vlc_module_end ()
173
174 static const char *const ppsz_filter_options[] = {
175     "marquee", "x", "y", "position", "color", "size", "timeout", "refresh",
176     "opacity","file",
177     NULL
178 };
179
180 static const struct vlc_filter_operations filter_ops = {
181     .source_sub = Filter, .close = DestroyFilter,
182 };
183
184 /*****************************************************************************
185  * CreateFilter: allocates marquee video filter
186  *****************************************************************************/
187 static int CreateFilter( filter_t *p_filter )
188 {
189     filter_sys_t *p_sys;
190
191     /* Allocate structure */
192     p_sys = p_filter->p_sys = malloc( sizeof( filter_sys_t ) );
193     if( p_sys == NULL )
194         return VLC_ENOMEM;
195
196     p_sys->p_style = text_style_Create( STYLE_NO_DEFAULTS );
197     if(unlikely(!p_sys->p_style))
198     {
199         free(p_sys);
200         return VLC_ENOMEM;
201     }
202     vlc_mutex_init( &p_sys->lock );
203
204     config_ChainParse( p_filter, CFG_PREFIX, ppsz_filter_options,
205                        p_filter->p_cfg );
206
207
208 #define CREATE_VAR( stor, type, var ) \
209     p_sys->stor = var_CreateGet##type##Command( p_filter, var ); \
210     var_AddCallback( p_filter, var, MarqueeCallback, p_sys );
211
212     CREATE_VAR( i_xoff, Integer, "marq-x" );
213     CREATE_VAR( i_yoff, Integer, "marq-y" );
214     p_sys->i_timeout = VLC_TICK_FROM_MS(var_CreateGetIntegerCommand( p_filter,
215                                                               "marq-timeout" ));
216     var_AddCallback( p_filter, "marq-timeout", MarqueeCallback, p_sys );
217     p_sys->i_refresh = VLC_TICK_FROM_MS(var_CreateGetIntegerCommand( p_filter,
218                                                               "marq-refresh" ));
219     var_AddCallback( p_filter, "marq-refresh", MarqueeCallback, p_sys );
220     CREATE_VAR( i_pos, Integer, "marq-position" );
221     CREATE_VAR( format, String, "marq-marquee" );
222     p_sys->filepath = var_InheritString( p_filter, "marq-file" );
223     p_sys->message = NULL;
224     p_sys->p_style->i_font_alpha = var_CreateGetIntegerCommand( p_filter,
225                                                             "marq-opacity" );
226     var_AddCallback( p_filter, "marq-opacity", MarqueeCallback, p_sys );
227     p_sys->p_style->i_features |= STYLE_HAS_FONT_ALPHA;
228     CREATE_VAR( p_style->i_font_color, Integer, "marq-color" );
229     p_sys->p_style->i_features |= STYLE_HAS_FONT_COLOR;
230     CREATE_VAR( p_style->i_font_size, Integer, "marq-size" );
231
232     /* Misc init */
233     p_filter->ops = &filter_ops;
234     p_sys->last_time = 0;
235
236     return VLC_SUCCESS;
237 }
238 /*****************************************************************************
239  * DestroyFilter: destroy marquee video filter
240  *****************************************************************************/
241 static void DestroyFilter( filter_t *p_filter )
242 {
243     filter_sys_t *p_sys = p_filter->p_sys;
244
245     /* Delete the marquee variables */
246 #define DEL_VAR(var) \
247     var_DelCallback( p_filter, var, MarqueeCallback, p_sys ); \
248     var_Destroy( p_filter, var );
249     DEL_VAR( "marq-x" );
250     DEL_VAR( "marq-y" );
251     DEL_VAR( "marq-timeout" );
252     DEL_VAR( "marq-refresh" );
253     DEL_VAR( "marq-position" );
254     DEL_VAR( "marq-marquee" );
255     DEL_VAR( "marq-opacity" );
256     DEL_VAR( "marq-color" );
257     DEL_VAR( "marq-size" );
258
259     text_style_Delete( p_sys->p_style );
260     free( p_sys->format );
261     free( p_sys->filepath );
262     free( p_sys->message );
263     free( p_sys );
264 }
265
266 /****************************************************************************
267  * Filter: the whole thing
268  ****************************************************************************
269  * This function outputs subpictures at regular time intervals.
270  ****************************************************************************/
271 static subpicture_t *Filter( filter_t *p_filter, vlc_tick_t date )
272 {
273     filter_sys_t *p_sys = p_filter->p_sys;
274     subpicture_t *p_spu = NULL;
275
276     vlc_mutex_lock( &p_sys->lock );
277     if( p_sys->last_time + p_sys->i_refresh > date )
278         goto out;
279
280     if( p_sys->filepath != NULL )
281     {
282         char *fmt = MarqueeReadFile( p_filter, p_sys->filepath );
283         if( fmt != NULL )
284         {
285             free( p_sys->format );
286             p_sys->format = fmt;
287         }
288     }
289
290     char *msg = vlc_strftime( p_sys->format ? p_sys->format : "" );
291     if( unlikely( msg == NULL ) )
292         goto out;
293     if( p_sys->message != NULL && !strcmp( msg, p_sys->message ) )
294     {
295         free( msg );
296         goto out;
297     }
298     free( p_sys->message );
299     p_sys->message = msg;
300
301     p_spu = filter_NewSubpicture( p_filter );
302     if( !p_spu )
303         goto out;
304
305     video_format_t vfmt;
306     video_format_Init( &vfmt, VLC_CODEC_TEXT );
307     vfmt.i_sar_den = vfmt.i_sar_num = 1;
308     p_spu->p_region = subpicture_region_New( &vfmt );
309     if( !p_spu->p_region )
310     {
311         subpicture_Delete( p_spu );
312         p_spu = NULL;
313         goto out;
314     }
315
316     p_sys->last_time = date;
317
318     p_spu->p_region->p_text = text_segment_New( msg );
319     p_spu->i_start = date;
320     p_spu->i_stop  = p_sys->i_timeout == 0 ? 0 : date + p_sys->i_timeout;
321     p_spu->b_ephemer = true;
322
323     /*  where to locate the string: */
324     if( p_sys->i_pos < 0 )
325     {   /*  set to an absolute xy */
326         p_spu->p_region->i_align = SUBPICTURE_ALIGN_LEFT | SUBPICTURE_ALIGN_TOP;
327         p_spu->b_absolute = true;
328     }
329     else
330     {   /* set to one of the 9 relative locations */
331         p_spu->p_region->i_align = p_sys->i_pos;
332         p_spu->b_absolute = false;
333     }
334
335     p_spu->p_region->i_x = p_sys->i_xoff;
336     p_spu->p_region->i_y = p_sys->i_yoff;
337
338     p_spu->p_region->p_text->style = text_style_Duplicate( p_sys->p_style );
339
340 out:
341     vlc_mutex_unlock( &p_sys->lock );
342     return p_spu;
343 }
344
345 static char *MarqueeReadFile( filter_t *obj, const char *path )
346 {
347     FILE *stream = vlc_fopen( path, "rt" );
348     if( stream == NULL )
349     {
350         msg_Err( obj, "cannot open %s: %s", path, vlc_strerror_c(errno) );
351         return NULL;
352     }
353
354     char *line = NULL;
355
356     ssize_t len = getline( &line, &(size_t){ 0 }, stream );
357     if( len == -1 )
358     {
359         msg_Err( obj, "cannot read %s: %s", path, vlc_strerror_c(errno) );
360         clearerr( stream );
361         line = NULL;
362     }
363     fclose( stream );
364
365     if( len >= 1 && line[len - 1] == '\n' )
366         line[--len]  = '\0';
367     return line;
368 }
369
370 /**********************************************************************
371  * Callback to update params on the fly
372  **********************************************************************/
373 static int MarqueeCallback( vlc_object_t *p_this, char const *psz_var,
374                             vlc_value_t oldval, vlc_value_t newval,
375                             void *p_data )
376 {
377     filter_sys_t *p_sys = (filter_sys_t *) p_data;
378
379     VLC_UNUSED(oldval);
380     VLC_UNUSED(p_this);
381
382     vlc_mutex_lock( &p_sys->lock );
383     if( !strcmp( psz_var, "marq-marquee" ) )
384     {
385         free( p_sys->format );
386         p_sys->format = strdup( newval.psz_string );
387     }
388     else if ( !strcmp( psz_var, "marq-x" ) )
389     {
390         p_sys->i_xoff = newval.i_int;
391     }
392     else if ( !strcmp( psz_var, "marq-y" ) )
393     {
394         p_sys->i_yoff = newval.i_int;
395     }
396     else if ( !strcmp( psz_var, "marq-color" ) )
397     {
398         p_sys->p_style->i_font_color = newval.i_int;
399     }
400     else if ( !strcmp( psz_var, "marq-opacity" ) )
401     {
402         p_sys->p_style->i_font_alpha = newval.i_int;
403     }
404     else if ( !strcmp( psz_var, "marq-size" ) )
405     {
406         p_sys->p_style->i_font_size = newval.i_int;
407     }
408     else if ( !strcmp( psz_var, "marq-timeout" ) )
409     {
410         p_sys->i_timeout = VLC_TICK_FROM_MS(newval.i_int);
411     }
412     else if ( !strcmp( psz_var, "marq-refresh" ) )
413     {
414         p_sys->i_refresh = VLC_TICK_FROM_MS(newval.i_int);
415     }
416     else if ( !strcmp( psz_var, "marq-position" ) )
417     /* willing to accept a match against marq-pos */
418     {
419         p_sys->i_pos = newval.i_int;
420     }
421
422     free( p_sys->message );
423     p_sys->message = NULL; /* force update */
424
425     vlc_mutex_unlock( &p_sys->lock );
426     return VLC_SUCCESS;
427 }