I’ve developed a set of patches that enable FFmpeg to be optionally given a 
single
JSON containing all commands and parameters.
This…

  *   makes building FFMPEG commands programmatically easier and more reliable
  *   avoids difficulties that come with very long FFMPEG command-line strings
  *   avoids the requirement to escape slashes and other common characters

(particularly useful when passing urls or presigned urls)

  *   makes it easier to save and modify ffmpeg “recipes” (taking a template,

modifying one or more parameters, saving as a new ffmpeg command)
The only slight caveat is that filter complexes must still be defined as a 
string.

I believe that this design is independent of any particular command-line 
parameters, and so it shouldn’t break when
commands or parameters are added or changed (so, it shouldn’t be a maintenance 
headache).

There is also a json_cmd_gen command that converts a FFMPEG command-line string 
into the equivalent JSON,
and a json_cmd_print command that converts a FFMPEG JSON into the equivalent 
command-line string.

Add -json_cmd option that reads ffmpeg parameters from a structured
JSON file. The JSON format supports global options, input/output file
specifications with per-file options, and loopback decoder definitions.

The implementation includes a self-contained JSON parser (no external
dependencies) and an argv builder that translates JSON key-value pairs
into the equivalent command-line arguments.

Option values support strings, booleans (true for flags, false to skip),
numbers, arrays (for repeated options like -map), and null (skip).
Keys starting with "/" use FFmpeg's file-loading syntax.

Signed-off-by: Tom Vaughan
---
fftools/Makefile      |   1 +
fftools/ffmpeg.c      |  14 +
fftools/ffmpeg_json.c | 831 ++++++++++++++++++++++++++++++++++++++++++
fftools/ffmpeg_json.h |  76 ++++
fftools/ffmpeg_opt.c  |  12 +
5 files changed, 934 insertions(+)
create mode 100644 fftools/ffmpeg_json.c
create mode 100644 fftools/ffmpeg_json.h

diff --git a/fftools/Makefile b/fftools/Makefile
index 01b16fa8f4..e6eea056eb 100644
--- a/fftools/Makefile
+++ b/fftools/Makefile
@@ -17,6 +17,7 @@ OBJS-ffmpeg +=                  \
     fftools/ffmpeg_enc.o        \
     fftools/ffmpeg_filter.o     \
     fftools/ffmpeg_hw.o         \
+    fftools/ffmpeg_json.o       \
     fftools/ffmpeg_mux.o        \
     fftools/ffmpeg_mux_init.o   \
     fftools/ffmpeg_opt.o        \
diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c
index b394243f59..5dcd641700 100644
--- a/fftools/ffmpeg.c
+++ b/fftools/ffmpeg.c
@@ -82,6 +82,7 @@
#include "compat/android/binder.h"
#endif
#include "ffmpeg.h"
+#include "ffmpeg_json.h"
#include "ffmpeg_sched.h"
#include "ffmpeg_utils.h"
#include "graph/graphprint.h"
@@ -984,6 +985,8 @@ int main(int argc, char **argv)
     int ret;
     BenchmarkTimeStamps ti;
+    int json_argc = 0;
+    char **json_argv = NULL;
     init_dynload();
@@ -999,6 +1002,15 @@ int main(int argc, char **argv)
     show_banner(argc, argv, options);
+    /* Handle -json_cmd <file>: parse JSON file into argc/argv */
+    if (argc == 3 && !strcmp(argv[1], "-json_cmd")) {
+        ret = ffmpeg_json_parse_file(argv[2], &json_argc, &json_argv);
+        if (ret < 0)
+            goto finish;
+        argc = json_argc;
+        argv = json_argv;
+    }
+
     sch = sch_alloc();
     if (!sch) {
         ret = AVERROR(ENOMEM);
@@ -1051,6 +1063,8 @@ finish:
     sch_free(&sch);
+    ffmpeg_json_free_argv(json_argc, &json_argv);
+
     av_log(NULL, AV_LOG_VERBOSE, "\n");
     av_log(NULL, AV_LOG_VERBOSE, "Exiting with exit code %d\n", ret);
diff --git a/fftools/ffmpeg_json.c b/fftools/ffmpeg_json.c
new file mode 100644
index 0000000000..3fdc1884e5
--- /dev/null
+++ b/fftools/ffmpeg_json.c
@@ -0,0 +1,831 @@
+/*
+ * JSON command file support for FFmpeg
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "libavutil/avstring.h"
+#include "libavutil/avutil.h"
+#include "libavutil/error.h"
+#include "libavutil/log.h"
+#include "libavutil/mem.h"
+
+#include "cmdutils.h"
+#include "ffmpeg.h"
+#include "ffmpeg_json.h"
+
+/* ------------------------------------------------------------------ */
+/*  Minimal JSON parser                                               */
+/* ------------------------------------------------------------------ */
+
+/** JSON value type tag. */
+typedef enum {
+    JSON_NULL,
+    JSON_BOOL,
+    JSON_NUMBER,
+    JSON_STRING,
+    JSON_ARRAY,
+    JSON_OBJECT,
+} JsonType;
+
+typedef struct JsonValue JsonValue;
+
+/** Single key/value pair in a JSON object. */
+typedef struct JsonObjectEntry {
+    char      *key;       /**< property name */
+    JsonValue *value;     /**< property value */
+} JsonObjectEntry;
+
+/** Parsed JSON value node. */
+struct JsonValue {
+    JsonType type;               /**< discriminator tag */
+    union {
+        int          bool_val;     /**< JSON_BOOL   */
+        double       num_val;      /**< JSON_NUMBER */
+        char        *str_val;      /**< JSON_STRING */
+        struct {                   /**< JSON_ARRAY  */
+            JsonValue **items;     /**< array elements */
+            int         nb_items;  /**< element count  */
+        } arr;
+        struct {                   /**< JSON_OBJECT */
+            JsonObjectEntry *entries;   /**< key/value pairs */
+            int              nb_entries; /**< pair count      */
+        } obj;
+    } u;
+};
+
+/* Forward declarations */
+static JsonValue *json_parse_value(const char **p);
+static void json_free(JsonValue *v);
+
+static void skip_whitespace(const char **p)
+{
+    while (**p == ' ' || **p == '\t' || **p == '\n' || **p == '\r')
+        (*p)++;
+}
+
+static JsonValue *json_alloc(JsonType type)
+{
+    JsonValue *v = av_mallocz(sizeof(*v));
+    if (v)
+        v->type = type;
+    return v;
+}
+
+/**
+ * Parse a JSON string literal.
+ * The opening '"' must be the current character at @p *p.
+ *
+ * @param p  pointer to the current parse position (advanced past
+ *           the closing quote on success)
+ * @return   heap-allocated string, or NULL on error
+ */
+static char *json_parse_string_raw(const char **p)
+{
+    const char *s;
+    char *out, *dst;
+    size_t len;
+
+    if (**p != '"')
+        return NULL;
+    (*p)++; /* skip opening quote */
+    s = *p;
+
+    /* First pass: find the closing quote and measure output length */
+    len = 0;
+    while (*s && *s != '"') {
+        if (*s == '\\') {
+            s++;
+            if (!*s)
+                return NULL;
+            if (*s == 'u') {
+                /* \uXXXX – simplified: just store as-is in UTF-8 later */
+                if (!s[1] || !s[2] || !s[3] || !s[4])
+                    return NULL;
+                s += 4;
+                len += 4; /* conservative upper bound for UTF-8 */
+            }
+        }
+        len++;
+        s++;
+    }
+    if (*s != '"')
+        return NULL;
+
+    out = av_malloc(len + 1);
+    if (!out)
+        return NULL;
+
+    dst = out;
+    while (**p != '"') {
+        if (**p == '\\') {
+            (*p)++;
+            switch (**p) {
+            case '"':  *dst++ = '"';  break;
+            case '\\': *dst++ = '\\'; break;
+            case '/':  *dst++ = '/';  break;
+            case 'b':  *dst++ = '\b'; break;
+            case 'f':  *dst++ = '\f'; break;
+            case 'n':  *dst++ = '\n'; break;
+            case 'r':  *dst++ = '\r'; break;
+            case 't':  *dst++ = '\t'; break;
+            case 'u': {
+                /* Simplified \uXXXX: decode to UTF-8 */
+                unsigned cp = 0;
+                int i;
+                for (i = 0; i < 4; i++) {
+                    (*p)++;
+                    cp <<= 4;
+                    if (**p >= '0' && **p <= '9')      cp |= **p - '0';
+                    else if (**p >= 'a' && **p <= 'f')  cp |= **p - 'a' + 10;
+                    else if (**p >= 'A' && **p <= 'F')  cp |= **p - 'A' + 10;
+                    else { av_free(out); return NULL; }
+                }
+                if (cp < 0x80) {
+                    *dst++ = (char)cp;
+                } else if (cp < 0x800) {
+                    *dst++ = (char)(0xC0 | (cp >> 6));
+                    *dst++ = (char)(0x80 | (cp & 0x3F));
+                } else {
+                    *dst++ = (char)(0xE0 | (cp >> 12));
+                    *dst++ = (char)(0x80 | ((cp >> 6) & 0x3F));
+                    *dst++ = (char)(0x80 | (cp & 0x3F));
+                }
+                break;
+            }
+            default:
+                av_free(out);
+                return NULL;
+            }
+        } else {
+            *dst++ = **p;
+        }
+        (*p)++;
+    }
+    *dst = '\0';
+    (*p)++; /* skip closing quote */
+    return out;
+}
+
+/** Parse a JSON string and wrap it in a JsonValue. */
+static JsonValue *json_parse_string(const char **p)
+{
+    JsonValue *v;
+    char *s = json_parse_string_raw(p);
+    if (!s)
+        return NULL;
+    v = json_alloc(JSON_STRING);
+    if (!v) {
+        av_free(s);
+        return NULL;
+    }
+    v->u.str_val = s;
+    return v;
+}
+
+/** Parse a JSON number literal and return it as a JsonValue. */
+static JsonValue *json_parse_number(const char **p)
+{
+    JsonValue *v;
+    char *end;
+    double d;
+
+    d = strtod(*p, &end);
+    if (end == *p)
+        return NULL;
+    v = json_alloc(JSON_NUMBER);
+    if (!v)
+        return NULL;
+    v->u.num_val = d;
+    *p = end;
+    return v;
+}
+
+/** Parse a JSON array and return it as a JsonValue. */
+static JsonValue *json_parse_array(const char **p)
+{
+    JsonValue *v = json_alloc(JSON_ARRAY);
+    if (!v)
+        return NULL;
+
+    (*p)++; /* skip '[' */
+    skip_whitespace(p);
+
+    if (**p == ']') {
+        (*p)++;
+        return v;
+    }
+
+    for (;;) {
+        JsonValue *item;
+        JsonValue **new_items;
+
+        skip_whitespace(p);
+        item = json_parse_value(p);
+        if (!item)
+            goto fail;
+
+        new_items = av_realloc_array(v->u.arr.items,
+                                     v->u.arr.nb_items + 1,
+                                     sizeof(*v->u.arr.items));
+        if (!new_items) {
+            json_free(item);
+            goto fail;
+        }
+        v->u.arr.items = new_items;
+        v->u.arr.items[v->u.arr.nb_items++] = item;
+
+        skip_whitespace(p);
+        if (**p == ']') {
+            (*p)++;
+            return v;
+        }
+        if (**p != ',')
+            goto fail;
+        (*p)++;
+    }
+
+fail:
+    json_free(v);
+    return NULL;
+}
+
+/** Parse a JSON object and return it as a JsonValue. */
+static JsonValue *json_parse_object(const char **p)
+{
+    JsonValue *v = json_alloc(JSON_OBJECT);
+    if (!v)
+        return NULL;
+
+    (*p)++; /* skip '{' */
+    skip_whitespace(p);
+
+    if (**p == '}') {
+        (*p)++;
+        return v;
+    }
+
+    for (;;) {
+        char *key;
+        JsonValue *val;
+        JsonObjectEntry *new_entries;
+
+        skip_whitespace(p);
+        key = json_parse_string_raw(p);
+        if (!key)
+            goto fail;
+
+        skip_whitespace(p);
+        if (**p != ':') {
+            av_free(key);
+            goto fail;
+        }
+        (*p)++;
+
+        skip_whitespace(p);
+        val = json_parse_value(p);
+        if (!val) {
+            av_free(key);
+            goto fail;
+        }
+
+        new_entries = av_realloc_array(v->u.obj.entries,
+                                       v->u.obj.nb_entries + 1,
+                                       sizeof(*v->u.obj.entries));
+        if (!new_entries) {
+            av_free(key);
+            json_free(val);
+            goto fail;
+        }
+        v->u.obj.entries = new_entries;
+        v->u.obj.entries[v->u.obj.nb_entries].key   = key;
+        v->u.obj.entries[v->u.obj.nb_entries].value  = val;
+        v->u.obj.nb_entries++;
+
+        skip_whitespace(p);
+        if (**p == '}') {
+            (*p)++;
+            return v;
+        }
+        if (**p != ',')
+            goto fail;
+        (*p)++;
+    }
+
+fail:
+    json_free(v);
+    return NULL;
+}
+
+/** Dispatch to the appropriate parser based on the next character. */
+static JsonValue *json_parse_value(const char **p)
+{
+    skip_whitespace(p);
+
+    switch (**p) {
+    case '"':
+        return json_parse_string(p);
+    case '{':
+        return json_parse_object(p);
+    case '[':
+        return json_parse_array(p);
+    case 't':
+        if (strncmp(*p, "true", 4) == 0) {
+            JsonValue *v = json_alloc(JSON_BOOL);
+            if (v)
+                v->u.bool_val = 1;
+            *p += 4;
+            return v;
+        }
+        return NULL;
+    case 'f':
+        if (strncmp(*p, "false", 5) == 0) {
+            JsonValue *v = json_alloc(JSON_BOOL);
+            if (v)
+                v->u.bool_val = 0;
+            *p += 5;
+            return v;
+        }
+        return NULL;
+    case 'n':
+        if (strncmp(*p, "null", 4) == 0) {
+            JsonValue *v = json_alloc(JSON_NULL);
+            *p += 4;
+            return v;
+        }
+        return NULL;
+    default:
+        if (**p == '-' || (**p >= '0' && **p <= '9'))
+            return json_parse_number(p);
+        return NULL;
+    }
+}
+
+/** Recursively free a parsed JSON value tree. */
+static void json_free(JsonValue *v)
+{
+    int i;
+    if (!v)
+        return;
+    switch (v->type) {
+    case JSON_STRING:
+        av_free(v->u.str_val);
+        break;
+    case JSON_ARRAY:
+        for (i = 0; i < v->u.arr.nb_items; i++)
+            json_free(v->u.arr.items[i]);
+        av_free(v->u.arr.items);
+        break;
+    case JSON_OBJECT:
+        for (i = 0; i < v->u.obj.nb_entries; i++) {
+            av_free(v->u.obj.entries[i].key);
+            json_free(v->u.obj.entries[i].value);
+        }
+        av_free(v->u.obj.entries);
+        break;
+    default:
+        break;
+    }
+    av_free(v);
+}
+
+/** Look up a key in a JSON object, returning the value or NULL. */
+static JsonValue *json_object_get(const JsonValue *obj, const char *key)
+{
+    int i;
+    if (!obj || obj->type != JSON_OBJECT)
+        return NULL;
+    for (i = 0; i < obj->u.obj.nb_entries; i++) {
+        if (strcmp(obj->u.obj.entries[i].key, key) == 0)
+            return obj->u.obj.entries[i].value;
+    }
+    return NULL;
+}
+
+/* ------------------------------------------------------------------ */
+/*  argv builder                                                      */
+/* ------------------------------------------------------------------ */
+
+/** Growable argument vector used while building argc/argv. */
+typedef struct ArgvBuilder {
+    char **args;       /**< argument strings */
+    int    nb_args;    /**< current argument count */
+    int    capacity;   /**< allocated slots in @p args */
+} ArgvBuilder;
+
+/** Append a single argument string to the builder. */
+static int argv_add(ArgvBuilder *b, const char *arg)
+{
+    char **new_args;
+
+    if (b->nb_args >= b->capacity) {
+        int new_cap = b->capacity ? b->capacity * 2 : 32;
+        new_args = av_realloc_array(b->args, new_cap, sizeof(*b->args));
+        if (!new_args)
+            return AVERROR(ENOMEM);
+        b->args     = new_args;
+        b->capacity = new_cap;
+    }
+    b->args[b->nb_args] = av_strdup(arg);
+    if (!b->args[b->nb_args])
+        return AVERROR(ENOMEM);
+    b->nb_args++;
+    return 0;
+}
+
+/** Free all strings and reset the builder to empty. */
+static void argv_builder_free(ArgvBuilder *b)
+{
+    int i;
+    for (i = 0; i < b->nb_args; i++)
+        av_free(b->args[i]);
+    av_free(b->args);
+    memset(b, 0, sizeof(*b));
+}
+
+/**
+ * Add a JSON value as a CLI argument string to the argv builder.
+ * Handles strings of any length (no fixed buffer for JSON_STRING).
+ */
+static int argv_add_json_value(ArgvBuilder *b, const JsonValue *v)
+{
+    char buf[64];
+    switch (v->type) {
+    case JSON_STRING:
+        return argv_add(b, v->u.str_val);
+    case JSON_NUMBER:
+        /* Print integers without decimal point where possible */
+        if (v->u.num_val == (int64_t)v->u.num_val)
+            snprintf(buf, sizeof(buf), "%" PRId64, (int64_t)v->u.num_val);
+        else
+            snprintf(buf, sizeof(buf), "%g", v->u.num_val);
+        return argv_add(b, buf);
+    case JSON_BOOL:
+        snprintf(buf, sizeof(buf), "%d", v->u.bool_val);
+        return argv_add(b, buf);
+    default:
+        return AVERROR(EINVAL);
+    }
+}
+
+/**
+ * Build an option key string for the argv builder.
+ *
+ * Keys starting with "/" use FFmpeg's file-loading syntax: -/option filename
+ * reads the option value from a file.  In JSON this is represented as:
+ *   "/filter_complex": "graph.txt"  =>  -/filter_complex graph.txt
+ *
+ * All other keys are emitted as -key.
+ */
+static int argv_add_option_key(ArgvBuilder *b, const char *key)
+{
+    char buf[256];
+    if (key[0] == '/') {
+        /* File-loading syntax: -/option */
+        snprintf(buf, sizeof(buf), "-%s", key);
+    } else {
+        snprintf(buf, sizeof(buf), "-%s", key);
+    }
+    return argv_add(b, buf);
+}
+
+/**
+ * Emit the options from an object into the argv builder.
+ *
+ * Each key becomes "-key" and each value becomes the argument:
+ *   "c:v": "libx264"        =>  -c:v libx264
+ *   "y": true                =>  -y
+ *   "an": false              =>  (skipped; use "an": true to disable audio)
+ *   "map": ["0:v","0:a"]    =>  -map 0:v -map 0:a
+ *   "/filter_complex": "f.txt" => -/filter_complex f.txt  (load from file)
+ */
+static int emit_options(ArgvBuilder *b, const JsonValue *opts)
+{
+    int i, j, ret;
+
+    if (!opts)
+        return 0;
+    if (opts->type != JSON_OBJECT) {
+        av_log(NULL, AV_LOG_ERROR,
+               "JSON: \"options\" must be an object.\n");
+        return AVERROR(EINVAL);
+    }
+
+    for (i = 0; i < opts->u.obj.nb_entries; i++) {
+        const char      *key = opts->u.obj.entries[i].key;
+        const JsonValue *val = opts->u.obj.entries[i].value;
+
+        if (val->type == JSON_BOOL) {
+            if (val->u.bool_val) {
+                /* -flag */
+                ret = argv_add_option_key(b, key);
+                if (ret < 0) return ret;
+            }
+            /* false booleans are intentionally skipped (no-op) */
+            continue;
+        }
+
+        if (val->type == JSON_ARRAY) {
+            /* Repeated option: -key elem0 -key elem1 ... */
+            for (j = 0; j < val->u.arr.nb_items; j++) {
+                ret = argv_add_option_key(b, key);
+                if (ret < 0) return ret;
+
+                ret = argv_add_json_value(b, val->u.arr.items[j]);
+                if (ret < 0) {
+                    av_log(NULL, AV_LOG_ERROR,
+                           "JSON: unsupported type in array for option 
\"%s\".\n", key);
+                    return ret;
+                }
+            }
+            continue;
+        }
+
+        if (val->type == JSON_NULL) {
+            continue;
+        }
+
+        /* Scalar: -key value */
+        ret = argv_add_option_key(b, key);
+        if (ret < 0) return ret;
+
+        ret = argv_add_json_value(b, val);
+        if (ret < 0) {
+            av_log(NULL, AV_LOG_ERROR,
+                   "JSON: unsupported value type for option \"%s\".\n", key);
+            return ret;
+        }
+    }
+
+    return 0;
+}
+
+/* ------------------------------------------------------------------ */
+/*  Public API                                                        */
+/* ------------------------------------------------------------------ */
+
+/** @sa ffmpeg_json_parse_file() in ffmpeg_json.h for full documentation. */
+int ffmpeg_json_parse_file(const char *filename, int *out_argc, char 
***out_argv)
+{
+    FILE *f = NULL;
+    long fsize;
+    char *data = NULL;
+    const char *p;
+    JsonValue *root = NULL;
+    JsonValue *global_opts, *inputs, *outputs, *decoders;
+    ArgvBuilder b = { 0 };
+    int ret, i;
+
+    *out_argc = 0;
+    *out_argv = NULL;
+
+    /* Read file contents */
+    f = fopen(filename, "rb");
+    if (!f) {
+        av_log(NULL, AV_LOG_ERROR,
+               "JSON: cannot open file '%s': %s\n", filename, strerror(errno));
+        return AVERROR(errno);
+    }
+
+    if (fseek(f, 0, SEEK_END) < 0) {
+        ret = AVERROR(errno);
+        goto fail;
+    }
+    fsize = ftell(f);
+    if (fsize < 0 || fsize > 100 * 1024 * 1024) { /* 100 MB sanity limit */
+        av_log(NULL, AV_LOG_ERROR,
+               "JSON: file '%s' is too large or unreadable.\n", filename);
+        ret = AVERROR(EINVAL);
+        goto fail;
+    }
+    if (fseek(f, 0, SEEK_SET) < 0) {
+        ret = AVERROR(errno);
+        goto fail;
+    }
+
+    data = av_malloc(fsize + 1);
+    if (!data) {
+        ret = AVERROR(ENOMEM);
+        goto fail;
+    }
+    if (fread(data, 1, fsize, f) != (size_t)fsize) {
+        av_log(NULL, AV_LOG_ERROR, "JSON: failed to read '%s'.\n", filename);
+        ret = AVERROR(EIO);
+        goto fail;
+    }
+    data[fsize] = '\0';
+    fclose(f);
+    f = NULL;
+
+    /* Parse JSON */
+    p = data;
+    root = json_parse_value(&p);
+    if (!root || root->type != JSON_OBJECT) {
+        av_log(NULL, AV_LOG_ERROR,
+               "JSON: failed to parse '%s' – root must be an object.\n", 
filename);
+        ret = AVERROR(EINVAL);
+        goto fail;
+    }
+
+    /* Verify there are no trailing non-whitespace characters */
+    skip_whitespace(&p);
+    if (*p != '\0') {
+        av_log(NULL, AV_LOG_ERROR,
+               "JSON: trailing content after root object in '%s'.\n", 
filename);
+        ret = AVERROR(EINVAL);
+        goto fail;
+    }
+
+    /* Build argv: start with program name */
+    ret = argv_add(&b, "ffmpeg");
+    if (ret < 0) goto fail;
+
+    /* 1. Global options */
+    global_opts = json_object_get(root, "global_options");
+    if (global_opts) {
+        ret = emit_options(&b, global_opts);
+        if (ret < 0) goto fail;
+    }
+
+    /* 2. Input files: options then -i url */
+    inputs = json_object_get(root, "inputs");
+    if (inputs) {
+        if (inputs->type != JSON_ARRAY) {
+            av_log(NULL, AV_LOG_ERROR, "JSON: \"inputs\" must be an array.\n");
+            ret = AVERROR(EINVAL);
+            goto fail;
+        }
+        for (i = 0; i < inputs->u.arr.nb_items; i++) {
+            JsonValue *input = inputs->u.arr.items[i];
+            JsonValue *url, *opts;
+
+            if (input->type != JSON_OBJECT) {
+                av_log(NULL, AV_LOG_ERROR,
+                       "JSON: each input must be an object.\n");
+                ret = AVERROR(EINVAL);
+                goto fail;
+            }
+
+            url = json_object_get(input, "url");
+            if (!url || url->type != JSON_STRING) {
+                av_log(NULL, AV_LOG_ERROR,
+                       "JSON: input %d missing \"url\" string.\n", i);
+                ret = AVERROR(EINVAL);
+                goto fail;
+            }
+
+            /* Emit input options before -i */
+            opts = json_object_get(input, "options");
+            ret = emit_options(&b, opts);
+            if (ret < 0) goto fail;
+
+            ret = argv_add(&b, "-i");
+            if (ret < 0) goto fail;
+            ret = argv_add(&b, url->u.str_val);
+            if (ret < 0) goto fail;
+        }
+    }
+
+    /* 3. Output files: options then url */
+    outputs = json_object_get(root, "outputs");
+    if (outputs) {
+        if (outputs->type != JSON_ARRAY) {
+            av_log(NULL, AV_LOG_ERROR, "JSON: \"outputs\" must be an 
array.\n");
+            ret = AVERROR(EINVAL);
+            goto fail;
+        }
+        for (i = 0; i < outputs->u.arr.nb_items; i++) {
+            JsonValue *output = outputs->u.arr.items[i];
+            JsonValue *url, *opts;
+
+            if (output->type != JSON_OBJECT) {
+                av_log(NULL, AV_LOG_ERROR,
+                       "JSON: each output must be an object.\n");
+                ret = AVERROR(EINVAL);
+                goto fail;
+            }
+
+            url = json_object_get(output, "url");
+            if (!url || url->type != JSON_STRING) {
+                av_log(NULL, AV_LOG_ERROR,
+                       "JSON: output %d missing \"url\" string.\n", i);
+                ret = AVERROR(EINVAL);
+                goto fail;
+            }
+
+            /* Emit output options before the output url */
+            opts = json_object_get(output, "options");
+            ret = emit_options(&b, opts);
+            if (ret < 0) goto fail;
+
+            ret = argv_add(&b, url->u.str_val);
+            if (ret < 0) goto fail;
+        }
+    }
+
+    /* 4. Loopback decoders: options then -dec name */
+    decoders = json_object_get(root, "decoders");
+    if (decoders) {
+        if (decoders->type != JSON_ARRAY) {
+            av_log(NULL, AV_LOG_ERROR, "JSON: \"decoders\" must be an 
array.\n");
+            ret = AVERROR(EINVAL);
+            goto fail;
+        }
+        for (i = 0; i < decoders->u.arr.nb_items; i++) {
+            JsonValue *decoder = decoders->u.arr.items[i];
+            JsonValue *name, *opts;
+
+            if (decoder->type != JSON_OBJECT) {
+                av_log(NULL, AV_LOG_ERROR,
+                       "JSON: each decoder must be an object.\n");
+                ret = AVERROR(EINVAL);
+                goto fail;
+            }
+
+            name = json_object_get(decoder, "name");
+            if (!name || name->type != JSON_STRING) {
+                av_log(NULL, AV_LOG_ERROR,
+                       "JSON: decoder %d missing \"name\" string.\n", i);
+                ret = AVERROR(EINVAL);
+                goto fail;
+            }
+
+            /* Emit decoder options before -dec */
+            opts = json_object_get(decoder, "options");
+            ret = emit_options(&b, opts);
+            if (ret < 0) goto fail;
+
+            ret = argv_add(&b, "-dec");
+            if (ret < 0) goto fail;
+            ret = argv_add(&b, name->u.str_val);
+            if (ret < 0) goto fail;
+        }
+    }
+
+    /* Add NULL sentinel for execv-style usage */
+    if (b.nb_args >= b.capacity) {
+        char **new_args = av_realloc_array(b.args, b.nb_args + 1, 
sizeof(*b.args));
+        if (!new_args) {
+            ret = AVERROR(ENOMEM);
+            goto fail;
+        }
+        b.args = new_args;
+    }
+    b.args[b.nb_args] = NULL;
+
+    *out_argc = b.nb_args;
+    *out_argv = b.args;
+
+    /* Log the constructed command line */
+    {
+        int k;
+        av_log(NULL, AV_LOG_INFO, "JSON: constructed command line:\n ");
+        for (k = 0; k < b.nb_args; k++)
+            av_log(NULL, AV_LOG_INFO, " %s", b.args[k]);
+        av_log(NULL, AV_LOG_INFO, "\n");
+    }
+
+    json_free(root);
+    av_free(data);
+    return 0;
+
+fail:
+    if (f)
+        fclose(f);
+    json_free(root);
+    av_free(data);
+    argv_builder_free(&b);
+    return ret;
+}
+
+/** @sa ffmpeg_json_free_argv() in ffmpeg_json.h for full documentation. */
+void ffmpeg_json_free_argv(int argc, char ***argv)
+{
+    int i;
+    if (!argv || !*argv)
+        return;
+    for (i = 0; i < argc; i++)
+        av_free((*argv)[i]);
+    av_free(*argv);
+    *argv = NULL;
+}
diff --git a/fftools/ffmpeg_json.h b/fftools/ffmpeg_json.h
new file mode 100644
index 0000000000..d509f142d7
--- /dev/null
+++ b/fftools/ffmpeg_json.h
@@ -0,0 +1,76 @@
+/*
+ * JSON command file support for FFmpeg
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef FFTOOLS_FFMPEG_JSON_H
+#define FFTOOLS_FFMPEG_JSON_H
+
+/**
+ * Parse a JSON command file and convert it to an argc/argv pair
+ * compatible with ffmpeg_parse_options().
+ *
+ * The JSON file should have the following structure:
+ * {
+ *   "global_options": { "option_name": "value", ... },
+ *   "inputs": [
+ *     { "url": "input_file", "options": { "option_name": "value", ... } },
+ *     ...
+ *   ],
+ *   "outputs": [
+ *     { "url": "output_file", "options": { "option_name": "value", ... } },
+ *     ...
+ *   ],
+ *   "decoders": [
+ *     { "name": "decoder_name", "options": { ... } },
+ *     ...
+ *   ]
+ * }
+ *
+ * Option values can be:
+ *   - string: added as -option value  (no length limit)
+ *   - true:   added as -option (flag)
+ *   - false:  skipped (no-op); to negate a boolean, use the "no"-prefixed
+ *             name with true, e.g. "noautorotate": true
+ *   - number: converted to string and added as -option value
+ *   - array:  option is repeated for each element, e.g. "map": ["0:v","0:a"]
+ *             becomes -map 0:v -map 0:a
+ *   - null:   skipped
+ *
+ * Keys starting with "/" use FFmpeg's file-loading syntax:
+ *   "/filter_complex": "graph.txt"  =>  -/filter_complex graph.txt
+ *
+ * All fields except the root object are optional.
+ *
+ * @param filename   Path to the JSON file
+ * @param[out] argc  Number of arguments in the constructed argv
+ * @param[out] argv  Constructed argument vector (caller must free with
+ *                   ffmpeg_json_free_argv())
+ * @return 0 on success, negative AVERROR code on failure
+ */
+int ffmpeg_json_parse_file(const char *filename, int *argc, char ***argv);
+
+/**
+ * Free an argv array previously allocated by ffmpeg_json_parse_file().
+ *
+ * @param argc  Number of arguments
+ * @param argv  Pointer to the argument vector (set to NULL on return)
+ */
+void ffmpeg_json_free_argv(int argc, char ***argv);
+
+#endif /* FFTOOLS_FFMPEG_JSON_H */
diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c
index 48e6816c19..abdc81988c 100644
--- a/fftools/ffmpeg_opt.c
+++ b/fftools/ffmpeg_opt.c
@@ -1610,6 +1610,15 @@ static int opt_adrift_threshold(void *optctx, const char 
*opt, const char *arg)
}
#endif
+static int opt_json_cmd(void *optctx, const char *opt, const char *arg)
+{
+    av_log(NULL, AV_LOG_ERROR,
+           "-json_cmd requires exactly one argument: the path to a JSON file\n"
+           "containing ffmpeg commands and parameters. No other options are 
allowed.\n"
+           "Usage: ffmpeg -json_cmd <file.json>\n");
+    return AVERROR(EINVAL);
+}
+
static const char *const alt_channel_layout[] = { "ch_layout", NULL};
static const char *const alt_codec[]          = { "c", "acodec", "vcodec", 
"scodec", "dcodec", NULL };
static const char *const alt_filter[]         = { "af", "vf", NULL };
@@ -1622,6 +1631,9 @@ static const char *const alt_tag[]            = { "atag", 
"vtag", "stag", NULL }
const OptionDef options[] = {
     /* main options */
     CMDUTILS_COMMON_OPTIONS
+    { "json_cmd",               OPT_TYPE_FUNC, OPT_FUNC_ARG | OPT_EXIT,
+        { .func_arg = opt_json_cmd },
+        "read options from a JSON command file", "file" },
     { "f",                      OPT_TYPE_STRING, OPT_OFFSET | OPT_INPUT | 
OPT_OUTPUT,
         { .off       = OFFSET(format) },
         "force container format (auto-detected otherwise)", "fmt" },
--
2.49.0
_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to