[FFmpeg-devel] [PATCH 4/4] lavfi/drawtext: implement more generic expansion.

Clément Bœsch ubitux at gmail.com
Wed Nov 14 00:41:25 CET 2012


On Sun, Nov 11, 2012 at 06:56:45PM +0100, Nicolas George wrote:
> The new expansion mechanism uses the %{...} notation.

Thanks, much better than '$' :)

> For compatibility reasons, it must be enabled explicitly,
> but a warning is printed if a change is likely to happen.
> 

Don't forget to bump (or maybe only when we change the defaults?). And I'd
suggest to mention the format change in the Changelog (push the feature in
the entry, but say that old syntax is deprecated)

> Signed-off-by: Nicolas George <nicolas.george at normalesup.org>
> ---
>  doc/filters.texi          |   50 ++++++++++++++-
>  libavfilter/vf_drawtext.c |  149 +++++++++++++++++++++++++++++++++++++++++++--
>  2 files changed, 192 insertions(+), 7 deletions(-)
> 
> diff --git a/doc/filters.texi b/doc/filters.texi
> index eaf0f42..973573e 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().
> + at subsection Syntax
>  
>  The filter accepts parameters as a list of @var{key}=@var{value} pairs,
>  separated by ":".
> @@ -1865,6 +1864,10 @@ Either a string (e.g. "yellow") or in 0xRRGGBB[AA] format
>  (e.g. "0xff00ff"), possibly followed by an alpha specifier.
>  The default value of @var{boxcolor} is "white".
>  
> + at item compat_expand (or old)
> +Use the old strftime-style expansion for the text. It is enabled by default
> +for now.
> +
>  @item draw
>  Set an expression which specifies if the text should be drawn. If the
>  expression evaluates to 0, the text is not drawn. This is useful for
> @@ -2039,6 +2042,43 @@ 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.
>  
> + at subsection Text expansion
> +
> +If @option{compat_expand} is enabled (which is the default for now),
> +the filter also recognizes strftime() sequences in the provided text
> +and expands them accordingly. Check the documentation of strftime().
> +This feature is deprecated.
> +
> +If @option{compat_expand} is not enabled, a more general expansion mechanism
> +is used.
> +
> +The backslash character '\', followed by any character, always expand 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 exist:
> +

nit++: "are available"

> + at table @command
> +
> + at 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.
> +
> + at item pts
> +The timestamp of the current frame, in seconds, with microsecond accuracy.
> +
> + at end table
> +
> + at subsection Examples
> +
>  Some examples follow.
>  
>  @itemize
> @@ -2104,6 +2144,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
>  
> + at item
> +Print the date of a real-time encoding (see strftime(3)):
> + at example
> +drawtext='fontfile=FreeSans.ttf:compat_expand=0:text=%@{localtime:%a %b %d %Y@}'
> + at end example
> +
>  @end itemize
>  
>  For more information about libfreetype, check:
> diff --git a/libavfilter/vf_drawtext.c b/libavfilter/vf_drawtext.c
> index f9c69ae..e359793 100644
> --- a/libavfilter/vf_drawtext.c
> +++ b/libavfilter/vf_drawtext.c
> @@ -115,6 +115,7 @@ enum var_name {
>  
>  typedef struct {
>      const AVClass *class;
> +    int compat_expand;              ///< use old strftime-style expand
>      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 +182,8 @@ 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},
> +{"compat_expand", "use old strftime-style expand", OFFSET(compat_expand), AV_OPT_TYPE_INT, {.i64=1},      0,        1, FLAGS},
> +{"old",           "use old strftime-style expand", OFFSET(compat_expand), AV_OPT_TYPE_INT, {.i64=1},      0,        1, FLAGS},
>  {"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 +487,10 @@ static av_cold int init(AVFilterContext *ctx, const char *args)
>      }
>      dtext->tabsize *= glyph->advance;
>  
> +    if (dtext->compat_expand &&
> +        (strchr(dtext->text, '%') || strchr(dtext->text, '\\')))
> +        av_log(ctx, AV_LOG_WARNING, "Compat expand is deprecated.\n");
> +
>      av_bprint_init(&dtext->expanded_text, 0, AV_BPRINT_SIZE_UNLIMITED);
>  
>      return 0;
> @@ -585,6 +592,138 @@ 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, char **argv, unsigned argc, 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, char **argv, unsigned argc, int tag)

nit++: strange to have argv before argc

> +{
> +    const char *fmt = argc ? argv[0] : "%Y-%m-%d %H:%M:%S";
> +    time_t now;
> +    struct tm tm;
> +
> +    time(&now);
> +    localtime_r(&now, &tm);
> +    av_bprint_strftime(bp, fmt, &tm);
> +    return 0;
> +}
> +
> +static const struct drawtext_function {
> +    const char *name;
> +    unsigned argc_min, argc_max;
> +    int tag;
> +    int (*func)(AVFilterContext *, AVBPrint *, char *, char **, unsigned, int);
> +} functions[] = {
> +    { "pts",       0, 0, 0,   func_pts      },
> +    { "localtime", 0, 1, 'L', func_strftime },
> +};
> +
> +static int eval_function(AVFilterContext *ctx, AVBPrint *bp, char *fct,
> +                         char **argv, unsigned argc)

ditto ac/av

> +{
> +    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, argv, argc, 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], argv + 1, argc - 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 +787,13 @@ 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
> +    if (dtext->compat_expand) {
>      localtime_r(&now, &ltime);
> -#else
> -    if(strchr(dtext->text, '%'))
> -        ltime= *localtime(&now);
> -#endif
>      av_bprint_strftime(bp, dtext->text, &ltime);
> +    } else {
> +        if ((ret = expand_text(ctx)) < 0)
> +            return ret;
> +    }
>  
>      if (dtext->tc_opt_string) {
>          char tcbuf[AV_TIMECODE_STR_SIZE];

Beside these nits, since my previous comments are honored, nothing more to
say, LGTM, maybe wait a little more though.

-- 
Clément B.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 490 bytes
Desc: not available
URL: <http://ffmpeg.org/pipermail/ffmpeg-devel/attachments/20121114/3bb6bd31/attachment.asc>


More information about the ffmpeg-devel mailing list