[PR] CLI: fix matching of non-apic streams and setting of default disposition (PR #24247)

Gyan Doshi via ffmpeg-devel <[email protected]>
Newsgroups gmane.comp.video.ffmpeg.devel
Message-ID <[email protected]>
PR #24247 opened by Gyan Doshi (GyanD)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24247
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24247.patch

Two fixes for the ffmpeg CLI.

**First commit**

The capital V specifier as in '-c:V' is supposed to match only non-attached-pic video streams. However, a user reported that apic streams were matched as well.
Confirmed by testing. Apparently a regression since before v3.4 (earliest ver I could test).

This happens because encoder selection occurs before stream dispositions are set. Fixed by setting disposition before other spec options are applied.

----
**Second commit**

The current implementation of fn set_dispositions lets multiple streams of the same media type be marked as default streams.

The function is reworked to give precedence to user-assigned default disposition. Singular default disposition per media type is enforced.
Function is renamed to make its primary role clearer.


>From 1e61b20a06d4cde5715b517afe5dbaadce10067a Mon Sep 17 00:00:00 2001
From: Gyan Doshi <[email protected]>
Date: Tue, 18 Aug 2026 19:16:49 +0530
Subject: [PATCH 1/2] ffmpeg: fix matching of non-apic stream specifiers.

The capital V specifier as in '-c:V' is supposed to match only
non-attached-pic video streams. However, a user reported that apic
streams were matched as well.

Confirmed by testing. Apparently a regression since before 3.4 (earliest ver I could test).

This happens because encoder selection occurs before stream dispositions are set.

Fixed by setting disposition before other spec options are applied.
---
 fftools/ffmpeg_mux_init.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/fftools/ffmpeg_mux_init.c b/fftools/ffmpeg_mux_init.c
index f80b4427b4..9f3037f831 100644
--- a/fftools/ffmpeg_mux_init.c
+++ b/fftools/ffmpeg_mux_init.c
@@ -1147,7 +1147,7 @@ static int ost_add(Muxer *mux, const OptionsContext *o, enum AVMediaType type,
     int threads_manual = 0;
     AVRational enc_tb = { 0, 0 };
     enum VideoSyncMethod vsync_method = VSYNC_AUTO;
-    const char *bsfs = NULL, *time_base = NULL, *codec_tag = NULL;
+    const char *bsfs = NULL, *time_base = NULL, *codec_tag = NULL, *manual_disp = NULL;
     char  *next;
     double qscale = -1;
 
@@ -1204,6 +1204,13 @@ static int ost_add(Muxer *mux, const OptionsContext *o, enum AVMediaType type,
     ms->par_in->codec_type   = type;
     st->codecpar->codec_type = type;
 
+    if (ost->ist)
+        ost->st->disposition = ost->ist->st->disposition;
+
+    opt_match_per_stream_str(ost, &o->disposition, oc, st, &manual_disp);
+    if (manual_disp)
+        ret = av_opt_set(ost->st, "disposition", manual_disp, 0);
+
     ret = choose_encoder(o, oc, ms, &enc);
     if (ret < 0) {
         av_log(ost, AV_LOG_FATAL, "Error selecting an encoder\n");
-- 
2.52.0


>From f152f098f622af4681c9ce5ce63754e7190882d3 Mon Sep 17 00:00:00 2001
From: Gyan Doshi <[email protected]>
Date: Fri, 21 Aug 2026 18:44:36 +0530
Subject: [PATCH 2/2] ffmpeg: fix setting of default stream disposition.

The current implementation of fn set_dispositions lets multiple streams
of the same media type be marked as default streams.

The function is reworked to give precedence to user assigned default
disposition. Singular default disposition per media type is enforced.

Function is renamed to make its primary role clearer.
---
 fftools/ffmpeg_mux_init.c | 109 ++++++++++++++++++++------------------
 1 file changed, 58 insertions(+), 51 deletions(-)

diff --git a/fftools/ffmpeg_mux_init.c b/fftools/ffmpeg_mux_init.c
index 9f3037f831..af917af4d6 100644
--- a/fftools/ffmpeg_mux_init.c
+++ b/fftools/ffmpeg_mux_init.c
@@ -3248,73 +3248,80 @@ static int copy_meta(Muxer *mux, const OptionsContext *o)
     return 0;
 }
 
-static int set_dispositions(Muxer *mux, const OptionsContext *o)
+static int set_default_dispositions(Muxer *mux, const OptionsContext *o)
 {
     OutputFile                    *of = &mux->of;
     AVFormatContext              *ctx = mux->fc;
 
     // indexed by type+1, because AVMEDIA_TYPE_UNKNOWN=-1
-    int nb_streams[AVMEDIA_TYPE_NB + 1]   = { 0 };
-    int have_default[AVMEDIA_TYPE_NB + 1] = { 0 };
-    int have_manual = 0;
+    int nb_streams[AVMEDIA_TYPE_NB + 1]        = { 0 };
+    int inherited_default[AVMEDIA_TYPE_NB + 1] = { 0 };
+    int manual_default[AVMEDIA_TYPE_NB + 1]    = { 0 };
+    int default_assigned[AVMEDIA_TYPE_NB + 1]  = { 0 };
     int ret = 0;
 
-    const char **dispositions;
-
-    dispositions = av_calloc(ctx->nb_streams, sizeof(*dispositions));
-    if (!dispositions)
-        return AVERROR(ENOMEM);
-
-    // first, copy the input dispositions
+    // apply disposition again as metadata is now available &
+    // track all defaults
     for (int i = 0; i < ctx->nb_streams; i++) {
         OutputStream *ost = of->streams[i];
+        const char *disposition = NULL;
 
         nb_streams[ost->type + 1]++;
 
-        opt_match_per_stream_str(ost, &o->disposition, ctx, ost->st, &dispositions[i]);
-
-        have_manual |= !!dispositions[i];
-
-        if (ost->ist) {
-            ost->st->disposition = ost->ist->st->disposition;
-
-            if (ost->st->disposition & AV_DISPOSITION_DEFAULT)
-                have_default[ost->type + 1] = 1;
-        }
-    }
-
-    if (have_manual) {
-        // process manually set dispositions - they override the above copy
-        for (int i = 0; i < ctx->nb_streams; i++) {
-            OutputStream *ost = of->streams[i];
-            const char  *disp = dispositions[i];
-
-            if (!disp)
-                continue;
-
-            ret = av_opt_set(ost->st, "disposition", disp, 0);
+        opt_match_per_stream_str(ost, &o->disposition, ctx, ost->st, &disposition);
+        if (disposition) {
+            ost->st->disposition = 0;
+            ret = av_opt_set(ost->st, "disposition", disposition, 0);
             if (ret < 0)
-                goto finish;
+                return ret;
+            if (ost->st->disposition & AV_DISPOSITION_DEFAULT) {
+                if (manual_default[ost->type + 1]) {
+                    av_log(mux, AV_LOG_ERROR, "Default disposition manually specified for multiple streams of type %s\n", av_get_media_type_string(ost->type));
+                    return AVERROR_INVALIDDATA;
+                }
+                manual_default[ost->type + 1] = i+1;
+            }
         }
-    } else {
-        // For each media type with more than one stream, find a suitable stream to
-        // mark as default, unless one is already marked default.
-        // "Suitable" means the first of that type, skipping attached pictures.
-        for (int i = 0; i < ctx->nb_streams; i++) {
-            OutputStream *ost = of->streams[i];
-            enum AVMediaType type = ost->type;
 
-            if (nb_streams[type + 1] < 2 || have_default[type + 1] ||
-                ost->st->disposition & AV_DISPOSITION_ATTACHED_PIC)
-                continue;
-
-            ost->st->disposition |= AV_DISPOSITION_DEFAULT;
-            have_default[type + 1] = 1;
+        if (ost->ist && ost->ist->st->disposition) {
+            if (disposition) {
+                ost->st->disposition = ost->ist->st->disposition;
+                ret = av_opt_set(ost->st, "disposition", disposition, 0);
+                if (ret < 0)
+                    return ret;
+            }
         }
+
+        if (ost->st->disposition & AV_DISPOSITION_ATTACHED_PIC) {
+            if (manual_default[ost->type + 1] == i+1)
+                manual_default[ost->type + 1] = 0;
+            continue;
+        }
+
+        if (ost->st->disposition & AV_DISPOSITION_DEFAULT &&
+            manual_default[ost->type + 1] != i+1 &&
+            !inherited_default[ost->type + 1])
+            inherited_default[ost->type + 1] = i+1;
     }
 
-finish:
-    av_freep(&dispositions);
+    for (int i = 0; i < ctx->nb_streams; i++) {
+        OutputStream *ost = of->streams[i];
+        int def_idx = -1;
+
+        if (manual_default[ost->type + 1])
+            def_idx = manual_default[ost->type + 1] - 1;
+        else if (inherited_default[ost->type + 1])
+            def_idx = inherited_default[ost->type + 1] - 1;
+        else if (nb_streams[ost->type + 1] > 1 && !default_assigned[ost->type + 1]) {
+            default_assigned[ost->type + 1] = i+1;
+            def_idx = i; 
+        }
+
+        if (def_idx == i)
+            ost->st->disposition |= AV_DISPOSITION_DEFAULT;
+        else
+            ost->st->disposition &= ~AV_DISPOSITION_DEFAULT;
+    }
 
     return ret;
 }
@@ -3629,9 +3636,9 @@ int of_open(const OptionsContext *o, const char *filename, Scheduler *sch)
     if (err < 0)
         return err;
 
-    err = set_dispositions(mux, o);
+    err = set_default_dispositions(mux, o);
     if (err < 0) {
-        av_log(mux, AV_LOG_FATAL, "Error setting output stream dispositions\n");
+        av_log(mux, AV_LOG_FATAL, "Error setting output stream default dispositions\n");
         return err;
     }
 
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.