|
@@ -47,7 +47,6 @@
|
|
|
#include "libavutil/avstring.h"
|
|
#include "libavutil/avstring.h"
|
|
|
#include "libavutil/bprint.h"
|
|
#include "libavutil/bprint.h"
|
|
|
#include "libavutil/common.h"
|
|
#include "libavutil/common.h"
|
|
|
-#include "libavutil/file.h"
|
|
|
|
|
#include "libavutil/eval.h"
|
|
#include "libavutil/eval.h"
|
|
|
#include "libavutil/opt.h"
|
|
#include "libavutil/opt.h"
|
|
|
#include "libavutil/random_seed.h"
|
|
#include "libavutil/random_seed.h"
|
|
@@ -62,6 +61,7 @@
|
|
|
#include "drawutils.h"
|
|
#include "drawutils.h"
|
|
|
#include "formats.h"
|
|
#include "formats.h"
|
|
|
#include "internal.h"
|
|
#include "internal.h"
|
|
|
|
|
+#include "textutils.h"
|
|
|
#include "video.h"
|
|
#include "video.h"
|
|
|
|
|
|
|
|
#if CONFIG_LIBFRIBIDI
|
|
#if CONFIG_LIBFRIBIDI
|
|
@@ -253,6 +253,7 @@ typedef struct TextMetrics {
|
|
|
typedef struct DrawTextContext {
|
|
typedef struct DrawTextContext {
|
|
|
const AVClass *class;
|
|
const AVClass *class;
|
|
|
int exp_mode; ///< expansion mode to use for the text
|
|
int exp_mode; ///< expansion mode to use for the text
|
|
|
|
|
+ FFExpandTextContext expand_text; ///< expand text in case exp_mode == NORMAL
|
|
|
int reinit; ///< tells if the filter is being reinited
|
|
int reinit; ///< tells if the filter is being reinited
|
|
|
#if CONFIG_LIBFONTCONFIG
|
|
#if CONFIG_LIBFONTCONFIG
|
|
|
uint8_t *font; ///< font to be used
|
|
uint8_t *font; ///< font to be used
|
|
@@ -631,40 +632,6 @@ static int load_font(AVFilterContext *ctx)
|
|
|
return err;
|
|
return err;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-static inline int is_newline(uint32_t c)
|
|
|
|
|
-{
|
|
|
|
|
- return c == '\n' || c == '\r' || c == '\f' || c == '\v';
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-static int load_textfile(AVFilterContext *ctx)
|
|
|
|
|
-{
|
|
|
|
|
- DrawTextContext *s = ctx->priv;
|
|
|
|
|
- int err;
|
|
|
|
|
- uint8_t *textbuf;
|
|
|
|
|
- uint8_t *tmp;
|
|
|
|
|
- size_t textbuf_size;
|
|
|
|
|
-
|
|
|
|
|
- if ((err = av_file_map(s->textfile, &textbuf, &textbuf_size, 0, ctx)) < 0) {
|
|
|
|
|
- av_log(ctx, AV_LOG_ERROR,
|
|
|
|
|
- "The text file '%s' could not be read or is empty\n",
|
|
|
|
|
- s->textfile);
|
|
|
|
|
- return err;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (textbuf_size > 0 && is_newline(textbuf[textbuf_size - 1]))
|
|
|
|
|
- textbuf_size--;
|
|
|
|
|
- if (textbuf_size > SIZE_MAX - 1 || !(tmp = av_realloc(s->text, textbuf_size + 1))) {
|
|
|
|
|
- av_file_unmap(textbuf, textbuf_size);
|
|
|
|
|
- return AVERROR(ENOMEM);
|
|
|
|
|
- }
|
|
|
|
|
- s->text = tmp;
|
|
|
|
|
- memcpy(s->text, textbuf, textbuf_size);
|
|
|
|
|
- s->text[textbuf_size] = 0;
|
|
|
|
|
- av_file_unmap(textbuf, textbuf_size);
|
|
|
|
|
-
|
|
|
|
|
- return 0;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
#if CONFIG_LIBFRIBIDI
|
|
#if CONFIG_LIBFRIBIDI
|
|
|
static int shape_text(AVFilterContext *ctx)
|
|
static int shape_text(AVFilterContext *ctx)
|
|
|
{
|
|
{
|
|
@@ -885,6 +852,123 @@ static int string_to_array(const char *source, int *result, int result_size)
|
|
|
return counter;
|
|
return counter;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+static int func_pict_type(void *ctx, AVBPrint *bp, const char *function_name, unsigned argc, char **argv)
|
|
|
|
|
+{
|
|
|
|
|
+ DrawTextContext *s = ((AVFilterContext *)ctx)->priv;
|
|
|
|
|
+
|
|
|
|
|
+ av_bprintf(bp, "%c", av_get_picture_type_char(s->var_values[VAR_PICT_TYPE]));
|
|
|
|
|
+ return 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static int func_pts(void *ctx, AVBPrint *bp, const char *function_name, unsigned argc, char **argv)
|
|
|
|
|
+{
|
|
|
|
|
+ DrawTextContext *s = ((AVFilterContext *)ctx)->priv;
|
|
|
|
|
+ const char *fmt;
|
|
|
|
|
+ const char *strftime_fmt = NULL;
|
|
|
|
|
+ const char *delta = NULL;
|
|
|
|
|
+ double pts = s->var_values[VAR_T];
|
|
|
|
|
+
|
|
|
|
|
+ // argv: pts, FMT, [DELTA, 24HH | strftime_fmt]
|
|
|
|
|
+
|
|
|
|
|
+ fmt = argc >= 1 ? argv[0] : "flt";
|
|
|
|
|
+ if (argc >= 2) {
|
|
|
|
|
+ delta = argv[1];
|
|
|
|
|
+ }
|
|
|
|
|
+ if (argc >= 3) {
|
|
|
|
|
+ if (!strcmp(fmt, "hms")) {
|
|
|
|
|
+ if (!strcmp(argv[2], "24HH")) {
|
|
|
|
|
+ av_log(ctx, AV_LOG_WARNING, "pts third argument 24HH is deprected, use pts:hms24hh instead\n");
|
|
|
|
|
+ fmt = "hms24";
|
|
|
|
|
+ } else {
|
|
|
|
|
+ av_log(ctx, AV_LOG_ERROR, "Invalid argument '%s', '24HH' was expected\n", argv[2]);
|
|
|
|
|
+ return AVERROR(EINVAL);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ strftime_fmt = argv[2];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return ff_print_pts(ctx, bp, pts, delta, fmt, strftime_fmt);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static int func_frame_num(void *ctx, AVBPrint *bp, const char *function_name, unsigned argc, char **argv)
|
|
|
|
|
+{
|
|
|
|
|
+ DrawTextContext *s = ((AVFilterContext *)ctx)->priv;
|
|
|
|
|
+
|
|
|
|
|
+ av_bprintf(bp, "%d", (int)s->var_values[VAR_N]);
|
|
|
|
|
+ return 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static int func_metadata(void *ctx, AVBPrint *bp, const char *function_name, unsigned argc, char **argv)
|
|
|
|
|
+{
|
|
|
|
|
+ DrawTextContext *s = ((AVFilterContext *)ctx)->priv;
|
|
|
|
|
+ AVDictionaryEntry *e = av_dict_get(s->metadata, argv[0], NULL, 0);
|
|
|
|
|
+
|
|
|
|
|
+ if (e && e->value)
|
|
|
|
|
+ av_bprintf(bp, "%s", e->value);
|
|
|
|
|
+ else if (argc >= 2)
|
|
|
|
|
+ av_bprintf(bp, "%s", argv[1]);
|
|
|
|
|
+ return 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static int func_strftime(void *ctx, AVBPrint *bp, const char *function_name, unsigned argc, char **argv)
|
|
|
|
|
+{
|
|
|
|
|
+ const char *strftime_fmt = argc ? argv[0] : NULL;
|
|
|
|
|
+
|
|
|
|
|
+ return ff_print_time(ctx, bp, strftime_fmt, !strcmp(function_name, "localtime"));
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static int func_eval_expr(void *ctx, AVBPrint *bp, const char *function_name, unsigned argc, char **argv)
|
|
|
|
|
+{
|
|
|
|
|
+ DrawTextContext *s = ((AVFilterContext *)ctx)->priv;
|
|
|
|
|
+
|
|
|
|
|
+ return ff_print_eval_expr(ctx, bp, argv[0],
|
|
|
|
|
+ fun2_names, fun2,
|
|
|
|
|
+ var_names, s->var_values, &s->prng);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static int func_eval_expr_int_format(void *ctx, AVBPrint *bp, const char *function_name, unsigned argc, char **argv)
|
|
|
|
|
+{
|
|
|
|
|
+ DrawTextContext *s = ((AVFilterContext *)ctx)->priv;
|
|
|
|
|
+ int ret;
|
|
|
|
|
+ int positions = -1;
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * argv[0] expression to be converted to `int`
|
|
|
|
|
+ * argv[1] format: 'x', 'X', 'd' or 'u'
|
|
|
|
|
+ * argv[2] positions printed (optional)
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+ if (argc == 3) {
|
|
|
|
|
+ ret = sscanf(argv[2], "%u", &positions);
|
|
|
|
|
+ if (ret != 1) {
|
|
|
|
|
+ av_log(ctx, AV_LOG_ERROR, "expr_int_format(): Invalid number of positions"
|
|
|
|
|
+ " to print: '%s'\n", argv[2]);
|
|
|
|
|
+ return AVERROR(EINVAL);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return ff_print_formatted_eval_expr(ctx, bp, argv[0],
|
|
|
|
|
+ fun2_names, fun2,
|
|
|
|
|
+ var_names, s->var_values,
|
|
|
|
|
+ &s->prng,
|
|
|
|
|
+ argv[1][0], positions);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static FFExpandTextFunction expand_text_functions[] = {
|
|
|
|
|
+ { "e", 1, 1, func_eval_expr },
|
|
|
|
|
+ { "eif", 2, 3, func_eval_expr_int_format },
|
|
|
|
|
+ { "expr", 1, 1, func_eval_expr },
|
|
|
|
|
+ { "expr_int_format", 2, 3, func_eval_expr_int_format },
|
|
|
|
|
+ { "frame_num", 0, 0, func_frame_num },
|
|
|
|
|
+ { "gmtime", 0, 1, func_strftime },
|
|
|
|
|
+ { "localtime", 0, 1, func_strftime },
|
|
|
|
|
+ { "metadata", 1, 2, func_metadata },
|
|
|
|
|
+ { "n", 0, 0, func_frame_num },
|
|
|
|
|
+ { "pict_type", 0, 0, func_pict_type },
|
|
|
|
|
+ { "pts", 0, 3, func_pts }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
static av_cold int init(AVFilterContext *ctx)
|
|
static av_cold int init(AVFilterContext *ctx)
|
|
|
{
|
|
{
|
|
|
int err;
|
|
int err;
|
|
@@ -907,7 +991,7 @@ static av_cold int init(AVFilterContext *ctx)
|
|
|
"Both text and text file provided. Please provide only one\n");
|
|
"Both text and text file provided. Please provide only one\n");
|
|
|
return AVERROR(EINVAL);
|
|
return AVERROR(EINVAL);
|
|
|
}
|
|
}
|
|
|
- if ((err = load_textfile(ctx)) < 0)
|
|
|
|
|
|
|
+ if ((err = ff_load_textfile(ctx, (const char *)s->textfile, &s->text, NULL)) < 0)
|
|
|
return err;
|
|
return err;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -950,6 +1034,12 @@ static av_cold int init(AVFilterContext *ctx)
|
|
|
return AVERROR(EINVAL);
|
|
return AVERROR(EINVAL);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ s->expand_text = (FFExpandTextContext) {
|
|
|
|
|
+ .log_ctx = ctx,
|
|
|
|
|
+ .functions = expand_text_functions,
|
|
|
|
|
+ .functions_nb = FF_ARRAY_ELEMS(expand_text_functions)
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
#if CONFIG_LIBFRIBIDI
|
|
#if CONFIG_LIBFRIBIDI
|
|
|
if (s->text_shaping)
|
|
if (s->text_shaping)
|
|
|
if ((err = shape_text(ctx)) < 0)
|
|
if ((err = shape_text(ctx)) < 0)
|
|
@@ -1160,367 +1250,6 @@ fail:
|
|
|
return ret;
|
|
return ret;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-static int func_pict_type(AVFilterContext *ctx, AVBPrint *bp,
|
|
|
|
|
- char *fct, unsigned argc, char **argv, int tag)
|
|
|
|
|
-{
|
|
|
|
|
- DrawTextContext *s = ctx->priv;
|
|
|
|
|
-
|
|
|
|
|
- av_bprintf(bp, "%c", av_get_picture_type_char(s->var_values[VAR_PICT_TYPE]));
|
|
|
|
|
- return 0;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-static int func_pts(AVFilterContext *ctx, AVBPrint *bp,
|
|
|
|
|
- char *fct, unsigned argc, char **argv, int tag)
|
|
|
|
|
-{
|
|
|
|
|
- DrawTextContext *s = ctx->priv;
|
|
|
|
|
- const char *fmt;
|
|
|
|
|
- double pts = s->var_values[VAR_T];
|
|
|
|
|
- int ret;
|
|
|
|
|
-
|
|
|
|
|
- fmt = argc >= 1 ? argv[0] : "flt";
|
|
|
|
|
- if (argc >= 2) {
|
|
|
|
|
- int64_t delta;
|
|
|
|
|
- if ((ret = av_parse_time(&delta, argv[1], 1)) < 0) {
|
|
|
|
|
- av_log(ctx, AV_LOG_ERROR, "Invalid delta '%s'\n", argv[1]);
|
|
|
|
|
- return ret;
|
|
|
|
|
- }
|
|
|
|
|
- pts += (double)delta / AV_TIME_BASE;
|
|
|
|
|
- }
|
|
|
|
|
- if (!strcmp(fmt, "flt")) {
|
|
|
|
|
- av_bprintf(bp, "%.6f", pts);
|
|
|
|
|
- } else if (!strcmp(fmt, "hms")) {
|
|
|
|
|
- if (isnan(pts)) {
|
|
|
|
|
- av_bprintf(bp, " ??:??:??.???");
|
|
|
|
|
- } else {
|
|
|
|
|
- int64_t ms = llrint(pts * 1000);
|
|
|
|
|
- char sign = ' ';
|
|
|
|
|
- if (ms < 0) {
|
|
|
|
|
- sign = '-';
|
|
|
|
|
- ms = -ms;
|
|
|
|
|
- }
|
|
|
|
|
- if (argc >= 3) {
|
|
|
|
|
- if (!strcmp(argv[2], "24HH")) {
|
|
|
|
|
- ms %= 24 * 60 * 60 * 1000;
|
|
|
|
|
- } else {
|
|
|
|
|
- av_log(ctx, AV_LOG_ERROR, "Invalid argument '%s'\n", argv[2]);
|
|
|
|
|
- return AVERROR(EINVAL);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- av_bprintf(bp, "%c%02d:%02d:%02d.%03d", sign,
|
|
|
|
|
- (int)(ms / (60 * 60 * 1000)),
|
|
|
|
|
- (int)(ms / (60 * 1000)) % 60,
|
|
|
|
|
- (int)(ms / 1000) % 60,
|
|
|
|
|
- (int)(ms % 1000));
|
|
|
|
|
- }
|
|
|
|
|
- } else if (!strcmp(fmt, "localtime") ||
|
|
|
|
|
- !strcmp(fmt, "gmtime")) {
|
|
|
|
|
- struct tm tm;
|
|
|
|
|
- time_t ms = (time_t)pts;
|
|
|
|
|
- const char *timefmt = argc >= 3 ? argv[2] : "%Y-%m-%d %H:%M:%S";
|
|
|
|
|
- if (!strcmp(fmt, "localtime"))
|
|
|
|
|
- localtime_r(&ms, &tm);
|
|
|
|
|
- else
|
|
|
|
|
- gmtime_r(&ms, &tm);
|
|
|
|
|
- av_bprint_strftime(bp, timefmt, &tm);
|
|
|
|
|
- } else {
|
|
|
|
|
- av_log(ctx, AV_LOG_ERROR, "Invalid format '%s'\n", fmt);
|
|
|
|
|
- return AVERROR(EINVAL);
|
|
|
|
|
- }
|
|
|
|
|
- return 0;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-static int func_frame_num(AVFilterContext *ctx, AVBPrint *bp,
|
|
|
|
|
- char *fct, unsigned argc, char **argv, int tag)
|
|
|
|
|
-{
|
|
|
|
|
- DrawTextContext *s = ctx->priv;
|
|
|
|
|
-
|
|
|
|
|
- av_bprintf(bp, "%d", (int)s->var_values[VAR_N]);
|
|
|
|
|
- return 0;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-static int func_metadata(AVFilterContext *ctx, AVBPrint *bp,
|
|
|
|
|
- char *fct, unsigned argc, char **argv, int tag)
|
|
|
|
|
-{
|
|
|
|
|
- DrawTextContext *s = ctx->priv;
|
|
|
|
|
- AVDictionaryEntry *e = av_dict_get(s->metadata, argv[0], NULL, 0);
|
|
|
|
|
-
|
|
|
|
|
- if (e && e->value)
|
|
|
|
|
- av_bprintf(bp, "%s", e->value);
|
|
|
|
|
- else if (argc >= 2)
|
|
|
|
|
- av_bprintf(bp, "%s", argv[1]);
|
|
|
|
|
- return 0;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-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";
|
|
|
|
|
- const char *fmt_begin = fmt;
|
|
|
|
|
- int64_t unow;
|
|
|
|
|
- time_t now;
|
|
|
|
|
- struct tm tm;
|
|
|
|
|
- const char *begin;
|
|
|
|
|
- const char *tmp;
|
|
|
|
|
- int len;
|
|
|
|
|
- int div;
|
|
|
|
|
- AVBPrint fmt_bp;
|
|
|
|
|
-
|
|
|
|
|
- av_bprint_init(&fmt_bp, 0, AV_BPRINT_SIZE_UNLIMITED);
|
|
|
|
|
-
|
|
|
|
|
- unow = av_gettime();
|
|
|
|
|
- now = unow / 1000000;
|
|
|
|
|
- if (tag == 'L' || tag == 'm')
|
|
|
|
|
- localtime_r(&now, &tm);
|
|
|
|
|
- else
|
|
|
|
|
- tm = *gmtime_r(&now, &tm);
|
|
|
|
|
-
|
|
|
|
|
- // manually parse format for %N (fractional seconds)
|
|
|
|
|
- begin = fmt;
|
|
|
|
|
- while ((begin = strchr(begin, '%'))) {
|
|
|
|
|
- tmp = begin + 1;
|
|
|
|
|
- len = 0;
|
|
|
|
|
-
|
|
|
|
|
- // skip escaped "%%"
|
|
|
|
|
- if (*tmp == '%') {
|
|
|
|
|
- begin = tmp + 1;
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // count digits between % and possible N
|
|
|
|
|
- while (*tmp != '\0' && av_isdigit((int)*tmp)) {
|
|
|
|
|
- len++;
|
|
|
|
|
- tmp++;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // N encountered, insert time
|
|
|
|
|
- if (*tmp == 'N') {
|
|
|
|
|
- int num_digits = 3; // default show millisecond [1,6]
|
|
|
|
|
-
|
|
|
|
|
- // if digit given, expect [1,6], warn & clamp otherwise
|
|
|
|
|
- if (len == 1) {
|
|
|
|
|
- num_digits = av_clip(*(begin + 1) - '0', 1, 6);
|
|
|
|
|
- } else if (len > 1) {
|
|
|
|
|
- av_log(ctx, AV_LOG_WARNING, "Invalid number of decimals for %%N, using default of %i\n", num_digits);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- len += 2; // add % and N to get length of string part
|
|
|
|
|
-
|
|
|
|
|
- div = pow(10, 6 - num_digits);
|
|
|
|
|
-
|
|
|
|
|
- av_bprintf(&fmt_bp, "%.*s%0*d", (int)(begin - fmt_begin), fmt_begin, num_digits, (int)(unow % 1000000) / div);
|
|
|
|
|
-
|
|
|
|
|
- begin += len;
|
|
|
|
|
- fmt_begin = begin;
|
|
|
|
|
-
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- begin = tmp;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- av_bprintf(&fmt_bp, "%s", fmt_begin);
|
|
|
|
|
- if (!av_bprint_is_complete(&fmt_bp)) {
|
|
|
|
|
- av_log(ctx, AV_LOG_WARNING, "Format string truncated at %u/%u.", fmt_bp.size, fmt_bp.len);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- av_bprint_strftime(bp, fmt_bp.str, &tm);
|
|
|
|
|
-
|
|
|
|
|
- av_bprint_finalize(&fmt_bp, NULL);
|
|
|
|
|
-
|
|
|
|
|
- return 0;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-static int func_eval_expr(AVFilterContext *ctx, AVBPrint *bp,
|
|
|
|
|
- char *fct, unsigned argc, char **argv, int tag)
|
|
|
|
|
-{
|
|
|
|
|
- DrawTextContext *s = ctx->priv;
|
|
|
|
|
- double res;
|
|
|
|
|
- int ret;
|
|
|
|
|
-
|
|
|
|
|
- ret = av_expr_parse_and_eval(&res, argv[0], var_names, s->var_values,
|
|
|
|
|
- NULL, NULL, fun2_names, fun2,
|
|
|
|
|
- &s->prng, 0, ctx);
|
|
|
|
|
- if (ret < 0)
|
|
|
|
|
- av_log(ctx, AV_LOG_ERROR,
|
|
|
|
|
- "Expression '%s' for the expr text expansion function is not valid\n",
|
|
|
|
|
- argv[0]);
|
|
|
|
|
- else
|
|
|
|
|
- av_bprintf(bp, "%f", res);
|
|
|
|
|
-
|
|
|
|
|
- return ret;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-static int func_eval_expr_int_format(AVFilterContext *ctx, AVBPrint *bp,
|
|
|
|
|
- char *fct, unsigned argc, char **argv, int tag)
|
|
|
|
|
-{
|
|
|
|
|
- DrawTextContext *s = ctx->priv;
|
|
|
|
|
- double res;
|
|
|
|
|
- int intval;
|
|
|
|
|
- int ret;
|
|
|
|
|
- unsigned int positions = 0;
|
|
|
|
|
- char fmt_str[30] = "%";
|
|
|
|
|
-
|
|
|
|
|
- /*
|
|
|
|
|
- * argv[0] expression to be converted to `int`
|
|
|
|
|
- * argv[1] format: 'x', 'X', 'd' or 'u'
|
|
|
|
|
- * argv[2] positions printed (optional)
|
|
|
|
|
- */
|
|
|
|
|
-
|
|
|
|
|
- ret = av_expr_parse_and_eval(&res, argv[0], var_names, s->var_values,
|
|
|
|
|
- NULL, NULL, fun2_names, fun2,
|
|
|
|
|
- &s->prng, 0, ctx);
|
|
|
|
|
- if (ret < 0) {
|
|
|
|
|
- av_log(ctx, AV_LOG_ERROR,
|
|
|
|
|
- "Expression '%s' for the expr text expansion function is not valid\n",
|
|
|
|
|
- argv[0]);
|
|
|
|
|
- return ret;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (!strchr("xXdu", argv[1][0])) {
|
|
|
|
|
- av_log(ctx, AV_LOG_ERROR, "Invalid format '%c' specified,"
|
|
|
|
|
- " allowed values: 'x', 'X', 'd', 'u'\n", argv[1][0]);
|
|
|
|
|
- return AVERROR(EINVAL);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (argc == 3) {
|
|
|
|
|
- ret = sscanf(argv[2], "%u", &positions);
|
|
|
|
|
- if (ret != 1) {
|
|
|
|
|
- av_log(ctx, AV_LOG_ERROR, "expr_int_format(): Invalid number of positions"
|
|
|
|
|
- " to print: '%s'\n", argv[2]);
|
|
|
|
|
- return AVERROR(EINVAL);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- feclearexcept(FE_ALL_EXCEPT);
|
|
|
|
|
- intval = res;
|
|
|
|
|
-#if defined(FE_INVALID) && defined(FE_OVERFLOW) && defined(FE_UNDERFLOW)
|
|
|
|
|
- if ((ret = fetestexcept(FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW))) {
|
|
|
|
|
- av_log(ctx, AV_LOG_ERROR, "Conversion of floating-point result to int failed. Control register: 0x%08x. Conversion result: %d\n", ret, intval);
|
|
|
|
|
- return AVERROR(EINVAL);
|
|
|
|
|
- }
|
|
|
|
|
-#endif
|
|
|
|
|
-
|
|
|
|
|
- if (argc == 3)
|
|
|
|
|
- av_strlcatf(fmt_str, sizeof(fmt_str), "0%u", positions);
|
|
|
|
|
- av_strlcatf(fmt_str, sizeof(fmt_str), "%c", argv[1][0]);
|
|
|
|
|
-
|
|
|
|
|
- av_log(ctx, AV_LOG_DEBUG, "Formatting value %f (expr '%s') with spec '%s'\n",
|
|
|
|
|
- res, argv[0], fmt_str);
|
|
|
|
|
-
|
|
|
|
|
- av_bprintf(bp, fmt_str, intval);
|
|
|
|
|
-
|
|
|
|
|
- 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[] = {
|
|
|
|
|
- { "expr", 1, 1, 0, func_eval_expr },
|
|
|
|
|
- { "e", 1, 1, 0, func_eval_expr },
|
|
|
|
|
- { "expr_int_format", 2, 3, 0, func_eval_expr_int_format },
|
|
|
|
|
- { "eif", 2, 3, 0, func_eval_expr_int_format },
|
|
|
|
|
- { "pict_type", 0, 0, 0, func_pict_type },
|
|
|
|
|
- { "pts", 0, 3, 0, func_pts },
|
|
|
|
|
- { "gmtime", 0, 1, 'G', func_strftime },
|
|
|
|
|
- { "localtime", 0, 1, 'L', func_strftime },
|
|
|
|
|
- { "frame_num", 0, 0, 0, func_frame_num },
|
|
|
|
|
- { "n", 0, 0, 0, func_frame_num },
|
|
|
|
|
- { "metadata", 1, 2, 0, func_metadata },
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-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, char *text, AVBPrint *bp)
|
|
|
|
|
-{
|
|
|
|
|
- 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 void update_color_with_alpha(DrawTextContext *s, FFDrawColor *color, const FFDrawColor incolor)
|
|
static void update_color_with_alpha(DrawTextContext *s, FFDrawColor *color, const FFDrawColor incolor)
|
|
|
{
|
|
{
|
|
|
*color = incolor;
|
|
*color = incolor;
|
|
@@ -1688,7 +1417,7 @@ static int measure_text(AVFilterContext *ctx, TextMetrics *metrics)
|
|
|
for (i = 0, p = text; 1; i++) {
|
|
for (i = 0, p = text; 1; i++) {
|
|
|
GET_UTF8(code, *p ? *p++ : 0, code = 0xfffd; goto continue_on_failed;);
|
|
GET_UTF8(code, *p ? *p++ : 0, code = 0xfffd; goto continue_on_failed;);
|
|
|
continue_on_failed:
|
|
continue_on_failed:
|
|
|
- if (is_newline(code) || code == 0) {
|
|
|
|
|
|
|
+ if (ff_is_newline(code) || code == 0) {
|
|
|
++line_count;
|
|
++line_count;
|
|
|
if (code == 0) {
|
|
if (code == 0) {
|
|
|
break;
|
|
break;
|
|
@@ -1729,7 +1458,7 @@ continue_on_failed:
|
|
|
}
|
|
}
|
|
|
GET_UTF8(code, *p ? *p++ : 0, code = 0xfffd; goto continue_on_failed2;);
|
|
GET_UTF8(code, *p ? *p++ : 0, code = 0xfffd; goto continue_on_failed2;);
|
|
|
continue_on_failed2:
|
|
continue_on_failed2:
|
|
|
- if (is_newline(code) || code == 0) {
|
|
|
|
|
|
|
+ if (ff_is_newline(code) || code == 0) {
|
|
|
TextLine *cur_line = &s->lines[line_count];
|
|
TextLine *cur_line = &s->lines[line_count];
|
|
|
HarfbuzzData *hb = &cur_line->hb_data;
|
|
HarfbuzzData *hb = &cur_line->hb_data;
|
|
|
cur_line->cluster_offset = line_offset;
|
|
cur_line->cluster_offset = line_offset;
|
|
@@ -1861,7 +1590,7 @@ static int draw_text(AVFilterContext *ctx, AVFrame *frame)
|
|
|
av_bprintf(bp, "%s", s->text);
|
|
av_bprintf(bp, "%s", s->text);
|
|
|
break;
|
|
break;
|
|
|
case EXP_NORMAL:
|
|
case EXP_NORMAL:
|
|
|
- if ((ret = expand_text(ctx, s->text, &s->expanded_text)) < 0)
|
|
|
|
|
|
|
+ if ((ret = ff_expand_text(&s->expand_text, s->text, &s->expanded_text)) < 0)
|
|
|
return ret;
|
|
return ret;
|
|
|
break;
|
|
break;
|
|
|
case EXP_STRFTIME:
|
|
case EXP_STRFTIME:
|
|
@@ -1883,7 +1612,7 @@ static int draw_text(AVFilterContext *ctx, AVFrame *frame)
|
|
|
if (s->fontcolor_expr[0]) {
|
|
if (s->fontcolor_expr[0]) {
|
|
|
/* If expression is set, evaluate and replace the static value */
|
|
/* If expression is set, evaluate and replace the static value */
|
|
|
av_bprint_clear(&s->expanded_fontcolor);
|
|
av_bprint_clear(&s->expanded_fontcolor);
|
|
|
- if ((ret = expand_text(ctx, s->fontcolor_expr, &s->expanded_fontcolor)) < 0)
|
|
|
|
|
|
|
+ if ((ret = ff_expand_text(&s->expand_text, s->fontcolor_expr, &s->expanded_fontcolor)) < 0)
|
|
|
return ret;
|
|
return ret;
|
|
|
if (!av_bprint_is_complete(&s->expanded_fontcolor))
|
|
if (!av_bprint_is_complete(&s->expanded_fontcolor))
|
|
|
return AVERROR(ENOMEM);
|
|
return AVERROR(ENOMEM);
|
|
@@ -2125,7 +1854,7 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (s->reload && !(inlink->frame_count_out % s->reload)) {
|
|
if (s->reload && !(inlink->frame_count_out % s->reload)) {
|
|
|
- if ((ret = load_textfile(ctx)) < 0) {
|
|
|
|
|
|
|
+ if ((ret = ff_load_textfile(ctx, (const char *)s->textfile, &s->text, NULL)) < 0) {
|
|
|
av_frame_free(&frame);
|
|
av_frame_free(&frame);
|
|
|
return ret;
|
|
return ret;
|
|
|
}
|
|
}
|