Skip to content

Expression variables & constants

Mailing-list subject (draft): [PATCH RFC] libavfilter: expose expression variable metadata via AVFilter struct

Many filters take expression parameters — drawtext’s x/y, geq, overlay’s position — that reference variables like w, h, t, n, text_w, and named constants (the AV_OPT_TYPE_CONST values grouped under an option’s unit). These are documented and used in filter strings worldwide, but they are not programmatically discoverable: the variable names live in internal var_names[] arrays inside each filter’s .c.

For this binding it’s the blocker for a specific feature we want: letting an expression parameter be a PHP closure evaluated per frame. To safely parse and evaluate an expression in the host language — and to inject the right values — we have to know which variables and constants that filter’s expression accepts. Today the only options are to hard-code FFmpeg’s internal arrays and keep them in sync by hand (fragile, and drifts every release) or to fall back to opaque string-only parameters. Every GUI, video editor, and language binding that builds graphs programmatically hits the same wall.

These variables and constants are already stable, user-facing API — they can’t change without breaking existing filter strings. The stability contract exists; this proposal just extends programmatic access to match what string users already have. Nothing here is a behaviour change.

Add an expression_vars field to AVFilter, mirroring how AVOption exposes options.

typedef struct AVFilterExprVar {
const char *name; /* e.g. "text_w" */
const char *help; /* e.g. "Width of rendered text" */
} AVFilterExprVar;
typedef struct AVFilter {
// ... existing fields ...
/** NULL-terminated array of expression variables this filter accepts,
* or NULL if it does no expression evaluation. */
const AVFilterExprVar *expression_vars;
} AVFilter;

Each filter keeps its existing internal var_names[] (used by av_expr_parse() et al.) unchanged, and adds an adjacent public, documented array:

static const AVFilterExprVar drawtext_expr_vars[] = {
{ "w", "Video width" },
{ "h", "Video height" },
{ "t", "Timestamp in seconds" },
{ "n", "Frame number (from 0)" },
{ "text_w", "Width of rendered text" },
// …
{ NULL }
};

Consumers then discover them from any AVFilter *:

const AVFilter *f = avfilter_get_by_name("drawtext");
for (const AVFilterExprVar *v = f->expression_vars; v && v->name; v++)
printf("%s%s\n", v->name, v->help);

The same gap applies to an option’s named constants (AV_OPT_TYPE_CONST grouped by unit). Those are already reachable by walking the AVOption table, so a binding can generate enums for them — but expression-referenced constants (and which unit an expression may use) want the same discoverability as the variables. The proposal can carry constant metadata in the same additive spirit if maintainers prefer one mechanism for both.

  • Precedent — mirrors AVOption; a pattern developers already know.
  • Discoverable & self-documenting — help text (already in the .texi docs) becomes machine-readable; no per-filter symbol knowledge needed.
  • Purely additive — internal functions keep using var_names[] unchanged; no signature changes, no behaviour change. Minor-release compatible. The struct can later gain type hints/flags without breaking users.

Yes, a filter would carry two adjacent arrays with overlapping content — intentional: they sit together so drift is obvious in review, and the existing array feeds several internal functions that a major release could later consolidate. If duplication is a concern, an X-macro generates both from one definition:

#define DRAWTEXT_EXPR_VARS \
X("w", "Video width") X("h", "Video height") X("t", "Timestamp in seconds")
#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

If extending AVFilter is unwelcome, exporting the existing symbols (const char *const ff_drawtext_var_names[]) or a per-filter accessor also solve the immediate problem — but without discoverability or metadata. If we’re committing to API stability for these variables, the struct approach has the better long-term value.

A search of the archives found no prior proposal for this specific feature; the closest is the 2013 Scripting RFC on binding lavfi to other languages. This addresses one narrow, concrete part of that.

RFC drafted; ready to offer a patch series (the AVFilterExprVar struct + field, drawtext as a proof of concept, then other expression-enabled filters and docs). Seeking feedback on the shape before submitting.

An Artisan Build project.

Built on FFmpeg — an independent binding, not affiliated with or endorsed by the FFmpeg project. Support FFmpeg.