X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libavfilter%2Fvf_drawtext.c;h=b7a295f5f761a539d747690710796213dc66853d;hb=074bb7515e33780be35dd62e881cacd485cea382;hp=0d829a6d5e8b5c25472bf74ad39637f64e7e7836;hpb=19b9e07ef5fdd8558735538a04fac7f428df190c;p=ffmpeg diff --git a/libavfilter/vf_drawtext.c b/libavfilter/vf_drawtext.c index 0d829a6d5e8..b7a295f5f76 100644 --- a/libavfilter/vf_drawtext.c +++ b/libavfilter/vf_drawtext.c @@ -37,6 +37,7 @@ #if HAVE_UNISTD_H #include #endif +#include #if CONFIG_LIBFONTCONFIG #include @@ -59,6 +60,10 @@ #include "internal.h" #include "video.h" +#if CONFIG_LIBFRIBIDI +#include +#endif + #include #include FT_FREETYPE_H #include FT_GLYPH_H @@ -138,6 +143,8 @@ typedef struct DrawTextContext { uint8_t *fontfile; ///< font to be used uint8_t *text; ///< text to be drawn AVBPrint expanded_text; ///< used to contain the expanded text + uint8_t *fontcolor_expr; ///< fontcolor expression to evaluate + AVBPrint expanded_fontcolor; ///< used to contain the expanded fontcolor spec int ft_load_flags; ///< flags used for loading fonts, see FT_LOAD_* FT_Vector *positions; ///< positions for each element in the text size_t nb_positions; ///< number of elements of positions array @@ -182,6 +189,9 @@ typedef struct DrawTextContext { int tc24hmax; ///< 1 if timecode is wrapped to 24 hours, 0 otherwise int reload; ///< reload text file for each frame int start_number; ///< starting frame number for n/frame_num var +#if CONFIG_LIBFRIBIDI + int text_shaping; ///< 1 to shape the text before drawing it +#endif AVDictionary *metadata; } DrawTextContext; @@ -193,6 +203,7 @@ static const AVOption drawtext_options[]= { {"text", "set text", OFFSET(text), AV_OPT_TYPE_STRING, {.str=NULL}, CHAR_MIN, CHAR_MAX, FLAGS}, {"textfile", "set text file", OFFSET(textfile), AV_OPT_TYPE_STRING, {.str=NULL}, CHAR_MIN, CHAR_MAX, FLAGS}, {"fontcolor", "set foreground color", OFFSET(fontcolor.rgba), AV_OPT_TYPE_COLOR, {.str="black"}, CHAR_MIN, CHAR_MAX, FLAGS}, + {"fontcolor_expr", "set foreground color expression", OFFSET(fontcolor_expr), AV_OPT_TYPE_STRING, {.str=""}, CHAR_MIN, CHAR_MAX, FLAGS}, {"boxcolor", "set box color", OFFSET(boxcolor.rgba), AV_OPT_TYPE_COLOR, {.str="white"}, CHAR_MIN, CHAR_MAX, FLAGS}, {"bordercolor", "set border color", OFFSET(bordercolor.rgba), AV_OPT_TYPE_COLOR, {.str="black"}, CHAR_MIN, CHAR_MAX, FLAGS}, {"shadowcolor", "set shadow color", OFFSET(shadowcolor.rgba), AV_OPT_TYPE_COLOR, {.str="black"}, CHAR_MIN, CHAR_MAX, FLAGS}, @@ -226,6 +237,10 @@ static const AVOption drawtext_options[]= { {"fix_bounds", "if true, check and fix text coords to avoid clipping", OFFSET(fix_bounds), AV_OPT_TYPE_INT, {.i64=1}, 0, 1, FLAGS}, {"start_number", "start frame number for n/frame_num variable", OFFSET(start_number), AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, FLAGS}, +#if CONFIG_LIBFRIBIDI + {"text_shaping", "attempt to shape text before drawing", OFFSET(text_shaping), AV_OPT_TYPE_INT, {.i64=1}, 0, 1, FLAGS}, +#endif + /* FT_LOAD_* flags */ { "ft_load_flags", "set font loading flags for libfreetype", OFFSET(ft_load_flags), AV_OPT_TYPE_FLAGS, { .i64 = FT_LOAD_DEFAULT }, 0, INT_MAX, FLAGS, "ft_load_flags" }, { "default", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = FT_LOAD_DEFAULT }, .flags = FLAGS, .unit = "ft_load_flags" }, @@ -482,6 +497,99 @@ static int load_textfile(AVFilterContext *ctx) return 0; } +static inline int is_newline(uint32_t c) +{ + return c == '\n' || c == '\r' || c == '\f' || c == '\v'; +} + +#if CONFIG_LIBFRIBIDI +static int shape_text(AVFilterContext *ctx) +{ + DrawTextContext *s = ctx->priv; + uint8_t *tmp; + int ret = AVERROR(ENOMEM); + static const FriBidiFlags flags = FRIBIDI_FLAGS_DEFAULT | + FRIBIDI_FLAGS_ARABIC; + FriBidiChar *unicodestr = NULL; + FriBidiStrIndex len; + FriBidiParType direction = FRIBIDI_PAR_LTR; + FriBidiStrIndex line_start = 0; + FriBidiStrIndex line_end = 0; + FriBidiLevel *embedding_levels = NULL; + FriBidiArabicProp *ar_props = NULL; + FriBidiCharType *bidi_types = NULL; + FriBidiStrIndex i,j; + + len = strlen(s->text); + if (!(unicodestr = av_malloc_array(len, sizeof(*unicodestr)))) { + goto out; + } + len = fribidi_charset_to_unicode(FRIBIDI_CHAR_SET_UTF8, + s->text, len, unicodestr); + + bidi_types = av_malloc_array(len, sizeof(*bidi_types)); + if (!bidi_types) { + goto out; + } + + fribidi_get_bidi_types(unicodestr, len, bidi_types); + + embedding_levels = av_malloc_array(len, sizeof(*embedding_levels)); + if (!embedding_levels) { + goto out; + } + + if (!fribidi_get_par_embedding_levels(bidi_types, len, &direction, + embedding_levels)) { + goto out; + } + + ar_props = av_malloc_array(len, sizeof(*ar_props)); + if (!ar_props) { + goto out; + } + + fribidi_get_joining_types(unicodestr, len, ar_props); + fribidi_join_arabic(bidi_types, len, embedding_levels, ar_props); + fribidi_shape(flags, embedding_levels, len, ar_props, unicodestr); + + for (line_end = 0, line_start = 0; line_end < len; line_end++) { + if (is_newline(unicodestr[line_end]) || line_end == len - 1) { + if (!fribidi_reorder_line(flags, bidi_types, + line_end - line_start + 1, line_start, + direction, embedding_levels, unicodestr, + NULL)) { + goto out; + } + line_start = line_end + 1; + } + } + + /* Remove zero-width fill chars put in by libfribidi */ + for (i = 0, j = 0; i < len; i++) + if (unicodestr[i] != FRIBIDI_CHAR_FILL) + unicodestr[j++] = unicodestr[i]; + len = j; + + if (!(tmp = av_realloc(s->text, (len * 4 + 1) * sizeof(*s->text)))) { + /* Use len * 4, as a unicode character can be up to 4 bytes in UTF-8 */ + goto out; + } + + s->text = tmp; + len = fribidi_unicode_to_charset(FRIBIDI_CHAR_SET_UTF8, + unicodestr, len, s->text); + ret = 0; + +out: + av_free(unicodestr); + av_free(embedding_levels); + av_free(ar_props); + av_free(bidi_types); + return ret; +} +#endif + static av_cold int init(AVFilterContext *ctx) { int err; @@ -509,6 +617,12 @@ static av_cold int init(AVFilterContext *ctx) return err; } +#if CONFIG_LIBFRIBIDI + if (s->text_shaping) + if ((err = shape_text(ctx)) < 0) + return err; +#endif + if (s->reload && !s->textfile) av_log(ctx, AV_LOG_WARNING, "No file to reload\n"); @@ -572,6 +686,7 @@ static av_cold int init(AVFilterContext *ctx) av_log(ctx, AV_LOG_WARNING, "expansion=strftime is deprecated.\n"); av_bprint_init(&s->expanded_text, 0, AV_BPRINT_SIZE_UNLIMITED); + av_bprint_init(&s->expanded_fontcolor, 0, AV_BPRINT_SIZE_UNLIMITED); return 0; } @@ -615,11 +730,7 @@ static av_cold void uninit(AVFilterContext *ctx) FT_Done_FreeType(s->library); av_bprint_finalize(&s->expanded_text, NULL); -} - -static inline int is_newline(uint32_t c) -{ - return c == '\n' || c == '\r' || c == '\f' || c == '\v'; + av_bprint_finalize(&s->expanded_fontcolor, NULL); } static int config_input(AVFilterLink *inlink) @@ -803,6 +914,66 @@ static int func_eval_expr(AVFilterContext *ctx, AVBPrint *bp, 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 ((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); + } + + 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; @@ -811,6 +982,8 @@ static const struct drawtext_function { } 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, 2, 0, func_pts }, { "gmtime", 0, 1, 'G', func_strftime }, @@ -887,11 +1060,8 @@ end: return ret; } -static int expand_text(AVFilterContext *ctx) +static int expand_text(AVFilterContext *ctx, char *text, AVBPrint *bp) { - DrawTextContext *s = ctx->priv; - char *text = s->text; - AVBPrint *bp = &s->expanded_text; int ret; av_bprint_clear(bp); @@ -987,7 +1157,7 @@ static int draw_text(AVFilterContext *ctx, AVFrame *frame, av_bprintf(bp, "%s", s->text); break; case EXP_NORMAL: - if ((ret = expand_text(ctx)) < 0) + if ((ret = expand_text(ctx, s->text, &s->expanded_text)) < 0) return ret; break; case EXP_STRFTIME: @@ -1013,6 +1183,20 @@ static int draw_text(AVFilterContext *ctx, AVFrame *frame, s->nb_positions = len; } + if (s->fontcolor_expr[0]) { + /* If expression is set, evaluate and replace the static value */ + av_bprint_clear(&s->expanded_fontcolor); + if ((ret = expand_text(ctx, s->fontcolor_expr, &s->expanded_fontcolor)) < 0) + return ret; + if (!av_bprint_is_complete(&s->expanded_fontcolor)) + return AVERROR(ENOMEM); + av_log(s, AV_LOG_DEBUG, "Evaluated fontcolor is '%s'\n", s->expanded_fontcolor.str); + ret = av_parse_color(s->fontcolor.rgba, s->expanded_fontcolor.str, -1, s); + if (ret) + return ret; + ff_draw_color(&s->dc, &s->fontcolor, s->fontcolor.rgba); + } + x = 0; y = 0; @@ -1132,9 +1316,15 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *frame) DrawTextContext *s = ctx->priv; int ret; - if (s->reload) + if (s->reload) { if ((ret = load_textfile(ctx)) < 0) return ret; +#if CONFIG_LIBFRIBIDI + if (s->text_shaping) + if ((ret = shape_text(ctx)) < 0) + return ret; +#endif + } s->var_values[VAR_N] = inlink->frame_count+s->start_number; s->var_values[VAR_T] = frame->pts == AV_NOPTS_VALUE ?