The branch, master has been updated
       via  a979c9b9358aef55612eec4b14c098efba350dcd (commit)
       via  5ff8395e7806ad27743829b047067098c288782a (commit)
       via  433d18a1d99dbfca48ca1b16e38a2a032d140a7a (commit)
       via  dec3cc01385c2eda49fec359dd6bf88e5177dec0 (commit)
       via  e7cf188bb61157870e87eafaa2932ab516f60787 (commit)
       via  9dcd25b7cd885c1c576bec1021736e8e5e4c78d6 (commit)
       via  cce85642c90097e235086e65ddfc0f41c5f90608 (commit)
       via  c751ad2c3690e2dd16de7dfefa651779e6a9498b (commit)
       via  99ec0752d7e47d78b38c4e8767d2615cfc503504 (commit)
       via  ba0dc3d49eda6b692d7f4ecd4e8c565739685112 (commit)
       via  7b18beb477f1716b8abac15df91410dc967be2e9 (commit)
       via  7ac1b410e12ff0952be49ad9aff0dba0f0406f8f (commit)
       via  6879c8ee5dd0cd8e8643d9ebbc3f95ea34a7fa1f (commit)
      from  0242cb36a576721ee6fb9bbbf70616dacb9957b3 (commit)


- Log -----------------------------------------------------------------
commit a979c9b9358aef55612eec4b14c098efba350dcd
Author:     James Almer <[email protected]>
AuthorDate: Wed Oct 22 14:55:01 2025 -0300
Commit:     James Almer <[email protected]>
CommitDate: Thu Oct 30 11:02:01 2025 -0300

    tests/ffmpeg: add test for HEIF automatic tile merging
    
    Signed-off-by: James Almer <[email protected]>

diff --git a/tests/fate/ffmpeg.mak b/tests/fate/ffmpeg.mak
index 360e62ebbe..57028a7936 100644
--- a/tests/fate/ffmpeg.mak
+++ b/tests/fate/ffmpeg.mak
@@ -267,3 +267,16 @@ FATE_FFMPEG-$(call ENCDEC2, MPEG2VIDEO, FFV1, NUT, 
HSTACK_FILTER PIPE_PROTOCOL F
 # test matching by stream disposition
 fate-ffmpeg-spec-disposition: CMD = framecrc -i 
$(TARGET_SAMPLES)/mpegts/pmtchange.ts -map 
'0:disp:visual_impaired+descriptions:1' -c copy
 FATE_SAMPLES_FFMPEG-$(call FRAMECRC, MPEGTS,,) += fate-ffmpeg-spec-disposition
+
+# test heif image merging using internally defined filtegraphs
+# picking the stream group if not mapping any specific stream
+fate-ffmpeg-heif-merge: CMD = framecrc -i 
$(TARGET_SAMPLES)/heif-conformance/C007.heic
+FATE_SAMPLES_FFMPEG-$(call FRAMECRC, MOV, HEVC, HEVC_PARSER) += 
fate-ffmpeg-heif-merge
+
+# mapping the stream group
+fate-ffmpeg-heif-merge-mapped: CMD = framecrc -i 
$(TARGET_SAMPLES)/heif-conformance/C007.heic -map '[0:g:0]'
+FATE_SAMPLES_FFMPEG-$(call FRAMECRC, MOV, HEVC, HEVC_PARSER) += 
fate-ffmpeg-heif-merge-mapped
+
+# binding the internal filtegraph with a caller defined filtergraph
+fate-ffmpeg-heif-merge-filtergraph: CMD = framecrc -i 
$(TARGET_SAMPLES)/heif-conformance/C007.heic -filter_complex 
"sws_flags=+accurate_rnd+bitexact\;[0:g:0]scale=w=1280:h=720[out]" -map "[out]"
+FATE_SAMPLES_FFMPEG-$(call FRAMECRC, MOV, HEVC, HEVC_PARSER SCALE_FILTER) += 
fate-ffmpeg-heif-merge-filtergraph
diff --git a/tests/ref/fate/ffmpeg-heif-merge b/tests/ref/fate/ffmpeg-heif-merge
new file mode 100644
index 0000000000..4ccc3274f5
--- /dev/null
+++ b/tests/ref/fate/ffmpeg-heif-merge
@@ -0,0 +1,6 @@
+#tb 0: 1/1
+#media_type 0: video
+#codec_id 0: rawvideo
+#dimensions 0: 2560x1440
+#sar 0: 0/1
+0,          0,          0,        1,  5529600, 0x3bf5d001
diff --git a/tests/ref/fate/ffmpeg-heif-merge-filtergraph 
b/tests/ref/fate/ffmpeg-heif-merge-filtergraph
new file mode 100644
index 0000000000..dfc8c7d868
--- /dev/null
+++ b/tests/ref/fate/ffmpeg-heif-merge-filtergraph
@@ -0,0 +1,6 @@
+#tb 0: 1/1
+#media_type 0: video
+#codec_id 0: rawvideo
+#dimensions 0: 1280x720
+#sar 0: 0/1
+0,          0,          0,        1,  1382400, 0x0f97ebd5
diff --git a/tests/ref/fate/ffmpeg-heif-merge-mapped 
b/tests/ref/fate/ffmpeg-heif-merge-mapped
new file mode 100644
index 0000000000..4ccc3274f5
--- /dev/null
+++ b/tests/ref/fate/ffmpeg-heif-merge-mapped
@@ -0,0 +1,6 @@
+#tb 0: 1/1
+#media_type 0: video
+#codec_id 0: rawvideo
+#dimensions 0: 2560x1440
+#sar 0: 0/1
+0,          0,          0,        1,  5529600, 0x3bf5d001

commit 5ff8395e7806ad27743829b047067098c288782a
Author:     James Almer <[email protected]>
AuthorDate: Mon Oct 20 19:04:29 2025 -0300
Commit:     James Almer <[email protected]>
CommitDate: Thu Oct 30 11:02:01 2025 -0300

    fftools/ffmpeg_demux: create a filtegraph to merge HEIF tiles automatically
    
    Signed-off-by: James Almer <[email protected]>

diff --git a/fftools/ffmpeg_demux.c b/fftools/ffmpeg_demux.c
index 6c9834746f..6901f59dd0 100644
--- a/fftools/ffmpeg_demux.c
+++ b/fftools/ffmpeg_demux.c
@@ -1641,14 +1641,102 @@ static DemuxStreamGroup 
*demux_stream_group_alloc(Demuxer *d, AVStreamGroup *stg
     return dsg;
 }
 
+static int istg_parse_tile_grid(const OptionsContext *o, Demuxer *d, 
InputStreamGroup *istg)
+{
+    InputFile *f = &d->f;
+    AVFormatContext *ic = d->f.ctx;
+    AVStreamGroup *stg = istg->stg;
+    const AVStreamGroupTileGrid *tg = stg->params.tile_grid;
+    OutputFilterOptions opts;
+    AVBPrint bp;
+    char *graph_str;
+    int autorotate = 1;
+    const char *apply_cropping = NULL;
+    int  ret;
+
+    if (tg->nb_tiles == 1)
+        return 0;
+
+    memset(&opts, 0, sizeof(opts));
+
+    opt_match_per_stream_group_int(istg, &o->autorotate, ic, stg, &autorotate);
+    if (autorotate)
+        opts.flags |= OFILTER_FLAG_AUTOROTATE;
+
+    opts.flags |= OFILTER_FLAG_CROP;
+    opt_match_per_stream_group_str(istg, &o->apply_cropping, ic, stg, 
&apply_cropping);
+    if (apply_cropping) {
+        char *p;
+        int crop = strtol(apply_cropping, &p, 0);
+        if (*p)
+            return AVERROR(EINVAL);
+        if (!crop)
+            opts.flags &= ~OFILTER_FLAG_CROP;
+    }
+
+    av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
+    for (int i = 0; i < tg->nb_tiles; i++)
+        av_bprintf(&bp, "[%d:g:%d:%d]", f->index, stg->index, 
tg->offsets[i].idx);
+    av_bprintf(&bp, "xstack=inputs=%d:layout=", tg->nb_tiles);
+    for (int i = 0; i < tg->nb_tiles - 1; i++)
+        av_bprintf(&bp, "%d_%d|", tg->offsets[i].horizontal,
+                                  tg->offsets[i].vertical);
+    av_bprintf(&bp, "%d_%d:fill=0x%02X%02X%02X@0x%02X", 
tg->offsets[tg->nb_tiles - 1].horizontal,
+                                                        
tg->offsets[tg->nb_tiles - 1].vertical,
+                                                        tg->background[0], 
tg->background[1],
+                                                        tg->background[2], 
tg->background[3]);
+    av_bprintf(&bp, "[%d:g:%d]", f->index, stg->index);
+    ret = av_bprint_finalize(&bp, &graph_str);
+    if (ret < 0)
+        return ret;
+
+    if (tg->coded_width != tg->width || tg->coded_height != tg->height) {
+        opts.crop_top    = tg->vertical_offset;
+        opts.crop_bottom = tg->coded_height - tg->height - tg->vertical_offset;
+        opts.crop_left   = tg->horizontal_offset;
+        opts.crop_right  = tg->coded_width - tg->width - tg->horizontal_offset;
+    }
+
+    for (int i = 0; i < tg->nb_coded_side_data; i++) {
+        const AVPacketSideData *sd = &tg->coded_side_data[i];
+
+        ret = av_packet_side_data_to_frame(&opts.side_data, 
&opts.nb_side_data, sd, 0);
+        if (ret < 0 && ret != AVERROR(EINVAL))
+            return ret;
+    }
+
+    ret = fg_create(NULL, graph_str, d->sch, &opts);
+    if (ret < 0)
+        return ret;
+
+    istg->fg = filtergraphs[nb_filtergraphs-1];
+    istg->fg->is_internal = 1;
+
+    return 0;
+}
+
 static int istg_add(const OptionsContext *o, Demuxer *d, AVStreamGroup *stg)
 {
     DemuxStreamGroup *dsg;
+    InputStreamGroup *istg;
+    int ret;
 
     dsg = demux_stream_group_alloc(d, stg);
     if (!dsg)
         return AVERROR(ENOMEM);
 
+    istg = &dsg->istg;
+
+    switch (stg->type) {
+    case AV_STREAM_GROUP_PARAMS_TILE_GRID:
+        ret = istg_parse_tile_grid(o, d, istg);
+        if (ret < 0)
+            return ret;
+        break;
+    default:
+        break;
+    }
+
     return 0;
 }
 
diff --git a/fftools/ffmpeg_mux_init.c b/fftools/ffmpeg_mux_init.c
index 7c7302bac1..be1dbad479 100644
--- a/fftools/ffmpeg_mux_init.c
+++ b/fftools/ffmpeg_mux_init.c
@@ -1630,6 +1630,12 @@ static int map_auto_video(Muxer *mux, const 
OptionsContext *o)
             }
 
             switch (istg->stg->type) {
+            case AV_STREAM_GROUP_PARAMS_TILE_GRID: {
+                const AVStreamGroupTileGrid *tg = istg->stg->params.tile_grid;
+                score += tg->width * (int64_t)tg->height
+                           + 5000000*!!(istg->stg->disposition & 
AV_DISPOSITION_DEFAULT);
+                break;
+            }
             default:
                 continue;
             }

commit 433d18a1d99dbfca48ca1b16e38a2a032d140a7a
Author:     James Almer <[email protected]>
AuthorDate: Mon Oct 20 19:04:22 2025 -0300
Commit:     James Almer <[email protected]>
CommitDate: Thu Oct 30 11:02:01 2025 -0300

    fftools/ffmpeg_filter: handle metadata from stream group in relevant the 
filtergraphs
    
    Signed-off-by: James Almer <[email protected]>

diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index 56dea32ac8..c866980251 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -300,6 +300,8 @@ enum OFilterFlags {
     // produce 24-bit audio
     OFILTER_FLAG_AUDIO_24BIT        = (1 << 1),
     OFILTER_FLAG_AUTOSCALE          = (1 << 2),
+    OFILTER_FLAG_AUTOROTATE         = (1 << 3),
+    OFILTER_FLAG_CROP               = (1 << 4),
 };
 
 typedef struct OutputFilterOptions {
@@ -333,6 +335,11 @@ typedef struct OutputFilterOptions {
     enum AVColorRange   color_range;
     enum AVAlphaMode    alpha_mode;
 
+    unsigned            crop_top;
+    unsigned            crop_bottom;
+    unsigned            crop_left;
+    unsigned            crop_right;
+
     enum VideoSyncMethod vsync_method;
     AVRational           frame_rate;
     AVRational           max_frame_rate;
@@ -348,6 +355,9 @@ typedef struct OutputFilterOptions {
     const enum AVColorRange  *color_ranges;
     const enum AVAlphaMode   *alpha_modes;
 
+    AVFrameSideData   **side_data;
+    int                 nb_side_data;
+
     // for simple filtergraphs only, view specifier passed
     // along to the decoder
     const ViewSpecifier *vs;
@@ -827,7 +837,8 @@ int ofilter_bind_enc(OutputFilter *ofilter,
  * @param graph_desc Graph description; an av_malloc()ed string, filtergraph
  *                   takes ownership of it.
  */
-int fg_create(FilterGraph **pfg, char *graph_desc, Scheduler *sch);
+int fg_create(FilterGraph **pfg, char *graph_desc, Scheduler *sch,
+              const OutputFilterOptions *opts);
 
 void fg_free(FilterGraph **pfg);
 
diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index c8aec3d3f7..b0244fa774 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -204,6 +204,11 @@ typedef struct OutputFilterPriv {
     enum AVColorRange       color_range;
     enum AVAlphaMode        alpha_mode;
 
+    unsigned                crop_top;
+    unsigned                crop_bottom;
+    unsigned                crop_left;
+    unsigned                crop_right;
+
     AVFrameSideData       **side_data;
     int                     nb_side_data;
 
@@ -228,6 +233,8 @@ typedef struct OutputFilterPriv {
     const enum AVColorRange *color_ranges;
     const enum AVAlphaMode *alpha_modes;
 
+    int32_t                 displaymatrix[9];
+
     AVRational              enc_timebase;
     int64_t                 trim_start_us;
     int64_t                 trim_duration_us;
@@ -818,7 +825,7 @@ int ofilter_bind_enc(OutputFilter *ofilter, unsigned 
sched_idx_enc,
     ofp->needed = ofilter->bound = 1;
     av_freep(&ofilter->linklabel);
 
-    ofp->flags        = opts->flags;
+    ofp->flags       |= opts->flags;
     ofp->ts_offset    = opts->ts_offset;
     ofp->enc_timebase = opts->output_tb;
 
@@ -1078,7 +1085,8 @@ static const AVClass fg_class = {
     .category   = AV_CLASS_CATEGORY_FILTER,
 };
 
-int fg_create(FilterGraph **pfg, char *graph_desc, Scheduler *sch)
+int fg_create(FilterGraph **pfg, char *graph_desc, Scheduler *sch,
+              const OutputFilterOptions *opts)
 {
     FilterGraphPriv *fgp;
     FilterGraph      *fg;
@@ -1175,11 +1183,13 @@ int fg_create(FilterGraph **pfg, char *graph_desc, 
Scheduler *sch)
         const enum AVMediaType type = 
avfilter_pad_get_type(cur->filter_ctx->output_pads,
                                                             cur->pad_idx);
         OutputFilter *const ofilter = ofilter_alloc(fg, type);
+        OutputFilterPriv *ofp;
 
         if (!ofilter) {
             ret = AVERROR(ENOMEM);
             goto fail;
         }
+        ofp = ofp_from_ofilter(ofilter);
 
         ofilter->linklabel = cur->name;
         cur->name          = NULL;
@@ -1189,6 +1199,25 @@ int fg_create(FilterGraph **pfg, char *graph_desc, 
Scheduler *sch)
             ret = AVERROR(ENOMEM);
             goto fail;
         }
+
+        // opts should only be needed in this function to fill fields from 
filtergraphs
+        // whose output is meant to be treated as if it was stream, e.g. 
merged HEIF
+        // tile groups.
+        if (opts) {
+            ofp->flags        = opts->flags;
+            ofp->side_data    = opts->side_data;
+            ofp->nb_side_data = opts->nb_side_data;
+
+            ofp->crop_top     = opts->crop_top;
+            ofp->crop_bottom  = opts->crop_bottom;
+            ofp->crop_left    = opts->crop_left;
+            ofp->crop_right   = opts->crop_right;
+
+            const AVFrameSideData *sd = av_frame_side_data_get(ofp->side_data, 
ofp->nb_side_data,
+                                                               
AV_FRAME_DATA_DISPLAYMATRIX);
+            if (sd)
+                memcpy(ofp->displaymatrix, sd->data, 
sizeof(ofp->displaymatrix));
+        }
     }
 
     if (!fg->nb_outputs) {
@@ -1225,7 +1254,7 @@ int fg_create_simple(FilterGraph **pfg,
     FilterGraphPriv *fgp;
     int ret;
 
-    ret = fg_create(pfg, graph_desc, sch);
+    ret = fg_create(pfg, graph_desc, sch, NULL);
     if (ret < 0)
         return ret;
     fg  = *pfg;
@@ -1603,6 +1632,53 @@ static int configure_output_video_filter(FilterGraphPriv 
*fgp, AVFilterGraph *gr
     if (ret < 0)
         return ret;
 
+    if (ofp->flags & OFILTER_FLAG_CROP) {
+        char crop_buf[64];
+        snprintf(crop_buf, sizeof(crop_buf), "w=iw-%u-%u:h=ih-%u-%u:x=%u:y=%u",
+                 ofp->crop_left, ofp->crop_right,
+                 ofp->crop_top,  ofp->crop_bottom,
+                 ofp->crop_left, ofp->crop_top);
+        ret = insert_filter(&last_filter, &pad_idx, "crop", crop_buf);
+        if (ret < 0)
+            return ret;
+    }
+
+    if (ofp->flags & OFILTER_FLAG_AUTOROTATE) {
+        int32_t *displaymatrix = ofp->displaymatrix;
+        double theta;
+
+        theta = get_rotation(displaymatrix);
+
+        if (fabs(theta - 90) < 1.0) {
+            ret = insert_filter(&last_filter, &pad_idx, "transpose",
+                                displaymatrix[3] > 0 ? "cclock_flip" : 
"clock");
+        } else if (fabs(theta - 180) < 1.0) {
+            if (displaymatrix[0] < 0) {
+                ret = insert_filter(&last_filter, &pad_idx, "hflip", NULL);
+                if (ret < 0)
+                    return ret;
+            }
+            if (displaymatrix[4] < 0) {
+                ret = insert_filter(&last_filter, &pad_idx, "vflip", NULL);
+            }
+        } else if (fabs(theta - 270) < 1.0) {
+            ret = insert_filter(&last_filter, &pad_idx, "transpose",
+                                displaymatrix[3] < 0 ? "clock_flip" : 
"cclock");
+        } else if (fabs(theta) > 1.0) {
+            char rotate_buf[64];
+            snprintf(rotate_buf, sizeof(rotate_buf), "%f*PI/180", theta);
+            ret = insert_filter(&last_filter, &pad_idx, "rotate", rotate_buf);
+        } else if (fabs(theta) < 1.0) {
+            if (displaymatrix && displaymatrix[4] < 0) {
+                ret = insert_filter(&last_filter, &pad_idx, "vflip", NULL);
+            }
+        }
+        if (ret < 0)
+            return ret;
+
+        av_frame_side_data_remove(&ofp->side_data, &ofp->nb_side_data, 
AV_FRAME_DATA_DISPLAYMATRIX);
+    }
+
     if ((ofp->width || ofp->height) && (ofp->flags & OFILTER_FLAG_AUTOSCALE)) {
         char args[255];
         AVFilterContext *filter;
@@ -2100,12 +2176,11 @@ static int configure_filtergraph(FilterGraph *fg, 
FilterGraphThread *fgt)
         ret = av_buffersink_get_ch_layout(sink, &ofp->ch_layout);
         if (ret < 0)
             goto fail;
-        av_frame_side_data_free(&ofp->side_data, &ofp->nb_side_data);
         sd = av_buffersink_get_side_data(sink, &nb_sd);
         if (nb_sd)
             for (int j = 0; j < nb_sd; j++) {
                 ret = av_frame_side_data_clone(&ofp->side_data, 
&ofp->nb_side_data,
-                                               sd[j], 0);
+                                               sd[j], 
AV_FRAME_SIDE_DATA_FLAG_REPLACE);
                 if (ret < 0) {
                     av_frame_side_data_free(&ofp->side_data, 
&ofp->nb_side_data);
                     goto fail;
diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c
index 381360c16a..f51523d2e0 100644
--- a/fftools/ffmpeg_opt.c
+++ b/fftools/ffmpeg_opt.c
@@ -1496,7 +1496,7 @@ int ffmpeg_parse_options(int argc, char **argv, Scheduler 
*sch)
 
     /* create complex filtergraphs */
     for (int i = 0; i < go.nb_filtergraphs; i++) {
-        ret = fg_create(NULL, go.filtergraphs[i], sch);
+        ret = fg_create(NULL, go.filtergraphs[i], sch, NULL);
         go.filtergraphs[i] = NULL;
         if (ret < 0)
             goto fail;

commit dec3cc01385c2eda49fec359dd6bf88e5177dec0
Author:     James Almer <[email protected]>
AuthorDate: Sat Oct 18 21:37:33 2025 -0300
Commit:     James Almer <[email protected]>
CommitDate: Thu Oct 30 11:02:01 2025 -0300

    fftools/ffmpeg_filter: allow binding unlabeled filtergraphs
    
    Signed-off-by: James Almer <[email protected]>

diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index 642c69e975..c8aec3d3f7 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -1383,6 +1383,33 @@ static int fg_complex_bind_input(FilterGraph *fg, 
InputFilter *ifilter, int comm
                    "Binding input with label '%s' to input stream %d:%d\n",
                    ifilter->linklabel, ist->file->index, ist->index);
     } else {
+        // try finding an unbound filtergraph output
+        for (int i = 0; i < nb_filtergraphs; i++) {
+            FilterGraph *fg_src = filtergraphs[i];
+
+            if (fg == fg_src)
+                continue;
+
+            for (int j = 0; j < fg_src->nb_outputs; j++) {
+                OutputFilter *ofilter = fg_src->outputs[j];
+
+                if (!ofilter->bound) {
+                    if (commit) {
+                        av_log(fg, AV_LOG_VERBOSE,
+                               "Binding unlabeled filtergraph input to 
filtergraph output %d:%d\n", i, j);
+
+                        ret = ifilter_bind_fg(ifp, fg_src, j);
+                        if (ret < 0) {
+                            av_log(fg, AV_LOG_ERROR, "Error binding 
filtergraph input %d:%d\n", i, j);
+                            return ret;
+                        }
+                    } else
+                        ofp_from_ofilter(ofilter)->needed = 1;
+                    return 0;
+                }
+            }
+        }
+
         ist = ist_find_unused(type);
         if (!ist) {
             av_log(fg, AV_LOG_FATAL,

commit e7cf188bb61157870e87eafaa2932ab516f60787
Author:     James Almer <[email protected]>
AuthorDate: Sat Oct 18 21:40:24 2025 -0300
Commit:     James Almer <[email protected]>
CommitDate: Thu Oct 30 11:02:01 2025 -0300

    fftools/ffmpeg_filter: reindent after the previous commit
    
    Signed-off-by: James Almer <[email protected]>

diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index c4df1f3055..642c69e975 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -1318,16 +1318,16 @@ static int fg_complex_bind_input(FilterGraph *fg, 
InputFilter *ifilter, int comm
                 if (!ofilter->bound && ofilter->linklabel &&
                     !strcmp(ofilter->linklabel, ifilter->linklabel)) {
                     if (commit) {
-                    av_log(fg, AV_LOG_VERBOSE,
-                           "Binding input with label '%s' to filtergraph 
output %d:%d\n",
-                           ifilter->linklabel, i, j);
-
-                    ret = ifilter_bind_fg(ifp, fg_src, j);
-                    if (ret < 0) {
-                        av_log(fg, AV_LOG_ERROR, "Error binding filtergraph 
input %s\n",
-                               ifilter->linklabel);
-                    return ret;
-                    }
+                        av_log(fg, AV_LOG_VERBOSE,
+                               "Binding input with label '%s' to filtergraph 
output %d:%d\n",
+                               ifilter->linklabel, i, j);
+
+                        ret = ifilter_bind_fg(ifp, fg_src, j);
+                        if (ret < 0) {
+                            av_log(fg, AV_LOG_ERROR, "Error binding 
filtergraph input %s\n",
+                                   ifilter->linklabel);
+                            return ret;
+                        }
                     } else
                         ofp_from_ofilter(ofilter)->needed = 1;
                     return 0;
@@ -1379,9 +1379,9 @@ static int fg_complex_bind_input(FilterGraph *fg, 
InputFilter *ifilter, int comm
         ist = input_files[file_idx]->streams[st->index];
 
         if (commit)
-        av_log(fg, AV_LOG_VERBOSE,
-               "Binding input with label '%s' to input stream %d:%d\n",
-               ifilter->linklabel, ist->file->index, ist->index);
+            av_log(fg, AV_LOG_VERBOSE,
+                   "Binding input with label '%s' to input stream %d:%d\n",
+                   ifilter->linklabel, ist->file->index, ist->index);
     } else {
         ist = ist_find_unused(type);
         if (!ist) {
@@ -1393,20 +1393,20 @@ static int fg_complex_bind_input(FilterGraph *fg, 
InputFilter *ifilter, int comm
         }
 
         if (commit)
-        av_log(fg, AV_LOG_VERBOSE,
-               "Binding unlabeled input %d to input stream %d:%d\n",
-               ifilter->index, ist->file->index, ist->index);
+            av_log(fg, AV_LOG_VERBOSE,
+                   "Binding unlabeled input %d to input stream %d:%d\n",
+                   ifilter->index, ist->file->index, ist->index);
     }
     av_assert0(ist);
 
     if (commit) {
-    ret = ifilter_bind_ist(ifilter, ist, &vs);
-    if (ret < 0) {
-        av_log(fg, AV_LOG_ERROR,
-               "Error binding an input stream to complex filtergraph input 
%s.\n",
-               ifilter->name);
-        return ret;
-    }
+        ret = ifilter_bind_ist(ifilter, ist, &vs);
+        if (ret < 0) {
+            av_log(fg, AV_LOG_ERROR,
+                   "Error binding an input stream to complex filtergraph input 
%s.\n",
+                   ifilter->name);
+            return ret;
+        }
     }
 
     return 0;

commit 9dcd25b7cd885c1c576bec1021736e8e5e4c78d6
Author:     James Almer <[email protected]>
AuthorDate: Sat Oct 18 21:06:25 2025 -0300
Commit:     James Almer <[email protected]>
CommitDate: Thu Oct 30 11:02:01 2025 -0300

    fftools/ffmpeg_filter: allow removing filtergraphs that contain unbound 
outputs
    
    Actual practical implementation of the previous commit.
    
    Signed-off-by: James Almer <[email protected]>

diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index bf2c6c8c71..56dea32ac8 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -403,6 +403,11 @@ typedef struct FilterGraph {
     OutputFilter **outputs;
     int         nb_outputs;
 
+    // true when the filtergraph is created internally for
+    // purposes like stream group merging. Meant to be freed
+    // if unbound.
+    int                 is_internal;
+
     const char      *graph_desc;
     struct AVBPrint graph_print_buf;
 } FilterGraph;
diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index 1e1c423789..c4df1f3055 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -193,6 +193,8 @@ typedef struct OutputFilterPriv {
     void                   *log_parent;
     char                    log_name[32];
 
+    int                     needed;
+
     /* desired output stream properties */
     int                     format;
     int                     width, height;
@@ -813,7 +815,7 @@ int ofilter_bind_enc(OutputFilter *ofilter, unsigned 
sched_idx_enc,
     av_assert0(!opts->enc ||
                ofilter->type == opts->enc->type);
 
-    ofilter->bound = 1;
+    ofp->needed = ofilter->bound = 1;
     av_freep(&ofilter->linklabel);
 
     ofp->flags        = opts->flags;
@@ -924,7 +926,7 @@ static int ofilter_bind_ifilter(OutputFilter *ofilter, 
InputFilterPriv *ifp,
     av_assert0(!ofilter->bound);
     av_assert0(ofilter->type == ifp->ifilter.type);
 
-    ofilter->bound = 1;
+    ofp->needed = ofilter->bound = 1;
     av_freep(&ofilter->linklabel);
 
     ofilter->output_name = av_strdup(opts->name);
@@ -1264,7 +1266,7 @@ int fg_create_simple(FilterGraph **pfg,
     return 0;
 }
 
-static int fg_complex_bind_input(FilterGraph *fg, InputFilter *ifilter)
+static int fg_complex_bind_input(FilterGraph *fg, InputFilter *ifilter, int 
commit)
 {
     InputFilterPriv *ifp = ifp_from_ifilter(ifilter);
     InputStream *ist = NULL;
@@ -1315,15 +1317,20 @@ static int fg_complex_bind_input(FilterGraph *fg, 
InputFilter *ifilter)
 
                 if (!ofilter->bound && ofilter->linklabel &&
                     !strcmp(ofilter->linklabel, ifilter->linklabel)) {
+                    if (commit) {
                     av_log(fg, AV_LOG_VERBOSE,
                            "Binding input with label '%s' to filtergraph 
output %d:%d\n",
                            ifilter->linklabel, i, j);
 
                     ret = ifilter_bind_fg(ifp, fg_src, j);
-                    if (ret < 0)
+                    if (ret < 0) {
                         av_log(fg, AV_LOG_ERROR, "Error binding filtergraph 
input %s\n",
                                ifilter->linklabel);
                     return ret;
+                    }
+                    } else
+                        ofp_from_ofilter(ofilter)->needed = 1;
+                    return 0;
                 }
             }
         }
@@ -1371,6 +1378,7 @@ static int fg_complex_bind_input(FilterGraph *fg, 
InputFilter *ifilter)
         }
         ist = input_files[file_idx]->streams[st->index];
 
+        if (commit)
         av_log(fg, AV_LOG_VERBOSE,
                "Binding input with label '%s' to input stream %d:%d\n",
                ifilter->linklabel, ist->file->index, ist->index);
@@ -1384,12 +1392,14 @@ static int fg_complex_bind_input(FilterGraph *fg, 
InputFilter *ifilter)
             return AVERROR(EINVAL);
         }
 
+        if (commit)
         av_log(fg, AV_LOG_VERBOSE,
                "Binding unlabeled input %d to input stream %d:%d\n",
                ifilter->index, ist->file->index, ist->index);
     }
     av_assert0(ist);
 
+    if (commit) {
     ret = ifilter_bind_ist(ifilter, ist, &vs);
     if (ret < 0) {
         av_log(fg, AV_LOG_ERROR,
@@ -1397,11 +1407,12 @@ static int fg_complex_bind_input(FilterGraph *fg, 
InputFilter *ifilter)
                ifilter->name);
         return ret;
     }
+    }
 
     return 0;
 }
 
-static int bind_inputs(FilterGraph *fg)
+static int bind_inputs(FilterGraph *fg, int commit)
 {
     // bind filtergraph inputs to input streams or other filtergraphs
     for (int i = 0; i < fg->nb_inputs; i++) {
@@ -1411,7 +1422,7 @@ static int bind_inputs(FilterGraph *fg)
         if (ifp->bound)
             continue;
 
-        ret = fg_complex_bind_input(fg, &ifp->ifilter);
+        ret = fg_complex_bind_input(fg, &ifp->ifilter, commit);
         if (ret < 0)
             return ret;
     }
@@ -1424,27 +1435,49 @@ int fg_finalise_bindings(void)
     int ret;
 
     for (int i = 0; i < nb_filtergraphs; i++) {
-        ret = bind_inputs(filtergraphs[i]);
+        ret = bind_inputs(filtergraphs[i], 0);
         if (ret < 0)
             return ret;
     }
 
     // check that all outputs were bound
-    for (int i = 0; i < nb_filtergraphs; i++) {
+    for (int i = nb_filtergraphs - 1; i >= 0; i--) {
         FilterGraph *fg = filtergraphs[i];
+        FilterGraphPriv *fgp = fgp_from_fg(filtergraphs[i]);
 
         for (int j = 0; j < fg->nb_outputs; j++) {
             OutputFilter *output = fg->outputs[j];
-            if (!output->bound) {
-                av_log(fg, AV_LOG_FATAL,
-                       "Filter '%s' has output %d (%s) unconnected\n",
+            if (!ofp_from_ofilter(output)->needed) {
+                if (!fg->is_internal) {
+                    av_log(fg, AV_LOG_FATAL,
+                           "Filter '%s' has output %d (%s) unconnected\n",
+                           output->name, j,
+                           output->linklabel ? (const char *)output->linklabel 
: "unlabeled");
+                    return AVERROR(EINVAL);
+                }
+
+                av_log(fg, AV_LOG_DEBUG,
+                       "Internal filter '%s' has output %d (%s) unconnected. 
Removing graph\n",
                        output->name, j,
                        output->linklabel ? (const char *)output->linklabel : 
"unlabeled");
-                return AVERROR(EINVAL);
+                sch_remove_filtergraph(fgp->sch, fgp->sch_idx);
+                fg_free(&filtergraphs[i]);
+                nb_filtergraphs--;
+                if (nb_filtergraphs > 0)
+                    memmove(&filtergraphs[i],
+                            &filtergraphs[i + 1],
+                            (nb_filtergraphs - i) * sizeof(*filtergraphs));
+                break;
             }
         }
     }
 
+    for (int i = 0; i < nb_filtergraphs; i++) {
+        ret = bind_inputs(filtergraphs[i], 1);
+        if (ret < 0)
+            return ret;
+    }
+
     return 0;
 }
 

commit cce85642c90097e235086e65ddfc0f41c5f90608
Author:     James Almer <[email protected]>
AuthorDate: Sat Oct 18 20:08:49 2025 -0300
Commit:     James Almer <[email protected]>
CommitDate: Thu Oct 30 11:02:01 2025 -0300

    fftools/ffmpeg_sched: add a function to remove a filtergraph from the 
scheduler
    
    For the purpose of merging streams in a stream group, a filtergraph can't be
    created once we know it will be used. Therefore, allow filtergraphs to be
    removed from the scheduler after being added.
    
    Signed-off-by: James Almer <[email protected]>

diff --git a/fftools/ffmpeg_sched.c b/fftools/ffmpeg_sched.c
index d08f4a061d..ed85bb16de 100644
--- a/fftools/ffmpeg_sched.c
+++ b/fftools/ffmpeg_sched.c
@@ -405,6 +405,9 @@ static int task_start(SchTask *task)
 {
     int ret;
 
+    if (!task->parent)
+        return 0;
+
     av_log(task->func_arg, AV_LOG_VERBOSE, "Starting thread...\n");
 
     av_assert0(!task->thread_running);
@@ -454,6 +457,23 @@ static int64_t trailing_dts(const Scheduler *sch, int 
count_finished)
     return min_dts == INT64_MAX ? AV_NOPTS_VALUE : min_dts;
 }
 
+void sch_remove_filtergraph(Scheduler *sch, int idx)
+{
+    SchFilterGraph *fg = &sch->filters[idx];
+
+    av_assert0(!fg->task.thread_running);
+    memset(&fg->task, 0, sizeof(fg->task));
+
+    tq_free(&fg->queue);
+
+    av_freep(&fg->inputs);
+    fg->nb_inputs = 0;
+    av_freep(&fg->outputs);
+    fg->nb_outputs = 0;
+
+    fg->task_exited = 1;
+}
+
 void sch_free(Scheduler **psch)
 {
     Scheduler *sch = *psch;
@@ -2630,6 +2650,9 @@ static int task_stop(Scheduler *sch, SchTask *task)
     int ret;
     void *thread_ret;
 
+    if (!task->parent)
+        return 0;
+
     if (!task->thread_running)
         return task_cleanup(sch, task->node);
 
diff --git a/fftools/ffmpeg_sched.h b/fftools/ffmpeg_sched.h
index 0c01f558e4..2cf3034437 100644
--- a/fftools/ffmpeg_sched.h
+++ b/fftools/ffmpeg_sched.h
@@ -205,6 +205,8 @@ int sch_add_dec_output(Scheduler *sch, unsigned dec_idx);
 int sch_add_filtergraph(Scheduler *sch, unsigned nb_inputs, unsigned 
nb_outputs,
                         SchThreadFunc func, void *ctx);
 
+void sch_remove_filtergraph(Scheduler *sch, int idx);
+
 /**
  * Add a muxer to the scheduler.
  *

commit c751ad2c3690e2dd16de7dfefa651779e6a9498b
Author:     James Almer <[email protected]>
AuthorDate: Sat Oct 18 21:02:21 2025 -0300
Commit:     James Almer <[email protected]>
CommitDate: Thu Oct 30 11:02:01 2025 -0300

    fftools/ffmpeg_mux_init: allow creating streams from filtergraphs created 
out of stream groups
    
    Several formats are being designed where more than one independent video or
    audio stream within a container are part of what should be a single combined
    output. This is the case for HEIF (Defining a grid where the decoded output
    of several separate video streams are to be placed to generate a single 
output
    image) and IAMF (Defining audio streams where channels are present in 
separate
    coded stream).
    
    AVStreamGroup was designed and implemented in libavformat to convey this
    information, but the actual merging is left to the caller.
    This change allows the FFmpeg CLI to take said information, parse it, and
    create filtergraphs to merge the streams, making the combined output be 
usable
    automatically further in the process.
    
    Signed-off-by: James Almer <[email protected]>

diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index 0b9f8a20c6..bf2c6c8c71 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -500,6 +500,7 @@ typedef struct InputStreamGroup {
 
     int                   index;
 
+    FilterGraph          *fg;
     AVStreamGroup        *stg;
 } InputStreamGroup;
 
diff --git a/fftools/ffmpeg_mux_init.c b/fftools/ffmpeg_mux_init.c
index c24b69f2d1..7c7302bac1 100644
--- a/fftools/ffmpeg_mux_init.c
+++ b/fftools/ffmpeg_mux_init.c
@@ -1598,6 +1598,7 @@ fail:
 static int map_auto_video(Muxer *mux, const OptionsContext *o)
 {
     AVFormatContext *oc = mux->fc;
+    InputStreamGroup *best_istg = NULL;
     InputStream *best_ist = NULL;
     int64_t best_score = 0;
     int qcr;
@@ -1609,8 +1610,35 @@ static int map_auto_video(Muxer *mux, const 
OptionsContext *o)
     qcr = avformat_query_codec(oc->oformat, oc->oformat->video_codec, 0);
     for (int j = 0; j < nb_input_files; j++) {
         InputFile *ifile = input_files[j];
+        InputStreamGroup *file_best_istg = NULL;
         InputStream *file_best_ist = NULL;
         int64_t file_best_score = 0;
+        for (int i = 0; i < ifile->nb_stream_groups; i++) {
+            InputStreamGroup *istg = ifile->stream_groups[i];
+            int64_t score = 0;
+
+            if (!istg->fg)
+                continue;
+
+            for (int j = 0; j < istg->stg->nb_streams; j++) {
+                AVStream *st = istg->stg->streams[j];
+
+                if (st->event_flags & AVSTREAM_EVENT_FLAG_NEW_PACKETS) {
+                    score = 100000000;
+                    break;
+                }
+            }
+
+            switch (istg->stg->type) {
+            default:
+                continue;
+            }
+
+            if (score > file_best_score) {
+                file_best_score = score;
+                file_best_istg  = istg;
+            }
+        }
         for (int i = 0; i < ifile->nb_streams; i++) {
             InputStream *ist = ifile->streams[i];
             int64_t score;
@@ -1630,6 +1658,15 @@ static int map_auto_video(Muxer *mux, const 
OptionsContext *o)
                     continue;
                 file_best_score = score;
                 file_best_ist   = ist;
+                file_best_istg  = NULL;
+            }
+        }
+        if (file_best_istg) {
+            file_best_score -= 5000000*!!(file_best_istg->stg->disposition & 
AV_DISPOSITION_DEFAULT);
+            if (file_best_score > best_score) {
+                best_score = file_best_score;
+                best_istg = file_best_istg;
+                best_ist = NULL;
             }
         }
         if (file_best_ist) {
@@ -1639,9 +1676,19 @@ static int map_auto_video(Muxer *mux, const 
OptionsContext *o)
             if (file_best_score > best_score) {
                 best_score = file_best_score;
                 best_ist = file_best_ist;
+                best_istg = NULL;
             }
        }
     }
+    if (best_istg) {
+        FilterGraph *fg = best_istg->fg;
+        OutputFilter *ofilter = fg->outputs[0];
+
+        av_assert0(fg->nb_outputs == 1);
+        av_log(mux, AV_LOG_VERBOSE, "Creating output stream from stream group 
derived complex filtergraph %d.\n", fg->index);
+
+        return ost_add(mux, o, AVMEDIA_TYPE_VIDEO, NULL, ofilter, NULL, NULL);
+    }
     if (best_ist)
         return ost_add(mux, o, AVMEDIA_TYPE_VIDEO, best_ist, NULL, NULL, NULL);
 

commit 99ec0752d7e47d78b38c4e8767d2615cfc503504
Author:     James Almer <[email protected]>
AuthorDate: Sat Oct 18 20:58:21 2025 -0300
Commit:     James Almer <[email protected]>
CommitDate: Thu Oct 30 11:02:01 2025 -0300

    fftools/ffmpeg_demux: add InputStreamGroup to store stream groups
    
    Preparatory work for upcoming changes. Make ffmpeg keep track of stream 
groups
    internally.
    
    Signed-off-by: James Almer <[email protected]>

diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index 155674d303..0b9f8a20c6 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -492,6 +492,17 @@ typedef struct InputStream {
     int                nb_filters;
 } InputStream;
 
+typedef struct InputStreamGroup {
+    const AVClass        *class;
+
+    /* parent source */
+    struct InputFile     *file;
+
+    int                   index;
+
+    AVStreamGroup        *stg;
+} InputStreamGroup;
+
 typedef struct InputFile {
     const AVClass   *class;
 
@@ -513,6 +524,10 @@ typedef struct InputFile {
      * if new streams appear dynamically during demuxing */
     InputStream    **streams;
     int           nb_streams;
+
+    /* stream groups that ffmpeg is aware of; */
+    InputStreamGroup **stream_groups;
+    int           nb_stream_groups;
 } InputFile;
 
 enum forced_keyframes_const {
diff --git a/fftools/ffmpeg_demux.c b/fftools/ffmpeg_demux.c
index d2f0017aeb..6c9834746f 100644
--- a/fftools/ffmpeg_demux.c
+++ b/fftools/ffmpeg_demux.c
@@ -104,6 +104,13 @@ typedef struct DemuxStream {
     int64_t                  lag;
 } DemuxStream;
 
+typedef struct DemuxStreamGroup {
+    InputStreamGroup         istg;
+
+    // name used for logging
+    char                     log_name[32];
+} DemuxStreamGroup;
+
 typedef struct Demuxer {
     InputFile             f;
 
@@ -886,6 +893,16 @@ static void ist_free(InputStream **pist)
     av_freep(pist);
 }
 
+static void istg_free(InputStreamGroup **pistg)
+{
+    InputStreamGroup *istg = *pistg;
+
+    if (!istg)
+        return;
+
+    av_freep(pistg);
+}
+
 void ifile_close(InputFile **pf)
 {
     InputFile *f = *pf;
@@ -901,6 +918,10 @@ void ifile_close(InputFile **pf)
         ist_free(&f->streams[i]);
     av_freep(&f->streams);
 
+    for (int i = 0; i < f->nb_stream_groups; i++)
+        istg_free(&f->stream_groups[i]);
+    av_freep(&f->stream_groups);
+
     avformat_close_input(&f->ctx);
 
     av_packet_free(&d->pkt_heartbeat);
@@ -1586,6 +1607,51 @@ static int ist_add(const OptionsContext *o, Demuxer *d, 
AVStream *st, AVDictiona
     return 0;
 }
 
+static const char *input_stream_group_item_name(void *obj)
+{
+    const DemuxStreamGroup *dsg = obj;
+
+    return dsg->log_name;
+}
+
+static const AVClass input_stream_group_class = {
+    .class_name = "InputStreamGroup",
+    .version    = LIBAVUTIL_VERSION_INT,
+    .item_name  = input_stream_group_item_name,
+    .category   = AV_CLASS_CATEGORY_DEMUXER,
+};
+
+static DemuxStreamGroup *demux_stream_group_alloc(Demuxer *d, AVStreamGroup 
*stg)
+{
+    InputFile    *f = &d->f;
+    DemuxStreamGroup *dsg;
+
+    dsg = allocate_array_elem(&f->stream_groups, sizeof(*dsg), 
&f->nb_stream_groups);
+    if (!dsg)
+        return NULL;
+
+    dsg->istg.stg        = stg;
+    dsg->istg.file       = f;
+    dsg->istg.index      = stg->index;
+    dsg->istg.class      = &input_stream_group_class;
+
+    snprintf(dsg->log_name, sizeof(dsg->log_name), "istg#%d:%d/%s",
+             d->f.index, stg->index, avformat_stream_group_name(stg->type));
+
+    return dsg;
+}
+
+static int istg_add(const OptionsContext *o, Demuxer *d, AVStreamGroup *stg)
+{
+    DemuxStreamGroup *dsg;
+
+    dsg = demux_stream_group_alloc(d, stg);
+    if (!dsg)
+        return AVERROR(ENOMEM);
+
+    return 0;
+}
+
 static int dump_attachment(InputStream *ist, const char *filename)
 {
     AVStream *st = ist->st;
@@ -1954,6 +2020,13 @@ int ifile_open(const OptionsContext *o, const char 
*filename, Scheduler *sch)
         }
     }
 
+    /* Add all the stream groups from the given input file to the demuxer */
+    for (int i = 0; i < ic->nb_stream_groups; i++) {
+        ret = istg_add(o, d, ic->stream_groups[i]);
+        if (ret < 0)
+            return ret;
+    }
+
     /* dump the file content */
     av_dump_format(ic, f->index, filename, 0);
 

commit ba0dc3d49eda6b692d7f4ecd4e8c565739685112
Author:     James Almer <[email protected]>
AuthorDate: Sat Oct 18 20:46:14 2025 -0300
Commit:     James Almer <[email protected]>
CommitDate: Thu Oct 30 11:02:01 2025 -0300

    fftools/ffmpeg_opt: add helpers to match stream groups
    
    Will be used to check for specifiers that match a given stream group and not
    a stream within one.
    
    Signed-off-by: James Almer <[email protected]>

diff --git a/fftools/cmdutils.c b/fftools/cmdutils.c
index 5f79d269b2..35f786dba5 100644
--- a/fftools/cmdutils.c
+++ b/fftools/cmdutils.c
@@ -1350,6 +1350,77 @@ int check_stream_specifier(AVFormatContext *s, AVStream 
*st, const char *spec)
     return ret;
 }
 
+unsigned stream_group_specifier_match(const StreamSpecifier *ss,
+                                      const AVFormatContext *s, const 
AVStreamGroup *stg,
+                                      void *logctx)
+{
+    int start_stream_group = 0, nb_stream_groups;
+    int nb_matched = 0;
+
+    if (ss->idx >= 0)
+        return 0;
+
+    switch (ss->stream_list) {
+    case STREAM_LIST_STREAM_ID:
+    case STREAM_LIST_ALL:
+    case STREAM_LIST_PROGRAM:
+        return 0;
+    case STREAM_LIST_GROUP_ID:
+        // <n-th> stream with given ID makes no sense and should be impossible 
to request
+        av_assert0(ss->idx < 0);
+        // return early if we know for sure the stream does not match
+        if (stg->id != ss->list_id)
+            return 0;
+        start_stream_group = stg->index;
+        nb_stream_groups   = stg->index + 1;
+        break;
+    case STREAM_LIST_GROUP_IDX:
+        start_stream_group = ss->list_id >= 0 ? 0 : stg->index;
+        nb_stream_groups   = stg->index + 1;
+        break;
+    default: av_assert0(0);
+    }
+
+    for (int i = start_stream_group; i < nb_stream_groups; i++) {
+        const AVStreamGroup *candidate = s->stream_groups[i];
+
+        if (ss->meta_key) {
+            const AVDictionaryEntry *tag = av_dict_get(candidate->metadata,
+                                                       ss->meta_key, NULL, 0);
+
+            if (!tag)
+                continue;
+            if (ss->meta_val && strcmp(tag->value, ss->meta_val))
+                continue;
+        }
+
+        if (ss->usable_only) {
+            switch (candidate->type) {
+            case AV_STREAM_GROUP_PARAMS_TILE_GRID: {
+                const AVStreamGroupTileGrid *tg = candidate->params.tile_grid;
+                if (!tg->coded_width || !tg->coded_height || !tg->nb_tiles ||
+                    !tg->width       || !tg->height       || !tg->nb_tiles)
+                    continue;
+                break;
+            }
+            default:
+                continue;
+            }
+        }
+
+        if (ss->disposition &&
+            (candidate->disposition & ss->disposition) != ss->disposition)
+            continue;
+
+        if (stg == candidate)
+            return ss->list_id < 0 || ss->list_id == nb_matched;
+
+        nb_matched++;
+    }
+
+    return 0;
+}
+
 int filter_codec_opts(const AVDictionary *opts, enum AVCodecID codec_id,
                       AVFormatContext *s, AVStream *st, const AVCodec *codec,
                       AVDictionary **dst, AVDictionary **opts_used)
diff --git a/fftools/cmdutils.h b/fftools/cmdutils.h
index c4a3efcf52..93e05c7130 100644
--- a/fftools/cmdutils.h
+++ b/fftools/cmdutils.h
@@ -158,6 +158,10 @@ unsigned stream_specifier_match(const StreamSpecifier *ss,
                                 const AVFormatContext *s, const AVStream *st,
                                 void *logctx);
 
+unsigned stream_group_specifier_match(const StreamSpecifier *ss,
+                                      const AVFormatContext *s, const 
AVStreamGroup *stg,
+                                      void *logctx);
+
 void stream_specifier_uninit(StreamSpecifier *ss);
 
 typedef struct SpecifierOpt {
diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index 8f665f3432..155674d303 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -136,6 +136,7 @@ typedef struct StreamMap {
     int disabled;           /* 1 is this mapping is disabled by a negative map 
*/
     int file_index;
     int stream_index;
+    int group_index;
     char *linklabel;       /* name of an output link, for mapping lavfi 
outputs */
 
     ViewSpecifier vs;
@@ -932,6 +933,15 @@ void opt_match_per_stream_int64(void *logctx, const 
SpecifierOptList *sol,
 void opt_match_per_stream_dbl(void *logctx, const SpecifierOptList *sol,
                               AVFormatContext *fc, AVStream *st, double *out);
 
+void opt_match_per_stream_group_str(void *logctx, const SpecifierOptList *sol,
+                                    AVFormatContext *fc, AVStreamGroup *stg, 
const char **out);
+void opt_match_per_stream_group_int(void *logctx, const SpecifierOptList *sol,
+                                    AVFormatContext *fc, AVStreamGroup *stg, 
int *out);
+void opt_match_per_stream_group_int64(void *logctx, const SpecifierOptList 
*sol,
+                                      AVFormatContext *fc, AVStreamGroup *stg, 
int64_t *out);
+void opt_match_per_stream_group_dbl(void *logctx, const SpecifierOptList *sol,
+                                    AVFormatContext *fc, AVStreamGroup *stg, 
double *out);
+
 int view_specifier_parse(const char **pspec, ViewSpecifier *vs);
 
 int muxer_thread(void *arg);
diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c
index 4152bbf06f..381360c16a 100644
--- a/fftools/ffmpeg_opt.c
+++ b/fftools/ffmpeg_opt.c
@@ -242,6 +242,70 @@ OPT_MATCH_PER_STREAM(int,   int,          OPT_TYPE_INT,    
i);
 OPT_MATCH_PER_STREAM(int64, int64_t,      OPT_TYPE_INT64,  i64);
 OPT_MATCH_PER_STREAM(dbl,   double,       OPT_TYPE_DOUBLE, dbl);
 
+static unsigned opt_match_per_stream_group(void *logctx, enum OptionType type,
+                                           const SpecifierOptList *sol,
+                                           AVFormatContext *fc, AVStreamGroup 
*stg)
+{
+    int matches = 0, match_idx = -1;
+
+    av_assert0((type == sol->type) || !sol->nb_opt);
+
+    for (int i = 0; i < sol->nb_opt; i++) {
+        const StreamSpecifier *ss = &sol->opt[i].stream_spec;
+
+        if (stream_group_specifier_match(ss, fc, stg, logctx)) {
+            match_idx = i;
+            matches++;
+        }
+    }
+
+    if (matches > 1 && sol->opt_canon) {
+        const SpecifierOpt *so = &sol->opt[match_idx];
+        const char *spec = so->specifier && so->specifier[0] ? so->specifier : 
"";
+
+        char namestr[128] = "";
+        char optval_buf[32];
+        const char *optval = optval_buf;
+
+        snprintf(namestr, sizeof(namestr), "-%s", sol->opt_canon->name);
+        if (sol->opt_canon->flags & OPT_HAS_ALT) {
+            const char * const *names_alt = sol->opt_canon->u1.names_alt;
+            for (int i = 0; names_alt[i]; i++)
+                av_strlcatf(namestr, sizeof(namestr), "/-%s", names_alt[i]);
+        }
+
+        switch (sol->type) {
+        case OPT_TYPE_STRING: optval = so->u.str;                              
               break;
+        case OPT_TYPE_INT:    snprintf(optval_buf, sizeof(optval_buf), "%d", 
so->u.i);        break;
+        case OPT_TYPE_INT64:  snprintf(optval_buf, sizeof(optval_buf), 
"%"PRId64, so->u.i64); break;
+        case OPT_TYPE_FLOAT:  snprintf(optval_buf, sizeof(optval_buf), "%f", 
so->u.f);        break;
+        case OPT_TYPE_DOUBLE: snprintf(optval_buf, sizeof(optval_buf), "%f", 
so->u.dbl);      break;
+        default: av_assert0(0);
+        }
+
+        av_log(logctx, AV_LOG_WARNING, "Multiple %s options specified for "
+               "stream group %d, only the last option '-%s%s%s %s' will be 
used.\n",
+               namestr, stg->index, sol->opt_canon->name, spec[0] ? ":" : "",
+               spec, optval);
+    }
+
+    return match_idx + 1;
+}
+
+#define OPT_MATCH_PER_STREAM_GROUP(name, type, opt_type, m)                    
              \
+void opt_match_per_stream_group_ ## name(void *logctx, const SpecifierOptList 
*sol,          \
+                                         AVFormatContext *fc, AVStreamGroup 
*stg, type *out) \
+{                                                                              
              \
+    unsigned ret = opt_match_per_stream_group(logctx, opt_type, sol, fc, stg); 
              \
+    if (ret > 0)                                                               
              \
+        *out = sol->opt[ret - 1].u.m;                                          
              \
+}
+
+OPT_MATCH_PER_STREAM_GROUP(str,   const char *, OPT_TYPE_STRING, str);
+OPT_MATCH_PER_STREAM_GROUP(int,   int,          OPT_TYPE_INT,    i);
+OPT_MATCH_PER_STREAM_GROUP(int64, int64_t,      OPT_TYPE_INT64,  i64);
+OPT_MATCH_PER_STREAM_GROUP(dbl,   double,       OPT_TYPE_DOUBLE, dbl);
+
 int view_specifier_parse(const char **pspec, ViewSpecifier *vs)
 {
     const char *spec = *pspec;
@@ -504,8 +568,10 @@ static int opt_map(void *optctx, const char *opt, const 
char *arg)
     }
 
     if (arg[0] == '[') {
+        ViewSpecifier vs;
         /* this mapping refers to lavfi output */
         const char *c = arg + 1;
+        char *endptr;
 
         ret = GROW_ARRAY(o->stream_maps, o->nb_stream_maps);
         if (ret < 0)
@@ -518,6 +584,27 @@ static int opt_map(void *optctx, const char *opt, const 
char *arg)
             ret = AVERROR(EINVAL);
             goto fail;
         }
+
+        arg++;
+
+        m->group_index = -1;
+        file_idx = strtol(arg, &endptr, 0);
+        if (file_idx >= nb_input_files || file_idx < 0)
+            goto end;
+
+        arg = endptr;
+        ret = stream_specifier_parse(&ss, *arg == ':' ? arg + 1 : arg, 1, 
NULL);
+        if (ret < 0)
+            goto end;
+
+        arg = ss.remainder ? ss.remainder : "";
+        ret = view_specifier_parse(&arg, &vs);
+        if (ret < 0 || (*arg && strcmp(arg, "]")))
+            goto end;
+
+        m->file_index  = file_idx;
+        m->stream_index = ss.idx;
+        m->group_index = ss.stream_list == STREAM_LIST_GROUP_IDX ? ss.list_id 
: -1;
     } else {
         ViewSpecifier vs;
         char *endptr;
@@ -583,6 +670,7 @@ static int opt_map(void *optctx, const char *opt, const 
char *arg)
 
                 m->file_index   = file_idx;
                 m->stream_index = i;
+                m->group_index  = ss.stream_list == STREAM_LIST_GROUP_IDX ? 
ss.list_id : -1;
                 m->vs           = vs;
             }
     }
@@ -602,6 +690,7 @@ static int opt_map(void *optctx, const char *opt, const 
char *arg)
             goto fail;
         }
     }
+end:
     ret = 0;
 fail:
     stream_specifier_uninit(&ss);

commit 7b18beb477f1716b8abac15df91410dc967be2e9
Author:     James Almer <[email protected]>
AuthorDate: Sat Oct 18 20:38:39 2025 -0300
Commit:     James Almer <[email protected]>
CommitDate: Thu Oct 30 11:02:01 2025 -0300

    fftools: pass global side data through using a separate array
    
    This keeps global and per frame side data clearly separated, and actually
    propagates the former as it comes out from the buffersink filter.
    
    Signed-off-by: James Almer <[email protected]>

diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c
index cbeefb71d6..32289112a8 100644
--- a/fftools/ffmpeg.c
+++ b/fftools/ffmpeg.c
@@ -400,6 +400,7 @@ static void frame_data_free(void *opaque, uint8_t *data)
 {
     FrameData *fd = (FrameData *)data;
 
+    av_frame_side_data_free(&fd->side_data, &fd->nb_side_data);
     avcodec_parameters_free(&fd->par_enc);
 
     av_free(data);
@@ -429,6 +430,8 @@ static int frame_data_ensure(AVBufferRef **dst, int 
writable)
 
             memcpy(fd, fd_src, sizeof(*fd));
             fd->par_enc = NULL;
+            fd->side_data = NULL;
+            fd->nb_side_data = 0;
 
             if (fd_src->par_enc) {
                 int ret = 0;
@@ -444,6 +447,16 @@ static int frame_data_ensure(AVBufferRef **dst, int 
writable)
                 }
             }
 
+            if (fd_src->nb_side_data) {
+                int ret = clone_side_data(&fd->side_data, &fd->nb_side_data,
+                                          fd_src->side_data, 
fd_src->nb_side_data, 0);
+                if (ret < 0) {
+                    av_buffer_unref(dst);
+                    av_buffer_unref(&src);
+                    return ret;
+                }
+            }
+
             av_buffer_unref(&src);
         } else {
             fd->dec.frame_num = UINT64_MAX;
diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index 63da1d14df..8f665f3432 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -698,6 +698,9 @@ typedef struct FrameData {
     int64_t wallclock[LATENCY_PROBE_NB];
 
     AVCodecParameters *par_enc;
+
+    AVFrameSideData   **side_data;
+    int                 nb_side_data;
 } FrameData;
 
 extern InputFile   **input_files;
diff --git a/fftools/ffmpeg_enc.c b/fftools/ffmpeg_enc.c
index 84e7e0ca0e..8f07a10848 100644
--- a/fftools/ffmpeg_enc.c
+++ b/fftools/ffmpeg_enc.c
@@ -205,19 +205,10 @@ int enc_open(void *opaque, const AVFrame *frame)
         av_assert0(frame->opaque_ref);
         fd = (FrameData*)frame->opaque_ref->data;
 
-        for (int i = 0; i < frame->nb_side_data; i++) {
-            const AVSideDataDescriptor *desc = 
av_frame_side_data_desc(frame->side_data[i]->type);
-
-            if (!(desc->props & AV_SIDE_DATA_PROP_GLOBAL))
-                continue;
-
-            ret = av_frame_side_data_clone(&enc_ctx->decoded_side_data,
-                                           &enc_ctx->nb_decoded_side_data,
-                                           frame->side_data[i],
-                                           AV_FRAME_SIDE_DATA_FLAG_UNIQUE);
-            if (ret < 0)
-                return ret;
-        }
+        ret = clone_side_data(&enc_ctx->decoded_side_data, 
&enc_ctx->nb_decoded_side_data,
+                              fd->side_data, fd->nb_side_data, 
AV_FRAME_SIDE_DATA_FLAG_UNIQUE);
+        if (ret < 0)
+            return ret;
     }
 
     if (ist)
diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index a23bcbda95..1e1c423789 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -2129,7 +2129,8 @@ static int ifilter_parameters_from_frame(InputFilter 
*ifilter, const AVFrame *fr
     for (int i = 0; i < frame->nb_side_data; i++) {
         const AVSideDataDescriptor *desc = 
av_frame_side_data_desc(frame->side_data[i]->type);
 
-        if (!(desc->props & AV_SIDE_DATA_PROP_GLOBAL))
+        if (!(desc->props & AV_SIDE_DATA_PROP_GLOBAL) ||
+            frame->side_data[i]->type == AV_FRAME_DATA_DISPLAYMATRIX)
             continue;
 
         ret = av_frame_side_data_clone(&ifp->side_data,
@@ -2502,16 +2503,17 @@ static int close_output(OutputFilterPriv *ofp, 
FilterGraphThread *fgt)
             if (ret < 0)
                 return ret;
         }
-        av_frame_side_data_free(&frame->side_data, &frame->nb_side_data);
-        ret = clone_side_data(&frame->side_data, &frame->nb_side_data,
-                              ofp->side_data, ofp->nb_side_data, 0);
-        if (ret < 0)
-            return ret;
 
         fd = frame_data(frame);
         if (!fd)
             return AVERROR(ENOMEM);
 
+        av_frame_side_data_free(&fd->side_data, &fd->nb_side_data);
+        ret = clone_side_data(&fd->side_data, &fd->nb_side_data,
+                              ofp->side_data, ofp->nb_side_data, 0);
+        if (ret < 0)
+            return ret;
+
         fd->frame_rate_filter = ofp->fps.framerate;
 
         av_assert0(!frame->buf[0]);
@@ -2666,6 +2668,14 @@ static int fg_output_step(OutputFilterPriv *ofp, 
FilterGraphThread *fgt,
         return AVERROR(ENOMEM);
     }
 
+    av_frame_side_data_free(&fd->side_data, &fd->nb_side_data);
+    if (!fgt->got_frame) {
+        ret = clone_side_data(&fd->side_data, &fd->nb_side_data,
+                              ofp->side_data, ofp->nb_side_data, 0);
+        if (ret < 0)
+            return ret;
+    }
+
     fd->wallclock[LATENCY_PROBE_FILTER_POST] = av_gettime_relative();
 
     // only use bits_per_raw_sample passed through from the decoder

commit 7ac1b410e12ff0952be49ad9aff0dba0f0406f8f
Author:     James Almer <[email protected]>
AuthorDate: Sat Oct 18 20:28:46 2025 -0300
Commit:     James Almer <[email protected]>
CommitDate: Thu Oct 30 11:02:01 2025 -0300

    fftools/ffmpeg_filter: fix passing certain parameters to inputs bound to a 
filtergraph output
    
    Certain parameters, like calculated framerate, are unavailable when 
connecting
    the output of a filtergraph to the input of another.
    
    This fixes command lines like
    
    ffmpeg -lavfi "testsrc=rate=1:duration=1[out0]" -filter_complex 
"[out0]null[out1]" -map [out1] -y out.png
    
    Signed-off-by: James Almer <[email protected]>

diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index b9a51ad687..a23bcbda95 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -109,6 +109,9 @@ typedef struct InputFilterPriv {
     // used to hold submitted input
     AVFrame            *frame;
 
+    // For inputs bound to a filtergraph output
+    OutputFilter       *ofilter_src;
+
     // source data type: AVMEDIA_TYPE_SUBTITLE for sub2video,
     // same as type otherwise
     enum AVMediaType    type_src;
@@ -928,6 +931,8 @@ static int ofilter_bind_ifilter(OutputFilter *ofilter, 
InputFilterPriv *ifp,
     if (!ofilter->output_name)
         return AVERROR(EINVAL);
 
+    ifp->ofilter_src = ofilter;
+
     av_strlcatf(ofp->log_name, sizeof(ofp->log_name), "->%s", 
ofilter->output_name);
 
     return 0;
@@ -2155,6 +2160,27 @@ static int ifilter_parameters_from_frame(InputFilter 
*ifilter, const AVFrame *fr
     return 0;
 }
 
+static int ifilter_parameters_from_ofilter(InputFilter *ifilter, OutputFilter 
*ofilter)
+{
+    const OutputFilterPriv *ofp = ofp_from_ofilter(ofilter);
+    InputFilterPriv  *ifp = ifp_from_ifilter(ifilter);
+
+    if (!ifp->opts.framerate.num) {
+        ifp->opts.framerate = ofp->fps.framerate;
+        if (ifp->opts.framerate.num > 0 && ifp->opts.framerate.den > 0)
+            ifp->opts.flags |= IFILTER_FLAG_CFR;
+    }
+
+    for (int i = 0; i < ofp->nb_side_data; i++) {
+        int ret = av_frame_side_data_clone(&ifp->side_data, &ifp->nb_side_data,
+                                           ofp->side_data[i], 
AV_FRAME_SIDE_DATA_FLAG_REPLACE);
+        if (ret < 0)
+            return ret;
+    }
+
+    return 0;
+}
+
 int filtergraph_is_simple(const FilterGraph *fg)
 {
     const FilterGraphPriv *fgp = cfgp_from_cfg(fg);
@@ -2935,6 +2961,14 @@ static int send_frame(FilterGraph *fg, FilterGraphThread 
*fgt,
         ret = ifilter_parameters_from_frame(ifilter, frame);
         if (ret < 0)
             return ret;
+
+        /* Inputs bound to a filtergraph output will have some fields unset.
+         * Handle them here */
+        if (ifp->ofilter_src) {
+            ret = ifilter_parameters_from_ofilter(ifilter, ifp->ofilter_src);
+            if (ret < 0)
+                return ret;
+        }
     }
 
     /* (re)init the graph if possible, otherwise buffer the frame and return */

commit 6879c8ee5dd0cd8e8643d9ebbc3f95ea34a7fa1f
Author:     James Almer <[email protected]>
AuthorDate: Sat Oct 18 20:07:15 2025 -0300
Commit:     James Almer <[email protected]>
CommitDate: Thu Oct 30 10:54:01 2025 -0300

    avcodec/avcodec: add helpers to convert between packet and frame side data
    
    They will be used in following commits.
    
    Signed-off-by: James Almer <[email protected]>

diff --git a/doc/APIchanges b/doc/APIchanges
index 7c2ab3cc03..7de9072a47 100644
--- a/doc/APIchanges
+++ b/doc/APIchanges
@@ -2,6 +2,9 @@ The last version increases of all libraries were on 2025-03-28
 
 API changes, most recent first:
 
+2025-10-30 - xxxxxxxxxx - lavc 62.17.100 - packet.h
+  Add av_packet_side_data_from_frame() and av_packet_side_data_to_frame().
+
 2025-10-xx - xxxxxxxxxx - lavu 60.16.100 - pixfmt.h
   Add AVCOL_TRC_EXT_BASE, AVCOL_TRC_V_LOG,
   AVCOL_PRI_EXT_BASE and AVCOL_PRI_V_GAMUT.
diff --git a/libavcodec/avcodec.c b/libavcodec/avcodec.c
index 0355b7c338..db24b4ed52 100644
--- a/libavcodec/avcodec.c
+++ b/libavcodec/avcodec.c
@@ -815,3 +815,53 @@ int avcodec_get_supported_config(const AVCodecContext 
*avctx, const AVCodec *cod
         return ff_default_get_supported_config(avctx, codec, config, flags, 
out, out_num);
     }
 }
+
+int av_packet_side_data_from_frame(AVPacketSideData **psd, int *pnb_sd,
+                                   const AVFrameSideData *src, unsigned int 
flags)
+{
+    AVPacketSideData *sd = NULL;
+
+    for (unsigned j = 0; ff_sd_global_map[j].packet < AV_PKT_DATA_NB; j++) {
+        if (ff_sd_global_map[j].frame != src->type)
+            continue;
+
+        sd = av_packet_side_data_new(psd, pnb_sd, ff_sd_global_map[j].packet,
+                                     src->size, 0);
+
+        if (!sd)
+            return AVERROR(ENOMEM);
+
+        memcpy(sd->data, src->data, src->size);
+        break;
+    }
+
+    if (!sd)
+        return AVERROR(EINVAL);
+
+    return 0;
+}
+
+int av_packet_side_data_to_frame(AVFrameSideData ***psd, int *pnb_sd,
+                                 const AVPacketSideData *src, unsigned int 
flags)
+{
+    AVFrameSideData *sd = NULL;
+
+    for (unsigned j = 0; ff_sd_global_map[j].packet < AV_PKT_DATA_NB; j++) {
+        if (ff_sd_global_map[j].packet != src->type)
+            continue;
+
+        sd = av_frame_side_data_new(psd, pnb_sd, ff_sd_global_map[j].frame,
+                                    src->size, flags);
+
+        if (!sd)
+            return AVERROR(ENOMEM);
+
+        memcpy(sd->data, src->data, src->size);
+        break;
+    }
+
+    if (!sd)
+        return AVERROR(EINVAL);
+
+    return 0;
+}
diff --git a/libavcodec/packet.h b/libavcodec/packet.h
index 5e27f9ceb5..59bfddf4cc 100644
--- a/libavcodec/packet.h
+++ b/libavcodec/packet.h
@@ -489,6 +489,36 @@ void av_packet_side_data_remove(AVPacketSideData *sd, int 
*nb_sd,
  */
 void av_packet_side_data_free(AVPacketSideData **sd, int *nb_sd);
 
+struct AVFrameSideData;
+
+/**
+ * Add a new packet side data entry to an array based on existing frame
+ * side data, if a matching type exists for packet side data.
+ *
+ * @param flags              Currently unused. Must be 0.
+ * @retval >= 0              Success
+ * @retval AVERROR(EINVAL)   The frame side data type does not have a matching
+ *                           packet side data type.
+ * @retval AVERROR(ENOMEM)   Failed to add a side data entry to the array, or
+ *                           similar.
+ */
+int av_packet_side_data_from_frame(AVPacketSideData **sd, int *nb_sd,
+                                   const struct AVFrameSideData *src, unsigned 
int flags);
+/**
+ * Add a new frame side data entry to an array based on existing packet
+ * side data, if a matching type exists for frame side data.
+ *
+ * @param flags              Some combination of AV_FRAME_SIDE_DATA_FLAG_* 
flags,
+ *                           or 0.
+ * @retval >= 0              Success
+ * @retval AVERROR(EINVAL)   The packet side data type does not have a matching
+ *                           frame side data type.
+ * @retval AVERROR(ENOMEM)   Failed to add a side data entry to the array, or
+ *                           similar.
+ */
+int av_packet_side_data_to_frame(struct AVFrameSideData ***sd, int *nb_sd,
+                                 const AVPacketSideData *src, unsigned int 
flags);
+
 const char *av_packet_side_data_name(enum AVPacketSideDataType type);
 
 /**
diff --git a/libavcodec/version.h b/libavcodec/version.h
index 82a86fe9d9..2618016a83 100644
--- a/libavcodec/version.h
+++ b/libavcodec/version.h
@@ -29,7 +29,7 @@
 
 #include "version_major.h"
 
-#define LIBAVCODEC_VERSION_MINOR  16
+#define LIBAVCODEC_VERSION_MINOR  17
 #define LIBAVCODEC_VERSION_MICRO 100
 
 #define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \

-----------------------------------------------------------------------

Summary of changes:
 doc/APIchanges                                     |   3 +
 fftools/cmdutils.c                                 |  71 ++++++
 fftools/cmdutils.h                                 |   4 +
 fftools/ffmpeg.c                                   |  13 +
 fftools/ffmpeg.h                                   |  47 +++-
 fftools/ffmpeg_demux.c                             | 161 +++++++++++++
 fftools/ffmpeg_enc.c                               |  17 +-
 fftools/ffmpeg_filter.c                            | 265 +++++++++++++++++----
 fftools/ffmpeg_mux_init.c                          |  53 +++++
 fftools/ffmpeg_opt.c                               |  91 ++++++-
 fftools/ffmpeg_sched.c                             |  23 ++
 fftools/ffmpeg_sched.h                             |   2 +
 libavcodec/avcodec.c                               |  50 ++++
 libavcodec/packet.h                                |  30 +++
 libavcodec/version.h                               |   2 +-
 tests/fate/ffmpeg.mak                              |  13 +
 tests/ref/fate/ffmpeg-heif-merge                   |   6 +
 .../fate/{dxtory => ffmpeg-heif-merge-filtergraph} |   4 +-
 tests/ref/fate/ffmpeg-heif-merge-mapped            |   6 +
 19 files changed, 800 insertions(+), 61 deletions(-)
 create mode 100644 tests/ref/fate/ffmpeg-heif-merge
 copy tests/ref/fate/{dxtory => ffmpeg-heif-merge-filtergraph} (53%)
 create mode 100644 tests/ref/fate/ffmpeg-heif-merge-mapped


hooks/post-receive
-- 

_______________________________________________
ffmpeg-cvslog mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to