diff mbox series

[FFmpeg-devel] avfilter/f_sendcmd: implement expr flag

Message ID 20200228222935.24998-1-onemda@gmail.com
State Accepted
Headers show
Series [FFmpeg-devel] avfilter/f_sendcmd: implement expr flag | expand

Checks

Context Check Description
andriy/ffmpeg-patchwork success Make fate finished

Commit Message

Paul B Mahol Feb. 28, 2020, 10:29 p.m. UTC
Make possible to parse expressions and store results as arguments
for target filters.

Signed-off-by: Paul B Mahol <onemda@gmail.com>
---
 doc/filters.texi        | 19 ++++++++++++++++
 libavfilter/f_sendcmd.c | 48 ++++++++++++++++++++++++++++++++++++++---
 2 files changed, 64 insertions(+), 3 deletions(-)

Comments

Paul B Mahol March 3, 2020, 3:42 p.m. UTC | #1
Will apply soon.

On 2/28/20, Paul B Mahol <onemda@gmail.com> wrote:
> Make possible to parse expressions and store results as arguments
> for target filters.
>
> Signed-off-by: Paul B Mahol <onemda@gmail.com>
> ---
>  doc/filters.texi        | 19 ++++++++++++++++
>  libavfilter/f_sendcmd.c | 48 ++++++++++++++++++++++++++++++++++++++---
>  2 files changed, 64 insertions(+), 3 deletions(-)
diff mbox series

Patch

diff --git a/doc/filters.texi b/doc/filters.texi
index 1453ecd8b1..30a7b77b4f 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -23659,6 +23659,25 @@  The command is sent when the current frame timestamp leaves the
 specified interval. In other words, the command is sent when the
 previous frame timestamp was in the given interval, and the
 current is not.
+
+@item expr
+The command @var{ARG} is interpreted as expression and result of
+expression is passed as @var{ARG}.
+
+The expression is evaluated through the eval API and can contain the following
+constants:
+
+@table @option
+@item PTS
+The presentation timestamp in input.
+
+@item N
+The count of the input frame for video or audio, starting from 0.
+
+@item T
+The time in seconds of the current frame.
+@end table
+
 @end table
 
 If @var{FLAGS} is not specified, a default value of @code{[enter]} is
diff --git a/libavfilter/f_sendcmd.c b/libavfilter/f_sendcmd.c
index b8740e8883..81b424a3f0 100644
--- a/libavfilter/f_sendcmd.c
+++ b/libavfilter/f_sendcmd.c
@@ -25,6 +25,7 @@ 
 
 #include "libavutil/avstring.h"
 #include "libavutil/bprint.h"
+#include "libavutil/eval.h"
 #include "libavutil/file.h"
 #include "libavutil/opt.h"
 #include "libavutil/parseutils.h"
@@ -35,10 +36,25 @@ 
 
 #define COMMAND_FLAG_ENTER 1
 #define COMMAND_FLAG_LEAVE 2
+#define COMMAND_FLAG_EXPR  4
+
+static const char *const var_names[] = {
+    "T",     /* frame time in seconds */
+    "PTS",   /* frame pts */
+    "N",     /* frame number */
+    NULL
+};
+
+enum var_name {
+    VAR_T,
+    VAR_PTS,
+    VAR_N,
+    VAR_VARS_NB
+};
 
 static inline char *make_command_flags_str(AVBPrint *pbuf, int flags)
 {
-    static const char * const flag_strings[] = { "enter", "leave" };
+    static const char * const flag_strings[] = { "enter", "leave", "expr" };
     int i, is_first = 1;
 
     av_bprint_init(pbuf, 0, AV_BPRINT_SIZE_AUTOMATIC);
@@ -129,6 +145,7 @@  static int parse_command(Command *cmd, int cmd_count, int interval_count,
 
             if      (!strncmp(*buf, "enter", strlen("enter"))) cmd->flags |= COMMAND_FLAG_ENTER;
             else if (!strncmp(*buf, "leave", strlen("leave"))) cmd->flags |= COMMAND_FLAG_LEAVE;
+            else if (!strncmp(*buf, "expr",  strlen("expr")))  cmd->flags |= COMMAND_FLAG_EXPR;
             else {
                 char flag_buf[64];
                 av_strlcpy(flag_buf, *buf, sizeof(flag_buf));
@@ -450,6 +467,9 @@  static av_cold void uninit(AVFilterContext *ctx)
     av_freep(&s->intervals);
 }
 
+#define TS2D(ts) ((ts) == AV_NOPTS_VALUE ? NAN : (double)(ts))
+#define TS2T(ts, tb) ((ts) == AV_NOPTS_VALUE ? NAN : (double)(ts)*av_q2d(tb))
+
 static int filter_frame(AVFilterLink *inlink, AVFrame *ref)
 {
     AVFilterContext *ctx = inlink->dst;
@@ -476,6 +496,8 @@  static int filter_frame(AVFilterLink *inlink, AVFrame *ref)
             flags += COMMAND_FLAG_LEAVE;
             interval->enabled = 0;
         }
+        if (interval->enabled)
+            flags += COMMAND_FLAG_EXPR;
 
         if (flags) {
             AVBPrint pbuf;
@@ -487,19 +509,39 @@  static int filter_frame(AVFilterLink *inlink, AVFrame *ref)
 
             for (j = 0; flags && j < interval->nb_commands; j++) {
                 Command *cmd = &interval->commands[j];
+                char *cmd_arg = cmd->arg;
                 char buf[1024];
 
                 if (cmd->flags & flags) {
+                    if ((cmd->flags & COMMAND_FLAG_EXPR)) {
+                        double var_values[VAR_VARS_NB], res;
+
+                        var_values[VAR_N]   = inlink->frame_count_in;
+                        var_values[VAR_PTS] = TS2D(ref->pts);
+                        var_values[VAR_T]   = TS2T(ref->pts, inlink->time_base);
+
+                        if ((ret = av_expr_parse_and_eval(&res, cmd->arg, var_names, var_values,
+                                                          NULL, NULL, NULL, NULL, NULL, 0, NULL)) < 0) {
+                            av_log(ctx, AV_LOG_ERROR, "Invalid expression '%s' for command argument.\n", cmd->arg);
+                            return AVERROR(EINVAL);
+                        }
+
+                        cmd_arg = av_asprintf("%g", res);
+                        if (!cmd_arg)
+                            return AVERROR(ENOMEM);
+                    }
                     av_log(ctx, AV_LOG_VERBOSE,
                            "Processing command #%d target:%s command:%s arg:%s\n",
-                           cmd->index, cmd->target, cmd->command, cmd->arg);
+                           cmd->index, cmd->target, cmd->command, cmd_arg);
                     ret = avfilter_graph_send_command(inlink->graph,
-                                                      cmd->target, cmd->command, cmd->arg,
+                                                      cmd->target, cmd->command, cmd_arg,
                                                       buf, sizeof(buf),
                                                       AVFILTER_CMD_FLAG_ONE);
                     av_log(ctx, AV_LOG_VERBOSE,
                            "Command reply for command #%d: ret:%s res:%s\n",
                            cmd->index, av_err2str(ret), buf);
+                    if ((cmd->flags & COMMAND_FLAG_EXPR))
+                        av_freep(&cmd_arg);
                 }
             }
         }