[FFmpeg-devel] [PATCH] avformat: add option to parse/store ID3 PRIV tags in metadata.

Richard Shaffer rshaffer at tunein.com
Mon Jan 22 23:07:51 EET 2018


I wanted to push this thread to the top of the list one more time.

wm4 was kind enough to make some comments on the original patch, which I
think I've addressed. It would be good to know where it stands. If it might
be useful to other people, it would be nice to have it accepted in ffmpeg,
or to have some additional comments if more changes are needed.

On the other hand, I don't want to be a squeaky wheel. If there is no
response or interest, I won't have any hard feelings.

Thanks again,

-Richard

On Wed, Jan 17, 2018 at 4:34 PM, <rshaffer at tunein.com> wrote:

> From: Richard Shaffer <rshaffer at tunein.com>
>
> 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.
> ---
>
> Sorry. One more update. I realized there was a possibility of reading past
> the
> end of input in id3v2_put_priv, so I added a fix.
>
> -Richard
>
>  libavformat/id3v2.c    | 48 +++++++++++++++++++++++++++++++++++++
>  libavformat/id3v2.h    | 15 ++++++++++++
>  libavformat/id3v2enc.c | 64 ++++++++++++++++++++++++++++++
> ++++++++++++++++++++
>  libavformat/utils.c    |  2 ++
>  4 files changed, 129 insertions(+)
>
> diff --git a/libavformat/id3v2.c b/libavformat/id3v2.c
> index 6c216ba7a2..e46174d7c7 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);
> +}
> \ No newline at end of file
> 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..d5e358c8b6 100644
> --- a/libavformat/id3v2enc.c
> +++ b/libavformat/id3v2enc.c
> @@ -96,6 +96,63 @@ 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));
> +            } else {
> +                // '\x' wasn't followed by two hex digits. Just write out
> +                // whatever the 2-4 characters were.
> +                avio_write(dyn_buf, "\\x", 2);
> +                if (data[0]) {
> +                    avio_w8(dyn_buf, data[0]);
> +                    if (data[1])
> +                        avio_w8(dyn_buf, data[1]);
> +                }
> +            }
> +            data += 2;
> +        } 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 +243,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");
>      }
> --
> 2.14.3 (Apple Git-98)
>
>


More information about the ffmpeg-devel mailing list