Dear All,

currently stream specifiers may contain stream index, stream type,
stream id, program id, metadata key/value es usable config settings.
But you can not combine them. In some case, mainly working with
multiprogram mpeg-ts containers as input, this feature would be handy.
This patch makes it possible to combine them.
Examples: p:601:a  will select all audio streams of program 601,
a:m:language:hun will select all audio streams marked by its metadata
 as hun language,
p:604:v:0  will select first video stream of program 604,
a:m:language:hun:0 will select 1st audio streams with hun language.
p:403:s:0 will select the first avaiable sub_title tream of program 403,
and p:403:s:m:language:hun:0  will select the first hungarian language
sub_title stream of program 403.  To select first hungarian language
audio stream a:m:language:hun:0
The order of sub-specifiers (program/type/metadata/usable_config) is
arbitrary. So, you may also write v:p:604:0 to select first video stream
of program 604

Please consider to put this enhancement into the official ffmpeg code.

best regards,

Bela Bodecs

>From cc1d8139a6341c386b776afea9d7b132abb5906f Mon Sep 17 00:00:00 2001
From: Bela Bodecs <bode...@vivanet.hu>
Date: Mon, 12 Oct 2015 22:27:40 +0200
Subject: [PATCH 1/1] Stream specifier enhancement

currently stream specifiers may contain stream index, stream type,
stream id, program id, metadata key/value es usable config settings.
But you can not combine them. In some case, mainly working with
multiprogram mpeg-ts containers as input, this feature would be handy.
This patch makes it possible to combine them. Examples: p:601:a  will
select all audio streams of program 601, a:m:language:hun will select
all audio streams with hun language,
p:604:v:0  will select first video stream of program 604,
a:m:language:hun:0 will select 1st audio streams with hun language.
p:403:s:0 will select the first avaiable sub_title tream of program 403,
and p:403:s:m:language:hun:0  will select the first hungarian language
sub_title stream of program 403.  To select first hungarian language
audio stream a:m:language:hun:0
The order of sub-specifiers (program/type/metadata/usable_config) is
arbitrary. So, you may also write v:p:604:0 to select first video stream
of program 604

Signed-off-by: Bela Bodecs <bode...@vivanet.hu>
---
 doc/fftools-common-opts.texi |  24 +++-
 doc/formats.texi             |  30 +++-
 libavformat/utils.c          | 317 ++++++++++++++++++++++++++++++-------------
 3 files changed, 268 insertions(+), 103 deletions(-)

diff --git a/doc/fftools-common-opts.texi b/doc/fftools-common-opts.texi
index 509c8bc..411b092 100644
--- a/doc/fftools-common-opts.texi
+++ b/doc/fftools-common-opts.texi
@@ -45,14 +45,17 @@ streams of this type.
 @item p:@var{program_id}[:@var{stream_index}]
 If @var{stream_index} is given, then it matches the stream with number @var{stream_index}
 in the program with the id @var{program_id}. Otherwise, it matches all streams in the
-program.
+program. @var{program_id} may be decimal number, hexadecimal number with 0x prefix and octal number with 0 prefix.
+
 @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}]
+Match the stream by stream id (e.g. PID in MPEG-TS container). @var{stream_id} may be decimal number, hexadecimal number with 0x prefix and octal number with 0 prefix.
+
+@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.
-@item u
+@var{value} is not given or empty, matches streams that contain the given tag with any
+value. 
+
+@item u[:@var{stream_index}]
 Matches streams with usable configuration, the codec must be defined and the
 essential information such as video dimension or audio sample rate must be present.
 
@@ -60,6 +63,15 @@ Note that in @command{ffmpeg}, matching by metadata will only work properly for
 input files.
 @end table
 
+You may arbitrarily combine program_id, metadata, stream type and usable configuration specifier inside a specifier expression and they evaluate as logical AND. For example, 
+@code{-map 0:p:601:a} would match all audio streams of program with PID value 601 (e.g. in a MPEG-TS container). But
+@code{-map 0:p:601:a:1} would match only the 2nd audio stream of the same program. 
+@code{-map 0:p:0x123:a:m:language:hun} would match all audio streams of program with PID 291 (0x123) but only those ones that have language metadata tag with hun value.
+To make sure you will get only one audio stream even in those cases when there are two audio stream in the program and both have hun metadat atag, you may use
+@code{-map 0:p:0x123:a:m:language:hun:0} to match only the very first one. The order of sub-specifiers are arbitrary when you combine them, so in the latter case you may write 
+ @code{-map 0:a:m:language:hun:p:0x123:0}, but @code{-map 0:a:m:language:hun:p:0x123:0} has the same effect.
+
+
 @section Generic options
 
 These options are shared amongst the ff* tools.
diff --git a/doc/formats.texi b/doc/formats.texi
index 617cda5..8c31681 100644
--- a/doc/formats.texi
+++ b/doc/formats.texi
@@ -221,8 +221,10 @@ Possible forms of stream specifiers are:
 Matches the stream with this index.
 
 @item @var{stream_type}[:@var{stream_index}]
-@var{stream_type} is one of following: 'v' for video, 'a' for audio,
-'s' for subtitle, 'd' for data, and 't' for attachments. If
+@var{stream_type} is one of following: 'v'  or 'V' for video, 'a' for audio,
+'s' for subtitle, 'd' for data, and 't' for attachments. 'v' matches all video
+streams, 'V' only matches video streams which are not attached pictures, video
+thumbnails or cover arts. If
 @var{stream_index} is given, then it matches the stream number
 @var{stream_index} of this type. Otherwise, it matches all streams of
 this type.
@@ -230,12 +232,32 @@ this type.
 @item p:@var{program_id}[:@var{stream_index}]
 If @var{stream_index} is given, then it matches the stream with number
 @var{stream_index} in the program with the id
-@var{program_id}. Otherwise, it matches all streams in the program.
+@var{program_id}. Otherwise, it matches all streams in the program. @var{program_id} may be decimal number, hexadecimal number with 0x prefix and octal number with 0 prefix.
+
+@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 or empty, matches streams that contain the given tag with any
+value. 
 
 @item #@var{stream_id}
-Matches the stream by a format-specific ID.
+Matches the stream by a format-specific ID.  (e.g. PID in MPEG-TS container) @var{stream_id} may be decimal number, hexadecimal number with 0x prefix and octal number with 0 prefix.
+
+@item u[:@var{stream_index}]
+Matches streams with usable configuration, the codec must be defined and the
+essential information such as video dimension or audio sample rate must be present.
+
+Note that in @command{ffmpeg}, matching by metadata will only work properly for
+input files.
 @end table
 
+You may arbitrarily combine program_id, metadata, stream type and usable configuration specifier inside a specifier expression and they evaluate as logical AND. For example, 
+@code{-map 0:p:601:a} would match all audio streams of program with PID value 601 (e.g. in a MPEG-TS container). But
+@code{-map 0:p:601:a:1} would match only the 2nd audio stream of the same program. 
+@code{-map 0:p:0x123:a:m:language:hun} would match all audio streams of program with PID 291 (0x123) but only those ones that have language metadata tag with hun value.
+To make sure you will get only one audio stream even in those cases when there are two audio stream in the program and both have hun metadat atag, you may use
+@code{-map 0:p:0x123:a:m:language:hun:0} to match only the very first one. The order of sub-specifiers are arbitrary when you combine them, so in the latter case you may write 
+ @code{-map 0:a:m:language:hun:p:0x123:0}, but @code{-map 0:a:m:language:hun:p:0x123:0} has the same effect.
+
 The exact semantics of stream specifiers is defined by the
 @code{avformat_match_stream_specifier()} function declared in the
 @file{libavformat/avformat.h} header.
diff --git a/libavformat/utils.c b/libavformat/utils.c
index 689473e..e38a36d 100644
--- a/libavformat/utils.c
+++ b/libavformat/utils.c
@@ -4298,115 +4298,246 @@ AVRational av_guess_frame_rate(AVFormatContext *format, AVStream *st, AVFrame *f
 int avformat_match_stream_specifier(AVFormatContext *s, AVStream *st,
                                     const char *spec)
 {
-    if (*spec <= '9' && *spec >= '0') /* opt:index */
-        return strtol(spec, NULL, 0) == st->index;
-    else if (*spec == 'v' || *spec == 'a' || *spec == 's' || *spec == 'd' ||
-             *spec == 't' || *spec == 'V') { /* opt:[vasdtV] */
-        enum AVMediaType type;
-        int nopic = 0;
-
-        switch (*spec++) {
-        case 'v': type = AVMEDIA_TYPE_VIDEO;      break;
-        case 'a': type = AVMEDIA_TYPE_AUDIO;      break;
-        case 's': type = AVMEDIA_TYPE_SUBTITLE;   break;
-        case 'd': type = AVMEDIA_TYPE_DATA;       break;
-        case 't': type = AVMEDIA_TYPE_ATTACHMENT; break;
-        case 'V': type = AVMEDIA_TYPE_VIDEO; nopic = 1; break;
-        default:  av_assert0(0);
-        }
-        if (type != st->codec->codec_type)
-            return 0;
-        if (nopic && (st->disposition & AV_DISPOSITION_ATTACHED_PIC))
-            return 0;
-        if (*spec++ == ':') { /* possibly followed by :index */
-            int i, index = strtol(spec, NULL, 0);
-            for (i = 0; i < s->nb_streams; i++)
-                if (s->streams[i]->codec->codec_type == type &&
-                    !(nopic && (st->disposition & AV_DISPOSITION_ATTACHED_PIC)) &&
-                    index-- == 0)
-                    return i == st->index;
-            return 0;
-        }
-        return 1;
-    } else if (*spec == 'p' && *(spec + 1) == ':') {
-        int prog_id, i, j;
-        char *endptr;
-        spec += 2;
-        prog_id = strtol(spec, &endptr, 0);
-        for (i = 0; i < s->nb_programs; i++) {
-            if (s->programs[i]->id != prog_id)
-                continue;
+    int *slist = NULL, prog_id, i, j, k, ret = 0, evaluate_index = 0, ok, found;
+    int has_program_spec = 0, has_type_spec = 0, has_metadata_spec = 0, has_u_spec = 0;
+    char *endptr;
 
-            if (*endptr++ == ':') {
-                int stream_idx = strtol(endptr, NULL, 0);
-                return stream_idx >= 0 &&
-                    stream_idx < s->programs[i]->nb_stream_indexes &&
-                    st->index == s->programs[i]->stream_index[stream_idx];
-            }
-
-            for (j = 0; j < s->programs[i]->nb_stream_indexes; j++)
-                if (st->index == s->programs[i]->stream_index[j])
-                    return 1;
-        }
-        return 0;
+    if (av_isdigit((int)*spec)) { /* opt:index */
+        int stream_index = strtol(spec, &endptr, 0);
+        if (!*endptr)
+            return  stream_index == st->index;
+        av_log(s, AV_LOG_ERROR, "Invalid stream specifier: %s.\n", endptr);
+        return AVERROR(EINVAL);
     } else if (*spec == '#' ||
                (*spec == 'i' && *(spec + 1) == ':')) {
         int stream_id;
-        char *endptr;
         spec += 1 + (*spec == 'i');
         stream_id = strtol(spec, &endptr, 0);
         if (!*endptr)
             return stream_id == st->id;
-    } else if (*spec == 'm' && *(spec + 1) == ':') {
-        AVDictionaryEntry *tag;
-        char *key, *val;
-        int ret;
+        av_log(s, AV_LOG_ERROR, "Invalid stream specifier: %s.\n", endptr);
+        return AVERROR(EINVAL);
+    } else if (!*spec) /* empty specifier, matches everything */
+        return 1;
 
-        spec += 2;
-        val = strchr(spec, ':');
+    /* and now the possibly compound cases */
+    slist = av_mallocz(s->nb_streams * sizeof(int));
+    if (!slist)
+        return AVERROR(ENOMEM);
 
-        key = val ? av_strndup(spec, val - spec) : av_strdup(spec);
-        if (!key)
-            return AVERROR(ENOMEM);
+    for (i = 0; i < s->nb_streams; i++) {
+        slist[i] = 1;
+        if (s->streams[i]->index >= s->nb_streams) {
+          av_log(s, AV_LOG_ERROR, "Unexpected stream index value: %d.\n", s->streams[i]->index);
+          av_freep(&slist);
+          return AVERROR(EINVAL);
+        }
+    }
 
-        tag = av_dict_get(st->metadata, key, NULL, 0);
-        if (tag) {
-            if (!val || !strcmp(tag->value, val + 1))
-                ret = 1;
-            else
-                ret = 0;
-        } else
-            ret = 0;
+    while (slist[st->index] && *spec) {
+        if (*spec == 'v' || *spec == 'a' || *spec == 's' || *spec == 'd' ||
+             *spec == 't' || *spec == 'V') { /* opt:[vasdtV] */
 
-        av_freep(&key);
-        return ret;
-    } else if (*spec == 'u') {
-        AVCodecContext *avctx = st->codec;
-        int val;
-        switch (avctx->codec_type) {
-        case AVMEDIA_TYPE_AUDIO:
-            val = avctx->sample_rate && avctx->channels;
-            if (avctx->sample_fmt == AV_SAMPLE_FMT_NONE)
-                return 0;
-            break;
-        case AVMEDIA_TYPE_VIDEO:
-            val = avctx->width && avctx->height;
-            if (avctx->pix_fmt == AV_PIX_FMT_NONE)
-                return 0;
-            break;
-        case AVMEDIA_TYPE_UNKNOWN:
-            val = 0;
+            enum AVMediaType type;
+            int nopic = 0;
+
+            if (has_type_spec) {
+                av_log(s, AV_LOG_ERROR, "More than one stream type specifier: %s.\n", spec);
+                ret = AVERROR(EINVAL);
+                break;
+            }
+            has_type_spec = 1;
+
+            switch (*spec++) {
+            case 'v': type = AVMEDIA_TYPE_VIDEO;      break;
+            case 'a': type = AVMEDIA_TYPE_AUDIO;      break;
+            case 's': type = AVMEDIA_TYPE_SUBTITLE;   break;
+            case 'd': type = AVMEDIA_TYPE_DATA;       break;
+            case 't': type = AVMEDIA_TYPE_ATTACHMENT; break;
+            case 'V': type = AVMEDIA_TYPE_VIDEO; nopic = 1; break;
+            default:  av_assert0(0);
+            }
+            for (i = 0; i < s->nb_streams; i++)
+                if (s->streams[i]->codec->codec_type != type ||
+                    (nopic && (st->disposition & AV_DISPOSITION_ATTACHED_PIC)))
+                    slist[i] = 0;
+        } else if (*spec == 'p' && *(spec + 1) == ':') {
+            spec += 2;
+
+            if (has_program_spec) {
+                av_log(s, AV_LOG_ERROR, "More than one program id specifier: %s.\n", spec);
+                ret = AVERROR(EINVAL);
+                break;
+            }
+            has_program_spec = 1;
+            prog_id = strtol(spec, &endptr, 0);
+
+            for (k = 0; k < s->nb_streams; k++) {
+                ok = 0;
+                found = 0;
+                for (i = 0; !found && i < s->nb_programs; i++)
+                    for (j = 0; j < s->programs[i]->nb_stream_indexes; j++)
+                        if (s->streams[k]->index == s->programs[i]->stream_index[j]) {
+                            ok = (s->programs[i]->id == prog_id);
+                            found = 1;
+                            break;
+                        }
+
+                if (!ok)
+                    slist[k] = 0;
+            }
+            spec = endptr;
+        } else if (*spec == 'm' && *(spec + 1) == ':') {
+            AVDictionaryEntry *tag;
+            char *mkey = NULL, *mval = NULL, *next = NULL;
+            if (has_metadata_spec) {
+                av_log(s, AV_LOG_ERROR, "More than one metadata specifier: %s.\n", spec);
+                ret = AVERROR(EINVAL);
+                break;
+            }
+            has_metadata_spec = 1;
+            spec += 2;
+
+            if (!*spec || *spec == ':') {
+                av_log(s, AV_LOG_ERROR, "Empty metadata key in stream specifier.\n");
+                ret = AVERROR(EINVAL);
+                break;
+            }
+
+            mval = strchr(spec, ':');
+            mkey = mval ? av_strndup(spec, mval - spec) : av_strdup(spec);
+            if (!mkey) {
+                ret = AVERROR(ENOMEM);
+                break;
+            }
+
+            spec += strlen(mkey);
+            if (mval) {
+                if (*spec == ':' && (*(spec + 1) == ':' || !*(spec + 1)))
+                    mval = NULL;
+                spec ++;
+            }
+
+            if (mval) {
+                next = strchr(spec, ':');
+                mval = next ? av_strndup(spec, next - spec) : av_strdup(spec);
+                if (!mval) {
+                    ret = AVERROR(ENOMEM);
+                    av_freep(&mkey);
+                    break;
+                }
+                spec += strlen(mval);
+            }
+
+            for (i = 0; i < s->nb_streams; i++) {
+                if (!slist[i])
+                    continue;
+
+                tag = av_dict_get(s->streams[i]->metadata, mkey, NULL, 0);
+                if (tag) {
+                    if (mval && 0!=strcmp(tag->value, mval))
+                        slist[i] = 0;
+                } else
+                    slist[i] = 0;
+            }
+
+            av_freep(&mkey);
+            av_freep(&mval);
+        } else if (*spec == 'u') {
+            if (has_u_spec) {
+                av_log(s, AV_LOG_ERROR, "More than one usable config specifier: %s.\n", spec);
+                ret = AVERROR(EINVAL);
+                break;
+            }
+            has_u_spec = 0;
+
+            for (i = 0; i < s->nb_streams; i++) {
+                AVCodecContext *avctx = s->streams[i]->codec;
+                int val = 1;
+
+                if (!slist[i])
+                    continue;
+
+                switch (avctx->codec_type) {
+                case AVMEDIA_TYPE_AUDIO:
+                    val = avctx->sample_rate && avctx->channels;
+                    if (avctx->sample_fmt == AV_SAMPLE_FMT_NONE)
+                        slist[i] = 0;
+                    break;
+                case AVMEDIA_TYPE_VIDEO:
+                    val = avctx->width && avctx->height;
+                    if (avctx->pix_fmt == AV_PIX_FMT_NONE)
+                        slist[i] = 0;
+                    break;
+                case AVMEDIA_TYPE_UNKNOWN:
+                    val = 0;
+                    break;
+                default:
+                    val = 1;
+                    break;
+                }
+                if (slist[i])
+                    slist[i] = avctx->codec_id != AV_CODEC_ID_NONE && val != 0;
+            }
+            spec++;
+        } else {
+            av_log(s, AV_LOG_ERROR, "Invalid stream specifier: %s.\n", spec);
+            ret = AVERROR(EINVAL);
             break;
-        default:
-            val = 1;
+        }
+
+        if (!slist[st->index]) {
+            ret = 0;
             break;
         }
-        return avctx->codec_id != AV_CODEC_ID_NONE && val != 0;
-    } else if (!*spec) /* empty specifier, matches everything */
-        return 1;
 
-    av_log(s, AV_LOG_ERROR, "Invalid stream specifier: %s.\n", spec);
-    return AVERROR(EINVAL);
+        if (*spec++ == ':') { /* more specifier will come, possibly followed by :index or other sub-specifier */
+            if (!*spec) {
+                av_log(s, AV_LOG_ERROR, "Invalid stream specifier, unexpected end.\n");
+                ret = AVERROR(EINVAL);
+                break;
+            }
+            if (av_isdigit((int)*spec)) { /* last part is a numeric sub-index */
+                evaluate_index = 1;
+                break;
+            }
+            continue;
+        }
+
+        ret = slist[st->index];
+        break;
+    }
+
+    if (evaluate_index) {  /* opt:index */
+        int stream_sub_index = stream_sub_index = strtol(spec, &endptr, 0);
+
+        if (*endptr) {  /* unexpected garbage at the end, after numeric index */
+            av_log(s, AV_LOG_ERROR, "Invalid stream specifier: %s.\n", endptr);
+            ret = AVERROR(EINVAL);
+        } else {
+            int sub_index_counter = 0;
+            if (has_program_spec) {
+                for (j = 0; j < s->nb_programs; j++)
+                    if (s->programs[j]->id == prog_id) {
+                        for (i = 0; i <= s->programs[j]->nb_stream_indexes; i++) {
+                            if (slist[ s->programs[j]->stream_index[i] ])
+                                sub_index_counter ++;
+                            if (s->programs[j]->stream_index[i] == st->index)
+                                break;
+                        }
+                        break;
+                    }
+            } else {
+                for (i = 0; i <= st->index; i++)
+                    if (slist[i])
+                        sub_index_counter ++;
+            }
+            ret = ((sub_index_counter - 1) == stream_sub_index);
+        }
+    }
+    av_freep(&slist);
+
+    return ret;
 }
 
 int ff_generate_avci_extradata(AVStream *st)
-- 
2.5.3.windows.1

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
http://ffmpeg.org/mailman/listinfo/ffmpeg-devel

Reply via email to