PR #22880 opened by Practice2001 URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22880 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22880.patch
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 commands: ffmpeg -i /home/family/ffmpeg_sources/ffmpeg/fate-suite/mkv/dummy_multistream.mkv -map 0:s:m:language:fre:0 -c:s ass output.ass ffmpeg -i /home/family/ffmpeg_sources/ffmpeg/fate-suite/mkv/dummy_multistream.mkv -map 0:s:m:language:fre:0 -c:s srt output.srt This selects the first French subtitle stream in my test file. **Note**: the mapping works independently of the output codec; the -c copy or compatible subtitle encoder must be used for writing the file format. Example: ffmpeg -i /home/family/ffmpeg_sources/ffmpeg/fate-suite/mkv/dummy_multistream.mkv -map 0:s:m:language:fre:0 -c:s hdmv_pgs_subtitle output.sup All current FATE tests(original + new tests in this PR) pass on my local machine. Closes: #20149 >From 0ed40b59043161157811c6430c3a739bc2ad7364 Mon Sep 17 00:00:00 2001 From: Practice2001 <[email protected]> Date: Wed, 22 Apr 2026 02:13:53 +0530 Subject: [PATCH] [FEATURE] fftools: addition of optional index selector to metadata stream specifier --- doc/fftools-common-opts.texi | 8 ++-- fftools/cmdutils.c | 36 ++++++++++++++++-- tests/fate/ffmpeg.mak | 43 ++++++++++++++++++++++ tests/ref/fate/ffmpeg-map-metadata-index-0 | 3 ++ tests/ref/fate/ffmpeg-map-metadata-index-1 | 3 ++ 5 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 tests/ref/fate/ffmpeg-map-metadata-index-0 create mode 100644 tests/ref/fate/ffmpeg-map-metadata-index-1 diff --git a/doc/fftools-common-opts.texi b/doc/fftools-common-opts.texi index 7b5a11b634..13a9524df1 100644 --- a/doc/fftools-common-opts.texi +++ b/doc/fftools-common-opts.texi @@ -75,11 +75,11 @@ 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. +@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 2f9bae9f21..e0d0d864cb 100644 --- a/fftools/cmdutils.c +++ b/fftools/cmdutils.c @@ -1023,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); @@ -1032,8 +1032,9 @@ int stream_specifier_parse(StreamSpecifier *ss, const char *spec, av_log(logctx, AV_LOG_TRACE, "Parsed index: %d; remainder: %s\n", ss->idx, spec); - // this terminates the specifier - break; + if (*spec == ':') spec++; + + // continue parsing for possible index } else if ((*spec == 'v' || *spec == 'a' || *spec == 's' || *spec == 'd' || *spec == 't' || *spec == 'V') && !cmdutils_isalnum(*(spec + 1))) { /* opt:[vasdtV] */ @@ -1191,6 +1192,35 @@ 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; + } + + /* Reject trailing index for stream-ID selectors */ + if (ss->stream_list == STREAM_LIST_STREAM_ID && ss->idx >= 0) { + av_log(logctx, AV_LOG_ERROR, + "Cannot combine a stream ID selector with a trailing stream index\n"); + ret = AVERROR(EINVAL); + goto fail; + } + if (*spec) { if (!allow_remainder) { av_log(logctx, AV_LOG_ERROR, diff --git a/tests/fate/ffmpeg.mak b/tests/fate/ffmpeg.mak index cd00275638..c0f831f3b6 100644 --- a/tests/fate/ffmpeg.mak +++ b/tests/fate/ffmpeg.mak @@ -290,3 +290,46 @@ 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 + + +# tests for -map by metadata with index +# Register tests (guarded) +FATE_FFMPEG-$(CONFIG_LIBX264)$(CONFIG_AAC)$(CONFIG_MATROSKA_MUXER)$(CONFIG_SRT_MUXER)$(CONFIG_LAVFI_INDEV) += \ + fate-ffmpeg-map-metadata-index-0 \ + fate-ffmpeg-map-metadata-index-1 \ + fate-ffmpeg-map-metadata-index-error + +# No output comparison +fate-ffmpeg-map-metadata-index-error: CMP = null + +#Select the first French subtitle +fate-ffmpeg-map-metadata-index-0: tests/data/dummy_multistream.mkv +fate-ffmpeg-map-metadata-index-0: CMD = fmtstdout srt -i $(TARGET_PATH)/tests/data/dummy_multistream.mkv -map 0:s:m:language:fre:0 -c copy + +#Select the second French subtitle +fate-ffmpeg-map-metadata-index-1: tests/data/dummy_multistream.mkv +fate-ffmpeg-map-metadata-index-1: CMD = fmtstdout srt -i $(TARGET_PATH)/tests/data/dummy_multistream.mkv -map 0:s:m:language:fre:1 -c copy + +#Error on out-of-range index +fate-ffmpeg-map-metadata-index-error: tests/data/dummy_multistream.mkv +fate-ffmpeg-map-metadata-index-error: CMD = ffmpeg -i $(TARGET_PATH)/tests/data/dummy_multistream.mkv -map 0:s:m:language:fre:99 -c copy -f null - ; test $$? -ne 0 + +tests/data/sub1.srt: TAG = GEN +tests/data/sub1.srt: | tests/data + echo "1\n00:00:00,000 --> 00:00:01,000\nHello" > $(TARGET_PATH)/tests/data/sub1.srt + +tests/data/sub2.srt: TAG = GEN +tests/data/sub2.srt: | tests/data + echo "1\n00:00:00,000 --> 00:00:01,000\nBonjour" > $(TARGET_PATH)/tests/data/sub2.srt + +tests/data/dummy_multistream.mkv: TAG = GEN +tests/data/dummy_multistream.mkv: ffmpeg$(PROGSSUF)$(EXESUF) tests/data/sub1.srt tests/data/sub2.srt | tests/data + $(M)$(TARGET_EXEC) $(TARGET_PATH)/$< -nostdin \ + -f lavfi -i color=c=black:s=16x16:d=1 \ + -f lavfi -i anullsrc \ + -f srt -i $(TARGET_PATH)/tests/data/sub1.srt \ + -f srt -i $(TARGET_PATH)/tests/data/sub2.srt \ + -map 0:v -map 1:a -map 2:s -map 3:s \ + -metadata:s:s:0 language=fre -metadata:s:s:1 language=fre \ + -c:v libx264 -c:a aac -c:s srt \ + -y $(TARGET_PATH)/tests/data/dummy_multistream.mkv 2>/dev/null \ No newline at end of file diff --git a/tests/ref/fate/ffmpeg-map-metadata-index-0 b/tests/ref/fate/ffmpeg-map-metadata-index-0 new file mode 100644 index 0000000000..68337371de --- /dev/null +++ b/tests/ref/fate/ffmpeg-map-metadata-index-0 @@ -0,0 +1,3 @@ +1 +00:00:00,000 --> 00:00:01,000 +Hello \ No newline at end of file diff --git a/tests/ref/fate/ffmpeg-map-metadata-index-1 b/tests/ref/fate/ffmpeg-map-metadata-index-1 new file mode 100644 index 0000000000..97f7659f27 --- /dev/null +++ b/tests/ref/fate/ffmpeg-map-metadata-index-1 @@ -0,0 +1,3 @@ +1 +00:00:00,000 --> 00:00:01,000 +Bonjour \ No newline at end of file -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
