You've already forked FFmpeg
mirror of
https://github.com/FFmpeg/FFmpeg.git
synced 2025-08-10 06:10:52 +02:00
avfilter/vf_drawtext: add support for commands
This commit is contained in:
@@ -12791,11 +12791,34 @@ Full filter invocation with sendcmd would look like this:
|
|||||||
@example
|
@example
|
||||||
sendcmd=c='56.0 drawtext reinit fontsize=56\:fontcolor=green\:text=Hello\\ World'
|
sendcmd=c='56.0 drawtext reinit fontsize=56\:fontcolor=green\:text=Hello\\ World'
|
||||||
@end example
|
@end example
|
||||||
@end table
|
|
||||||
|
|
||||||
If the entire argument can't be parsed or applied as valid values then the filter will
|
If the entire argument can't be parsed or applied as valid values then the filter will
|
||||||
continue with its existing parameters.
|
continue with its existing parameters.
|
||||||
|
|
||||||
|
@end table
|
||||||
|
|
||||||
|
The following options are also supported as @ref{commands}:
|
||||||
|
|
||||||
|
@itemize @bullet
|
||||||
|
@item x
|
||||||
|
@item y
|
||||||
|
@item alpha
|
||||||
|
@item fontsize
|
||||||
|
@item fontcolor
|
||||||
|
@item boxcolor
|
||||||
|
@item bordercolor
|
||||||
|
@item shadowcolor
|
||||||
|
@item box
|
||||||
|
@item boxw
|
||||||
|
@item boxh
|
||||||
|
@item boxborderw
|
||||||
|
@item line_spacing
|
||||||
|
@item text_align
|
||||||
|
@item shadowx
|
||||||
|
@item shadowy
|
||||||
|
@item borderw
|
||||||
|
@end itemize
|
||||||
|
|
||||||
@subsection Examples
|
@subsection Examples
|
||||||
|
|
||||||
@itemize
|
@itemize
|
||||||
|
@@ -328,29 +328,30 @@ typedef struct DrawTextContext {
|
|||||||
|
|
||||||
#define OFFSET(x) offsetof(DrawTextContext, x)
|
#define OFFSET(x) offsetof(DrawTextContext, x)
|
||||||
#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
|
#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
|
||||||
|
#define TFLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_RUNTIME_PARAM
|
||||||
|
|
||||||
static const AVOption drawtext_options[]= {
|
static const AVOption drawtext_options[]= {
|
||||||
{"fontfile", "set font file", OFFSET(fontfile), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS},
|
{"fontfile", "set font file", OFFSET(fontfile), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS},
|
||||||
{"text", "set text", OFFSET(text), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS},
|
{"text", "set text", OFFSET(text), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, TFLAGS},
|
||||||
{"textfile", "set text file", OFFSET(textfile), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS},
|
{"textfile", "set text file", OFFSET(textfile), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS},
|
||||||
{"fontcolor", "set foreground color", OFFSET(fontcolor.rgba), AV_OPT_TYPE_COLOR, {.str="black"}, 0, 0, FLAGS},
|
{"fontcolor", "set foreground color", OFFSET(fontcolor.rgba), AV_OPT_TYPE_COLOR, {.str="black"}, 0, 0, TFLAGS},
|
||||||
{"fontcolor_expr", "set foreground color expression", OFFSET(fontcolor_expr), AV_OPT_TYPE_STRING, {.str=""}, 0, 0, FLAGS},
|
{"fontcolor_expr", "set foreground color expression", OFFSET(fontcolor_expr), AV_OPT_TYPE_STRING, {.str=""}, 0, 0, FLAGS},
|
||||||
{"boxcolor", "set box color", OFFSET(boxcolor.rgba), AV_OPT_TYPE_COLOR, {.str="white"}, 0, 0, FLAGS},
|
{"boxcolor", "set box color", OFFSET(boxcolor.rgba), AV_OPT_TYPE_COLOR, {.str="white"}, 0, 0, TFLAGS},
|
||||||
{"bordercolor", "set border color", OFFSET(bordercolor.rgba), AV_OPT_TYPE_COLOR, {.str="black"}, 0, 0, FLAGS},
|
{"bordercolor", "set border color", OFFSET(bordercolor.rgba), AV_OPT_TYPE_COLOR, {.str="black"}, 0, 0, TFLAGS},
|
||||||
{"shadowcolor", "set shadow color", OFFSET(shadowcolor.rgba), AV_OPT_TYPE_COLOR, {.str="black"}, 0, 0, FLAGS},
|
{"shadowcolor", "set shadow color", OFFSET(shadowcolor.rgba), AV_OPT_TYPE_COLOR, {.str="black"}, 0, 0, TFLAGS},
|
||||||
{"box", "set box", OFFSET(draw_box), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS},
|
{"box", "set box", OFFSET(draw_box), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, TFLAGS},
|
||||||
{"boxborderw", "set box borders width", OFFSET(boxborderw), AV_OPT_TYPE_STRING, {.str="0"}, 0, 0, FLAGS},
|
{"boxborderw", "set box borders width", OFFSET(boxborderw), AV_OPT_TYPE_STRING, {.str="0"}, 0, 0, TFLAGS},
|
||||||
{"line_spacing", "set line spacing in pixels", OFFSET(line_spacing), AV_OPT_TYPE_INT, {.i64=0}, INT_MIN, INT_MAX, FLAGS},
|
{"line_spacing", "set line spacing in pixels", OFFSET(line_spacing), AV_OPT_TYPE_INT, {.i64=0}, INT_MIN, INT_MAX, TFLAGS},
|
||||||
{"fontsize", "set font size", OFFSET(fontsize_expr), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS},
|
{"fontsize", "set font size", OFFSET(fontsize_expr), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, TFLAGS},
|
||||||
{"text_align", "set text alignment", OFFSET(text_align), AV_OPT_TYPE_STRING, {.str="TL"}, 0, 0, FLAGS},
|
{"text_align", "set text alignment", OFFSET(text_align), AV_OPT_TYPE_STRING, {.str="TL"}, 0, 0, TFLAGS},
|
||||||
{"x", "set x expression", OFFSET(x_expr), AV_OPT_TYPE_STRING, {.str="0"}, 0, 0, FLAGS},
|
{"x", "set x expression", OFFSET(x_expr), AV_OPT_TYPE_STRING, {.str="0"}, 0, 0, TFLAGS},
|
||||||
{"y", "set y expression", OFFSET(y_expr), AV_OPT_TYPE_STRING, {.str="0"}, 0, 0, FLAGS},
|
{"y", "set y expression", OFFSET(y_expr), AV_OPT_TYPE_STRING, {.str="0"}, 0, 0, TFLAGS},
|
||||||
{"boxw", "set box width", OFFSET(boxw), AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, FLAGS},
|
{"boxw", "set box width", OFFSET(boxw), AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, TFLAGS},
|
||||||
{"boxh", "set box height", OFFSET(boxh), AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, FLAGS},
|
{"boxh", "set box height", OFFSET(boxh), AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, TFLAGS},
|
||||||
{"shadowx", "set shadow x offset", OFFSET(shadowx), AV_OPT_TYPE_INT, {.i64=0}, INT_MIN, INT_MAX, FLAGS},
|
{"shadowx", "set shadow x offset", OFFSET(shadowx), AV_OPT_TYPE_INT, {.i64=0}, INT_MIN, INT_MAX, TFLAGS},
|
||||||
{"shadowy", "set shadow y offset", OFFSET(shadowy), AV_OPT_TYPE_INT, {.i64=0}, INT_MIN, INT_MAX, FLAGS},
|
{"shadowy", "set shadow y offset", OFFSET(shadowy), AV_OPT_TYPE_INT, {.i64=0}, INT_MIN, INT_MAX, TFLAGS},
|
||||||
{"borderw", "set border width", OFFSET(borderw), AV_OPT_TYPE_INT, {.i64=0}, INT_MIN, INT_MAX, FLAGS},
|
{"borderw", "set border width", OFFSET(borderw), AV_OPT_TYPE_INT, {.i64=0}, INT_MIN, INT_MAX, TFLAGS},
|
||||||
{"tabsize", "set tab size", OFFSET(tabsize), AV_OPT_TYPE_INT, {.i64=4}, 0, INT_MAX, FLAGS},
|
{"tabsize", "set tab size", OFFSET(tabsize), AV_OPT_TYPE_INT, {.i64=4}, 0, INT_MAX, TFLAGS},
|
||||||
{"basetime", "set base time", OFFSET(basetime), AV_OPT_TYPE_INT64, {.i64=AV_NOPTS_VALUE}, INT64_MIN, INT64_MAX, FLAGS},
|
{"basetime", "set base time", OFFSET(basetime), AV_OPT_TYPE_INT64, {.i64=AV_NOPTS_VALUE}, INT64_MIN, INT64_MAX, FLAGS},
|
||||||
#if CONFIG_LIBFONTCONFIG
|
#if CONFIG_LIBFONTCONFIG
|
||||||
{ "font", "Font name", OFFSET(font), AV_OPT_TYPE_STRING, { .str = "Sans" }, .flags = FLAGS },
|
{ "font", "Font name", OFFSET(font), AV_OPT_TYPE_STRING, { .str = "Sans" }, .flags = FLAGS },
|
||||||
@@ -360,7 +361,7 @@ static const AVOption drawtext_options[]= {
|
|||||||
{"none", "set no expansion", OFFSET(exp_mode), AV_OPT_TYPE_CONST, {.i64=EXP_NONE}, 0, 0, FLAGS, "expansion"},
|
{"none", "set no expansion", OFFSET(exp_mode), AV_OPT_TYPE_CONST, {.i64=EXP_NONE}, 0, 0, FLAGS, "expansion"},
|
||||||
{"normal", "set normal expansion", OFFSET(exp_mode), AV_OPT_TYPE_CONST, {.i64=EXP_NORMAL}, 0, 0, FLAGS, "expansion"},
|
{"normal", "set normal expansion", OFFSET(exp_mode), AV_OPT_TYPE_CONST, {.i64=EXP_NORMAL}, 0, 0, FLAGS, "expansion"},
|
||||||
{"strftime", "set strftime expansion (deprecated)", OFFSET(exp_mode), AV_OPT_TYPE_CONST, {.i64=EXP_STRFTIME}, 0, 0, FLAGS, "expansion"},
|
{"strftime", "set strftime expansion (deprecated)", OFFSET(exp_mode), AV_OPT_TYPE_CONST, {.i64=EXP_STRFTIME}, 0, 0, FLAGS, "expansion"},
|
||||||
{"y_align", "set the y alignment", OFFSET(y_align), AV_OPT_TYPE_INT, {.i64=YA_TEXT}, 0, 2, FLAGS, "y_align"},
|
{"y_align", "set the y alignment", OFFSET(y_align), AV_OPT_TYPE_INT, {.i64=YA_TEXT}, 0, 2, TFLAGS, "y_align"},
|
||||||
{"text", "y is referred to the top of the first text line", OFFSET(y_align), AV_OPT_TYPE_CONST, {.i64=YA_TEXT}, 0, 0, FLAGS, "y_align"},
|
{"text", "y is referred to the top of the first text line", OFFSET(y_align), AV_OPT_TYPE_CONST, {.i64=YA_TEXT}, 0, 0, FLAGS, "y_align"},
|
||||||
{"baseline", "y is referred to the baseline of the first line", OFFSET(y_align), AV_OPT_TYPE_CONST, {.i64=YA_BASELINE}, 0, 0, FLAGS, "y_align"},
|
{"baseline", "y is referred to the baseline of the first line", OFFSET(y_align), AV_OPT_TYPE_CONST, {.i64=YA_BASELINE}, 0, 0, FLAGS, "y_align"},
|
||||||
{"font", "y is referred to the font defined line metrics", OFFSET(y_align), AV_OPT_TYPE_CONST, {.i64=YA_FONT}, 0, 0, FLAGS, "y_align"},
|
{"font", "y is referred to the font defined line metrics", OFFSET(y_align), AV_OPT_TYPE_CONST, {.i64=YA_FONT}, 0, 0, FLAGS, "y_align"},
|
||||||
@@ -371,7 +372,7 @@ static const AVOption drawtext_options[]= {
|
|||||||
{"r", "set rate (timecode only)", OFFSET(tc_rate), AV_OPT_TYPE_RATIONAL, {.dbl=0}, 0, INT_MAX, FLAGS},
|
{"r", "set rate (timecode only)", OFFSET(tc_rate), AV_OPT_TYPE_RATIONAL, {.dbl=0}, 0, INT_MAX, FLAGS},
|
||||||
{"rate", "set rate (timecode only)", OFFSET(tc_rate), AV_OPT_TYPE_RATIONAL, {.dbl=0}, 0, INT_MAX, FLAGS},
|
{"rate", "set rate (timecode only)", OFFSET(tc_rate), AV_OPT_TYPE_RATIONAL, {.dbl=0}, 0, INT_MAX, FLAGS},
|
||||||
{"reload", "reload text file at specified frame interval", OFFSET(reload), AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, FLAGS},
|
{"reload", "reload text file at specified frame interval", OFFSET(reload), AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, FLAGS},
|
||||||
{"alpha", "apply alpha while rendering", OFFSET(a_expr), AV_OPT_TYPE_STRING, {.str = "1"}, .flags = FLAGS},
|
{"alpha", "apply alpha while rendering", OFFSET(a_expr), AV_OPT_TYPE_STRING, {.str = "1"}, .flags = TFLAGS},
|
||||||
{"fix_bounds", "check and fix text coords to avoid clipping", OFFSET(fix_bounds), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS},
|
{"fix_bounds", "check and fix text coords to avoid clipping", OFFSET(fix_bounds), AV_OPT_TYPE_BOOL, {.i64=0}, 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},
|
||||||
{"text_source", "the source of text", OFFSET(text_source_string), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 1, FLAGS },
|
{"text_source", "the source of text", OFFSET(text_source_string), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 1, FLAGS },
|
||||||
@@ -998,6 +999,23 @@ static int query_formats(AVFilterContext *ctx)
|
|||||||
return ff_set_common_formats(ctx, ff_draw_supported_pixel_formats(0));
|
return ff_set_common_formats(ctx, ff_draw_supported_pixel_formats(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int glyph_enu_border_free(void *opaque, void *elem)
|
||||||
|
{
|
||||||
|
Glyph *glyph = elem;
|
||||||
|
|
||||||
|
if (glyph->border_glyph != NULL) {
|
||||||
|
for (int t = 0; t < 16; ++t) {
|
||||||
|
if (glyph->border_bglyph[t] != NULL) {
|
||||||
|
FT_Done_Glyph((FT_Glyph)glyph->border_bglyph[t]);
|
||||||
|
glyph->border_bglyph[t] = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FT_Done_Glyph(glyph->border_glyph);
|
||||||
|
glyph->border_glyph = NULL;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int glyph_enu_free(void *opaque, void *elem)
|
static int glyph_enu_free(void *opaque, void *elem)
|
||||||
{
|
{
|
||||||
Glyph *glyph = elem;
|
Glyph *glyph = elem;
|
||||||
@@ -1120,8 +1138,28 @@ static int command(AVFilterContext *ctx, const char *cmd, const char *arg, char
|
|||||||
|
|
||||||
ctx->priv = new;
|
ctx->priv = new;
|
||||||
return config_input(ctx->inputs[0]);
|
return config_input(ctx->inputs[0]);
|
||||||
} else
|
} else {
|
||||||
return AVERROR(ENOSYS);
|
int old_borderw = old->borderw;
|
||||||
|
if ((ret = ff_filter_process_command(ctx, cmd, arg, res, res_len, flags)) < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
if (old->borderw != old_borderw) {
|
||||||
|
FT_Stroker_Set(old->stroker, old->borderw << 6, FT_STROKER_LINECAP_ROUND,
|
||||||
|
FT_STROKER_LINEJOIN_ROUND, 0);
|
||||||
|
// Dispose the old border glyphs
|
||||||
|
av_tree_enumerate(old->glyphs, NULL, NULL, glyph_enu_border_free);
|
||||||
|
} else if (strcmp(cmd, "text_align") == 0) {
|
||||||
|
if (validate_text_align(old->text_align) != 0) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR,
|
||||||
|
"Invalid command value '%s' for 'text_align'\n", old->text_align);
|
||||||
|
}
|
||||||
|
} else if (strcmp(cmd, "fontsize") == 0) {
|
||||||
|
av_expr_free(old->fontsize_pexpr);
|
||||||
|
old->fontsize_pexpr = NULL;
|
||||||
|
old->blank_advance64 = 0;
|
||||||
|
}
|
||||||
|
return config_input(ctx->inputs[0]);
|
||||||
|
}
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
av_log(ctx, AV_LOG_ERROR, "Failed to process command. Continuing with existing parameters.\n");
|
av_log(ctx, AV_LOG_ERROR, "Failed to process command. Continuing with existing parameters.\n");
|
||||||
|
Reference in New Issue
Block a user