|
|
@@ -59,6 +59,10 @@ |
|
|
|
#include "internal.h" |
|
|
|
#include "video.h" |
|
|
|
|
|
|
|
#if CONFIG_LIBFRIBIDI |
|
|
|
#include <fribidi.h> |
|
|
|
#endif |
|
|
|
|
|
|
|
#include <ft2build.h> |
|
|
|
#include FT_FREETYPE_H |
|
|
|
#include FT_GLYPH_H |
|
|
@@ -182,6 +186,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; |
|
|
|
|
|
|
@@ -226,6 +233,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 +493,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 +613,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"); |
|
|
|
|
|
|
@@ -617,11 +727,6 @@ static av_cold void uninit(AVFilterContext *ctx) |
|
|
|
av_bprint_finalize(&s->expanded_text, NULL); |
|
|
|
} |
|
|
|
|
|
|
|
static inline int is_newline(uint32_t c) |
|
|
|
{ |
|
|
|
return c == '\n' || c == '\r' || c == '\f' || c == '\v'; |
|
|
|
} |
|
|
|
|
|
|
|
static int config_input(AVFilterLink *inlink) |
|
|
|
{ |
|
|
|
AVFilterContext *ctx = inlink->dst; |
|
|
@@ -1132,9 +1237,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 ? |
|
|
|