diff options
author | Nicolas George <nicolas.george@normalesup.org> | 2012-11-10 22:46:37 +0400 |
---|---|---|
committer | Nicolas George <nicolas.george@normalesup.org> | 2012-11-18 18:55:47 +0400 |
commit | c437c251b8e58395eb8e81b73d8a0c321ae0f47b (patch) | |
tree | c6cdaa8603d455bd50c342fe27e264457481487e /libavfilter/vf_drawtext.c | |
parent | 4413a8428a759d33ea58a0617551ba522833f87b (diff) |
lavfi/drawtext: implement more generic expansion.
The new expansion mechanism uses the %{...} notation.
For compatibility reasons, it must be enabled explicitly,
but a warning is printed if a conflict is likely to happen.
Diffstat (limited to 'libavfilter/vf_drawtext.c')
-rw-r--r-- | libavfilter/vf_drawtext.c | 173 |
1 files changed, 166 insertions, 7 deletions
diff --git a/libavfilter/vf_drawtext.c b/libavfilter/vf_drawtext.c index f9c69ae833..a78a3ea9bf 100644 --- a/libavfilter/vf_drawtext.c +++ b/libavfilter/vf_drawtext.c @@ -113,8 +113,15 @@ enum var_name { VAR_VARS_NB }; +enum expansion_mode { + EXP_NONE, + EXP_NORMAL, + EXP_STRFTIME, +}; + typedef struct { const AVClass *class; + enum expansion_mode exp_mode; ///< expansion mode to use for the text int reinit; ///< tells if the filter is being reinited uint8_t *fontfile; ///< font to be used uint8_t *text; ///< text to be drawn @@ -181,6 +188,12 @@ static const AVOption drawtext_options[]= { {"tabsize", "set tab size", OFFSET(tabsize), AV_OPT_TYPE_INT, {.i64=4}, 0, INT_MAX , FLAGS}, {"basetime", "set base time", OFFSET(basetime), AV_OPT_TYPE_INT64, {.i64=AV_NOPTS_VALUE}, INT64_MIN, INT64_MAX , FLAGS}, {"draw", "if false do not draw", OFFSET(draw_expr), AV_OPT_TYPE_STRING, {.str="1"}, CHAR_MIN, CHAR_MAX, FLAGS}, + +{"expansion","set the expansion mode", OFFSET(exp_mode), AV_OPT_TYPE_INT, {.i64=EXP_STRFTIME}, 0, 2, FLAGS, "expansion"}, +{"none", "set no expansion", OFFSET(exp_mode), AV_OPT_TYPE_CONST, {.i64=EXP_NONE}, 0, 0, FLAGS, "expansion"}, +{"normal", "set normal expansion", OFFSET(exp_mode), AV_OPT_TYPE_CONST, {.i64=EXP_NORMAL}, 0, 0, FLAGS, "expansion"}, +{"strftime", "set strftime expansion (deprecated)", OFFSET(exp_mode), AV_OPT_TYPE_CONST, {.i64=EXP_STRFTIME}, 0, 0, FLAGS, "expansion"}, + {"timecode", "set initial timecode", OFFSET(tc_opt_string), AV_OPT_TYPE_STRING, {.str=NULL}, CHAR_MIN, CHAR_MAX, FLAGS}, {"tc24hmax", "set 24 hours max (timecode only)", OFFSET(tc24hmax), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, FLAGS}, {"timecode_rate", "set rate (timecode only)", OFFSET(tc_rate), AV_OPT_TYPE_RATIONAL, {.dbl=0}, 0, INT_MAX, FLAGS}, @@ -484,6 +497,10 @@ static av_cold int init(AVFilterContext *ctx, const char *args) } dtext->tabsize *= glyph->advance; + if (dtext->exp_mode == EXP_STRFTIME && + (strchr(dtext->text, '%') || strchr(dtext->text, '\\'))) + av_log(ctx, AV_LOG_WARNING, "expansion=strftime is deprecated.\n"); + av_bprint_init(&dtext->expanded_text, 0, AV_BPRINT_SIZE_UNLIMITED); return 0; @@ -585,6 +602,142 @@ static int command(AVFilterContext *ctx, const char *cmd, const char *arg, char return AVERROR(ENOSYS); } +static int func_pts(AVFilterContext *ctx, AVBPrint *bp, + char *fct, unsigned argc, char **argv, int tag) +{ + DrawTextContext *dtext = ctx->priv; + + av_bprintf(bp, "%.6f", dtext->var_values[VAR_T]); + return 0; +} + +#if !HAVE_LOCALTIME_R +static void localtime_r(const time_t *t, struct tm *tm) +{ + *tm = *localtime(t); +} +#endif + +static int func_strftime(AVFilterContext *ctx, AVBPrint *bp, + char *fct, unsigned argc, char **argv, int tag) +{ + const char *fmt = argc ? argv[0] : "%Y-%m-%d %H:%M:%S"; + time_t now; + struct tm tm; + + time(&now); + if (tag == 'L') + localtime_r(&now, &tm); + else + tm = *gmtime(&now); + av_bprint_strftime(bp, fmt, &tm); + return 0; +} + +static const struct drawtext_function { + const char *name; + unsigned argc_min, argc_max; + int tag; /** opaque argument to func */ + int (*func)(AVFilterContext *, AVBPrint *, char *, unsigned, char **, int); +} functions[] = { + { "pts", 0, 0, 0, func_pts }, + { "gmtime", 0, 1, 'G', func_strftime }, + { "localtime", 0, 1, 'L', func_strftime }, +}; + +static int eval_function(AVFilterContext *ctx, AVBPrint *bp, char *fct, + unsigned argc, char **argv) +{ + unsigned i; + + for (i = 0; i < FF_ARRAY_ELEMS(functions); i++) { + if (strcmp(fct, functions[i].name)) + continue; + if (argc < functions[i].argc_min) { + av_log(ctx, AV_LOG_ERROR, "%%{%s} requires at least %d arguments\n", + fct, functions[i].argc_min); + return AVERROR(EINVAL); + } + if (argc > functions[i].argc_max) { + av_log(ctx, AV_LOG_ERROR, "%%{%s} requires at most %d arguments\n", + fct, functions[i].argc_max); + return AVERROR(EINVAL); + } + break; + } + if (i >= FF_ARRAY_ELEMS(functions)) { + av_log(ctx, AV_LOG_ERROR, "%%{%s} is not known\n", fct); + return AVERROR(EINVAL); + } + return functions[i].func(ctx, bp, fct, argc, argv, functions[i].tag); +} + +static int expand_function(AVFilterContext *ctx, AVBPrint *bp, char **rtext) +{ + const char *text = *rtext; + char *argv[16] = { NULL }; + unsigned argc = 0, i; + int ret; + + if (*text != '{') { + av_log(ctx, AV_LOG_ERROR, "Stray %% near '%s'\n", text); + return AVERROR(EINVAL); + } + text++; + while (1) { + if (!(argv[argc++] = av_get_token(&text, ":}"))) { + ret = AVERROR(ENOMEM); + goto end; + } + if (!*text) { + av_log(ctx, AV_LOG_ERROR, "Unterminated %%{} near '%s'\n", *rtext); + ret = AVERROR(EINVAL); + goto end; + } + if (argc == FF_ARRAY_ELEMS(argv)) + av_freep(&argv[--argc]); /* error will be caught later */ + if (*text == '}') + break; + text++; + } + + if ((ret = eval_function(ctx, bp, argv[0], argc - 1, argv + 1)) < 0) + goto end; + ret = 0; + *rtext = (char *)text + 1; + +end: + for (i = 0; i < argc; i++) + av_freep(&argv[i]); + return ret; +} + +static int expand_text(AVFilterContext *ctx) +{ + DrawTextContext *dtext = ctx->priv; + char *text = dtext->text; + AVBPrint *bp = &dtext->expanded_text; + int ret; + + av_bprint_clear(bp); + while (*text) { + if (*text == '\\' && text[1]) { + av_bprint_chars(bp, text[1], 1); + text += 2; + } else if (*text == '%') { + text++; + if ((ret = expand_function(ctx, bp, &text)) < 0) + return ret; + } else { + av_bprint_chars(bp, *text, 1); + text++; + } + } + if (!av_bprint_is_complete(bp)) + return AVERROR(ENOMEM); + return 0; +} + static int draw_glyphs(DrawTextContext *dtext, AVFilterBufferRef *picref, int width, int height, const uint8_t rgbcolor[4], FFDrawColor *color, int x, int y) { @@ -648,13 +801,19 @@ static int draw_text(AVFilterContext *ctx, AVFilterBufferRef *picref, if(dtext->basetime != AV_NOPTS_VALUE) now= picref->pts*av_q2d(ctx->inputs[0]->time_base) + dtext->basetime/1000000; -#if HAVE_LOCALTIME_R - localtime_r(&now, <ime); -#else - if(strchr(dtext->text, '%')) - ltime= *localtime(&now); -#endif - av_bprint_strftime(bp, dtext->text, <ime); + switch (dtext->exp_mode) { + case EXP_NONE: + av_bprintf(bp, "%s", dtext->text); + break; + case EXP_NORMAL: + if ((ret = expand_text(ctx)) < 0) + return ret; + break; + case EXP_STRFTIME: + localtime_r(&now, <ime); + av_bprint_strftime(bp, dtext->text, <ime); + break; + } if (dtext->tc_opt_string) { char tcbuf[AV_TIMECODE_STR_SIZE]; |