* [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