|
|
@@ -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]; |
|
|
|