mirror of https://git.ffmpeg.org/ffmpeg.git
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.
This commit is contained in:
parent
4413a8428a
commit
c437c251b8
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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, \
|
||||
|
|
|
@ -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];
|
||||
|
|
Loading…
Reference in New Issue