diff options
author | Nicolas George <nicolas.george@normalesup.org> | 2012-11-10 19:46:37 +0100 |
---|---|---|
committer | Nicolas George <nicolas.george@normalesup.org> | 2012-11-18 15:55:47 +0100 |
commit | c437c251b8e58395eb8e81b73d8a0c321ae0f47b (patch) | |
tree | c6cdaa8603d455bd50c342fe27e264457481487e | |
parent | 4413a8428a759d33ea58a0617551ba522833f87b (diff) | |
download | ffmpeg-c437c251b8e58395eb8e81b73d8a0c321ae0f47b.tar.gz |
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.
-rw-r--r-- | Changelog | 1 | ||||
-rw-r--r-- | doc/filters.texi | 61 | ||||
-rw-r--r-- | libavfilter/version.h | 2 | ||||
-rw-r--r-- | libavfilter/vf_drawtext.c | 173 |
4 files changed, 227 insertions, 10 deletions
@@ -25,6 +25,7 @@ version <next>: - geq filter ported from libmpcodecs - remove ffserver daemon mode - AST demuxer +- new expansion syntax for drawtext version 1.0: diff --git a/doc/filters.texi b/doc/filters.texi index 80f0493e59..5bdd745fd7 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -1844,8 +1844,7 @@ libfreetype library. To enable compilation of this filter you need to configure FFmpeg with @code{--enable-libfreetype}. -The filter also recognizes strftime() sequences in the provided text -and expands them accordingly. Check the documentation of strftime(). +@subsection Syntax The filter accepts parameters as a list of @var{key}=@var{value} pairs, separated by ":". @@ -1875,6 +1874,12 @@ Default value is "1". See below for the list of accepted constants and functions. +@item expansion +Select how the @var{text} is expanded. Can be either @code{none}, +@code{strftime} (default for compatibity reasons but deprecated) or +@code{normal}. See the @ref{drawtext_expansion, Text expansion} section +below for details. + @item fix_bounds If true, check and fix text coords to avoid clipping. @@ -2039,6 +2044,52 @@ each other, so you can for example specify @code{y=x/dar}. If libavfilter was built with @code{--enable-fontconfig}, then @option{fontfile} can be a fontconfig pattern or omitted. +@anchor{drawtext_expansion} +@subsection Text expansion + +If @option{expansion} is set to @code{strftime} (which is the default for +now), the filter recognizes strftime() sequences in the provided text and +expands them accordingly. Check the documentation of strftime(). This +feature is deprecated. + +If @option{expansion} is set to @code{none}, the text is printed verbatim. + +If @option{expansion} is set to @code{normal} (which will be the default), +the following expansion mechanism is used. + +The backslash character '\', followed by any character, always expands to +the second character. + +Sequence of the form @code{%@{...@}} are expanded. The text between the +braces is a function name, possibly followed by arguments separated by ':'. +If the arguments contain special characters or delimiters (':' or '@}'), +they should be escaped. + +Note that they probably must also be escaped as the value for the +@option{text} option in the filter argument string and as the filter +argument in the filter graph description, and possibly also for the shell, +that makes up to four levels of escaping; using a text file avoids these +problems. + +The following functions are available: + +@table @command + +@item gmtime +The time at which the filter is running, expressed in UTC. +It can accept an argument: a strftime() format string. + +@item localtime +The time at which the filter is running, expressed in the local time zone. +It can accept an argument: a strftime() format string. + +@item pts +The timestamp of the current frame, in seconds, with microsecond accuracy. + +@end table + +@subsection Examples + Some examples follow. @itemize @@ -2104,6 +2155,12 @@ Use fontconfig to set the font. Note that the colons need to be escaped. drawtext='fontfile=Linux Libertine O-40\:style=Semibold:text=FFmpeg' @end example +@item +Print the date of a real-time encoding (see strftime(3)): +@example +drawtext='fontfile=FreeSans.ttf:expansion=normal:text=%@{localtime:%a %b %d %Y@}' +@end example + @end itemize For more information about libfreetype, check: diff --git a/libavfilter/version.h b/libavfilter/version.h index 9eda9b7270..ddebb1a985 100644 --- a/libavfilter/version.h +++ b/libavfilter/version.h @@ -30,7 +30,7 @@ #define LIBAVFILTER_VERSION_MAJOR 3 #define LIBAVFILTER_VERSION_MINOR 23 -#define LIBAVFILTER_VERSION_MICRO 100 +#define LIBAVFILTER_VERSION_MICRO 101 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \ LIBAVFILTER_VERSION_MINOR, \ 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]; |