Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
 help / color / mirror / Atom feed
* [FFmpeg-devel] [RFC 1/1] avfilter/f_sendcmd: add format expressions
@ 2025-06-11 11:27 Ignacy Gawędzki
  0 siblings, 0 replies; only message in thread
From: Ignacy Gawędzki @ 2025-06-11 11:27 UTC (permalink / raw)
  To: ffmpeg-devel

Add support for a "format" flag (exclusive with "expr") which indicates
that the argument is a format string followed by expressions to be
evaluated and formatted and the result sent as a string.  Only simple
'f' format is supported as expressions are evaluated to double.

Signed-off-by: Ignacy Gawędzki <bugs@qult.net>
---
 libavfilter/f_sendcmd.c | 161 +++++++++++++++++++++++++++++++++++-----
 1 file changed, 143 insertions(+), 18 deletions(-)

diff --git a/libavfilter/f_sendcmd.c b/libavfilter/f_sendcmd.c
index 9201fb5381..fadf1a4956 100644
--- a/libavfilter/f_sendcmd.c
+++ b/libavfilter/f_sendcmd.c
@@ -37,9 +37,10 @@
 #include "audio.h"
 #include "video.h"
 
-#define COMMAND_FLAG_ENTER 1
-#define COMMAND_FLAG_LEAVE 2
-#define COMMAND_FLAG_EXPR  4
+#define COMMAND_FLAG_ENTER  1
+#define COMMAND_FLAG_LEAVE  2
+#define COMMAND_FLAG_EXPR   4
+#define COMMAND_FLAG_FORMAT 8
 
 static const char *const var_names[] = {
     "N",     /* frame number */
@@ -67,7 +68,8 @@ enum var_name {
 
 static inline char *make_command_flags_str(AVBPrint *pbuf, int flags)
 {
-    static const char * const flag_strings[] = { "enter", "leave", "expr" };
+    static const char * const flag_strings[] = { "enter", "leave",
+                                                 "expr", "format" };
     int i, is_first = 1;
 
     av_bprint_init(pbuf, 0, AV_BPRINT_SIZE_AUTOMATIC);
@@ -159,6 +161,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 if (!strncmp(*buf, "format", strlen("format"))) cmd->flags |= COMMAND_FLAG_FORMAT;
             else {
                 char flag_buf[64];
                 av_strlcpy(flag_buf, *buf, sizeof(flag_buf));
@@ -191,6 +194,13 @@ static int parse_command(Command *cmd, int cmd_count, int interval_count,
         cmd->flags = COMMAND_FLAG_ENTER;
     }
 
+    if ((cmd->flags & COMMAND_FLAG_EXPR)
+        && (cmd->flags & COMMAND_FLAG_FORMAT)) {
+        av_log(log_ctx, AV_LOG_ERROR,
+               "Invalid flags: expr and format are mutually exclusive\n");
+        return AVERROR(EINVAL);
+    }
+
     *buf += strspn(*buf, SPACES);
     cmd->target = av_get_token(buf, COMMAND_DELIMS);
     if (!cmd->target || !cmd->target[0]) {
@@ -480,6 +490,65 @@ static av_cold void uninit(AVFilterContext *ctx)
     av_freep(&s->intervals);
 }
 
+static int format_expr(char **dest, char *format,
+                       int dargc, const double dargv[])
+{
+    char *p = format, *begin = format;
+    size_t size = 64, len;
+    int n = 0;
+    *dest = av_realloc_f(*dest, size, sizeof (char));
+    **dest = 0;
+    if (!*dest)
+        return AVERROR(ENOMEM);
+    while (*p) {
+        if (*p == '%') {
+            if (!p[1])
+                return AVERROR(EINVAL);
+            if (p[1] == '%') {
+                p += 2;
+                continue;
+            }
+            len = 1 + strspn(p + 1, "0123456789");
+            len += strspn(p + len, ".");
+            len += strspn(p + len, "0123456789");
+            if (!strspn(p + len, "fF"))
+                return AVERROR(EINVAL);
+            if (n) {
+                *p = 0;
+                len = strlen(*dest);
+                if (av_strlcatf(*dest, size, begin, dargv[n - 1]) > size - 1) {
+                    if (size > SIZE_MAX / 2)
+                        return AVERROR(ENOMEM);
+                    size *= 2;
+                    *dest = av_realloc_f(*dest, size, sizeof (char));
+                    if (!*dest)
+                        return AVERROR(ENOMEM);
+                    (*dest)[len] = 0;
+                    av_strlcatf(*dest, size, begin, dargv[n - 1]);
+                }
+                *p = '%';
+                begin = p;
+            }
+            ++n;
+        }
+        ++p;
+    }
+    if (begin < p) {
+        len = strlen(*dest);
+        if (av_strlcatf(*dest, size, begin, dargv[n - 1]) > size - 1) {
+            if (size > SIZE_MAX / 2)
+                return AVERROR(ENOMEM);
+            size *= 2;
+            *dest = av_realloc_f(*dest, size, sizeof (char));
+            if (!*dest)
+                return AVERROR(ENOMEM);
+            (*dest)[len] = 0;
+            av_strlcatf(*dest, size, begin, dargv[n - 1]);
+        }
+    }
+    return 0;
+}
+
 static int filter_frame(AVFilterLink *inlink, AVFrame *ref)
 {
     FilterLink *inl = ff_filter_link(inlink);
@@ -508,7 +577,7 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *ref)
             interval->enabled = 0;
         }
         if (interval->enabled)
-            flags += COMMAND_FLAG_EXPR;
+            flags += COMMAND_FLAG_EXPR | COMMAND_FLAG_FORMAT;
 
         if (flags) {
             AVBPrint pbuf;
@@ -524,7 +593,7 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *ref)
                 char buf[1024];
 
                 if (cmd->flags & flags) {
-                    if (cmd->flags & COMMAND_FLAG_EXPR) {
+                    if (cmd->flags & (COMMAND_FLAG_EXPR | COMMAND_FLAG_FORMAT)) {
                         double var_values[VAR_VARS_NB], res;
                         double start = TS2T(interval->start_ts, AV_TIME_BASE_Q);
                         double end = TS2T(interval->end_ts, AV_TIME_BASE_Q);
@@ -539,17 +608,73 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *ref)
                         var_values[VAR_W]   = ref->width;
                         var_values[VAR_H]   = ref->height;
 
-                        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);
-                            av_frame_free(&ref);
-                            return AVERROR(EINVAL);
-                        }
-
-                        cmd_arg = av_asprintf("%g", res);
-                        if (!cmd_arg) {
-                            av_frame_free(&ref);
-                            return AVERROR(ENOMEM);
+                        if (cmd->flags & COMMAND_FLAG_FORMAT) {
+                            const char  *args   = cmd->arg;
+                            char        *format = av_get_token(&args, ",");
+                            double      *dargv  = NULL;
+                            int         dargc   = 0;
+                            ret = 0;
+                            do {
+                                args += strspn(args, SPACES);
+                                if (*args == ',')
+                                    ++args;
+                                char    *arg = av_get_token(&args, ",");
+                                if (!arg) {
+                                    ret = AVERROR(ENOMEM);
+                                    goto end_format;
+                                }
+                                if (arg[0] == 0) {
+                                    av_free(arg);
+                                    break;
+                                }
+                                if ((ret = av_expr_parse_and_eval(&res, 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", arg);
+                                    av_free(arg);
+                                    ret = AVERROR(EINVAL);
+                                    goto end_format;
+                                }
+                                av_free(arg);
+                                dargv = av_realloc(dargv, ++dargc * sizeof (double));
+                                if (!dargv) {
+                                    ret = AVERROR(ENOMEM);
+                                    goto end_format;
+                                }
+                                dargv[dargc - 1] = res;
+                            } while (1);
+                            if (dargc == 0) {
+                                ret = AVERROR(EINVAL);
+                                av_log(ctx, AV_LOG_ERROR, "Invalid expression '%s' with no arguments for command.\n", cmd->arg);
+                                goto end_format;
+                            }
+                            cmd_arg = NULL;
+                            if ((ret = format_expr(&cmd_arg, format, dargc, dargv))) {
+                                av_free(cmd_arg);
+                                if (AVUNERROR(ret) == EINVAL) {
+                                    av_log(ctx, AV_LOG_ERROR, "Invalid format expression '%s' for command argument.\n", cmd->arg);
+                                }
+                            }
+                        end_format:
+                            av_free(dargv);
+                            av_free(format);
+
+                            if (ret) {
+                                av_frame_free(&ref);
+                                return ret;
+                            }
+                        } else {
+                            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);
+                                av_frame_free(&ref);
+                                return AVERROR(EINVAL);
+                            }
+
+                            cmd_arg = av_asprintf("%g", res);
+                            if (!cmd_arg) {
+                                av_frame_free(&ref);
+                                return AVERROR(ENOMEM);
+                            }
                         }
                     }
                     av_log(ctx, AV_LOG_VERBOSE,
@@ -562,7 +687,7 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *ref)
                     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)
+                    if (cmd->flags & (COMMAND_FLAG_EXPR | COMMAND_FLAG_FORMAT))
                         av_freep(&cmd_arg);
                 }
             }
-- 
2.48.1
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2025-06-11 11:53 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-06-11 11:27 [FFmpeg-devel] [RFC 1/1] avfilter/f_sendcmd: add format expressions Ignacy Gawędzki

Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

This inbox may be cloned and mirrored by anyone:

	git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
		ffmpegdev@gitmailbox.com
	public-inbox-index ffmpegdev

Example config snippet for mirrors.


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git