Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
 help / color / mirror / Atom feed
From: Nicolas George via ffmpeg-devel <ffmpeg-devel@ffmpeg.org>
To: FFmpeg development discussions and patches <ffmpeg-devel@ffmpeg.org>
Cc: Len Woodward <len@artisan.build>, Nicolas George <george@nsup.org>
Subject: [FFmpeg-devel] Re: [PATCH RFC] libavfilter: expose expression variable metadata via AVFilter struct
Date: Sat, 10 Jan 2026 21:03:39 +0100
Message-ID: <aWKwm1H9j8AqdXtz@phare.normalesup.org> (raw)
In-Reply-To: <CAEwAiAGMBmDhGvnh5wK7epp2cLPP_XBAhoPCCXAutAN8rid60A@mail.gmail.com>

Len Woodward via ffmpeg-devel (HE12026-01-09):
> ## Prior Discussion
> 
> A search of the mailing list archives found no prior proposals for this
> specific feature. The closest related discussion is the [2013 Scripting
> RFC](https://ffmpeg.org/pipermail/ffmpeg-devel/2013-November/151069.html),
> which raised the broader challenge of binding the lavfi API to other
> languages. This proposal addresses one narrow aspect of that: making filter
> expression variables discoverable.
> 
> ## Problem
> 
> Filters like `drawtext`, `geq`, and `overlay` accept expressions
> referencing variables such as `w`, `h`, `t`, `n`, etc. These variables are
> documented in FFmpeg's official documentation and used in countless filter
> strings worldwide—but they're not programmatically discoverable.
> 
> Applications building filter graphs programmatically (GUIs, language
> bindings, video editors) currently must duplicate FFmpeg's internal
> `var_names` arrays and manually keep them in sync. This is fragile and
> creates maintenance burden across the ecosystem.
> 
> ## Why These Are Already Stable API
> 
> These variables are documented, user-facing, and cannot be changed without
> breaking existing filter strings. The stability contract already
> exists—this proposal simply extends programmatic access to match what
> string-based users already have.
> 
> ## Proposed Solution
> 
> Add an `expression_vars` field to the `AVFilter` struct, following the
> pattern established by `AVOption` for filter options.

Hi. I think it is an excellent idea. See comments below.

> 
> **Public API addition (avfilter.h):**
> 
> ```c
> typedef struct AVFilterExprVar {
>     const char *name;    /* Variable name (e.g., "text_w") */
>     const char *help;    /* Description (e.g., "Width of rendered text") */
> } AVFilterExprVar;

Filters are the main users of expressions and variables, but they are
not the only ones. Therefore, I think this API should exist in
libavutil, not libavfilter.

> 
> typedef struct AVFilter {
>     // ... existing fields ...
> 
>     /**
>      * NULL-terminated array of expression variables accepted by this
> filter,
>      * or NULL if the filter does not use expression evaluation.
>      */
>     const AVFilterExprVar *expression_vars;
> } AVFilter;
> ```
> 
> **Filter implementation (e.g., vf_drawtext.c):**
> 
> ```c
> // Existing array - unchanged, used internally by av_expr_parse(),
> // ff_print_eval_expr(), and other internal functions
> static const char *const var_names[] = {
>     "dar", "h", "w", "n", "t", "text_h", "text_w",
>     // ...
>     NULL
> };
> 
> // New array - public metadata with documentation
> static const AVFilterExprVar drawtext_expr_vars[] = {
>     { "dar",    "Display aspect ratio" },
>     { "h",      "Video height" },
>     { "w",      "Video width" },
>     { "n",      "Frame number (starting at 0)" },
>     { "t",      "Timestamp in seconds" },
>     { "text_h", "Height of rendered text" },
>     { "text_w", "Width of rendered text" },
>     // ...
>     { NULL }
> };
> 
> const AVFilter ff_vf_drawtext = {
>     .name            = "drawtext",
>     // ... existing fields ...
>     .expression_vars = drawtext_expr_vars,
> };
> ```
> 
> **Application usage:**
> 
> ```c
> const AVFilter *f = avfilter_get_by_name("drawtext");
> if (f->expression_vars) {
>     for (const AVFilterExprVar *v = f->expression_vars; v->name; v++)
>         printf("  %s - %s\n", v->name, v->help);
> }
> ```
> 
> ## Why This Approach
> 
> 1. **Follows established precedent**: Mirrors how `AVOption` exposes filter
> options. Developers already understand this pattern.
> 2. **Discoverable**: Given any `AVFilter*`, check if it has expression
> variables. No need for per-filter symbol knowledge.
> 3. **Self-documenting**: Help text (already written in `.texi` files)
> becomes machine-readable.
> 4. **Purely additive**: Existing internal functions (`av_expr_parse()`,
> `ff_print_eval_expr()`, `ff_print_formatted_eval_expr()`, etc.) continue
> using `var_names` unchanged. No signature changes, no risk to existing code
> paths.
> 5. **Minor release compatible**: This can land in a minor release since it
> adds functionality without modifying existing behavior.

> 6. **Extensible**: The struct can later gain fields (type hints, flags)
> without breaking users.

You are mistaken on this point: adding fields to the structure would be
compatible at the API level but at the ABI level it would invalidate the
“v++” in the loop in “application usage” above.

That can be fixed by using a function to access each element, something
like “av_expr_get_variable(exprctx, index)”. Since we are in the
proximity of parsing and evaluating an expression, the overhead is not a
concern.

> 
> ## On Array Duplication
> 
> Yes, this means filters have two arrays with overlapping content. This is
> intentional:
> 
> - Both arrays live in the same file, adjacent to each other—drift is easy
> to spot during review
> - The existing `var_names` array is used by multiple internal functions;
> changing all of them is a larger undertaking better suited for a major
> release
> - The cost is a few hundred bytes of static storage per filter
> 
> If source-level duplication is a concern, an X-macro can generate both
> arrays from a single definition:
> 
> ```c
> #define DRAWTEXT_EXPR_VARS \
>     X("dar",    "Display aspect ratio") \
>     X("h",      "Video height") \
>     X("w",      "Video width")
>     // ...
> 
> #define X(name, help) name,
> static const char *const var_names[] = { DRAWTEXT_EXPR_VARS NULL };
> #undef X
> 
> #define X(name, help) { name, help },
> static const AVFilterExprVar drawtext_expr_vars[] = { DRAWTEXT_EXPR_VARS {
> NULL } };
> #undef X
> ```
> 
> This eliminates any possibility of drift between arrays. Either approach
> works—maintainer preference.
> 

> A future major release could consolidate to a single array by updating
> internal functions to accept `AVFilterExprVar*`, but that's a separate,
> larger effort. This proposal delivers value now without that scope.

I agree, the AVExpr API needs to be cleaned up and it would take care of
the duplication issue.

> 
> ## Simpler Alternatives
> 
> If extending `AVFilter` is undesirable, these alternatives also solve the
> immediate problem:
> 
> **Export existing symbols:**
> ```diff
> -static const char *const var_names[] = {
> +const char *const ff_drawtext_var_names[] = {
> ```
> 
> **Per-filter accessor:**
> ```c
> const char *const *avfilter_drawtext_get_var_names(void);
> ```
> 
> Both work, but lack discoverability and metadata. If committing to API
> stability for expression variables, the `AVFilter` struct approach provides
> better long-term value.

I think the add to AVFilter is preferable. Or maybe AVFilterContext,
depending on the details of how we want to design it.

> ## Scope
> 
> I'm prepared to submit patches for:
> 
> 1. `AVFilterExprVar` struct and `AVFilter` field addition
> 2. Migration of `drawtext` as proof of concept
> 3. Migration of other expression-enabled filters if desired
> 4. Documentation updates
> 
> Happy to start with `drawtext` only, or cover all filters—whichever
> maintainers prefer.

Thanks.

Regards,

-- 
  Nicolas George
_______________________________________________
ffmpeg-devel mailing list -- ffmpeg-devel@ffmpeg.org
To unsubscribe send an email to ffmpeg-devel-leave@ffmpeg.org

      reply	other threads:[~2026-01-10 20:04 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-01-09 19:54 [FFmpeg-devel] " Len Woodward via ffmpeg-devel
2026-01-10 20:03 ` Nicolas George via ffmpeg-devel [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=aWKwm1H9j8AqdXtz@phare.normalesup.org \
    --to=ffmpeg-devel@ffmpeg.org \
    --cc=george@nsup.org \
    --cc=len@artisan.build \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

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