avfilter/drawtext: Add basic text shaping using libfribidi
Fixes ticket #3758 Reviewed-by: Andrey Utkin <andrey.krieger.utkin@gmail.com> Signed-off-by: Michael Niedermayer <michaelni@gmx.at>
This commit is contained in:
		
							parent
							
								
									04980dbee8
								
							
						
					
					
						commit
						a0b71e9f3e
					
				@ -35,6 +35,7 @@ version <next>:
 | 
			
		||||
- LRC demuxer and muxer
 | 
			
		||||
- Samba protocol (via libsmbclient)
 | 
			
		||||
- WebM DASH Manifest muxer
 | 
			
		||||
- libfribidi support in drawtext
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
version 2.2:
 | 
			
		||||
 | 
			
		||||
@ -164,6 +164,7 @@
 | 
			
		||||
    • signalstats filter
 | 
			
		||||
    • hqx filter (hq2x, hq3x, hq4x)
 | 
			
		||||
    • flanger filter
 | 
			
		||||
    • libfribidi support in drawtext
 | 
			
		||||
 | 
			
		||||
 ┌────────────────────────────┐
 | 
			
		||||
 │ ⚠  Behaviour changes       │
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										3
									
								
								configure
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								configure
									
									
									
									
										vendored
									
									
								
							@ -209,6 +209,7 @@ External library support:
 | 
			
		||||
  --enable-libfdk-aac      enable AAC de/encoding via libfdk-aac [no]
 | 
			
		||||
  --enable-libflite        enable flite (voice synthesis) support via libflite [no]
 | 
			
		||||
  --enable-libfreetype     enable libfreetype [no]
 | 
			
		||||
  --enable-libfribidi      enable libfribidi [no]
 | 
			
		||||
  --enable-libgme          enable Game Music Emu via libgme [no]
 | 
			
		||||
  --enable-libgsm          enable GSM de/encoding via libgsm [no]
 | 
			
		||||
  --enable-libiec61883     enable iec61883 via libiec61883 [no]
 | 
			
		||||
@ -1333,6 +1334,7 @@ EXTERNAL_LIBRARY_LIST="
 | 
			
		||||
    libflite
 | 
			
		||||
    libfontconfig
 | 
			
		||||
    libfreetype
 | 
			
		||||
    libfribidi
 | 
			
		||||
    libgme
 | 
			
		||||
    libgsm
 | 
			
		||||
    libiec61883
 | 
			
		||||
@ -4729,6 +4731,7 @@ enabled libflite          && require2 libflite "flite/flite.h" flite_init $flite
 | 
			
		||||
enabled fontconfig        && enable libfontconfig
 | 
			
		||||
enabled libfontconfig     && require_pkg_config fontconfig "fontconfig/fontconfig.h" FcInit
 | 
			
		||||
enabled libfreetype       && require_libfreetype
 | 
			
		||||
enabled libfribidi        && require_pkg_config fribidi fribidi.h fribidi_version_info
 | 
			
		||||
enabled libgme            && require  libgme gme/gme.h gme_new_emu -lgme -lstdc++
 | 
			
		||||
enabled libgsm            && { for gsm_hdr in "gsm.h" "gsm/gsm.h"; do
 | 
			
		||||
                                   check_lib "${gsm_hdr}" gsm_create -lgsm && break;
 | 
			
		||||
 | 
			
		||||
@ -3653,6 +3653,8 @@ To enable compilation of this filter, you need to configure FFmpeg with
 | 
			
		||||
@code{--enable-libfreetype}.
 | 
			
		||||
To enable default font fallback and the @var{font} option you need to
 | 
			
		||||
configure FFmpeg with @code{--enable-libfontconfig}.
 | 
			
		||||
To enable the @var{text_shaping} option, you need to configure FFmpeg with
 | 
			
		||||
@code{--enable-libfribidi}.
 | 
			
		||||
 | 
			
		||||
@subsection Syntax
 | 
			
		||||
 | 
			
		||||
@ -3707,6 +3709,12 @@ This parameter is mandatory if the fontconfig support is disabled.
 | 
			
		||||
The font size to be used for drawing text.
 | 
			
		||||
The default value of @var{fontsize} is 16.
 | 
			
		||||
 | 
			
		||||
@item text_shaping
 | 
			
		||||
If set to 1, attempt to shape the text (for example, reverse the order of
 | 
			
		||||
right-to-left text and join Arabic characters) before drawing it.
 | 
			
		||||
Otherwise, just draw the text exactly as given.
 | 
			
		||||
By default 1 (if supported).
 | 
			
		||||
 | 
			
		||||
@item ft_load_flags
 | 
			
		||||
The flags to be used for loading the fonts.
 | 
			
		||||
 | 
			
		||||
@ -4010,6 +4018,9 @@ For more information about libfreetype, check:
 | 
			
		||||
For more information about fontconfig, check:
 | 
			
		||||
@url{http://freedesktop.org/software/fontconfig/fontconfig-user.html}.
 | 
			
		||||
 | 
			
		||||
For more information about libfribidi, check:
 | 
			
		||||
@url{http://fribidi.org/}.
 | 
			
		||||
 | 
			
		||||
@section edgedetect
 | 
			
		||||
 | 
			
		||||
Detect and draw edges. The filter uses the Canny Edge Detection algorithm.
 | 
			
		||||
 | 
			
		||||
@ -30,7 +30,7 @@
 | 
			
		||||
#include "libavutil/version.h"
 | 
			
		||||
 | 
			
		||||
#define LIBAVFILTER_VERSION_MAJOR   4
 | 
			
		||||
#define LIBAVFILTER_VERSION_MINOR  10
 | 
			
		||||
#define LIBAVFILTER_VERSION_MINOR  11
 | 
			
		||||
#define LIBAVFILTER_VERSION_MICRO 100
 | 
			
		||||
 | 
			
		||||
#define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \
 | 
			
		||||
 | 
			
		||||
@ -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 ?
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user