marq.c 14.9 KB
Newer Older
1 2 3
/*****************************************************************************
 * marq.c : marquee display video plugin for vlc
 *****************************************************************************
Jean-Baptiste Kempf's avatar
LGPL  
Jean-Baptiste Kempf committed
4
 * Copyright (C) 2003-2008 VLC authors and VideoLAN
5
 * $Id$
6 7
 *
 * Authors: Mark Moriarty
8 9
 *          Sigmund Augdal Helberg <dnumgis@videolan.org>
 *          Antoine Cellerier <dionoea . videolan \ org>
10
 *
Jean-Baptiste Kempf's avatar
LGPL  
Jean-Baptiste Kempf committed
11 12 13
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
14 15 16 17
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Jean-Baptiste Kempf's avatar
LGPL  
Jean-Baptiste Kempf committed
18 19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
20
 *
Jean-Baptiste Kempf's avatar
LGPL  
Jean-Baptiste Kempf committed
21 22 23
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24 25 26 27 28 29
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/

30 31 32 33
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

34 35
#include <errno.h>

36
#include <vlc_common.h>
37
#include <vlc_plugin.h>
Rémi Duraffort's avatar
Rémi Duraffort committed
38 39
#include <vlc_filter.h>
#include <vlc_block.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
40
#include <vlc_fs.h>
Rémi Duraffort's avatar
Rémi Duraffort committed
41
#include <vlc_strings.h>
42
#include <vlc_subpicture.h>
43 44 45 46 47 48 49 50

/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
static int  CreateFilter ( vlc_object_t * );
static void DestroyFilter( vlc_object_t * );
static subpicture_t *Filter( filter_t *, mtime_t );

51
static char *MarqueeReadFile( filter_t *, const char * );
52 53 54
static int MarqueeCallback( vlc_object_t *p_this, char const *psz_var,
                            vlc_value_t oldval, vlc_value_t newval,
                            void *p_data );
55 56
static const int pi_color_values[] = {
               0xf0000000, 0x00000000, 0x00808080, 0x00C0C0C0,
57 58 59
               0x00FFFFFF, 0x00800000, 0x00FF0000, 0x00FF00FF, 0x00FFFF00,
               0x00808000, 0x00008000, 0x00008080, 0x0000FF00, 0x00800080,
               0x00000080, 0x000000FF, 0x0000FFFF};
60 61 62
static const char *const ppsz_color_descriptions[] = {
               N_("Default"), N_("Black"), N_("Gray"),
               N_("Silver"), N_("White"), N_("Maroon"), N_("Red"),
63 64
               N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"),
               N_("Teal"), N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"),
65 66 67 68 69 70 71
               N_("Aqua") };

/*****************************************************************************
 * filter_sys_t: marquee filter descriptor
 *****************************************************************************/
struct filter_sys_t
{
72 73
    vlc_mutex_t lock;

74 75 76 77
    int i_xoff, i_yoff;  /* offsets for the display string in the video window */
    int i_pos; /* permit relative positioning (top, bottom, left, right, center) */
    int i_timeout;

78
    char *format; /**< marquee text format */
79
    char *filepath; /**< marquee file path */
80
    char *message; /**< marquee plain text */
81

82
    text_style_t *p_style; /* font control */
83

84 85
    mtime_t last_time;
    mtime_t i_refresh;
86 87
};

88
#define MSG_TEXT N_("Text")
89 90 91
#define MSG_LONGTEXT N_( \
    "Marquee text to display. " \
    "(Available format strings: " \
92 93
    "%Y = year, %m = month, %d = day, %H = hour, " \
    "%M = minute, %S = second, ...)" )
94 95
#define FILE_TEXT N_("Text file")
#define FILE_LONGTEXT N_("File to read the marquee text from.")
96
#define POSX_TEXT N_("X offset")
97
#define POSX_LONGTEXT N_("X offset, from the left screen edge." )
98
#define POSY_TEXT N_("Y offset")
99
#define POSY_LONGTEXT N_("Y offset, down from the top." )
100 101
#define TIMEOUT_TEXT N_("Timeout")
#define TIMEOUT_LONGTEXT N_("Number of milliseconds the marquee must remain " \
102
                            "displayed. Default value is " \
103
                            "0 (remains forever).")
104 105
#define REFRESH_TEXT N_("Refresh period in ms")
#define REFRESH_LONGTEXT N_("Number of milliseconds between string updates. " \
Christophe Mutricy's avatar
Typos  
Christophe Mutricy committed
106
                            "This is mainly useful when using meta data " \
107
                            "or time format string sequences.")
Clément Stenac's avatar
Clément Stenac committed
108
#define OPACITY_TEXT N_("Opacity")
109
#define OPACITY_LONGTEXT N_("Opacity (inverse of transparency) of " \
110
    "overlayed text. 0 = transparent, 255 = totally opaque." )
111
#define SIZE_TEXT N_("Font size, pixels")
112
#define SIZE_LONGTEXT N_("Font size, in pixels. Default is 0 (use default " \
113
    "font size)." )
114

115 116 117 118 119
#define COLOR_TEXT N_("Color")
#define COLOR_LONGTEXT N_("Color of the text that will be rendered on "\
    "the video. This must be an hexadecimal (like HTML colors). The first two "\
    "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
    " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
120 121 122 123 124

#define POS_TEXT N_("Marquee position")
#define POS_LONGTEXT N_( \
  "You can enforce the marquee position on the video " \
  "(0=center, 1=left, 2=right, 4=top, 8=bottom, you can " \
125
  "also use combinations of these values, eg 6 = top-right).")
126

127 128
static const int pi_pos_values[] = { 0, 1, 2, 4, 8, 5, 6, 9, 10 };
static const char *const ppsz_pos_descriptions[] =
129 130 131
     { N_("Center"), N_("Left"), N_("Right"), N_("Top"), N_("Bottom"),
     N_("Top-Left"), N_("Top-Right"), N_("Bottom-Left"), N_("Bottom-Right") };

132 133
#define CFG_PREFIX "marq-"

134 135
#define MARQUEE_HELP N_("Display text above the video")

136 137 138
/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
139
vlc_module_begin ()
140
    set_capability( "sub source", 0 )
141
    set_shortname( N_("Marquee" ))
142 143
    set_description( N_("Marquee display") )
    set_help(MARQUEE_HELP)
144 145 146
    set_callbacks( CreateFilter, DestroyFilter )
    set_category( CAT_VIDEO )
    set_subcategory( SUBCAT_VIDEO_SUBPIC )
147
    add_string( CFG_PREFIX "marquee", "VLC", MSG_TEXT, MSG_LONGTEXT,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
148
                false )
149
    add_loadfile( CFG_PREFIX "file", NULL, FILE_TEXT, FILE_LONGTEXT, true )
Clément Stenac's avatar
Clément Stenac committed
150

151
    set_section( N_("Position"), NULL )
152 153 154
    add_integer( CFG_PREFIX "x", 0, POSX_TEXT, POSX_LONGTEXT, true )
    add_integer( CFG_PREFIX "y", 0, POSY_TEXT, POSY_LONGTEXT, true )
    add_integer( CFG_PREFIX "position", -1, POS_TEXT, POS_LONGTEXT, false )
155
        change_integer_list( pi_pos_values, ppsz_pos_descriptions )
Clément Stenac's avatar
Clément Stenac committed
156

157
    set_section( N_("Font"), NULL )
158
    /* 5 sets the default to top [1] left [4] */
159
    add_integer_with_range( CFG_PREFIX "opacity", 255, 0, 255,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
160
        OPACITY_TEXT, OPACITY_LONGTEXT, false )
161
    add_rgb( CFG_PREFIX "color", 0xFFFFFF, COLOR_TEXT, COLOR_LONGTEXT,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
162
                 false )
163
        change_integer_list( pi_color_values, ppsz_color_descriptions )
164
    add_integer( CFG_PREFIX "size", 0, SIZE_TEXT, SIZE_LONGTEXT,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
165
                 false )
166
        change_integer_range( 0, 4096)
167

168
    set_section( N_("Misc"), NULL )
169
    add_integer( CFG_PREFIX "timeout", 0, TIMEOUT_TEXT, TIMEOUT_LONGTEXT,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
170
                 false )
171
    add_integer( CFG_PREFIX "refresh", 1000, REFRESH_TEXT,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
172
                 REFRESH_LONGTEXT, false )
Clément Stenac's avatar
Clément Stenac committed
173

174 175
    add_shortcut( "time" )
vlc_module_end ()
176

177
static const char *const ppsz_filter_options[] = {
178
    "marquee", "x", "y", "position", "color", "size", "timeout", "refresh",
179
    "opacity","file",
180
    NULL
181 182
};

183 184 185 186 187 188 189 190 191 192 193 194 195
/*****************************************************************************
 * CreateFilter: allocates marquee video filter
 *****************************************************************************/
static int CreateFilter( vlc_object_t *p_this )
{
    filter_t *p_filter = (filter_t *)p_this;
    filter_sys_t *p_sys;

    /* Allocate structure */
    p_sys = p_filter->p_sys = malloc( sizeof( filter_sys_t ) );
    if( p_sys == NULL )
        return VLC_ENOMEM;

196 197
    p_sys->p_style = text_style_Create( STYLE_NO_DEFAULTS );
    if(unlikely(!p_sys->p_style))
198 199 200 201 202
    {
        free(p_sys);
        return VLC_ENOMEM;
    }
    vlc_mutex_init( &p_sys->lock );
203

204 205 206
    config_ChainParse( p_filter, CFG_PREFIX, ppsz_filter_options,
                       p_filter->p_cfg );

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 213 214

    CREATE_VAR( i_xoff, Integer, "marq-x" );
    CREATE_VAR( i_yoff, Integer, "marq-y" );
    CREATE_VAR( i_timeout,Integer, "marq-timeout" );
215 216 217
    p_sys->i_refresh = 1000 * var_CreateGetIntegerCommand( p_filter,
                                                           "marq-refresh" );
    var_AddCallback( p_filter, "marq-refresh", MarqueeCallback, p_sys );
218
    CREATE_VAR( i_pos, Integer, "marq-position" );
219
    CREATE_VAR( format, String, "marq-marquee" );
220
    p_sys->filepath = var_InheritString( p_filter, "marq-file" );
221
    p_sys->message = NULL;
222
    p_sys->p_style->i_font_alpha = var_CreateGetIntegerCommand( p_filter,
223
                                                            "marq-opacity" );
224
    var_AddCallback( p_filter, "marq-opacity", MarqueeCallback, p_sys );
225
    p_sys->p_style->i_features |= STYLE_HAS_FONT_ALPHA;
226
    CREATE_VAR( p_style->i_font_color, Integer, "marq-color" );
227
    p_sys->p_style->i_features |= STYLE_HAS_FONT_COLOR;
228 229
    CREATE_VAR( p_style->i_font_size, Integer, "marq-size" );

230
    /* Misc init */
231
    p_filter->pf_sub_source = Filter;
232
    p_sys->last_time = 0;
233 234 235 236 237 238 239 240 241 242 243 244

    return VLC_SUCCESS;
}
/*****************************************************************************
 * DestroyFilter: destroy marquee video filter
 *****************************************************************************/
static void DestroyFilter( vlc_object_t *p_this )
{
    filter_t *p_filter = (filter_t *)p_this;
    filter_sys_t *p_sys = p_filter->p_sys;

    /* Delete the marquee variables */
245
#define DEL_VAR(var) \
246 247
    var_DelCallback( p_filter, var, MarqueeCallback, p_sys ); \
    var_Destroy( p_filter, var );
248 249 250
    DEL_VAR( "marq-x" );
    DEL_VAR( "marq-y" );
    DEL_VAR( "marq-timeout" );
251
    DEL_VAR( "marq-refresh" );
252
    DEL_VAR( "marq-position" );
253
    DEL_VAR( "marq-marquee" );
254
    DEL_VAR( "marq-opacity" );
255
    DEL_VAR( "marq-color" );
256
    DEL_VAR( "marq-size" );
257

258
    vlc_mutex_destroy( &p_sys->lock );
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 268 269 270 271 272 273
}

/****************************************************************************
 * Filter: the whole thing
 ****************************************************************************
 * This function outputs subpictures at regular time intervals.
 ****************************************************************************/
static subpicture_t *Filter( filter_t *p_filter, mtime_t date )
{
    filter_sys_t *p_sys = p_filter->p_sys;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
274
    subpicture_t *p_spu = NULL;
275

276
    vlc_mutex_lock( &p_sys->lock );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
277
    if( p_sys->last_time + p_sys->i_refresh > date )
278
        goto out;
279

280 281
    if( p_sys->filepath != NULL )
    {
282
        char *fmt = MarqueeReadFile( p_filter, p_sys->filepath );
283 284 285 286 287 288 289
        if( fmt != NULL )
        {
            free( p_sys->format );
            p_sys->format = fmt;
        }
    }

290
    char *msg = vlc_strftime( p_sys->format ? p_sys->format : "" );
291 292 293 294 295
    if( unlikely( msg == NULL ) )
        goto out;
    if( p_sys->message != NULL && !strcmp( msg, p_sys->message ) )
    {
        free( msg );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
296
        goto out;
297 298 299
    }
    free( p_sys->message );
    p_sys->message = msg;
300

Laurent Aimar's avatar
Laurent Aimar committed
301
    p_spu = filter_NewSubpicture( p_filter );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
302 303
    if( !p_spu )
        goto out;
304

305 306 307 308
    video_format_t vfmt;
    video_format_Init( &vfmt, VLC_CODEC_TEXT );
    vfmt.i_sar_den = vfmt.i_sar_num = 1;
    p_spu->p_region = subpicture_region_New( &vfmt );
309 310
    if( !p_spu->p_region )
    {
311
        subpicture_Delete( p_spu );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
312 313
        p_spu = NULL;
        goto out;
314 315
    }

316
    p_sys->last_time = date;
317

318
    p_spu->p_region->p_text = text_segment_New( msg );
319 320
    p_spu->i_start = date;
    p_spu->i_stop  = p_sys->i_timeout == 0 ? 0 : date + p_sys->i_timeout * 1000;
321
    p_spu->b_ephemer = true;
322 323

    /*  where to locate the string: */
324
    if( p_sys->i_pos < 0 )
Antoine Cellerier's avatar
Antoine Cellerier committed
325
    {   /*  set to an absolute xy */
Laurent Aimar's avatar
Laurent Aimar committed
326
        p_spu->p_region->i_align = SUBPICTURE_ALIGN_LEFT | SUBPICTURE_ALIGN_TOP;
327
        p_spu->b_absolute = true;
328
    }
329
    else
Antoine Cellerier's avatar
Antoine Cellerier committed
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 336
    p_spu->p_region->i_x = p_sys->i_xoff;
    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

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
340
out:
341
    vlc_mutex_unlock( &p_sys->lock );
342 343 344
    return p_spu;
}

345
static char *MarqueeReadFile( filter_t *obj, const char *path )
346
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
347
    FILE *stream = vlc_fopen( path, "rt" );
348 349
    if( stream == NULL )
    {
350
        msg_Err( obj, "cannot open %s: %s", path, vlc_strerror_c(errno) );
351 352 353 354 355 356 357 358
        return NULL;
    }

    char *line = NULL;

    ssize_t len = getline( &line, &(size_t){ 0 }, stream );
    if( len == -1 )
    {
359
        msg_Err( obj, "cannot read %s: %s", path, vlc_strerror_c(errno) );
360 361 362 363 364 365 366 367 368 369
        clearerr( stream );
        line = NULL;
    }
    fclose( stream );

    if( len >= 1 && line[len - 1] == '\n' )
        line[--len]  = '\0';
    return line;
}

370 371 372 373 374 375 376 377 378
/**********************************************************************
 * Callback to update params on the fly
 **********************************************************************/
static int MarqueeCallback( vlc_object_t *p_this, char const *psz_var,
                            vlc_value_t oldval, vlc_value_t newval,
                            void *p_data )
{
    filter_sys_t *p_sys = (filter_sys_t *) p_data;

379 380 381 382
    VLC_UNUSED(oldval);
    VLC_UNUSED(p_this);

    vlc_mutex_lock( &p_sys->lock );
383
    if( !strcmp( psz_var, "marq-marquee" ) )
384
    {
385 386
        free( p_sys->format );
        p_sys->format = strdup( newval.psz_string );
387
    }
388
    else if ( !strcmp( psz_var, "marq-x" ) )
389 390 391
    {
        p_sys->i_xoff = newval.i_int;
    }
392
    else if ( !strcmp( psz_var, "marq-y" ) )
393 394 395
    {
        p_sys->i_yoff = newval.i_int;
    }
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 411
    {
        p_sys->i_timeout = newval.i_int;
    }
412
    else if ( !strcmp( psz_var, "marq-refresh" ) )
413
    {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
414
        p_sys->i_refresh = newval.i_int * 1000;
415
    }
416
    else if ( !strcmp( psz_var, "marq-position" ) )
417 418 419 420
    /* willing to accept a match against marq-pos */
    {
        p_sys->i_pos = newval.i_int;
    }
421 422 423 424

    free( p_sys->message );
    p_sys->message = NULL; /* force update */

425
    vlc_mutex_unlock( &p_sys->lock );
426 427
    return VLC_SUCCESS;
}