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

Stefano Sabatini stefasab at gmail.com
Sat Nov 17 13:31:31 CET 2012


On date Saturday 2012-11-17 11:52:22 +0100, Nicolas George encoded:
> The new expansion mechanism uses the %{...} notation.
> For compatibility reasons, it must be enabled explicitly,

> but a warning is printed if a change is likely to happen.

Nit: I'm not sure what "change" refers here.

> 
> TODO micro bump
> 
> Signed-off-by: Nicolas George <nicolas.george at normalesup.org>
> ---
>  Changelog                 |    1 +
>  doc/filters.texi          |   57 ++++++++++++++-
>  libavfilter/vf_drawtext.c |  169 +++++++++++++++++++++++++++++++++++++++++++--
>  3 files changed, 218 insertions(+), 9 deletions(-)
> 
> diff --git a/Changelog b/Changelog
> index d084762..c27a330 100644
> --- a/Changelog
> +++ b/Changelog
> @@ -24,6 +24,7 @@ version <next>:
>  - AVR demuxer
>  - geq filter ported from libmpcodecs
>  - remove ffserver daemon mode
> +- new expansion syntax for drawtext
>  
>  
>  version 1.0:
> diff --git a/doc/filters.texi b/doc/filters.texi
> index 80f0493..558e327 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 ":".
> @@ -1875,6 +1874,12 @@ Default value is "1".
>  
>  See below for the list of accepted constants and functions.
>  

> + at item expand
> +Select how the @var{text} is expanded. Can be either @code{none},
> + at code{strftime} (default for compatibity reasons but deprecated) or
> + at code{expand}. See the @ref{drawtext_expansion, Text expansion} section
> +below for details.

Needs to be updated.

> +
>  @item fix_bounds
>  If true, check and fix text coords to avoid clipping.
>  
> @@ -2039,6 +2044,48 @@ 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 anchor{drawtext_expansion}
> + at subsection Text expansion
> +

> +If @option{expand} 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{expand} is set to @code{none}, the text is printed verbatim.
> +
> +If @option{expand} is set to @code{expand} (which will be the default), the
> +following expansion mechanism is used.

Need to be updated.

> +
> +The backslash character '\', followed by any character, always expands to
> +the second character.
> +
> +Sequence of the form @code{%@{...@}} are expanded. The text between the

Note: my version of texi2html is broken and doesn't de-escape "@}", I
have a local fix (yet to send) but right now this mean we will render
broken output (another option would be to use a completely different
software for the texi conversion, pandoc is an option - but it doesn't
currently support texinfo).

> +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
> + at 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:
> +
> + 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 +2151,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:expansion=expand: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..96e63d3 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_EXPAND,

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, "expand"},
> +{"none",     "set no expansion",     OFFSET(exp_mode),           AV_OPT_TYPE_CONST,  {.i64=EXP_NONE},     0,        0, FLAGS, "expand"},
> +{"normal",   "set normal expansion", OFFSET(exp_mode),           AV_OPT_TYPE_CONST,  {.i64=EXP_EXPAND},   0,        0, FLAGS, "expand"},
> +{"strftime", "set strftime expansion (deprecated)", OFFSET(exp_mode), AV_OPT_TYPE_CONST, {.i64=EXP_STRFTIME}, 0,    0, FLAGS, "expand"},
> +
>  {"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, "expand=strftime is deprecated.\n");

s/expand/expansion/

> +
>      av_bprint_init(&dtext->expanded_text, 0, AV_BPRINT_SIZE_UNLIMITED);
>  
>      return 0;
> @@ -585,6 +602,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, 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);
> +    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 *, unsigned, char **, int);
> +} functions[] = {
> +    { "pts",       0, 0, 0,   func_pts      },
> +    { "localtime", 0, 1, 'L', func_strftime },
> +};

I'm still a bit befuddled by "tag", documenting the field may help
with it, since currently the field is never used and it is no clear
how it could be used/specified (the other option is to remove it
altogether, until we have no use for it).

> +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 +797,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, &ltime);
> -#else
> -    if(strchr(dtext->text, '%'))
> -        ltime= *localtime(&now);
> -#endif
> -    av_bprint_strftime(bp, dtext->text, &ltime);
> +    switch (dtext->exp_mode) {
> +    case EXP_NONE:
> +        av_bprintf(bp, "%s", dtext->text);
> +        break;
> +    case EXP_EXPAND:
> +        if ((ret = expand_text(ctx)) < 0)
> +            return ret;
> +        break;
> +    case EXP_STRFTIME:
> +        localtime_r(&now, &ltime);
> +        av_bprint_strftime(bp, dtext->text, &ltime);
> +        break;
> +    }

LGTM otherwise, thanks.
-- 
FFmpeg = Furious & Freak Minimalistic Pure Elected Geisha


More information about the ffmpeg-devel mailing list