[FFmpeg-devel] [PATCH v3] avformat/concatdec: add support for setting input options

Jan Ekström jeebjp at gmail.com
Mon Feb 8 13:32:37 EET 2021


This way protocol or format related options can be set for all
of the files opened during concatenation both globally as well
as per-file.
---

Changes from v2:

1. Added an example, although I had issues figuring out something useful
   that is not a hack for something. Ended up doing a disablement of
   advanced edit list functionality, since that can sometimes lead to
   unwanted behavior.

   * First idea was to override the input format for a file without an
     extension. For that, we have no AVOption it seems.
   * Then came the idea of utilizing the framerate option in the raw
     h264 demuxer. That didn't work because apparently if there is a header
     in there that probed/parsed frame rate field gets utilized.
   * Third idea was either the one I picked, or analyzeduration/probesize
     for MPEG-TS. I opted for the mp4 case.

2. Quoted the : in documentation with @code{:}.
3. Fixed a duplicate space in a log message.
---

 doc/demuxers.texi       | 24 ++++++++++++++
 libavformat/concatdec.c | 69 ++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 92 insertions(+), 1 deletion(-)

diff --git a/doc/demuxers.texi b/doc/demuxers.texi
index 3c15ab9eee..20601f9575 100644
--- a/doc/demuxers.texi
+++ b/doc/demuxers.texi
@@ -149,6 +149,14 @@ Metadata of the packets of the file. The specified metadata will be set for
 each file packet. You can specify this directive multiple times to add multiple
 metadata entries.
 
+ at item @code{input_options @var{key=value:key2=value2}}
+Input options passed on when reading a specific file, using a @code{:}-separated
+list of key=value pairs. Requires @code{safe} to be non-positive. Global options
+for all files can be set with the @code{input_options} demuxer option. When using
+both options on the list of files as well as globally via the demuxer option,
+the global ones get applied first and the file-specific options are then applied
+on top of them.
+
 @item @code{stream}
 Introduce a stream in the virtual file.
 All subsequent stream-related directives apply to the last introduced
@@ -204,6 +212,10 @@ expressed in microseconds. The duration metadata is only set if it is known
 based on the concat file.
 The default is 0.
 
+ at item input_options
+Input options to be passed on for all opened inputs using a :-separated list of
+key=value pairs.
+
 @end table
 
 @subsection Examples
@@ -231,6 +243,18 @@ duration 20.0
 
 file subdir/file-2.wav
 @end example
+
+ at item
+Disabling advanced edit list capability for the first input file via
+input_options:
+ at example
+ffconcat version 1.0
+
+file file-1.mp4
+input_options advanced_editlist=false
+
+file file-2.mp4
+ at end example
 @end itemize
 
 @section dash
diff --git a/libavformat/concatdec.c b/libavformat/concatdec.c
index 6d5b9914f9..89d75cedc6 100644
--- a/libavformat/concatdec.c
+++ b/libavformat/concatdec.c
@@ -52,6 +52,7 @@ typedef struct {
     int64_t outpoint;
     AVDictionary *metadata;
     int nb_streams;
+    AVDictionary *input_options;
 } ConcatFile;
 
 typedef struct {
@@ -66,6 +67,7 @@ typedef struct {
     ConcatMatchMode stream_match_mode;
     unsigned auto_convert;
     int segment_time_metadata;
+    AVDictionary *input_options;
 } ConcatContext;
 
 static int concat_probe(const AVProbeData *probe)
@@ -329,6 +331,7 @@ static int open_file(AVFormatContext *avf, unsigned fileno)
 {
     ConcatContext *cat = avf->priv_data;
     ConcatFile *file = &cat->files[fileno];
+    AVDictionary *options = NULL;
     int ret;
 
     if (cat->avf)
@@ -344,12 +347,37 @@ static int open_file(AVFormatContext *avf, unsigned fileno)
     if ((ret = ff_copy_whiteblacklists(cat->avf, avf)) < 0)
         return ret;
 
-    if ((ret = avformat_open_input(&cat->avf, file->url, NULL, NULL)) < 0 ||
+    // Apply global AVOptions first
+    if (cat->input_options &&
+        (ret = av_dict_copy(&options, cat->input_options, 0) < 0))
+        return ret;
+
+    // then apply file-specific AVOptions
+    if (file->input_options &&
+        (ret = av_dict_copy(&options, file->input_options, 0) < 0))
+        return ret;
+
+    if ((ret = avformat_open_input(&cat->avf, file->url, NULL, &options)) < 0 ||
         (ret = avformat_find_stream_info(cat->avf, NULL)) < 0) {
         av_log(avf, AV_LOG_ERROR, "Impossible to open '%s'\n", file->url);
         avformat_close_input(&cat->avf);
+        av_dict_free(&options);
         return ret;
     }
+
+    if (av_dict_count(options)) {
+        AVDictionaryEntry *en = NULL;
+
+        while ((en = av_dict_get(options, "", en, AV_DICT_IGNORE_SUFFIX))) {
+            av_log(avf, AV_LOG_WARNING,
+                   "Option '%s' set to '%s' was ignored when opening %s "
+                   "with the %s reader!\n",
+                   en->key, en->value, file->url, cat->avf->iformat->name);
+        }
+    }
+
+    av_dict_free(&options);
+
     cat->cur_file = file;
     file->start_time = !fileno ? 0 :
                        cat->files[fileno - 1].start_time +
@@ -386,6 +414,7 @@ static int concat_read_close(AVFormatContext *avf)
         }
         av_freep(&cat->files[i].streams);
         av_dict_free(&cat->files[i].metadata);
+        av_dict_free(&cat->files[i].input_options);
     }
     if (cat->avf)
         avformat_close_input(&cat->avf);
@@ -457,6 +486,41 @@ static int concat_read_header(AVFormatContext *avf)
                 FAIL(AVERROR_INVALIDDATA);
             }
             av_freep(&metadata);
+        } else if (!strncmp(keyword, "input_options", 13)) {
+            if (!file) {
+                av_log(avf, AV_LOG_ERROR, "Line %d: %s without file\n",
+                       line, keyword);
+                FAIL(AVERROR_INVALIDDATA);
+            }
+
+            if (cat->safe > 0) {
+                av_log(avf, AV_LOG_ERROR,
+                       "Line %d: Input options cannot be set in file list in "
+                       "safe mode!\n", line);
+                FAIL(AVERROR(EPERM));
+            }
+
+            {
+                char *input_options = av_get_token((const char **)&cursor,
+                                                   SPACE_CHARS);
+                if (!input_options) {
+                    av_log(avf, AV_LOG_ERROR,
+                           "Line %d: key=value pairs required!\n", line);
+                    FAIL(AVERROR_INVALIDDATA);
+                }
+
+                if ((ret =
+                     av_dict_parse_string(&file->input_options, input_options,
+                                          "=", ":", 0)) < 0) {
+                    av_log(avf, AV_LOG_ERROR,
+                           "Line %d: failed to parse input options string\n",
+                           line);
+                    av_freep(&input_options);
+                    FAIL(AVERROR_INVALIDDATA);
+                }
+
+                av_freep(&input_options);
+            }
         } else if (!strcmp(keyword, "stream")) {
             if (!avformat_new_stream(avf, NULL))
                 FAIL(AVERROR(ENOMEM));
@@ -764,6 +828,9 @@ static const AVOption options[] = {
       OFFSET(auto_convert), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, DEC },
     { "segment_time_metadata", "output file segment start time and duration as packet metadata",
       OFFSET(segment_time_metadata), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DEC },
+    { "input_options",
+      "set options for all opened inputs using a :-separated list of key=value pairs",
+      OFFSET(input_options), AV_OPT_TYPE_DICT, { .str = NULL }, 0, 0, DEC },
     { NULL }
 };
 
-- 
2.29.2



More information about the ffmpeg-devel mailing list