From: Anton Khirnov <anton@khirnov.net> To: ffmpeg-devel@ffmpeg.org Subject: [FFmpeg-devel] [PATCH 3/6] fftools/cmdutils: put stream specifier handling back into cmdutils Date: Sat, 10 Aug 2024 19:16:18 +0200 Message-ID: <20240810171621.26757-3-anton@khirnov.net> (raw) In-Reply-To: <20240810171621.26757-1-anton@khirnov.net> Stream specifiers were originally designed exclusively for CLI use and were not intended to be public API. Handling them in avformat places major restrictions on how they are used. E.g. if ffmpeg CLI wishes to override some stream parameters, it has to change the demuxer fields (since avformat_match_stream_specifier() does not have access to anything else). However, such fields are supposed to be read-only for the caller. Furthermore having this code in avformat restricts extending the specifier syntax. An example of such an extension will be added in following commits. --- fftools/cmdutils.c | 211 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 210 insertions(+), 1 deletion(-) diff --git a/fftools/cmdutils.c b/fftools/cmdutils.c index 6286fd87f7..ffdcc85494 100644 --- a/fftools/cmdutils.c +++ b/fftools/cmdutils.c @@ -980,10 +980,219 @@ FILE *get_preset_file(char *filename, size_t filename_size, return f; } +/** + * Matches a stream specifier (but ignores requested index). + * + * @param indexptr set to point to the requested stream index if there is one + * + * @return <0 on error + * 0 if st is NOT a matching stream + * >0 if st is a matching stream + */ +static int match_stream_specifier(const AVFormatContext *s, const AVStream *st, + const char *spec, const char **indexptr, + const AVStreamGroup **g, const AVProgram **p) +{ + int match = 1; /* Stores if the specifier matches so far. */ + while (*spec) { + if (*spec <= '9' && *spec >= '0') { /* opt:index */ + if (indexptr) + *indexptr = spec; + return match; + } 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 (*spec && *spec++ != ':') /* If we are not at the end, then another specifier must follow. */ + return AVERROR(EINVAL); + + if (type != st->codecpar->codec_type) + match = 0; + if (nopic && (st->disposition & AV_DISPOSITION_ATTACHED_PIC)) + match = 0; + } else if (*spec == 'g' && *(spec + 1) == ':') { + int64_t group_idx = -1, group_id = -1; + int found = 0; + char *endptr; + spec += 2; + if (*spec == '#' || (*spec == 'i' && *(spec + 1) == ':')) { + spec += 1 + (*spec == 'i'); + group_id = strtol(spec, &endptr, 0); + if (spec == endptr || (*endptr && *endptr++ != ':')) + return AVERROR(EINVAL); + spec = endptr; + } else { + group_idx = strtol(spec, &endptr, 0); + /* Disallow empty id and make sure that if we are not at the end, then another specifier must follow. */ + if (spec == endptr || (*endptr && *endptr++ != ':')) + return AVERROR(EINVAL); + spec = endptr; + } + if (match) { + if (group_id > 0) { + for (unsigned i = 0; i < s->nb_stream_groups; i++) { + if (group_id == s->stream_groups[i]->id) { + group_idx = i; + break; + } + } + } + if (group_idx < 0 || group_idx >= s->nb_stream_groups) + return AVERROR(EINVAL); + for (unsigned j = 0; j < s->stream_groups[group_idx]->nb_streams; j++) { + if (st->index == s->stream_groups[group_idx]->streams[j]->index) { + found = 1; + if (g) + *g = s->stream_groups[group_idx]; + break; + } + } + } + if (!found) + match = 0; + } else if (*spec == 'p' && *(spec + 1) == ':') { + int prog_id; + int found = 0; + char *endptr; + spec += 2; + prog_id = strtol(spec, &endptr, 0); + /* Disallow empty id and make sure that if we are not at the end, then another specifier must follow. */ + if (spec == endptr || (*endptr && *endptr++ != ':')) + return AVERROR(EINVAL); + spec = endptr; + if (match) { + for (unsigned i = 0; i < s->nb_programs; i++) { + if (s->programs[i]->id != prog_id) + continue; + + for (unsigned j = 0; j < s->programs[i]->nb_stream_indexes; j++) { + if (st->index == s->programs[i]->stream_index[j]) { + found = 1; + if (p) + *p = s->programs[i]; + i = s->nb_programs; + break; + } + } + } + } + if (!found) + match = 0; + } else if (*spec == '#' || + (*spec == 'i' && *(spec + 1) == ':')) { + int stream_id; + char *endptr; + spec += 1 + (*spec == 'i'); + stream_id = strtol(spec, &endptr, 0); + if (spec == endptr || *endptr) /* Disallow empty id and make sure we are at the end. */ + return AVERROR(EINVAL); + return match && (stream_id == st->id); + } else if (*spec == 'm' && *(spec + 1) == ':') { + const AVDictionaryEntry *tag; + char *key, *val; + int ret; + + if (match) { + spec += 2; + val = strchr(spec, ':'); + + key = val ? av_strndup(spec, val - spec) : av_strdup(spec); + if (!key) + return AVERROR(ENOMEM); + + 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; + + av_freep(&key); + } + return match && ret; + } else if (*spec == 'u' && *(spec + 1) == '\0') { + const AVCodecParameters *par = st->codecpar; + int val; + switch (par->codec_type) { + case AVMEDIA_TYPE_AUDIO: + val = par->sample_rate && par->ch_layout.nb_channels; + if (par->format == AV_SAMPLE_FMT_NONE) + return 0; + break; + case AVMEDIA_TYPE_VIDEO: + val = par->width && par->height; + if (par->format == AV_PIX_FMT_NONE) + return 0; + break; + case AVMEDIA_TYPE_UNKNOWN: + val = 0; + break; + default: + val = 1; + break; + } + return match && (par->codec_id != AV_CODEC_ID_NONE && val != 0); + } else { + return AVERROR(EINVAL); + } + } + + return match; +} + int check_stream_specifier(AVFormatContext *s, AVStream *st, const char *spec) { - int ret = avformat_match_stream_specifier(s, st, spec); + int ret, index; + char *endptr; + const char *indexptr = NULL; + const AVStreamGroup *g = NULL; + const AVProgram *p = NULL; + int nb_streams; + + ret = match_stream_specifier(s, st, spec, &indexptr, &g, &p); if (ret < 0) + goto error; + + if (!indexptr) + return ret; + + index = strtol(indexptr, &endptr, 0); + if (*endptr) { /* We can't have anything after the requested index. */ + ret = AVERROR(EINVAL); + goto error; + } + + /* This is not really needed but saves us a loop for simple stream index specifiers. */ + if (spec == indexptr) + return (index == st->index); + + /* If we requested a matching stream index, we have to ensure st is that. */ + nb_streams = g ? g->nb_streams : (p ? p->nb_stream_indexes : s->nb_streams); + for (int i = 0; i < nb_streams && index >= 0; i++) { + unsigned idx = g ? g->streams[i]->index : (p ? p->stream_index[i] : i); + const AVStream *candidate = s->streams[idx]; + ret = match_stream_specifier(s, candidate, spec, NULL, NULL, NULL); + if (ret < 0) + goto error; + if (ret > 0 && index-- == 0 && st == candidate) + return 1; + } + return 0; + +error: + if (ret == AVERROR(EINVAL)) av_log(s, AV_LOG_ERROR, "Invalid stream specifier: %s.\n", spec); return ret; } -- 2.43.0 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
next prev parent reply other threads:[~2024-08-10 17:17 UTC|newest] Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top 2024-08-10 17:16 [FFmpeg-devel] [PATCH 1/6] fftools/ffmpeg: replace MATCH_PER_STREAM_OPT(.., str, ..) with a function Anton Khirnov 2024-08-10 17:16 ` [FFmpeg-devel] [PATCH 2/6] fftools/ffmpeg: replace remaining uses of MATCH_PER_STREAM_OPT() Anton Khirnov 2024-08-10 17:16 ` Anton Khirnov [this message] 2024-08-10 17:16 ` [FFmpeg-devel] [PATCH 4/6] fftools/cmdutils: split stream specifier parsing and matching Anton Khirnov 2024-08-10 17:16 ` [FFmpeg-devel] [PATCH 5/6] fftools/ffmpeg: use new stream specifier API in opt_match_per_stream*() Anton Khirnov 2024-08-10 17:16 ` [FFmpeg-devel] [PATCH 6/6] fftools/ffmpeg: switch -map parsing to new stream specifier API Anton Khirnov
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=20240810171621.26757-3-anton@khirnov.net \ --to=anton@khirnov.net \ --cc=ffmpeg-devel@ffmpeg.org \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: link
Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel This inbox may be cloned and mirrored by anyone: git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git # If you have public-inbox 1.1+ installed, you may # initialize and index your mirror using the following commands: public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \ ffmpegdev@gitmailbox.com public-inbox-index ffmpegdev Example config snippet for mirrors. AGPL code for this site: git clone https://public-inbox.org/public-inbox.git