A description of the accepted options follows.
@table @option
-@item x, y
+@item x
+@item y
Set the expression for the x and y coordinates of the overlayed video
-on the main video. Default value is 0.
-
-The @var{x} and @var{y} expressions can contain the following
-parameters:
-@table @option
-@item main_w, main_h
-main input width and height
+on the main video. Default value is "0" for both expressions. In case
+the expression is invalid, it is set to a huge value (meaning that the
+overlay will not be displayed within the output visible area).
-@item W, H
-same as @var{main_w} and @var{main_h}
+@item eval
+Set when the expressions for @option{x} and @option{y} are evaluated.
-@item overlay_w, overlay_h
-overlay input width and height
+It accepts the following values:
+@table @samp
+@item init
+only evaluate expressions once during the filter initialization
-@item w, h
-same as @var{overlay_w} and @var{overlay_h}
+@item frame
+evaluate expressions for each incoming frame
@end table
+Default value is @samp{frame}.
+
+@item shortest
+If set to 1, force the output to terminate when the shortest input
+terminates. Default value is 0.
+
@item format
Set the format for the output video.
If set to 1, force the filter to accept inputs in the RGB
color space. Default value is 0. This option is deprecated, use
@option{format} instead.
+@end table
-@item shortest
-If set to 1, force the output to terminate when the shortest input
-terminates. Default value is 0.
+The @option{x}, and @option{y} expressions can contain the following
+parameters.
+
+@table @option
+@item main_w, W
+@item main_h, H
+main input width and height
+
+@item overlay_w, w
+@item overlay_h, h
+overlay input width and height
+
+@item x
+@item y
+the computed values for @var{x} and @var{y}. They are evaluated for
+each new frame.
+
+@item hsub
+@item vsub
+horizontal and vertical chroma subsample values of the output
+format. For example for the pixel format "yuv422p" @var{hsub} is 2 and
+@var{vsub} is 1.
+
+@item n
+the number of input frame, starting from 0
+
+@item pos
+the position in the file of the input frame, NAN if unknown
+
+@item t
+timestamp expressed in seconds, NAN if the input timestamp is unknown
@end table
+Note that the @var{n}, @var{pos}, @var{t} variables are available only
+when evaluation is done @emph{per frame}, and will evaluate to NAN
+when @option{eval} is set to @samp{init}.
+
Be aware that frames are taken from each input video in timestamp
order, hence, if their initial timestamps differ, it is a a good idea
to pass the two inputs through a @var{setpts=PTS-STARTPTS} filter to
@end example
@item
+Make a sliding overlay appearing from the left to the right top part of the
+screen starting since time 2:
+@example
+overlay=x='if(gte(t,2), -w+(t-2)*20, NAN)':y=0
+@end example
+
+@item
Compose output by putting two input videos side to side:
@example
ffmpeg -i left.avi -i right.avi -filter_complex "
"main_h", "H", ///< height of the main video
"overlay_w", "w", ///< width of the overlay video
"overlay_h", "h", ///< height of the overlay video
+ "hsub",
+ "vsub",
+ "x",
+ "y",
+ "n", ///< number of frame
+ "pos", ///< position in the file
+ "t", ///< timestamp expressed in seconds
NULL
};
VAR_MAIN_H, VAR_MH,
VAR_OVERLAY_W, VAR_OW,
VAR_OVERLAY_H, VAR_OH,
+ VAR_HSUB,
+ VAR_VSUB,
+ VAR_X,
+ VAR_Y,
+ VAR_N,
+ VAR_POS,
+ VAR_T,
VAR_VARS_NB
};
uint8_t overlay_rgba_map[4];
uint8_t overlay_has_alpha;
enum OverlayFormat { OVERLAY_FORMAT_YUV420, OVERLAY_FORMAT_YUV444, OVERLAY_FORMAT_RGB, OVERLAY_FORMAT_NB} format;
+ enum EvalMode { EVAL_MODE_INIT, EVAL_MODE_FRAME, EVAL_MODE_NB } eval_mode;
AVFrame *overpicref;
struct FFBufQueue queue_main;
int hsub, vsub; ///< chroma subsampling values
int shortest; ///< terminate stream when the shortest input terminates
+ double var_values[VAR_VARS_NB];
char *x_expr, *y_expr;
+ AVExpr *x_pexpr, *y_pexpr;
} OverlayContext;
#define OFFSET(x) offsetof(OverlayContext, x)
static const AVOption overlay_options[] = {
{ "x", "set the x expression", OFFSET(x_expr), AV_OPT_TYPE_STRING, {.str = "0"}, CHAR_MIN, CHAR_MAX, FLAGS },
{ "y", "set the y expression", OFFSET(y_expr), AV_OPT_TYPE_STRING, {.str = "0"}, CHAR_MIN, CHAR_MAX, FLAGS },
+
+ { "eval", "specify when to evaluate expressions", OFFSET(eval_mode), AV_OPT_TYPE_INT, {.i64 = EVAL_MODE_FRAME}, 0, EVAL_MODE_NB-1, FLAGS, "eval" },
+ { "init", "eval expressions once during initialization", 0, AV_OPT_TYPE_CONST, {.i64=EVAL_MODE_INIT}, .flags = FLAGS, .unit = "eval" },
+ { "frame", "eval expressions per-frame", 0, AV_OPT_TYPE_CONST, {.i64=EVAL_MODE_FRAME}, .flags = FLAGS, .unit = "eval" },
+
{ "rgb", "force packed RGB in input and output (deprecated)", OFFSET(allow_packed_rgb), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, FLAGS },
{ "shortest", "force termination when the shortest input terminates", OFFSET(shortest), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, FLAGS },
av_frame_free(&over->overpicref);
ff_bufqueue_discard_all(&over->queue_main);
ff_bufqueue_discard_all(&over->queue_over);
+ av_expr_free(over->x_pexpr); over->x_pexpr = NULL;
+ av_expr_free(over->y_pexpr); over->y_pexpr = NULL;
}
static int query_formats(AVFilterContext *ctx)
return 0;
}
+static inline int normalize_xy(double d, int chroma_sub)
+{
+ if (isnan(d))
+ return INT_MAX;
+ return (int)d & ~((1 << chroma_sub) - 1);
+}
+
+static void eval_expr(AVFilterContext *ctx)
+{
+ OverlayContext *over = ctx->priv;
+
+ over->var_values[VAR_X] = av_expr_eval(over->x_pexpr, over->var_values, NULL);
+ over->var_values[VAR_Y] = av_expr_eval(over->y_pexpr, over->var_values, NULL);
+ over->var_values[VAR_X] = av_expr_eval(over->x_pexpr, over->var_values, NULL);
+ over->x = normalize_xy(over->var_values[VAR_X], over->hsub);
+ over->y = normalize_xy(over->var_values[VAR_Y], over->vsub);
+}
+
static int config_input_overlay(AVFilterLink *inlink)
{
AVFilterContext *ctx = inlink->dst;
OverlayContext *over = inlink->dst->priv;
char *expr;
- double var_values[VAR_VARS_NB], res;
int ret;
const AVPixFmtDescriptor *pix_desc = av_pix_fmt_desc_get(inlink->format);
/* Finish the configuration by evaluating the expressions
now when both inputs are configured. */
- var_values[VAR_MAIN_W ] = var_values[VAR_MW] = ctx->inputs[MAIN ]->w;
- var_values[VAR_MAIN_H ] = var_values[VAR_MH] = ctx->inputs[MAIN ]->h;
- var_values[VAR_OVERLAY_W] = var_values[VAR_OW] = ctx->inputs[OVERLAY]->w;
- var_values[VAR_OVERLAY_H] = var_values[VAR_OH] = ctx->inputs[OVERLAY]->h;
-
- if ((ret = av_expr_parse_and_eval(&res, (expr = over->x_expr), var_names, var_values,
- NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0)
- goto fail;
- over->x = res;
- if ((ret = av_expr_parse_and_eval(&res, (expr = over->y_expr), var_names, var_values,
- NULL, NULL, NULL, NULL, NULL, 0, ctx)))
+ over->var_values[VAR_MAIN_W ] = over->var_values[VAR_MW] = ctx->inputs[MAIN ]->w;
+ over->var_values[VAR_MAIN_H ] = over->var_values[VAR_MH] = ctx->inputs[MAIN ]->h;
+ over->var_values[VAR_OVERLAY_W] = over->var_values[VAR_OW] = ctx->inputs[OVERLAY]->w;
+ over->var_values[VAR_OVERLAY_H] = over->var_values[VAR_OH] = ctx->inputs[OVERLAY]->h;
+ over->var_values[VAR_HSUB] = 1<<pix_desc->log2_chroma_w;
+ over->var_values[VAR_VSUB] = 1<<pix_desc->log2_chroma_h;
+ over->var_values[VAR_X] = NAN;
+ over->var_values[VAR_Y] = NAN;
+ over->var_values[VAR_N] = 0;
+ over->var_values[VAR_T] = NAN;
+ over->var_values[VAR_POS] = NAN;
+
+ expr = over->x_expr;
+ if ((ret = av_expr_parse(&over->x_pexpr, expr, var_names,
+ NULL, NULL, NULL, NULL, 0, ctx)) < 0)
goto fail;
- over->y = res;
- /* x may depend on y */
- if ((ret = av_expr_parse_and_eval(&res, (expr = over->x_expr), var_names, var_values,
- NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0)
+ expr = over->y_expr;
+ if ((ret = av_expr_parse(&over->y_pexpr, expr, var_names,
+ NULL, NULL, NULL, NULL, 0, ctx)) < 0)
goto fail;
- over->x = res;
over->overlay_is_packed_rgb =
ff_fill_rgba_map(over->overlay_rgba_map, inlink->format) >= 0;
over->overlay_has_alpha = ff_fmt_is_in(inlink->format, alpha_pix_fmts);
+ if (over->eval_mode == EVAL_MODE_INIT) {
+ eval_expr(ctx);
+ av_log(ctx, AV_LOG_VERBOSE, "x:%f xi:%d y:%f yi:%d\n",
+ over->var_values[VAR_X], over->x,
+ over->var_values[VAR_Y], over->y);
+ }
+
av_log(ctx, AV_LOG_VERBOSE,
- "main w:%d h:%d fmt:%s overlay x:%d y:%d w:%d h:%d fmt:%s\n",
+ "main w:%d h:%d fmt:%s overlay w:%d h:%d fmt:%s\n",
ctx->inputs[MAIN]->w, ctx->inputs[MAIN]->h,
av_get_pix_fmt_name(ctx->inputs[MAIN]->format),
- over->x, over->y,
ctx->inputs[OVERLAY]->w, ctx->inputs[OVERLAY]->h,
av_get_pix_fmt_name(ctx->inputs[OVERLAY]->format));
-
- if (over->x < 0 || over->y < 0 ||
- over->x + var_values[VAR_OVERLAY_W] > var_values[VAR_MAIN_W] ||
- over->y + var_values[VAR_OVERLAY_H] > var_values[VAR_MAIN_H]) {
- av_log(ctx, AV_LOG_WARNING,
- "Overlay area with coordinates x1:%d y1:%d x2:%d y2:%d "
- "is not completely contained within the output with size %dx%d\n",
- over->x, over->y,
- (int)(over->x + var_values[VAR_OVERLAY_W]),
- (int)(over->y + var_values[VAR_OVERLAY_H]),
- (int)var_values[VAR_MAIN_W], (int)var_values[VAR_MAIN_H]);
- }
return 0;
fail:
av_log(NULL, AV_LOG_ERROR,
- "Error when evaluating the expression '%s'\n", expr);
+ "Error when parsing the expression '%s'\n", expr);
return ret;
}
static int try_filter_frame(AVFilterContext *ctx, AVFrame *mainpic)
{
OverlayContext *over = ctx->priv;
+ AVFilterLink *inlink = ctx->inputs[0];
AVFrame *next_overpic;
int ret;
av_ts2str(over->overpicref->pts), av_ts2timestr(over->overpicref->pts, &ctx->inputs[OVERLAY]->time_base));
av_dlog(ctx, "\n");
- if (over->overpicref)
+ if (over->overpicref) {
+ if (over->eval_mode == EVAL_MODE_FRAME) {
+ int64_t pos = av_frame_get_pkt_pos(mainpic);
+
+ over->var_values[VAR_T] = mainpic->pts == AV_NOPTS_VALUE ?
+ NAN : mainpic->pts * av_q2d(inlink->time_base);
+ over->var_values[VAR_POS] = pos == -1 ? NAN : pos;
+
+ eval_expr(ctx);
+ av_log(ctx, AV_LOG_DEBUG, "n:%f t:%f pos:%f x:%f xi:%d y:%f yi:%d\n",
+ over->var_values[VAR_N], over->var_values[VAR_T], over->var_values[VAR_POS],
+ over->var_values[VAR_X], over->x,
+ over->var_values[VAR_Y], over->y);
+ }
blend_image(ctx, mainpic, over->overpicref, over->x, over->y);
+
+ over->var_values[VAR_N] += 1.0;
+ }
ret = ff_filter_frame(ctx->outputs[0], mainpic);
av_assert1(ret != AVERROR(EAGAIN));
over->frame_requested = 0;