From patchwork Tue Jan 23 17:39:53 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: rshaffer@tunein.com X-Patchwork-Id: 7400 Delivered-To: ffmpegpatchwork@gmail.com Received: by 10.2.156.27 with SMTP id q27csp4577508jak; Tue, 23 Jan 2018 09:45:54 -0800 (PST) X-Google-Smtp-Source: AH8x225S8aKPEmW9zWTXesSr/0Ou/+w3cD2byykTwMS6Bl+TSEDC1epW9NFpp8KiGZ+dUbi1Wb2I X-Received: by 10.223.186.194 with SMTP id w2mr2897245wrg.154.1516729554759; Tue, 23 Jan 2018 09:45:54 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1516729554; cv=none; d=google.com; s=arc-20160816; b=LPgyAlzRGGywEds09lgJid0qdXCjkA0StT8rech2XL2yWXkzbu28CUsAGVBRpFZJIR 5a5F9o1XCqkXyxNpPm7umDB+EKBJ84ShWyACRt4FAf0TZIcOQWad6dDsPw19k8eXtoTO rcDevusEhNWrJgYbHIa/AnqgLUG+fCRm2qUfpFWOZfhVIDKa3qBSleV4+qQHrbt5JhBX yooaEIvwSP7oG+qEBa109BmKVZT9uabiCpBr7RoJ5JWpUopq9Ho6zunXuKMHVR7P/I2Q JUYTfzQTmDCL/+UNuD7EY9wOvPbJcGgBrBklYLMjGCWoT6HEW6G80xUBYqbodlsgoUP8 zpnQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:mime-version:cc:reply-to :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:message-id:date:to:from:dkim-signature :delivered-to:arc-authentication-results; bh=Hhy4lGsnncRX9aGtkLmQ1PNO+8CFOgFaUFWHDVrCfqk=; b=Y1VWFcelOk8yZzoWfO5GkEsoJZmmokCQpzEPOqyB/fz2I6dWnnrCdt9AIy2N6vxfba HavKAUR5hjjgJLgNhAZMGNChDGzdUkK3/SKg0yK1vtQCr5UaCkEzYm33uVHQgFXTWBhI zGD7kZoatqNQu03NWG4CAq94O3UuC2ZcA0SLcDP7uHGmaaITHxboVtr7XhHWRqlD8PHj ka5DmmruxCe2LhZAUL7+Meko55btg1BAkSJBF+1opBBqzRiRXwwfBFg58bt+ZtqlyyFu T4qr09+HuftG7J8xlVmNZCkreypWTfebgwioHiRyh/O1G+brohh/GsyCPNql5Yli89BA PuMQ== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@tunein-com.20150623.gappssmtp.com header.s=20150623 header.b=w2r8LsM+; 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 h17si590196wre.329.2018.01.23.09.45.54; Tue, 23 Jan 2018 09:45:54 -0800 (PST) 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=@tunein-com.20150623.gappssmtp.com header.s=20150623 header.b=w2r8LsM+; 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 A01EA689C89; Tue, 23 Jan 2018 19:45:50 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-it0-f42.google.com (mail-it0-f42.google.com [209.85.214.42]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 0538B689B08 for ; Tue, 23 Jan 2018 19:45:45 +0200 (EET) Received: by mail-it0-f42.google.com with SMTP id e1so1966421ita.0 for ; Tue, 23 Jan 2018 09:45:47 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=tunein-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id; bh=7puTeescI5xP+fynknriNOLV3CdKT66ABBsTQFVau8I=; b=w2r8LsM+LxzeWJN96iWhcqhKe8dO/gCQRszNsESwC7rPSFV3FrjAzbqJVLlsLmDzsS 0t3G5ZodxCMi8yrBBN8JpLQ411eZqzfURmZMG6flI1yLL+X9PYF8VF4BxF9OIh2vsNTK B3VQZpNUKbZFLvJ1XTKDC+awZnMOebhomel9sNI3lFaykxelSwyjWxFuzJ0cQF6ti6cL zMSTglZXXGXschLWqzOH/lRwVX99hTK2Q2KbQwvPXhQjwzRI0hYtno2uZuLmRXw607PT wMIm7qAgHd1HIYMfo+lHfHQStyKIbjRJrQKxjC4rIpENS52rjmKf5TUAr97FamSdw1IE 4fTg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id; bh=7puTeescI5xP+fynknriNOLV3CdKT66ABBsTQFVau8I=; b=pR7EeMdpINmKZNA+i91+MahR+wBt0/y3F1dSrbX9cuqIE4AT7sFLvjxB1yi9DA7Cda yQvMPGZ8DvTgTJBUNh33UDe65WVXVHRyYEXQ6BPIy5RPK8zcn60hbST15v3vV4rkNXkO +kGCa9JRE5h6LBKnVCslNcp3xfb28gpu0Pg+XQvrXQaSkiMkFRXeiuSpt5jqBiCLzxi7 e8hQ3298fsWLztRRVnKE+jj7oAyrFme6cbaZdYT41YvtJqtq0IEhdVGHJjHfzUFyjDLT m6UpRmKKidrOQwyvZaoUdQ5hxSj4gV5mPjagHBvHo9h1IxYGsdRwDXYXxREvDKLH6W5q 2MAA== X-Gm-Message-State: AKwxyte3B+zHRSy6RLGGwJwCZomO2BwK5zMx09GI7HAmtS98fj8tPMgo LSLV3nGxJU7pOMII7anxDPI6vOvFqGQKIw== X-Received: by 10.36.84.69 with SMTP id t66mr4506881ita.49.1516729200340; Tue, 23 Jan 2018 09:40:00 -0800 (PST) Received: from 000984.attlocal.net (104-58-207-63.lightspeed.sntcca.sbcglobal.net. [104.58.207.63]) by smtp.gmail.com with ESMTPSA id 82sm7609021iod.54.2018.01.23.09.39.59 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Tue, 23 Jan 2018 09:39:59 -0800 (PST) From: rshaffer@tunein.com To: ffmpeg-devel@ffmpeg.org Date: Tue, 23 Jan 2018 09:39:53 -0800 Message-Id: <20180123173953.63821-1-rshaffer@tunein.com> X-Mailer: git-send-email 2.14.3 (Apple Git-98) Subject: [FFmpeg-devel] [PATCH] avformat: add option to parse/store ID3 PRIV tags in metadata. X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 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 Cc: wm4 , Richard Shaffer , Moritz Barsnick MIME-Version: 1.0 Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" From: Richard Shaffer Enables getting access to ID3 PRIV tags from the command-line or metadata API when demuxing. The PRIV owner is stored as the metadata key prepended with "id3v2_priv.", and the data is stored as the metadata value. As PRIV tags may contain arbitrary data, non-printable characters, including NULL bytes, are escaped as \xXX. Similarly, any metadata tags that begin with "id3v2_priv." are inserted as ID3 PRIV tags into the output (assuming the format supports ID3). \xXX sequences in the value are un-escaped to their byte value. --- Whitespace changes re Moritz' comments on code format. libavformat/id3v2.c | 48 ++++++++++++++++++++++++++++++++++++++++ libavformat/id3v2.h | 15 +++++++++++++ libavformat/id3v2enc.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++ libavformat/utils.c | 2 ++ 4 files changed, 125 insertions(+) diff --git a/libavformat/id3v2.c b/libavformat/id3v2.c index 6c216ba7a2..b80178d67a 100644 --- a/libavformat/id3v2.c +++ b/libavformat/id3v2.c @@ -33,6 +33,7 @@ #endif #include "libavutil/avstring.h" +#include "libavutil/bprint.h" #include "libavutil/dict.h" #include "libavutil/intreadwrite.h" #include "avio_internal.h" @@ -1224,3 +1225,50 @@ end: av_freep(&chapters); return ret; } + +int ff_id3v2_parse_priv_dict(AVDictionary **metadata, ID3v2ExtraMeta **extra_meta) +{ + ID3v2ExtraMeta *cur; + int dict_flags = AV_DICT_DONT_OVERWRITE | AV_DICT_DONT_STRDUP_KEY | AV_DICT_DONT_STRDUP_VAL; + + for (cur = *extra_meta; cur; cur = cur->next) { + if (!strcmp(cur->tag, "PRIV")) { + ID3v2ExtraMetaPRIV *priv = cur->data; + AVBPrint bprint; + char *escaped, *key; + int i, ret; + + if ((key = av_asprintf(ID3v2_PRIV_METADATA_PREFIX "%s", priv->owner)) == NULL) { + return AVERROR(ENOMEM); + } + + av_bprint_init(&bprint, priv->datasize + 1, AV_BPRINT_SIZE_UNLIMITED); + + for (i = 0; i < priv->datasize; i++) { + if (priv->data[i] < 32 || priv->data[i] > 126 || priv->data[i] == '\\') { + av_bprintf(&bprint, "\\x%02x", priv->data[i]); + } else { + av_bprint_chars(&bprint, priv->data[i], 1); + } + } + + if ((ret = av_bprint_finalize(&bprint, &escaped)) < 0) { + av_free(key); + return ret; + } + + if ((ret = av_dict_set(metadata, key, escaped, dict_flags)) < 0) { + av_free(key); + av_free(escaped); + return ret; + } + } + } + + return 0; +} + +int ff_id3v2_parse_priv(AVFormatContext *s, ID3v2ExtraMeta **extra_meta) +{ + return ff_id3v2_parse_priv_dict(&s->metadata, extra_meta); +} diff --git a/libavformat/id3v2.h b/libavformat/id3v2.h index 5e64ead096..9de0bee374 100644 --- a/libavformat/id3v2.h +++ b/libavformat/id3v2.h @@ -39,6 +39,8 @@ #define ID3v2_FLAG_ENCRYPTION 0x0004 #define ID3v2_FLAG_COMPRESSION 0x0008 +#define ID3v2_PRIV_METADATA_PREFIX "id3v2_priv." + enum ID3v2Encoding { ID3v2_ENCODING_ISO8859 = 0, ID3v2_ENCODING_UTF16BOM = 1, @@ -167,6 +169,19 @@ int ff_id3v2_parse_apic(AVFormatContext *s, ID3v2ExtraMeta **extra_meta); */ int ff_id3v2_parse_chapters(AVFormatContext *s, ID3v2ExtraMeta **extra_meta); +/** + * Parse PRIV tags into a dictionary. The PRIV owner is the metadata key. The + * PRIV data is the value, with non-printable characters escaped. + */ +int ff_id3v2_parse_priv_dict(AVDictionary **d, ID3v2ExtraMeta **extra_meta); + +/** + * Add metadata for all PRIV tags in the ID3v2 header. The PRIV owner is the + * metadata key. The PRIV data is the value, with non-printable characters + * escaped. + */ +int ff_id3v2_parse_priv(AVFormatContext *s, ID3v2ExtraMeta **extra_meta); + extern const AVMetadataConv ff_id3v2_34_metadata_conv[]; extern const AVMetadataConv ff_id3v2_4_metadata_conv[]; diff --git a/libavformat/id3v2enc.c b/libavformat/id3v2enc.c index 14de76ac06..ffe358f019 100644 --- a/libavformat/id3v2enc.c +++ b/libavformat/id3v2enc.c @@ -96,6 +96,59 @@ static int id3v2_put_ttag(ID3v2EncContext *id3, AVIOContext *avioc, const char * return len + ID3v2_HEADER_SIZE; } +/** + * Write a priv frame with owner and data. 'key' is the owner prepended with + * ID3v2_PRIV_METADATA_PREFIX. 'data' is provided as a string. Any \xXX + * (where 'X' is a valid hex digit) will be unescaped to the byte value. + */ +static int id3v2_put_priv(ID3v2EncContext *id3, AVIOContext *avioc, const char *key, const char *data) +{ + int len; + uint8_t *pb; + AVIOContext *dyn_buf; + + if (!av_strstart(key, ID3v2_PRIV_METADATA_PREFIX, &key)) { + return 0; + } + + if (avio_open_dyn_buf(&dyn_buf) < 0) + return AVERROR(ENOMEM); + + // owner + null byte. + avio_write(dyn_buf, key, strlen(key) + 1); + + while (*data) { + if (av_strstart(data, "\\x", &data)) { + if (data[0] && data[1] && av_isxdigit(data[0]) && av_isxdigit(data[1])) { + char digits[] = {data[0], data[1], 0}; + avio_w8(dyn_buf, strtol(digits, NULL, 16)); + data += 2; + } else { + ffio_free_dyn_buf(&dyn_buf); + av_log(avioc, AV_LOG_ERROR, "Invalid escape '\\x%.2s' in metadata tag '" + ID3v2_PRIV_METADATA_PREFIX "%s'.\n", data, key); + return AVERROR(EINVAL); + } + } else { + avio_write(dyn_buf, data++, 1); + } + } + + len = avio_close_dyn_buf(dyn_buf, &pb); + + avio_wb32(avioc, MKBETAG('P', 'R', 'I', 'V')); + if (id3->version == 3) + avio_wb32(avioc, len); + else + id3v2_put_size(avioc, len); + avio_wb16(avioc, 0); + avio_write(avioc, pb, len); + + av_free(pb); + + return len + ID3v2_HEADER_SIZE; +} + static int id3v2_check_write_tag(ID3v2EncContext *id3, AVIOContext *pb, AVDictionaryEntry *t, const char table[][4], enum ID3v2Encoding enc) { @@ -186,6 +239,13 @@ static int write_metadata(AVIOContext *pb, AVDictionary **metadata, continue; } + if ((ret = id3v2_put_priv(id3, pb, t->key, t->value)) > 0) { + id3->len += ret; + continue; + } else if (ret < 0) { + return ret; + } + /* unknown tag, write as TXXX frame */ if ((ret = id3v2_put_ttag(id3, pb, t->key, t->value, MKBETAG('T', 'X', 'X', 'X'), enc)) < 0) return ret; diff --git a/libavformat/utils.c b/libavformat/utils.c index 3d733417e1..c15b8cc818 100644 --- a/libavformat/utils.c +++ b/libavformat/utils.c @@ -637,6 +637,8 @@ int avformat_open_input(AVFormatContext **ps, const char *filename, goto fail; if ((ret = ff_id3v2_parse_chapters(s, &id3v2_extra_meta)) < 0) goto fail; + if ((ret = ff_id3v2_parse_priv(s, &id3v2_extra_meta)) < 0) + goto fail; } else av_log(s, AV_LOG_DEBUG, "demuxer does not support additional id3 data, skipping\n"); }