ID3v2.3 tag for .mp3 files: C++98/STL parsing library

File extension

mp3

KS implementation details

License: CC0-1.0

References

This page hosts a formal specification of ID3v2.3 tag for .mp3 files using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Usage

Runtime library

All parsing code for C++98/STL generated by Kaitai Struct depends on the C++/STL runtime library. You have to install it before you can parse data.

For C++, the easiest way is to clone the runtime library sources and build them along with your project.

Code

Using Kaitai Struct in C++/STL usually consists of 3 steps.

  1. We need to create an STL input stream (std::istream). One can open local file for that, or use existing std::string or char* buffer.
    #include <fstream>
    
    std::ifstream is("path/to/local/file.mp3", std::ifstream::binary);
    
    #include <sstream>
    
    std::istringstream is(str);
    
    #include <sstream>
    
    const char buf[] = { ... };
    std::string str(buf, sizeof buf);
    std::istringstream is(str);
    
  2. We need to wrap our input stream into Kaitai stream:
    #include "kaitai/kaitaistream.h"
    
    kaitai::kstream ks(&is);
    
  3. And finally, we can invoke the parsing:
    id3v2_3_t data(&ks);
    

After that, one can get various attributes from the structure by invoking getter methods like:

data.tag() // => get tag

C++98/STL source code to parse ID3v2.3 tag for .mp3 files

id3v2_3.h

#ifndef ID3V2_3_H_
#define ID3V2_3_H_

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

#include "kaitai/kaitaistruct.h"
#include <stdint.h>
#include <vector>

#if KAITAI_STRUCT_VERSION < 9000L
#error "Incompatible Kaitai Struct C++/STL API: version 0.9 or later is required"
#endif

/**
 * \sa https://id3.org/id3v2.3.0 Source
 */

class id3v2_3_t : public kaitai::kstruct {

public:
    class u1be_synchsafe_t;
    class u2be_synchsafe_t;
    class tag_t;
    class u4be_synchsafe_t;
    class frame_t;
    class header_ex_t;
    class header_t;

    id3v2_3_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = 0, id3v2_3_t* p__root = 0);

private:
    void _read();
    void _clean_up();

public:
    ~id3v2_3_t();

    class u1be_synchsafe_t : public kaitai::kstruct {

    public:

        u1be_synchsafe_t(kaitai::kstream* p__io, id3v2_3_t::u2be_synchsafe_t* p__parent = 0, id3v2_3_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~u1be_synchsafe_t();

    private:
        bool m_padding;
        uint64_t m_value;
        id3v2_3_t* m__root;
        id3v2_3_t::u2be_synchsafe_t* m__parent;

    public:
        bool padding() const { return m_padding; }
        uint64_t value() const { return m_value; }
        id3v2_3_t* _root() const { return m__root; }
        id3v2_3_t::u2be_synchsafe_t* _parent() const { return m__parent; }
    };

    class u2be_synchsafe_t : public kaitai::kstruct {

    public:

        u2be_synchsafe_t(kaitai::kstream* p__io, id3v2_3_t::u4be_synchsafe_t* p__parent = 0, id3v2_3_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~u2be_synchsafe_t();

    private:
        bool f_value;
        int32_t m_value;

    public:
        int32_t value();

    private:
        u1be_synchsafe_t* m_byte0;
        u1be_synchsafe_t* m_byte1;
        id3v2_3_t* m__root;
        id3v2_3_t::u4be_synchsafe_t* m__parent;

    public:
        u1be_synchsafe_t* byte0() const { return m_byte0; }
        u1be_synchsafe_t* byte1() const { return m_byte1; }
        id3v2_3_t* _root() const { return m__root; }
        id3v2_3_t::u4be_synchsafe_t* _parent() const { return m__parent; }
    };

    /**
     * \sa Section 3. ID3v2 overview
     */

    class tag_t : public kaitai::kstruct {

    public:

        tag_t(kaitai::kstream* p__io, id3v2_3_t* p__parent = 0, id3v2_3_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~tag_t();

    private:
        header_t* m_header;
        header_ex_t* m_header_ex;
        bool n_header_ex;

    public:
        bool _is_null_header_ex() { header_ex(); return n_header_ex; };

    private:
        std::vector<frame_t*>* m_frames;
        std::string m_padding;
        bool n_padding;

    public:
        bool _is_null_padding() { padding(); return n_padding; };

    private:
        id3v2_3_t* m__root;
        id3v2_3_t* m__parent;

    public:
        header_t* header() const { return m_header; }
        header_ex_t* header_ex() const { return m_header_ex; }
        std::vector<frame_t*>* frames() const { return m_frames; }
        std::string padding() const { return m_padding; }
        id3v2_3_t* _root() const { return m__root; }
        id3v2_3_t* _parent() const { return m__parent; }
    };

    class u4be_synchsafe_t : public kaitai::kstruct {

    public:

        u4be_synchsafe_t(kaitai::kstream* p__io, id3v2_3_t::header_t* p__parent = 0, id3v2_3_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~u4be_synchsafe_t();

    private:
        bool f_value;
        int32_t m_value;

    public:
        int32_t value();

    private:
        u2be_synchsafe_t* m_short0;
        u2be_synchsafe_t* m_short1;
        id3v2_3_t* m__root;
        id3v2_3_t::header_t* m__parent;

    public:
        u2be_synchsafe_t* short0() const { return m_short0; }
        u2be_synchsafe_t* short1() const { return m_short1; }
        id3v2_3_t* _root() const { return m__root; }
        id3v2_3_t::header_t* _parent() const { return m__parent; }
    };

    /**
     * \sa Section 3.3. ID3v2 frame overview
     */

    class frame_t : public kaitai::kstruct {

    public:
        class flags_t;

        frame_t(kaitai::kstream* p__io, id3v2_3_t::tag_t* p__parent = 0, id3v2_3_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~frame_t();

        class flags_t : public kaitai::kstruct {

        public:

            flags_t(kaitai::kstream* p__io, id3v2_3_t::frame_t* p__parent = 0, id3v2_3_t* p__root = 0);

        private:
            void _read();
            void _clean_up();

        public:
            ~flags_t();

        private:
            bool m_flag_discard_alter_tag;
            bool m_flag_discard_alter_file;
            bool m_flag_read_only;
            uint64_t m_reserved1;
            bool m_flag_compressed;
            bool m_flag_encrypted;
            bool m_flag_grouping;
            uint64_t m_reserved2;
            id3v2_3_t* m__root;
            id3v2_3_t::frame_t* m__parent;

        public:
            bool flag_discard_alter_tag() const { return m_flag_discard_alter_tag; }
            bool flag_discard_alter_file() const { return m_flag_discard_alter_file; }
            bool flag_read_only() const { return m_flag_read_only; }
            uint64_t reserved1() const { return m_reserved1; }
            bool flag_compressed() const { return m_flag_compressed; }
            bool flag_encrypted() const { return m_flag_encrypted; }
            bool flag_grouping() const { return m_flag_grouping; }
            uint64_t reserved2() const { return m_reserved2; }
            id3v2_3_t* _root() const { return m__root; }
            id3v2_3_t::frame_t* _parent() const { return m__parent; }
        };

    private:
        bool f_is_invalid;
        bool m_is_invalid;

    public:
        bool is_invalid();

    private:
        std::string m_id;
        uint32_t m_size;
        flags_t* m_flags;
        std::string m_data;
        id3v2_3_t* m__root;
        id3v2_3_t::tag_t* m__parent;

    public:
        std::string id() const { return m_id; }
        uint32_t size() const { return m_size; }
        flags_t* flags() const { return m_flags; }
        std::string data() const { return m_data; }
        id3v2_3_t* _root() const { return m__root; }
        id3v2_3_t::tag_t* _parent() const { return m__parent; }
    };

    /**
     * ID3v2 extended header
     * \sa Section 3.2. ID3v2 extended header
     */

    class header_ex_t : public kaitai::kstruct {

    public:
        class flags_ex_t;

        header_ex_t(kaitai::kstream* p__io, id3v2_3_t::tag_t* p__parent = 0, id3v2_3_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~header_ex_t();

        class flags_ex_t : public kaitai::kstruct {

        public:

            flags_ex_t(kaitai::kstream* p__io, id3v2_3_t::header_ex_t* p__parent = 0, id3v2_3_t* p__root = 0);

        private:
            void _read();
            void _clean_up();

        public:
            ~flags_ex_t();

        private:
            bool m_flag_crc;
            uint64_t m_reserved;
            id3v2_3_t* m__root;
            id3v2_3_t::header_ex_t* m__parent;

        public:
            bool flag_crc() const { return m_flag_crc; }
            uint64_t reserved() const { return m_reserved; }
            id3v2_3_t* _root() const { return m__root; }
            id3v2_3_t::header_ex_t* _parent() const { return m__parent; }
        };

    private:
        uint32_t m_size;
        flags_ex_t* m_flags_ex;
        uint32_t m_padding_size;
        uint32_t m_crc;
        bool n_crc;

    public:
        bool _is_null_crc() { crc(); return n_crc; };

    private:
        id3v2_3_t* m__root;
        id3v2_3_t::tag_t* m__parent;

    public:
        uint32_t size() const { return m_size; }
        flags_ex_t* flags_ex() const { return m_flags_ex; }
        uint32_t padding_size() const { return m_padding_size; }
        uint32_t crc() const { return m_crc; }
        id3v2_3_t* _root() const { return m__root; }
        id3v2_3_t::tag_t* _parent() const { return m__parent; }
    };

    /**
     * ID3v2 fixed header
     * \sa Section 3.1. ID3v2 header
     */

    class header_t : public kaitai::kstruct {

    public:
        class flags_t;

        header_t(kaitai::kstream* p__io, id3v2_3_t::tag_t* p__parent = 0, id3v2_3_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~header_t();

        class flags_t : public kaitai::kstruct {

        public:

            flags_t(kaitai::kstream* p__io, id3v2_3_t::header_t* p__parent = 0, id3v2_3_t* p__root = 0);

        private:
            void _read();
            void _clean_up();

        public:
            ~flags_t();

        private:
            bool m_flag_unsynchronization;
            bool m_flag_headerex;
            bool m_flag_experimental;
            uint64_t m_reserved;
            id3v2_3_t* m__root;
            id3v2_3_t::header_t* m__parent;

        public:
            bool flag_unsynchronization() const { return m_flag_unsynchronization; }
            bool flag_headerex() const { return m_flag_headerex; }
            bool flag_experimental() const { return m_flag_experimental; }
            uint64_t reserved() const { return m_reserved; }
            id3v2_3_t* _root() const { return m__root; }
            id3v2_3_t::header_t* _parent() const { return m__parent; }
        };

    private:
        std::string m_magic;
        uint8_t m_version_major;
        uint8_t m_version_revision;
        flags_t* m_flags;
        u4be_synchsafe_t* m_size;
        id3v2_3_t* m__root;
        id3v2_3_t::tag_t* m__parent;

    public:
        std::string magic() const { return m_magic; }
        uint8_t version_major() const { return m_version_major; }
        uint8_t version_revision() const { return m_version_revision; }
        flags_t* flags() const { return m_flags; }
        u4be_synchsafe_t* size() const { return m_size; }
        id3v2_3_t* _root() const { return m__root; }
        id3v2_3_t::tag_t* _parent() const { return m__parent; }
    };

private:
    tag_t* m_tag;
    id3v2_3_t* m__root;
    kaitai::kstruct* m__parent;

public:
    tag_t* tag() const { return m_tag; }
    id3v2_3_t* _root() const { return m__root; }
    kaitai::kstruct* _parent() const { return m__parent; }
};

#endif  // ID3V2_3_H_

id3v2_3.cpp

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

#include "id3v2_3.h"
#include "kaitai/exceptions.h"

id3v2_3_t::id3v2_3_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, id3v2_3_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = this;
    m_tag = 0;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void id3v2_3_t::_read() {
    m_tag = new tag_t(m__io, this, m__root);
}

id3v2_3_t::~id3v2_3_t() {
    _clean_up();
}

void id3v2_3_t::_clean_up() {
    if (m_tag) {
        delete m_tag; m_tag = 0;
    }
}

id3v2_3_t::u1be_synchsafe_t::u1be_synchsafe_t(kaitai::kstream* p__io, id3v2_3_t::u2be_synchsafe_t* p__parent, id3v2_3_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void id3v2_3_t::u1be_synchsafe_t::_read() {
    m_padding = m__io->read_bits_int_be(1);
    m_value = m__io->read_bits_int_be(7);
}

id3v2_3_t::u1be_synchsafe_t::~u1be_synchsafe_t() {
    _clean_up();
}

void id3v2_3_t::u1be_synchsafe_t::_clean_up() {
}

id3v2_3_t::u2be_synchsafe_t::u2be_synchsafe_t(kaitai::kstream* p__io, id3v2_3_t::u4be_synchsafe_t* p__parent, id3v2_3_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_byte0 = 0;
    m_byte1 = 0;
    f_value = false;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void id3v2_3_t::u2be_synchsafe_t::_read() {
    m_byte0 = new u1be_synchsafe_t(m__io, this, m__root);
    m_byte1 = new u1be_synchsafe_t(m__io, this, m__root);
}

id3v2_3_t::u2be_synchsafe_t::~u2be_synchsafe_t() {
    _clean_up();
}

void id3v2_3_t::u2be_synchsafe_t::_clean_up() {
    if (m_byte0) {
        delete m_byte0; m_byte0 = 0;
    }
    if (m_byte1) {
        delete m_byte1; m_byte1 = 0;
    }
}

int32_t id3v2_3_t::u2be_synchsafe_t::value() {
    if (f_value)
        return m_value;
    m_value = ((byte0()->value() << 7) | byte1()->value());
    f_value = true;
    return m_value;
}

id3v2_3_t::tag_t::tag_t(kaitai::kstream* p__io, id3v2_3_t* p__parent, id3v2_3_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_header = 0;
    m_header_ex = 0;
    m_frames = 0;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void id3v2_3_t::tag_t::_read() {
    m_header = new header_t(m__io, this, m__root);
    n_header_ex = true;
    if (header()->flags()->flag_headerex()) {
        n_header_ex = false;
        m_header_ex = new header_ex_t(m__io, this, m__root);
    }
    m_frames = new std::vector<frame_t*>();
    {
        int i = 0;
        frame_t* _;
        do {
            _ = new frame_t(m__io, this, m__root);
            m_frames->push_back(_);
            i++;
        } while (!( (((_io()->pos() + _->size()) > header()->size()->value()) || (_->is_invalid())) ));
    }
    n_padding = true;
    if (header()->flags()->flag_headerex()) {
        n_padding = false;
        m_padding = m__io->read_bytes((header_ex()->padding_size() - _io()->pos()));
    }
}

id3v2_3_t::tag_t::~tag_t() {
    _clean_up();
}

void id3v2_3_t::tag_t::_clean_up() {
    if (m_header) {
        delete m_header; m_header = 0;
    }
    if (!n_header_ex) {
        if (m_header_ex) {
            delete m_header_ex; m_header_ex = 0;
        }
    }
    if (m_frames) {
        for (std::vector<frame_t*>::iterator it = m_frames->begin(); it != m_frames->end(); ++it) {
            delete *it;
        }
        delete m_frames; m_frames = 0;
    }
    if (!n_padding) {
    }
}

id3v2_3_t::u4be_synchsafe_t::u4be_synchsafe_t(kaitai::kstream* p__io, id3v2_3_t::header_t* p__parent, id3v2_3_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_short0 = 0;
    m_short1 = 0;
    f_value = false;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void id3v2_3_t::u4be_synchsafe_t::_read() {
    m_short0 = new u2be_synchsafe_t(m__io, this, m__root);
    m_short1 = new u2be_synchsafe_t(m__io, this, m__root);
}

id3v2_3_t::u4be_synchsafe_t::~u4be_synchsafe_t() {
    _clean_up();
}

void id3v2_3_t::u4be_synchsafe_t::_clean_up() {
    if (m_short0) {
        delete m_short0; m_short0 = 0;
    }
    if (m_short1) {
        delete m_short1; m_short1 = 0;
    }
}

int32_t id3v2_3_t::u4be_synchsafe_t::value() {
    if (f_value)
        return m_value;
    m_value = ((short0()->value() << 14) | short1()->value());
    f_value = true;
    return m_value;
}

id3v2_3_t::frame_t::frame_t(kaitai::kstream* p__io, id3v2_3_t::tag_t* p__parent, id3v2_3_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_flags = 0;
    f_is_invalid = false;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void id3v2_3_t::frame_t::_read() {
    m_id = kaitai::kstream::bytes_to_str(m__io->read_bytes(4), std::string("ASCII"));
    m_size = m__io->read_u4be();
    m_flags = new flags_t(m__io, this, m__root);
    m_data = m__io->read_bytes(size());
}

id3v2_3_t::frame_t::~frame_t() {
    _clean_up();
}

void id3v2_3_t::frame_t::_clean_up() {
    if (m_flags) {
        delete m_flags; m_flags = 0;
    }
}

id3v2_3_t::frame_t::flags_t::flags_t(kaitai::kstream* p__io, id3v2_3_t::frame_t* p__parent, id3v2_3_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void id3v2_3_t::frame_t::flags_t::_read() {
    m_flag_discard_alter_tag = m__io->read_bits_int_be(1);
    m_flag_discard_alter_file = m__io->read_bits_int_be(1);
    m_flag_read_only = m__io->read_bits_int_be(1);
    m_reserved1 = m__io->read_bits_int_be(5);
    m_flag_compressed = m__io->read_bits_int_be(1);
    m_flag_encrypted = m__io->read_bits_int_be(1);
    m_flag_grouping = m__io->read_bits_int_be(1);
    m_reserved2 = m__io->read_bits_int_be(5);
}

id3v2_3_t::frame_t::flags_t::~flags_t() {
    _clean_up();
}

void id3v2_3_t::frame_t::flags_t::_clean_up() {
}

bool id3v2_3_t::frame_t::is_invalid() {
    if (f_is_invalid)
        return m_is_invalid;
    m_is_invalid = id() == (std::string("\000\000\000\000", 4));
    f_is_invalid = true;
    return m_is_invalid;
}

id3v2_3_t::header_ex_t::header_ex_t(kaitai::kstream* p__io, id3v2_3_t::tag_t* p__parent, id3v2_3_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_flags_ex = 0;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void id3v2_3_t::header_ex_t::_read() {
    m_size = m__io->read_u4be();
    m_flags_ex = new flags_ex_t(m__io, this, m__root);
    m_padding_size = m__io->read_u4be();
    n_crc = true;
    if (flags_ex()->flag_crc()) {
        n_crc = false;
        m_crc = m__io->read_u4be();
    }
}

id3v2_3_t::header_ex_t::~header_ex_t() {
    _clean_up();
}

void id3v2_3_t::header_ex_t::_clean_up() {
    if (m_flags_ex) {
        delete m_flags_ex; m_flags_ex = 0;
    }
    if (!n_crc) {
    }
}

id3v2_3_t::header_ex_t::flags_ex_t::flags_ex_t(kaitai::kstream* p__io, id3v2_3_t::header_ex_t* p__parent, id3v2_3_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void id3v2_3_t::header_ex_t::flags_ex_t::_read() {
    m_flag_crc = m__io->read_bits_int_be(1);
    m_reserved = m__io->read_bits_int_be(15);
}

id3v2_3_t::header_ex_t::flags_ex_t::~flags_ex_t() {
    _clean_up();
}

void id3v2_3_t::header_ex_t::flags_ex_t::_clean_up() {
}

id3v2_3_t::header_t::header_t(kaitai::kstream* p__io, id3v2_3_t::tag_t* p__parent, id3v2_3_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_flags = 0;
    m_size = 0;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void id3v2_3_t::header_t::_read() {
    m_magic = m__io->read_bytes(3);
    if (!(magic() == std::string("\x49\x44\x33", 3))) {
        throw kaitai::validation_not_equal_error<std::string>(std::string("\x49\x44\x33", 3), magic(), _io(), std::string("/types/header/seq/0"));
    }
    m_version_major = m__io->read_u1();
    m_version_revision = m__io->read_u1();
    m_flags = new flags_t(m__io, this, m__root);
    m_size = new u4be_synchsafe_t(m__io, this, m__root);
}

id3v2_3_t::header_t::~header_t() {
    _clean_up();
}

void id3v2_3_t::header_t::_clean_up() {
    if (m_flags) {
        delete m_flags; m_flags = 0;
    }
    if (m_size) {
        delete m_size; m_size = 0;
    }
}

id3v2_3_t::header_t::flags_t::flags_t(kaitai::kstream* p__io, id3v2_3_t::header_t* p__parent, id3v2_3_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void id3v2_3_t::header_t::flags_t::_read() {
    m_flag_unsynchronization = m__io->read_bits_int_be(1);
    m_flag_headerex = m__io->read_bits_int_be(1);
    m_flag_experimental = m__io->read_bits_int_be(1);
    m_reserved = m__io->read_bits_int_be(5);
}

id3v2_3_t::header_t::flags_t::~flags_t() {
    _clean_up();
}

void id3v2_3_t::header_t::flags_t::_clean_up() {
}