From patchwork Sat Aug 10 17:16:18 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Anton Khirnov X-Patchwork-Id: 50967 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:612c:1ff2:b0:489:2eb3:e4c4 with SMTP id ks18csp527176vqb; Sat, 10 Aug 2024 10:17:16 -0700 (PDT) X-Forwarded-Encrypted: i=2; AJvYcCXIHR0lAXzykyKrlSRr5MVjFGXSOjpN+eTe38E22pAIpq2AgKeuJLBkEYJGRasmJTWvD6LfKAn4UubiGQl0l2Cbtyp52/pxWBHnxA== X-Google-Smtp-Source: AGHT+IECWE3mVxgyfdBdr/hfFo79xRf3i05WobQxz7VeG4Vts33nyepD+og4q+XH0HA+y9Cci6v/ X-Received: by 2002:a05:6512:15a3:b0:52c:e402:4dc1 with SMTP id 2adb3069b0e04-530eea425b6mr3792061e87.55.1723310236275; Sat, 10 Aug 2024 10:17:16 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1723310236; cv=none; d=google.com; s=arc-20160816; b=yrrOmT5Yb6E1jzYRrmGaYOoYMz4mAeuauiTr6amnqBoINDVNZZwl8WcBN/uOQNMOSv 2DWoYcC1qgq8UsmZJLigevv+FaResYHwei3X3aq5l0a+LM1eEkFWuj3tSgiPhM3YXscX BD/CFTuHQvdO/qVVoL5PHYL9h0FVfa7fc9oakJFi+SrWgE3IiGHUI1ngqulV2T+dUMvl 4wVROImAT8e885j0YIB5WZPZN44CLMrW17fv7YbEjezqe5acBKVah08Ff5itrH/CBJfP t9AORIv1GLRBTtBK3oaYg1uBpS9cmla3U+j0fq1ShDMjO9nGBFioCUi40VAoWnE3uS4h AHCQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:reply-to:list-subscribe :list-help:list-post:list-archive:list-unsubscribe:list-id :precedence:subject:mime-version:references:in-reply-to:message-id :date:to:from:dkim-signature:delivered-to; bh=ZzwXZa78szPlTjzjHJ+j4Z225FYlp9z4izZRLtF6D4U=; fh=YOA8vD9MJZuwZ71F/05pj6KdCjf6jQRmzLS+CATXUQk=; b=sKugARZs6I2ztplEu/cENjrX/+uaXnx+rQVNr+2/5wrGuoqW9rGozPZRk+fYc1l19k mGexZCRJEQ9+hNYw1ak5HeLis+anh1qCnycButftlpQhOVDblO0HWcH1IuBcTuM+lUWh O3yQI3yNCQ6002S5sbDGC8rhjnUBNvyS2sRmjB/aMOp+mAXN61MoWssHGkItvl4xuENx x3t+yLNWgKV3CiNvBxKQt+mRTZ35BxxF04no68x+CatFEopUzVKoriI0Y192gY9IjWJQ UoZU2FhY0CYqKI8EbUFOecRQC6oDdAxW2sjx4Ch2tFkjqnCxeCmxu55U29hJcdyjiXYa 8jYQ==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@khirnov.net header.s=mail header.b=HKSdp2gy; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id 4fb4d7f45d1cf-5bd1a5f8da4si1010084a12.401.2024.08.10.10.17.15; Sat, 10 Aug 2024 10:17:16 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@khirnov.net header.s=mail header.b=HKSdp2gy; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id D9D9968DAE2; Sat, 10 Aug 2024 20:16:44 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail1.khirnov.net (quelana.khirnov.net [94.230.150.81]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id DC63968D7EF for ; Sat, 10 Aug 2024 20:16:35 +0300 (EEST) Authentication-Results: mail1.khirnov.net; dkim=pass (2048-bit key; unprotected) header.d=khirnov.net header.i=@khirnov.net header.a=rsa-sha256 header.s=mail header.b=HKSdp2gy; dkim-atps=neutral Received: from localhost (mail1.khirnov.net [IPv6:::1]) by mail1.khirnov.net (Postfix) with ESMTP id 74E484DDB for ; Sat, 10 Aug 2024 19:16:35 +0200 (CEST) Received: from mail1.khirnov.net ([IPv6:::1]) by localhost (mail1.khirnov.net [IPv6:::1]) (amavis, port 10024) with ESMTP id ue0K3l8mpuwE for ; Sat, 10 Aug 2024 19:16:35 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=khirnov.net; s=mail; t=1723310193; bh=Qu2Ib4djTwse5rNTVe1VMztTjXNXFwnxUGqjChm3vAM=; h=From:To:Subject:Date:In-Reply-To:References:From; b=HKSdp2gyYYqtuNOex+FLLkSZU2Pk8hIVbSrkQXX9fv4unBamSkawlUmxyHqXQB00Y JG9ie7sSttuSBn8/cLvLWkc/JNW2Sg/ooyRKg8MYjOnO3vim0aFCbiC5irTMCZOcC4 USPPlr+rafiuRFGA16S8ZEwK9fQxP40aDVMNALbA9DjvRnW/xJnaj4Huiq0qKjXtji iblD0bv1pz7YzK8dIUBLlE8maxzJICfaU0PmTDXp02PK+dtl/kQEZoxXOobcNHzZgk 6bk1jx4aHk21w+mgWCirnCp5Vk63F29CxIDmb8qRpOKJEjklaGZMlpLr6bfWDQsvZW ATp2UGmo6cjQA== Received: from libav.khirnov.net (libav.khirnov.net [IPv6:2a00:c500:561:201::7]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256 client-signature RSA-PSS (2048 bits) client-digest SHA256) (Client CN "libav.khirnov.net", Issuer "smtp.khirnov.net SMTP CA" (verified OK)) by mail1.khirnov.net (Postfix) with ESMTPS id 0E8714DD7 for ; Sat, 10 Aug 2024 19:16:33 +0200 (CEST) Received: from libav.khirnov.net (libav.khirnov.net [IPv6:::1]) by libav.khirnov.net (Postfix) with ESMTP id 51EF83A0F81 for ; Sat, 10 Aug 2024 19:16:26 +0200 (CEST) From: Anton Khirnov To: ffmpeg-devel@ffmpeg.org Date: Sat, 10 Aug 2024 19:16:18 +0200 Message-ID: <20240810171621.26757-3-anton@khirnov.net> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20240810171621.26757-1-anton@khirnov.net> References: <20240810171621.26757-1-anton@khirnov.net> MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH 3/6] fftools/cmdutils: put stream specifier handling back into cmdutils X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: POlZkBWfzHL0 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; }