mirror of
https://git.ffmpeg.org/ffmpeg.git
synced 2025-02-04 13:53:26 +00:00
drawtext: make x and y parametric
Introduce variables "E", "PHI", "PI", "main_w"/"W", "main_h"/"H", "text_w"/"w", "text_h"/"h", "x", "y", "n" and "t" in line with vf_overlay and refactor the code accordingly.
This commit is contained in:
parent
ec11ff8407
commit
2cf74eca70
@ -505,6 +505,32 @@ If both text and textfile are specified, an error is thrown.
|
||||
@item x, y
|
||||
The offsets where text will be drawn within the video frame.
|
||||
Relative to the top/left border of the output image.
|
||||
They accept expressions similar to the @ref{overlay} filter:
|
||||
@table @option
|
||||
|
||||
@item x, y
|
||||
the computed values for @var{x} and @var{y}. They are evaluated for
|
||||
each new frame.
|
||||
|
||||
@item main_w, main_h
|
||||
main input width and height
|
||||
|
||||
@item W, H
|
||||
same as @var{main_w} and @var{main_h}
|
||||
|
||||
@item text_w, text_h
|
||||
rendered text width and height
|
||||
|
||||
@item w, h
|
||||
same as @var{text_w} and @var{text_h}
|
||||
|
||||
@item n
|
||||
the number of frames processed, starting from 0
|
||||
|
||||
@item t
|
||||
timestamp expressed in seconds, NAN if the input timestamp is unknown
|
||||
|
||||
@end table
|
||||
|
||||
The default value of @var{x} and @var{y} is 0.
|
||||
|
||||
@ -1048,6 +1074,7 @@ other parameters is 0.
|
||||
These parameters correspond to the parameters assigned to the
|
||||
libopencv function @code{cvSmooth}.
|
||||
|
||||
@anchor{overlay}
|
||||
@section overlay
|
||||
|
||||
Overlay one video on top of another.
|
||||
|
@ -31,7 +31,9 @@
|
||||
|
||||
#include "libavutil/colorspace.h"
|
||||
#include "libavutil/file.h"
|
||||
#include "libavutil/eval.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/mathematics.h"
|
||||
#include "libavutil/parseutils.h"
|
||||
#include "libavutil/pixdesc.h"
|
||||
#include "libavutil/tree.h"
|
||||
@ -45,6 +47,36 @@
|
||||
#include FT_FREETYPE_H
|
||||
#include FT_GLYPH_H
|
||||
|
||||
static const char *var_names[] = {
|
||||
"E",
|
||||
"PHI",
|
||||
"PI",
|
||||
"main_w", "W", ///< width of the main video
|
||||
"main_h", "H", ///< height of the main video
|
||||
"text_w", "w", ///< width of the overlay text
|
||||
"text_h", "h", ///< height of the overlay text
|
||||
"x",
|
||||
"y",
|
||||
"n", ///< number of processed frames
|
||||
"t", ///< timestamp expressed in seconds
|
||||
NULL
|
||||
};
|
||||
|
||||
enum var_name {
|
||||
VAR_E,
|
||||
VAR_PHI,
|
||||
VAR_PI,
|
||||
VAR_MAIN_W, VAR_MW,
|
||||
VAR_MAIN_H, VAR_MH,
|
||||
VAR_TEXT_W, VAR_TW,
|
||||
VAR_TEXT_H, VAR_TH,
|
||||
VAR_X,
|
||||
VAR_Y,
|
||||
VAR_N,
|
||||
VAR_T,
|
||||
VAR_VARS_NB
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const AVClass *class;
|
||||
uint8_t *fontfile; ///< font to be used
|
||||
@ -81,6 +113,10 @@ typedef struct {
|
||||
int pixel_step[4]; ///< distance in bytes between the component of each pixel
|
||||
uint8_t rgba_map[4]; ///< map RGBA offsets to the positions in the packed RGBA format
|
||||
uint8_t *box_line[4]; ///< line used for filling the box background
|
||||
char *x_expr, *y_expr;
|
||||
AVExpr *x_pexpr, *y_pexpr; ///< parsed expressions for x and y
|
||||
double var_values[VAR_VARS_NB];
|
||||
int draw; ///< set to zero to prevent drawing
|
||||
} DrawTextContext;
|
||||
|
||||
#define OFFSET(x) offsetof(DrawTextContext, x)
|
||||
@ -94,8 +130,8 @@ static const AVOption drawtext_options[]= {
|
||||
{"shadowcolor", "set shadow color", OFFSET(shadowcolor_string), AV_OPT_TYPE_STRING, {.str=NULL}, CHAR_MIN, CHAR_MAX },
|
||||
{"box", "set box", OFFSET(draw_box), AV_OPT_TYPE_INT, {.dbl=0}, 0, 1 },
|
||||
{"fontsize", "set font size", OFFSET(fontsize), AV_OPT_TYPE_INT, {.dbl=16}, 1, 72 },
|
||||
{"x", "set x", OFFSET(x), AV_OPT_TYPE_INT, {.dbl=0}, 0, INT_MAX },
|
||||
{"y", "set y", OFFSET(y), AV_OPT_TYPE_INT, {.dbl=0}, 0, INT_MAX },
|
||||
{"x", "set x", OFFSET(x_expr), AV_OPT_TYPE_STRING, {.str="0"}, CHAR_MIN, CHAR_MAX },
|
||||
{"y", "set y", OFFSET(y_expr), AV_OPT_TYPE_STRING, {.str="0"}, CHAR_MIN, CHAR_MAX },
|
||||
{"shadowx", "set x", OFFSET(shadowx), AV_OPT_TYPE_INT, {.dbl=0}, INT_MIN, INT_MAX },
|
||||
{"shadowy", "set y", OFFSET(shadowy), AV_OPT_TYPE_INT, {.dbl=0}, INT_MIN, INT_MAX },
|
||||
{"tabsize", "set tab size", OFFSET(tabsize), AV_OPT_TYPE_INT, {.dbl=4}, 0, INT_MAX },
|
||||
@ -374,7 +410,7 @@ static inline int is_newline(uint32_t c)
|
||||
return (c == '\n' || c == '\r' || c == '\f' || c == '\v');
|
||||
}
|
||||
|
||||
static int dtext_prepare_text(AVFilterContext *ctx, int width, int height)
|
||||
static int dtext_prepare_text(AVFilterContext *ctx)
|
||||
{
|
||||
DrawTextContext *dtext = ctx->priv;
|
||||
uint32_t code = 0, prev_code = 0;
|
||||
@ -387,6 +423,8 @@ static int dtext_prepare_text(AVFilterContext *ctx, int width, int height)
|
||||
FT_Vector delta;
|
||||
Glyph *glyph = NULL, *prev_glyph = NULL;
|
||||
Glyph dummy = { 0 };
|
||||
int width = ctx->inputs[0]->w;
|
||||
int height = ctx->inputs[0]->h;
|
||||
|
||||
#if HAVE_LOCALTIME_R
|
||||
time_t now = time(0);
|
||||
@ -504,6 +542,27 @@ static int config_input(AVFilterLink *inlink)
|
||||
dtext->hsub = pix_desc->log2_chroma_w;
|
||||
dtext->vsub = pix_desc->log2_chroma_h;
|
||||
|
||||
dtext->var_values[VAR_E ] = M_E;
|
||||
dtext->var_values[VAR_PHI] = M_PHI;
|
||||
dtext->var_values[VAR_PI ] = M_PI;
|
||||
|
||||
dtext->var_values[VAR_MAIN_W] =
|
||||
dtext->var_values[VAR_MW] = ctx->inputs[0]->w;
|
||||
dtext->var_values[VAR_MAIN_H] =
|
||||
dtext->var_values[VAR_MH] = ctx->inputs[0]->h;
|
||||
|
||||
dtext->var_values[VAR_X] = 0;
|
||||
dtext->var_values[VAR_Y] = 0;
|
||||
dtext->var_values[VAR_N] = 0;
|
||||
dtext->var_values[VAR_T] = NAN;
|
||||
|
||||
|
||||
if ((ret = av_expr_parse(&dtext->x_pexpr, dtext->x_expr, var_names,
|
||||
NULL, NULL, NULL, NULL, 0, ctx)) < 0 ||
|
||||
(ret = av_expr_parse(&dtext->y_pexpr, dtext->y_expr, var_names,
|
||||
NULL, NULL, NULL, NULL, 0, ctx)) < 0)
|
||||
return AVERROR(EINVAL);
|
||||
|
||||
if ((ret =
|
||||
ff_fill_line_with_color(dtext->box_line, dtext->pixel_step,
|
||||
inlink->w, dtext->boxcolor,
|
||||
@ -524,7 +583,9 @@ static int config_input(AVFilterLink *inlink)
|
||||
dtext->shadowcolor[3] = rgba[3];
|
||||
}
|
||||
|
||||
return dtext_prepare_text(ctx, ctx->inputs[0]->w, ctx->inputs[0]->h);
|
||||
dtext->draw = 1;
|
||||
|
||||
return dtext_prepare_text(ctx);
|
||||
}
|
||||
|
||||
#define GET_BITMAP_VAL(r, c) \
|
||||
@ -697,15 +758,71 @@ static int draw_text(AVFilterContext *ctx, AVFilterBufferRef *picref,
|
||||
|
||||
static void null_draw_slice(AVFilterLink *link, int y, int h, int slice_dir) { }
|
||||
|
||||
static inline int normalize_double(int *n, double d)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (isnan(d)) {
|
||||
ret = AVERROR(EINVAL);
|
||||
} else if (d > INT_MAX || d < INT_MIN) {
|
||||
*n = d > INT_MAX ? INT_MAX : INT_MIN;
|
||||
ret = AVERROR(EINVAL);
|
||||
} else
|
||||
*n = round(d);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void start_frame(AVFilterLink *inlink, AVFilterBufferRef *inpicref)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
DrawTextContext *dtext = ctx->priv;
|
||||
|
||||
if (dtext_prepare_text(ctx) < 0) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Can't draw text\n");
|
||||
dtext->draw = 0;
|
||||
}
|
||||
|
||||
dtext->var_values[VAR_T] = inpicref->pts == AV_NOPTS_VALUE ?
|
||||
NAN : inpicref->pts * av_q2d(inlink->time_base);
|
||||
dtext->var_values[VAR_X] =
|
||||
av_expr_eval(dtext->x_pexpr, dtext->var_values, NULL);
|
||||
dtext->var_values[VAR_Y] =
|
||||
av_expr_eval(dtext->y_pexpr, dtext->var_values, NULL);
|
||||
dtext->var_values[VAR_X] =
|
||||
av_expr_eval(dtext->x_pexpr, dtext->var_values, NULL);
|
||||
|
||||
normalize_double(&dtext->x, dtext->var_values[VAR_X]);
|
||||
normalize_double(&dtext->y, dtext->var_values[VAR_Y]);
|
||||
|
||||
if (dtext->x < 0) dtext->x = 0;
|
||||
if (dtext->y < 0) dtext->y = 0;
|
||||
if ((unsigned)dtext->x + (unsigned)dtext->w > inlink->w)
|
||||
dtext->x = inlink->w - dtext->w;
|
||||
if ((unsigned)dtext->y + (unsigned)dtext->h > inlink->h)
|
||||
dtext->y = inlink->h - dtext->h;
|
||||
|
||||
dtext->x &= ~((1 << dtext->hsub) - 1);
|
||||
dtext->y &= ~((1 << dtext->vsub) - 1);
|
||||
|
||||
av_dlog(ctx, "n:%d t:%f x:%d y:%d x+w:%d y+h:%d\n",
|
||||
(int)dtext->var_values[VAR_N], dtext->var_values[VAR_T],
|
||||
dtext->x, dtext->y, dtext->x+dtext->w, dtext->y+dtext->h);
|
||||
|
||||
avfilter_start_frame(inlink->dst->outputs[0], inpicref);
|
||||
}
|
||||
|
||||
static void end_frame(AVFilterLink *inlink)
|
||||
{
|
||||
AVFilterLink *outlink = inlink->dst->outputs[0];
|
||||
AVFilterBufferRef *picref = inlink->cur_buf;
|
||||
int err = dtext_prepare_text(inlink->dst,
|
||||
picref->video->w, picref->video->h);
|
||||
if (!err)
|
||||
DrawTextContext *dtext = inlink->dst->priv;
|
||||
|
||||
if (dtext->draw)
|
||||
draw_text(inlink->dst, picref, picref->video->w, picref->video->h);
|
||||
|
||||
dtext->var_values[VAR_N] += 1.0;
|
||||
|
||||
avfilter_draw_slice(outlink, 0, picref->video->h, 1);
|
||||
avfilter_end_frame(outlink);
|
||||
}
|
||||
@ -721,7 +838,7 @@ AVFilter avfilter_vf_drawtext = {
|
||||
.inputs = (AVFilterPad[]) {{ .name = "default",
|
||||
.type = AVMEDIA_TYPE_VIDEO,
|
||||
.get_video_buffer = avfilter_null_get_video_buffer,
|
||||
.start_frame = avfilter_null_start_frame,
|
||||
.start_frame = start_frame,
|
||||
.draw_slice = null_draw_slice,
|
||||
.end_frame = end_frame,
|
||||
.config_props = config_input,
|
||||
|
Loading…
Reference in New Issue
Block a user