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:
Marc Jeffreys 2014-06-21 05:41:45 +01:00 committed by Michael Niedermayer
parent 04980dbee8
commit a0b71e9f3e
6 changed files with 134 additions and 7 deletions

View File

@ -35,6 +35,7 @@ version <next>:
- LRC demuxer and muxer - LRC demuxer and muxer
- Samba protocol (via libsmbclient) - Samba protocol (via libsmbclient)
- WebM DASH Manifest muxer - WebM DASH Manifest muxer
- libfribidi support in drawtext
version 2.2: version 2.2:

View File

@ -164,6 +164,7 @@
• signalstats filter • signalstats filter
• hqx filter (hq2x, hq3x, hq4x) • hqx filter (hq2x, hq3x, hq4x)
• flanger filter • flanger filter
• libfribidi support in drawtext
┌────────────────────────────┐ ┌────────────────────────────┐
│ ⚠ Behaviour changes │ │ ⚠ Behaviour changes │

3
configure vendored
View File

@ -209,6 +209,7 @@ External library support:
--enable-libfdk-aac enable AAC de/encoding via libfdk-aac [no] --enable-libfdk-aac enable AAC de/encoding via libfdk-aac [no]
--enable-libflite enable flite (voice synthesis) support via libflite [no] --enable-libflite enable flite (voice synthesis) support via libflite [no]
--enable-libfreetype enable libfreetype [no] --enable-libfreetype enable libfreetype [no]
--enable-libfribidi enable libfribidi [no]
--enable-libgme enable Game Music Emu via libgme [no] --enable-libgme enable Game Music Emu via libgme [no]
--enable-libgsm enable GSM de/encoding via libgsm [no] --enable-libgsm enable GSM de/encoding via libgsm [no]
--enable-libiec61883 enable iec61883 via libiec61883 [no] --enable-libiec61883 enable iec61883 via libiec61883 [no]
@ -1333,6 +1334,7 @@ EXTERNAL_LIBRARY_LIST="
libflite libflite
libfontconfig libfontconfig
libfreetype libfreetype
libfribidi
libgme libgme
libgsm libgsm
libiec61883 libiec61883
@ -4729,6 +4731,7 @@ enabled libflite && require2 libflite "flite/flite.h" flite_init $flite
enabled fontconfig && enable libfontconfig enabled fontconfig && enable libfontconfig
enabled libfontconfig && require_pkg_config fontconfig "fontconfig/fontconfig.h" FcInit enabled libfontconfig && require_pkg_config fontconfig "fontconfig/fontconfig.h" FcInit
enabled libfreetype && require_libfreetype 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 libgme && require libgme gme/gme.h gme_new_emu -lgme -lstdc++
enabled libgsm && { for gsm_hdr in "gsm.h" "gsm/gsm.h"; do enabled libgsm && { for gsm_hdr in "gsm.h" "gsm/gsm.h"; do
check_lib "${gsm_hdr}" gsm_create -lgsm && break; check_lib "${gsm_hdr}" gsm_create -lgsm && break;

View File

@ -3653,6 +3653,8 @@ To enable compilation of this filter, you need to configure FFmpeg with
@code{--enable-libfreetype}. @code{--enable-libfreetype}.
To enable default font fallback and the @var{font} option you need to To enable default font fallback and the @var{font} option you need to
configure FFmpeg with @code{--enable-libfontconfig}. configure FFmpeg with @code{--enable-libfontconfig}.
To enable the @var{text_shaping} option, you need to configure FFmpeg with
@code{--enable-libfribidi}.
@subsection Syntax @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 font size to be used for drawing text.
The default value of @var{fontsize} is 16. 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 @item ft_load_flags
The flags to be used for loading the fonts. 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: For more information about fontconfig, check:
@url{http://freedesktop.org/software/fontconfig/fontconfig-user.html}. @url{http://freedesktop.org/software/fontconfig/fontconfig-user.html}.
For more information about libfribidi, check:
@url{http://fribidi.org/}.
@section edgedetect @section edgedetect
Detect and draw edges. The filter uses the Canny Edge Detection algorithm. Detect and draw edges. The filter uses the Canny Edge Detection algorithm.

View File

@ -30,7 +30,7 @@
#include "libavutil/version.h" #include "libavutil/version.h"
#define LIBAVFILTER_VERSION_MAJOR 4 #define LIBAVFILTER_VERSION_MAJOR 4
#define LIBAVFILTER_VERSION_MINOR 10 #define LIBAVFILTER_VERSION_MINOR 11
#define LIBAVFILTER_VERSION_MICRO 100 #define LIBAVFILTER_VERSION_MICRO 100
#define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \ #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \

View File

@ -59,6 +59,10 @@
#include "internal.h" #include "internal.h"
#include "video.h" #include "video.h"
#if CONFIG_LIBFRIBIDI
#include <fribidi.h>
#endif
#include <ft2build.h> #include <ft2build.h>
#include FT_FREETYPE_H #include FT_FREETYPE_H
#include FT_GLYPH_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 tc24hmax; ///< 1 if timecode is wrapped to 24 hours, 0 otherwise
int reload; ///< reload text file for each frame int reload; ///< reload text file for each frame
int start_number; ///< starting frame number for n/frame_num var 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; AVDictionary *metadata;
} DrawTextContext; } 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}, {"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}, {"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 */
{ "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" }, { "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" }, { "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; 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) static av_cold int init(AVFilterContext *ctx)
{ {
int err; int err;
@ -509,6 +613,12 @@ static av_cold int init(AVFilterContext *ctx)
return err; return err;
} }
#if CONFIG_LIBFRIBIDI
if (s->text_shaping)
if ((err = shape_text(ctx)) < 0)
return err;
#endif
if (s->reload && !s->textfile) if (s->reload && !s->textfile)
av_log(ctx, AV_LOG_WARNING, "No file to reload\n"); 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); 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) static int config_input(AVFilterLink *inlink)
{ {
AVFilterContext *ctx = inlink->dst; AVFilterContext *ctx = inlink->dst;
@ -1132,9 +1237,15 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
DrawTextContext *s = ctx->priv; DrawTextContext *s = ctx->priv;
int ret; int ret;
if (s->reload) if (s->reload) {
if ((ret = load_textfile(ctx)) < 0) if ((ret = load_textfile(ctx)) < 0)
return ret; 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_N] = inlink->frame_count+s->start_number;
s->var_values[VAR_T] = frame->pts == AV_NOPTS_VALUE ? s->var_values[VAR_T] = frame->pts == AV_NOPTS_VALUE ?