This feature was suggested at: https://code.ffmpeg.org/FFmpeg/FFmpeg/issues/20149
On Mon, Jan 5, 2026 at 10:22 PM Practice2001 <[email protected]> wrote: > This patch introduces an optional index selector to the metadata stream > specifier (e.g., -map 0:s:m:language:fre:0). > > Currently, mapping streams by metadata (like language) selects all > matching streams. This is problematic in files with multiple streams > of the same language (e.g., a full French subtitle track and a > forced/SDH French track). While users can currently find the absolute > stream index via ffprobe, this is cumbersome for batch processing > where indices vary across files. > > By adding a trailing index, users can now select a specific occurrence > from the subset of streams that matched the metadata criteria. > > Example: > -map 0:s:m:language:fre:0 # Selects only the first French subtitle > -map 0:s:m:language:fre:1 # Selects only the second French subtitle > > I have verified this change with the following command: > ffmpeg -i input.mkv -map 0:s:m:language:fre:0 -c copy output.sup > This correctly selects the second French subtitle stream in my test file. > All current FATE tests pass. > > Signed-off-by: Practice2001 <[email protected]> > --- > doc/fftools-common-opts.texi | 7 ++++--- > fftools/cmdutils.c | 31 ++++++++++++++++++++++++++----- > tests/fate/ffmpeg.mak | 15 +++++++++++++++ > 3 files changed, 45 insertions(+), 8 deletions(-) > > diff --git a/doc/fftools-common-opts.texi b/doc/fftools-common-opts.texi > index 7b5a11b634..a42e85d91b 100644 > --- a/doc/fftools-common-opts.texi > +++ b/doc/fftools-common-opts.texi > @@ -75,11 +75,12 @@ are part of the program and match the > @var{additional_stream_specifier}. > > @item #@var{stream_id} or i:@var{stream_id} > Match the stream by stream id (e.g. PID in MPEG-TS container). > -@item m:@var{key}[:@var{value}] > +@item m:@var{key}[:@var{value}][:@var{stream_index}] > Matches streams with the metadata tag @var{key} having the specified > value. If > @var{value} is not given, matches streams that contain the given tag with > any > -value. The colon character ':' in @var{key} or @var{value} needs to be > -backslash-escaped. > +value. If @var{stream_index} is given, matches the @var{stream_index}-th > stream > +among those matching the key/value criteria. The colon character ':' in > +@var{key} or @var{value} needs to be backslash-escaped. > @item disp:@var{dispositions}[:@var{additional_stream_specifier}] > Matches streams with the given disposition(s). @var{dispositions} is a > list of > one or more dispositions (as printed by the @option{-dispositions} option) > diff --git a/fftools/cmdutils.c b/fftools/cmdutils.c > index e906d4506d..ca3641ceed 100644 > --- a/fftools/cmdutils.c > +++ b/fftools/cmdutils.c > @@ -962,8 +962,7 @@ FILE *get_preset_file(char *filename, size_t > filename_size, > datadir, desired_size, sizeof *datadir); > if (new_datadir) { > datadir = new_datadir; > - datadir[datadir_len] = 0; > - strncat(datadir, "/ffpresets", desired_size - 1 - > datadir_len); > + strcpy(datadir + datadir_len, "/ffpresets"); > base[2] = datadir; > } > } > @@ -1024,7 +1023,7 @@ int stream_specifier_parse(StreamSpecifier *ss, > const char *spec, > av_log(logctx, AV_LOG_TRACE, "Parsing stream specifier: %s\n", spec); > > while (*spec) { > - if (*spec <= '9' && *spec >= '0') { /* opt:index */ > + if (ss->idx == -1 && *spec <= '9' && *spec >= '0') { /* opt:index > */ > ss->idx = strtol(spec, &endptr, 0); > > av_assert0(endptr > spec); > @@ -1176,8 +1175,9 @@ int stream_specifier_parse(StreamSpecifier *ss, > const char *spec, > "Parsed metadata: %s:%s; remainder: %s", ss->meta_key, > ss->meta_val ? ss->meta_val : "<any value>", spec); > > - // this terminates the specifier > - break; > + if (*spec == ':') spec++; > + > + // continue parsing for possible index > } else if (*spec == 'u' && (*(spec + 1) == '\0' || *(spec + 1) == > ':')) { > ss->usable_only = 1; > spec++; > @@ -1192,6 +1192,27 @@ int stream_specifier_parse(StreamSpecifier *ss, > const char *spec, > spec++; > } > > + if (*spec >= '0' && *spec <= '9') { > + char *endptr; > + > + ss->idx = strtol(spec, &endptr, 0); > + > + av_log(logctx, AV_LOG_TRACE, > + "Parsed trailing index: %d; remainder: %s\n", ss->idx, > endptr); > + > + spec = endptr; > + } else if (*spec == ':' && *(spec + 1) >= '0' && *(spec + 1) <= '9') { > + char *endptr; > + > + spec++; > + ss->idx = strtol(spec, &endptr, 0); > + > + av_log(logctx, AV_LOG_TRACE, > + "Parsed trailing index: %d; remainder: %s\n", ss->idx, > endptr); > + > + spec = endptr; > + } > + > if (*spec) { > if (!allow_remainder) { > av_log(logctx, AV_LOG_ERROR, > diff --git a/tests/fate/ffmpeg.mak b/tests/fate/ffmpeg.mak > index 57028a7936..297e9d55ae 100644 > --- a/tests/fate/ffmpeg.mak > +++ b/tests/fate/ffmpeg.mak > @@ -280,3 +280,18 @@ FATE_SAMPLES_FFMPEG-$(call FRAMECRC, MOV, HEVC, > HEVC_PARSER) += fate-ffmpeg-heif > # 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 > + > +# Test: Select first French subtitle from multiple French subtitles > +FATE_FFMPEG += fate-ffmpeg-map-metadata-index-0 > +fate-ffmpeg-map-metadata-index-0: CMD = ffmpeg -i > $(TARGET_SAMPLES)/subtitles/multi-french.mkv \ > + -map 0:s:m:language:fre:0 -c copy -f null - > + > +# Test: Select second French subtitle > +FATE_FFMPEG += fate-ffmpeg-map-metadata-index-1 > +fate-ffmpeg-map-metadata-index-1: CMD = ffmpeg -i > $(TARGET_SAMPLES)/subtitles/multi-french.mkv \ > + -map 0:s:m:language:fre:1 -c copy -f null - > + > +# Test: Error on out-of-range index > +FATE_FFMPEG += fate-ffmpeg-map-metadata-index-error > +fate-ffmpeg-map-metadata-index-error: CMD = ffmpeg -i > $(TARGET_SAMPLES)/subtitles/multi-french.mkv \ > + -map 0:s:m:language:fre:99 -c copy -f null - ; test $$? -ne 0 > -- > 2.34.1 > > _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
