..
  Audio Tools, a module and set of tools for manipulating audio data
  Copyright (C) 2007-2015  Brian Langenberger

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

Meta Data Formats
=================

Although it's more convenient to manipulate the high-level
:class:`audiotools.MetaData` base class, one sometimes needs to be
able to view and modify the low-level implementation also.

ApeTag
------

.. class:: ApeTag(tags[, contains_header][, contains_footer])

   This is an APEv2_ tag used by the WavPack, Monkey's Audio
   and Musepack formats, among others.
   During initialization, it takes a list of :class:`ApeTagItem` objects
   and optional ``contains_header``, ``contains_footer`` booleans.
   It can then be manipulated like a regular Python dict
   with keys as strings and values as :class:`ApeTagItem` objects.
   Note that this is also a :class:`audiotools.MetaData` subclass
   with all of the same methods.

   For example:

   >>> tag = ApeTag([ApeTagItem(0, False, 'Title', u'Track Title'.encode('utf-8'))])
   >>> tag.track_name
   u'Track Title'
   >>> tag['Title']
   ApeTagItem(0, False, 'Title', 'Track Title')
   >>> tag['Title'] = ApeTagItem(0, False, 'Title', u'New Title'.encode('utf-8'))
   >>> tag.track_name
   u'New Title'
   >>> tag.track_name = u'Yet Another Title'
   >>> tag['Title']
   ApeTagItem(0, False, 'Title', 'Yet Another Title')

   The fields are mapped between :class:`ApeTag` and
   :class:`audiotools.MetaData` as follows:

   =============== ================================
   APEv2           Metadata
   --------------- --------------------------------
   ``Title``       ``track_name``
   ``Track``       ``track_number``/``track_total``
   ``Media``       ``album_number``/``album_total``
   ``Album``       ``album_name``
   ``Artist``      ``artist_name``
   ``Performer``   ``performer_name``
   ``Composer``    ``composer_name``
   ``Conductor``   ``conductor_name``
   ``ISRC``        ``ISRC``
   ``Catalog``     ``catalog``
   ``Copyright``   ``copyright``
   ``Publisher``   ``publisher``
   ``Year``        ``year``
   ``Record Date`` ``date``
   ``Comment``     ``comment``
   =============== ================================

   Note that ``Track`` and ``Media`` may be "/"-separated integer values
   where the first is the current number and the second is the total number.

   >>> tag = ApeTag([ApeTagItem(0, False, 'Track', u'1'.encode('utf-8'))])
   >>> tag.track_number
   1
   >>> tag.track_total  # None
   >>> tag = ApeTag([ApeTagItem(0, False, 'Track', u'2/3'.encode('utf-8'))])
   >>> tag.track_number
   2
   >>> tag.track_total
   3

.. classmethod:: ApeTag.read(file)

   Takes an open file object and returns an :class:`ApeTag` object
   of that file's APEv2 data, or ``None`` if the tag cannot be found.

.. method:: ApeTag.build(writer)

   Given a :class:`audiotools.bitstream.BitstreamWriter`
   object positioned at the end of the file,
   this writes the ApeTag to that stream.

.. method:: ApeTag.total_size()

   Returns the minimum size of the entire APEv2 tag, in bytes.

.. class:: ApeTagItem(item_type, read_only, key, data)

   This is the container for :class:`ApeTag` data.
   ``item_type`` is an integer with one of the following values:

   = =============
   1 UTF-8 data
   2 binary data
   3 external data
   4 reserved
   = =============

   ``read_only`` is a boolean set to ``True`` if the tag-item is read-only.
   ``key`` is an ASCII string.
   ``data`` is a regular Python string (not Unicode).

.. method:: ApeTagItem.build()

   Returns this tag item's data as a string.

.. classmethod:: ApeTagItem.binary(key, data)

   A convenience classmethod which takes strings of key and value data
   and returns a populated :class:`ApeTagItem` object of the
   appropriate type.

.. classmethod:: ApeTagItem.external(key, data)

   A convenience classmethod which takes strings of key and value data
   and returns a populated :class:`ApeTagItem` object of the
   appropriate type.

.. classmethod:: ApeTagItem.string(key, unicode)

   A convenience classmethod which takes a key string and value Unicode
   and returns a populated :class:`ApeTagItem` object of the
   appropriate type.

FLAC
----

.. class:: FlacMetaData(blocks)

   This is a FLAC_ tag which is prepended to FLAC and Ogg FLAC files.
   It is initialized with a list of FLAC metadata block
   objects which it stores internally as a list.
   It also supports all :class:`audiotools.MetaData` methods.

   For example:

   >>> tag = FlacMetaData([Flac_VORBISCOMMENT(
   ...                     [u'TITLE=Track Title'], u'Vendor String')])
   >>> tag.track_name
   u'Track Title'
   >>> tag.get_block(Flac_VORBISCOMMENT.BLOCK_ID)
   Flac_VORBISCOMMENT([u'TITLE=Track Title'], u'Vendor String')
   >>> tag.replace_blocks(Flac_VORBISCOMMENT.BLOCK_ID,
   ...                    [Flac_VORBISCOMMENT([u'TITLE=New Track Title'], u'Vendor String')])
   >>> tag.track_name
   u'New Track Title'

.. method:: FlacMetaData.has_block(block_id)

   Returns ``True`` if the given block ID integer is present
   in this metadata's list of blocks.

.. method:: FlacMetaData.add_block(block)

   Adds the given block to this metadata's list of blocks.
   Blocks are added such that STREAMINFO will always be first.

.. method:: FlacMetaData.get_block(block_id)

   Returns the first instance of the given block ID.
   May raise :exc:`IndexError` if the block ID is not present.

.. method:: FlacMetaData.get_blocks(block_id)

   Returns all instances of the given block ID as a list,
   which may be empty if no matching blocks are present.

.. method:: FlacMetaData.replace_blocks(block_id, blocks)

   Replaces all instances of the given block ID
   with those taken from the list of blocks.
   If insufficient blocks are found to replace,
   this uses :meth:`FlacMetaData.add_block` to populate the remainder.
   If additional blocks are found, they are removed.

.. method:: FlacMetaData.blocks()

   Yields a set of all blocks this metadata contains.

.. classmethod:: FlacMetaData.parse(reader)

   Given a :class:`audiotools.bitstream.BitstreamReader`
   positioned past the FLAC file's ``"fLaC"`` file header,
   returns a parsed :class:`FlacMetaData` object.
   May raise :exc:`IOError` or :exc:`ValueError` if
   some error occurs parsing the metadata.

.. method:: FlacMetaData.raw_info()

   Returns this metadata as a human-readable Unicode string.

.. method:: FlacMetaData.size()

   Returns the size of all metadata blocks
   including the 32-bit block headers
   but not including the file's 4-byte ``"fLaC"`` ID.

STREAMINFO
^^^^^^^^^^

.. class:: Flac_STREAMINFO(minimum_block_size, maximum_block_size, minimum_frame_size, maximum_frame_size, sample_rate, channels, bits_per_sample, total_samples, md5sum)

   All values are non-negative integers except for ``md5sum``,
   which is a 16-byte binary string.
   All are stored in this metadata block as-is.

.. data:: Flac_STREAMINFO.BLOCK_ID

   This metadata's block ID as a non-negative integer.

.. method:: Flac_STREAMINFO.copy()

   Returns a copy of this metadata block.

.. method:: Flac_STREAMINFO.raw_info()

   Returns this metadata block as a human-readable Unicode string.

.. classmethod:: Flac_STREAMINFO.parse(reader)

   Given a :class:`audiotools.bitstream.BitstreamReader`, returns a parsed :class:`Flac_STREAMINFO` object.
   This presumes its 32-bit metadata header has already been read.

.. method:: Flac_STREAMINFO.build(writer)

   Writes this metadata block to the given :class:`audiotools.bitstream.BitstreamWriter`,
   not including its 32-bit metadata header.

.. method:: Flac_STREAMINFO.size()

   Returns the size of the metadata block,
   not including its 32-bit metadata header.

PADDING
^^^^^^^

.. class:: Flac_PADDING(length)

   Length is the length of the padding, in bytes.

.. data:: Flac_PADDING.BLOCK_ID

   This metadata's block ID as a non-negative integer.

.. method:: Flac_PADDING.copy()

   Returns a copy of this metadata block.

.. method:: Flac_PADDING.raw_info()

   Returns this metadata block as a human-readable Unicode string.

.. classmethod:: Flac_PADDING.parse(reader)

   Given a :class:`audiotools.bitstream.BitstreamReader`, returns a parsed :class:`Flac_PADDING` object.
   This presumes its 32-bit metadata header has already been read.

.. method:: Flac_PADDING.build(writer)

   Writes this metadata block to the given :class:`audiotools.bitstream.BitstreamWriter`,
   not including its 32-bit metadata header.

.. method:: Flac_PADDING.size()

   Returns the size of the metadata block,
   not including its 32-bit metadata header.

APPLICATION
^^^^^^^^^^^

.. class:: Flac_APPLICATION(application_id, data)

   ``application_id`` is a 4-byte binary string.
   ``data`` is a binary string.

.. data:: Flac_APPLICATION.BLOCK_ID

   This metadata's block ID as a non-negative integer.

.. method:: Flac_APPLICATION.copy()

   Returns a copy of this metadata block.

.. method:: Flac_APPLICATION.raw_info()

   Returns this metadata block as a human-readable Unicode string.

.. classmethod:: Flac_APPLICATION.parse(reader)

   Given a :class:`audiotools.bitstream.BitstreamReader`, returns a parsed :class:`Flac_APPLICATION` object.
   This presumes its 32-bit metadata header has already been read.

.. method:: Flac_APPLICATION.build(writer)

   Writes this metadata block to the given :class:`audiotools.bitstream.BitstreamWriter`,
   not including its 32-bit metadata header.

.. method:: Flac_APPLICATION.size()

   Returns the size of the metadata block,
   not including its 32-bit metadata header.

SEEKTABLE
^^^^^^^^^

.. class:: Flac_SEEKTABLE(seekpoints)

   ``seekpoints`` is a list of
   ``(PCM frame offset, byte offset, PCM frame count)`` tuples
   for each seek point in the seektable.

.. data:: Flac_SEEKTABLE.BLOCK_ID

   This metadata's block ID as a non-negative integer.

.. method:: Flac_SEEKTABLE.copy()

   Returns a copy of this metadata block.

.. method:: Flac_SEEKTABLE.raw_info()

   Returns this metadata block as a human-readable Unicode string.

.. classmethod:: Flac_SEEKTABLE.parse(reader)

   Given a :class:`audiotools.bitstream.BitstreamReader`, returns a parsed :class:`Flac_SEEKTABLE` object.
   This presumes its 32-bit metadata header has already been read.

.. method:: Flac_SEEKTABLE.build(writer)

   Writes this metadata block to the given :class:`audiotools.bitstream.BitstreamWriter`,
   not including its 32-bit metadata header.

.. method:: Flac_SEEKTABLE.size()

   Returns the size of the metadata block,
   not including its 32-bit metadata header.

.. method:: Flac_SEEKTABLE.clean(fixes_performed)

   Returns a fixed FLAC seektable with empty seek points removed
   and ``byte offset`` / ``PCM frame count`` values reordered
   to be incrementing.
   Any fixes performed are appended to the ``fixes_performed``
   list as Unicode strings.

VORBISCOMMENT
^^^^^^^^^^^^^

.. class:: Flac_VORBISCOMMENT(comment_strings, vendor_string)

   ``comment_strings`` is a list of Unicode strings
   and ``vendor_string`` is a Unicode string.

   See :class:`VorbisComment` on how to map strings to attributes.

   >>> Flac_VORBISCOMMENT([u"TITLE=Foo", u"ARTIST=Bar"], u"Python Audio Tools")

.. data:: Flac_VORBISCOMMENT.BLOCK_ID

   This metadata's block ID as a non-negative integer.

.. method:: Flac_VORBISCOMMENT.copy()

   Returns a copy of this metadata block.

.. method:: Flac_VORBISCOMMENT.raw_info()

   Returns this metadata block as a human-readable Unicode string.

.. classmethod:: Flac_VORBISCOMMENT.parse(reader)

   Given a :class:`audiotools.bitstream.BitstreamReader`, returns a parsed :class:`Flac_VORBISCOMMENT` object.
   This presumes its 32-bit metadata header has already been read.

.. method:: Flac_VORBISCOMMENT.build(writer)

   Writes this metadata block to the given :class:`audiotools.bitstream.BitstreamWriter`,
   not including its 32-bit metadata header.

.. method:: Flac_VORBISCOMMENT.size()

   Returns the size of the metadata block,
   not including its 32-bit metadata header.

.. classmethod:: Flac_VORBISCOMMENT.converted(metadata)

   Given a :class:`audiotools.MetaData`-compatible object,
   returns a :class:`Flac_VORBISCOMMENT` object.

CUESHEET
^^^^^^^^

.. class:: Flac_CUESHEET(catalog_number, lead_in_samples, is_cdda, tracks)

   ``catalog_number`` is a 128 byte binary string.
   ``lead_in_samples`` is a non-negative integer.
   ``is_cdda`` is 1 if the cuesheet is from an audio CD, 0 if not.
   ``tracks`` is a list of :class:`Flac_CUESHEET_track` objects.

.. data:: Flac_CUESHEET.BLOCK_ID

   This metadata's block ID as a non-negative integer.

.. method:: Flac_CUESHEET.copy()

   Returns a copy of this metadata block.

.. method:: Flac_CUESHEET.raw_info()

   Returns this metadata block as a human-readable Unicode string.

.. classmethod:: Flac_CUESHEET.parse(reader)

   Given a :class:`audiotools.bitstream.BitstreamReader`, returns a parsed :class:`Flac_CUESHEET` object.
   This presumes its 32-bit metadata header has already been read.

.. method:: Flac_CUESHEET.build(writer)

   Writes this metadata block to the given :class:`audiotools.bitstream.BitstreamWriter`,
   not including its 32-bit metadata header.

.. method:: Flac_CUESHEET.size()

   Returns the size of the metadata block,
   not including its 32-bit metadata header.

.. classmethod:: Flac_CUESHEET.converted(cuesheet, total_frames[, sample rate])

   Given a :class:`audiotools.Sheet`-compatible object,
   total length of the file in PCM frames and an optional sample rate,
   returns a new :class:`Flac_CUESHEET` object.

.. method:: Flac_CUESHEET.track_numbers()

   Returns a list of all track numbers in the sheet,
   typically starting from 1.

.. method:: Flac_CUESHEET.track(track_number)

   Given a track number, returns the corresponnding
   :class:`Flac_CUESHEET_track` object, or raises :exc:`KeyError`
   if the track is not found.

.. method:: Flac_CUESHEET.pre_gap()

   Returns the pre-gap of the entire disc as a :class:`Fraction`
   number of seconds.

.. method:: Flac_CUESHEET.track_offset(track_number)

   Given a track number, returns the offset to track track
   from the start of the stream as a :class:`Fraction` number of seconds.

   May raise :exc:`KeyError` if the track is not found.

.. method:: Flac_CUESHEET.track_length(track_number)

   Given a track number, returns the length of that track
   as a :class:`Fraction` number of seconds.

.. note::

   Unlike with regular :class:`audiotools.Sheet` objects
   in which the final track's length may be ``None``,
   the lengths of all valid FLAC cuesheet tracks will be known.

.. method:: Flac_CUESHEET.image_formatted()

   Always returns ``True`` for FLAC cuesheets.

.. method:: Flac_CUESHEET.get_metadata()

   Returns a :class:`audiotools.MetaData` object
   for sheet-specific metadata embedded in the cuesheet.
   For FLAC cuesheets, this will only contain the catalog number
   as a Unicode string.

CUESHEET track
^^^^^^^^^^^^^^

.. class:: Flac_CUESHEET_track(offset, number, ISRC, track_type, pre_emphasis, index_points)

   ``offset`` is the track's first index point offset from the start of the stream, in PCM frames.
   ``number`` number is the track's number on the CD, typically starting from 1.
   ``ISRC`` is the track's ISRC number as a 12 byte binary string.
   ``track_type`` is 0 for audio, 1 for non-audio.
   ``pre_emphasis`` is 0 for tracks with no pre-emphasis, 1 for tracks with pre-emphasis.
   ``index_points`` is a list of :class:`Flac_CUESHEET_index` objects.

.. method:: Flac_CUESHEET_track.copy()

   Returns a copy of this cuesheet track.

.. method:: Flac_CUESHEET_track.raw_info()

   Returns this cuesheet track as a human-readable Unicode string.

.. classmethod:: Flac_CUESHEET_track.parse(reader)

   Given a :class:`audiotools.bitstream.BitstreamReader`,
   returns a parsed :class:`Flac_CUESHEET_track` object.

.. method:: Flac_CUESHEET_track.build(writer)

   Writes this cuesheet track to the given :class:`audiotools.bitstream.BitstreamWriter`.

.. method:: Flac_CUESHEET_track.number()

   Returns the track's number, typically starting from 1.

.. method:: Flac_CUESHEET_track.indexes()

   Returns a list of all index points numbers in the track.
   A point number of 0 indicates a pre-gap index point.

.. method:: Flac_CUESHEET_track.index(index_number)

   Given an index number, returns a :class:`Flac_CUESHEET_index` object.

   May raise :exc:`KeyError` if the index point is not found.

.. method:: Flac_CUESHEET_track.get_metadata()

   Returns a :class:`audiotools.MetaData` object of the
   track's metadata.
   For FLAC cuesheets, this will be limited to the ISRC.

.. method:: Flac_CUESHEET_track.filename()

   Returns the track's filename as a string.
   For FLAC cuesheets, this will always be the FLAC file
   which contains the cuesheet.

.. method:: Flac_CUESHEET_track.is_audio()

   Always returns ``True``.

.. method:: Flac_CUESHEET_track.pre_emphasis()

   Returns ``True`` if the track has pre-emphasis.

.. method:: Flac_CUESHEET_track.copy_permitted()

   Always returns ``True``.

CUESHEET index
^^^^^^^^^^^^^^

.. class:: Flac_CUESHEET_index(offset, number)

   ``offset`` is the index point's offset.
   ``number`` is the index point's number in the set.

.. method:: Flac_CUESHEET_index.copy()

   Returns a copy of this cuesheet index.

.. classmethod:: Flac_CUESHEET_index.parse(reader)

   Given a :class:`audiotools.bitstream.BitstreamReader`,
   returns a parsed :class:`Flac_CUESHEET_index` object.

.. method:: Flac_CUESHEET_index.build(writer)

   Writes this index point to the given :class:`audiotools.bitstream.BitstreamWriter`.

.. method:: Flac_CUESHEET_index.number()

   Returns this index point's number where 0 indicates a pre-gap index.

.. method:: Flac_CUESHEET_index.offset()

   Returns this index point's offset from the start of the stream
   as a :class:`Fraction` number of seconds.

PICTURE
^^^^^^^

.. class:: Flac_PICTURE(picture_type, mime_type, description, width, height, color_depth, color_count, data)

   ``picture_type`` is one of the following:

   == ===================================
   0  Other
   1  32x32 pixels 'file icon' (PNG only)
   2  Other file icon
   3  Cover (front)
   4  Cover (back)
   5  Leaflet page
   6  Media (e.g. label side of CD)
   7  Lead artist/lead performer/soloist
   8  Artist/performer
   9  Conductor
   10 Band/Orchestra
   11 Composer
   12 Lyricist/text writer
   13 Recording Location
   14 During recording
   15 During performance
   16 Movie/video screen capture
   17 A bright colored fish
   18 Illustration
   19 Band/artist logotype
   20 Publisher/Studio logotype
   == ===================================

   ``mime_type`` and ``description`` are Unicode strings.
   ``width`` and ``height`` are integer number of pixels.
   ``color_depth`` is an integer number of bits per pixel.
   ``color_count`` is an integer number of colors for images
   with indexed colors, or 0 for formats such as JPEG with no indexed colors.
   ``data`` is a binary string of raw image data.

   This is a subclass of :class:`audiotools.Image`
   which shares all the same methods and attributes.

.. data:: Flac_IMAGE.BLOCK_ID

   This metadata's block ID as a non-negative integer.

.. method:: Flac_IMAGE.copy()

   Returns a copy of this metadata block.

.. method:: Flac_IMAGE.raw_info()

   Returns this metadata block as a human-readable Unicode string.

.. classmethod:: Flac_IMAGE.parse(reader)

   Given a :class:`audiotools.bitstream.BitstreamReader`,
   returns a parsed :class:`Flac_IMAGE` object.
   This presumes its 32-bit metadata header has already been read.

.. method:: Flac_IMAGE.build(writer)

   Writes this metadata block to the given :class:`audiotools.bitstream.BitstreamWriter`,
   not including its 32-bit metadata header.

.. method:: Flac_IMAGE.size()

   Returns the size of the metadata block,
   not including its 32-bit metadata header.

.. method:: Flac_IMAGE.converted(image)

   Given an :class:`Flac_IMAGE`-compatible object,
   returns a new :class:`Flac_IMAGE` block.

.. method:: Flac_IMAGE.clean(fixes_performed)

   Returns a new :class:`Flac_IMAGE` block with
   metadata fields cleaned up according to the metrics
   of the contained raw image data.
   Any fixes are appended to the ``fixes_performed`` list
   as Unicode strings.

ID3v1
-----

.. class:: ID3v1Comment([track_name][, artist_name][, album_name][, year][, comment][, track_number][, genre])

   All fields are binary strings with a fixed length:

   ============= ======
   field         length
   ------------- ------
   track_name        30
   artist_name       30
   album_name        30
   year               4
   comment           28
   track_number       1
   genre              1
   ============= ======

   >>> tag = ID3v1Comment(track_name='Track Title' + chr(0) * 19,  # note the NULL byte padding
   ...                    track_number=chr(1))
   >>> tag.track_name
   u'Track Title'
   >>> tag.track_name = u'New Track Name'
   >>> tag.track_name
   u'New Track Name'

   Undefined fields are filled with NULL bytes.
   Attempting to initialize a field with an incorrect size
   will raise a :exc:`ValueError`.

.. method:: ID3v1Comment.raw_info()

   Returns this metadata as a human-readable Unicode string.

.. classmethod:: ID3v1Comment.parse(mp3_file)

   Given a seekable file object of an MP3 file,
   returns an :class:`ID3v1Comment` object.
   Raises :exc:`ValueError` if the comment is invalid.

.. method:: ID3v1Comment.build(mp3_file)

   Given a file object positioned at the end of an MP3 file,
   appends this ID3v1 comment to that file.

ID3v2.2
-------

.. class:: ID3v22Comment(frames[, total_size])

   This is an ID3v2.2_ tag, one of the three ID3v2 variants used by MP3 files.
   During initialization, it takes a list of :class:`ID3v22_Frame`-compatible
   objects.
   It can then be manipulated like a regular Python dict with keys
   as 3 character frame identifiers and values as lists of :class:`ID3v22_Frame`
   objects - since each frame identifier may occur multiple times.

   For example:

   >>> tag = ID3v22Comment([ID3v22_T__Frame('TT2', 0, u'Track Title')])
   >>> tag.track_name
   u'Track Title'
   >>> tag['TT2']
   [<audiotools.__id3__.ID3v22_T__Frame instance at 0x1004c17a0>]
   >>> tag['TT2'] = [ID3v22_T__Frame('TT2', 0, u'New Track Title')]
   >>> tag.track_name
   u'New Track Title'

   Fields are mapped between ID3v2.2 frame identifiers,
   :class:`audiotools.MetaData` and :class:`ID3v22Frame` objects as follows:

   ========== ================================ ========================
   Identifier MetaData                         Object
   ---------- -------------------------------- ------------------------
   ``TT2``    ``track_name``                   :class:`ID3v22_T__Frame`
   ``TRK``    ``track_number``/``track_total`` :class:`ID3v22_T__Frame`
   ``TPA``    ``album_number``/``album_total`` :class:`ID3v22_T__Frame`
   ``TAL``    ``album_name``                   :class:`ID3v22_T__Frame`
   ``TP1``    ``artist_name``                  :class:`ID3v22_T__Frame`
   ``TP2``    ``performer_name``               :class:`ID3v22_T__Frame`
   ``TP3``    ``conductor_name``               :class:`ID3v22_T__Frame`
   ``TCM``    ``composer_name``                :class:`ID3v22_T__Frame`
   ``TMT``    ``media``                        :class:`ID3v22_T__Frame`
   ``TRC``    ``ISRC``                         :class:`ID3v22_T__Frame`
   ``TCR``    ``copyright``                    :class:`ID3v22_T__Frame`
   ``TPB``    ``publisher``                    :class:`ID3v22_T__Frame`
   ``TYE``    ``year``                         :class:`ID3v22_T__Frame`
   ``TRD``    ``date``                         :class:`ID3v22_T__Frame`
   ``COM``    ``comment``                      :class:`ID3v22_COM_Frame`
   ``PIC``    ``images()``                     :class:`ID3v22_PIC_Frame`
   ========== ================================ ========================

   The optional ``total_size`` field is the total size of the tag,
   not including the 6 byte ID3 header.
   If the total size is larger than the size of all tags,
   the remaining space will be padded with NULL bytes.

ID3v2.2 Frame
^^^^^^^^^^^^^

.. class:: ID3v22_Frame(frame_id, data)

   This is the base class for the various ID3v2.2 frames.
   ``frame_id`` is a 3 character string and ``data`` is
   the frame's contents as a string.

.. method:: ID3v22Frame.copy()

   Returns a new copy of this frame.

.. method:: ID3v22Frame.raw_info()

   Returns this frame as a human-readable Unicode string.

.. classmethod:: ID3v22Frame.parse(frame_id, frame_size, reader)

   Given a 3 byte frame ID, frame size and
   :class:`audiotools.bitstream.BitstreamReader`
   (positioned past the 6 byte frame header)
   returns a parsed :class:`ID3v22Frame` object.

.. method:: ID3v22Frame.build(writer)

   Writes frame to the given
   :class:`audiotools.bitstream.BitstreamWriter`,
   not including its 6 byte frame header.

.. method:: ID3v22Frame.size()

   Returns the frame's size, not including its 6 byte frame header.

.. method:: ID3v22Frame.clean(fixes_performed)

   Returns a new :class:`ID3v22Frame` object that's been cleaned
   of any problems.
   Any fixes performed are appended to ``fixes_performed``
   as Unicode strings.

ID3v2.2 Text Frames
^^^^^^^^^^^^^^^^^^^

.. class:: ID3v22_T__Frame(frame_id, encoding, data)

   This :class:`ID3v22_Frame`-compatible object is a container
   for textual data.
   ``frame_id`` is a 3 character string, ``data`` is a binary string
   and ``encoding`` is one of the following integers representing a
   text encoding:

   = ========
   0 Latin-1_
   1 UCS-2_
   = ========

.. method:: ID3v22_T__Frame.number()

   Returns the first integer portion of the frame data as an int
   if the frame is container for numerical data such as
   ``TRK`` or ``TPA``.

.. method:: ID3v22_T__Frame.total()

   Returns the second integer portion of the frame data as an int
   if the frame is a numerical container and has a "total" field.
   For example:

   >>> f = ID3v22_T__Frame('TRK', 0, '1/2')
   >>> f.number()
   1
   >>> f.total()
   2

.. classmethod:: ID3v22_T__Frame.converted(frame_id, unicode_string)

   Given a 3 byte frame ID and Unicode string,
   returns a new :class:`ID3v22_T__Frame` object.

.. class:: ID3v22_TXX_Frame(encoding, description, data)

   This subclass of :class:`ID3v22_T__Frame` contains
   an additional ``description`` binary string field
   to hold user-defined textual data.

ID3v2.2 Web Frames
^^^^^^^^^^^^^^^^^^

.. class:: ID3v22_W__Frame(frame_id, url)

   This :class:`ID3v22_Frame`-compatible object is a container
   for web links.
   ``frame_id`` is a 3 character string, ``url`` is a binary string.

.. class:: ID3v22_WXX_Frame(encoding, description, url)

   This subclass of :class:`ID3v22_W__Frame` contains
   an additional ``description`` binary string field
   to hold user-defined web link data.

ID3v2.2 COM Frame
^^^^^^^^^^^^^^^^^

.. class:: ID3v22_COM_Frame(encoding, language, short_description, data)

   This :class:`ID3v22_Frame`-compatible object is for holding
   a potentially large block of comment data.
   ``encoding`` is the same as in text frames:

   = ========
   0 Latin-1_
   1 UCS-2_
   = ========

   ``language`` is a 3 character string, such as ``"eng"`` for English.
   ``short_description`` is a :class:`C_string` object.
   ``data`` is a binary string.

.. classmethod:: ID3v22_COM_Frame.converted(frame_id, unicode_string)

   Given a 3 byte ``"COM"`` frame ID and Unicode string,
   returns a new :class:`ID3v22_COM_Frame` object.

ID3v2.2 PIC Frame
^^^^^^^^^^^^^^^^^

.. class:: ID3v22_PIC_Frame(image_format, picture_type, description, data)

   This is a subclass of :class:`audiotools.Image`, in addition
   to being a :class:`ID3v22_Frame`-compatible object.
   ``image_format`` is one of the following:

   ========== ======
   ``"PNG"``  PNG
   ``"JPG"``  JPEG
   ``"BMP"``  Bitmap
   ``"GIF"``  GIF
   ``"TIF"``  TIFF
   ========== ======

   ``picture_type`` is an integer representing one of the following:

   == ======================================
   0  Other
   1  32x32 pixels 'file icon' (PNG only)
   2  Other file icon
   3  Cover (front)
   4  Cover (back)
   5  Leaflet page
   6  Media (e.g. label side of CD)
   7  Lead artist / Lead performer / Soloist
   8  Artist / Performer
   9  Conductor
   10 Band / Orchestra
   11 Composer
   12 Lyricist / Text writer
   13 Recording Location
   14 During recording
   15 During performance
   16 Movie / Video screen capture
   17 A bright colored fish
   18 Illustration
   19 Band / Artist logotype
   20 Publisher / Studio logotype
   == ======================================

   ``description`` is a :class:`C_string`.
   ``data`` is a string of binary image data.

.. method:: ID3v22_PIC_Frame.type_string()

   Returns the ``picture_type`` as a plain string.

.. classmethod:: ID3v22_PIC_Frame.converted(frame_id, image)

   Given an :class:`audiotools.Image` object,
   returns a new :class:`ID3v22_PIC_Frame` object.


ID3v2.3
-------

.. class:: ID3v23Comment(frames[, total_size])

   This is an ID3v2.3_ tag, one of the three ID3v2 variants used by MP3 files.
   During initialization, it takes a list of :class:`ID3v23_Frame`-compatible
   objects.
   It can then be manipulated like a regular Python dict with keys
   as 4 character frame identifiers and values as lists of :class:`ID3v23_Frame`
   objects - since each frame identifier may occur multiple times.

   For example:

   >>> tag = ID3v23Comment([ID3v23_T___Frame('TIT2', 0, u'Track Title')])
   >>> tag.track_name
   u'Track Title'
   >>> tag['TIT2']
   [ID3v23_T___Frame('TIT2', 0, u'Track Title')]
   >>> tag['TIT2'] = [ID3v23_T___Frame('TIT2', 0, u'New Track Title')]
   >>> tag.track_name
   u'New Track Title'

   Fields are mapped between ID3v2.3 frame identifiers,
   :class:`audiotools.MetaData` and :class:`ID3v23_Frame` objects as follows:

   ========== ================================ ==========================
   Identifier MetaData                         Object
   ---------- -------------------------------- --------------------------
   ``TIT2``   ``track_name``                   :class:`ID3v23_T___Frame`
   ``TRCK``   ``track_number``/``track_total`` :class:`ID3v23_T___Frame`
   ``TPOS``   ``album_number``/``album_total`` :class:`ID3v23_T___Frame`
   ``TALB``   ``album_name``                   :class:`ID3v23_T___Frame`
   ``TPE1``   ``artist_name``                  :class:`ID3v23_T___Frame`
   ``TPE2``   ``performer_name``               :class:`ID3v23_T___Frame`
   ``TPE3``   ``conductor_name``               :class:`ID3v23_T___Frame`
   ``TCOM``   ``composer_name``                :class:`ID3v23_T___Frame`
   ``TMED``   ``media``                        :class:`ID3v23_T___Frame`
   ``TSRC``   ``ISRC``                         :class:`ID3v23_T___Frame`
   ``TCOP``   ``copyright``                    :class:`ID3v23_T___Frame`
   ``TPUB``   ``publisher``                    :class:`ID3v23_T___Frame`
   ``TYER``   ``year``                         :class:`ID3v23_T___Frame`
   ``TRDA``   ``date``                         :class:`ID3v23_T___Frame`
   ``COMM``   ``comment``                      :class:`ID3v23_COMM_Frame`
   ``APIC``   ``images()``                     :class:`ID3v23_APIC_Frame`
   ========== ================================ ==========================

   The optional ``total_size`` field is the total size of the tag,
   not including the 6 byte ID3 header.
   If the total size is larger than the size of all tags,
   the remaining space will be padded with NULL bytes.

ID3v2.3 Frame
^^^^^^^^^^^^^

.. class:: ID3v23_Frame(frame_id, data)

   This is the base class for the various ID3v2.3 frames.
   ``frame_id`` is a 4 character string and ``data`` is
   the frame's contents as a string.

.. method:: ID3v23_Frame.copy()

   Returns a new copy of this frame.

.. method:: ID3v23_Frame.raw_info()

   Returns this frame as a human-readable Unicode string.

.. classmethod:: ID3v23_Frame.parse(frame_id, frame_size, reader)

   Given a 4 byte frame ID, frame size and
   :class:`audiotools.bitstream.BitstreamReader`
   (positioned past the 10 byte frame header)
   returns a parsed :class:`ID3v23_Frame` object.

.. method:: ID3v23_Frame.build(writer)

   Writes frame to the given
   :class:`audiotools.bitstream.BitstreamWriter`,
   not including its 10 byte frame header.

.. method:: ID3v23_Frame.size()

   Returns the frame's size, not including its 10 byte frame header.

.. method:: ID3v23_Frame.clean(fixes_performed)

   Returns a new :class:`ID3v23_Frame` object that's been cleaned
   of any problems.
   Any fixes performed are appended to ``fixes_performed``
   as Unicode strings.

ID3v2.3 Text Frames
^^^^^^^^^^^^^^^^^^^

.. class:: ID3v23_T___Frame(frame_id, encoding, data)

   This :class:`ID3v23_Frame`-compatible object is a container
   for textual data.
   ``frame_id`` is a 3 character string, ``data`` is a binary string
   and ``encoding`` is one of the following integers representing a
   text encoding:

   = ========
   0 Latin-1_
   1 UCS-2_
   = ========

.. method:: ID3v23_T___Frame.number()

   Returns the first integer portion of the frame data as an int
   if the frame is container for numerical data such as
   ``TRCK`` or ``TPOS``.

.. method:: ID3v23_T___Frame.total()

   Returns the second integer portion of the frame data as an int
   if the frame is a numerical container and has a "total" field.
   For example:

   >>> f = ID3v23_T___Frame('TRCK', 0, '1/2')
   >>> f.number()
   1
   >>> f.total()
   2

.. classmethod:: ID3v23_T___Frame.converted(frame_id, unicode_string)

   Given a 4 byte frame ID and Unicode string,
   returns a new :class:`ID3v23_T___Frame` object.

.. class:: ID3v23_TXXX_Frame(encoding, description, data)

   This subclass of :class:`ID3v23_T___Frame` contains
   an additional ``description`` binary string field
   to hold user-defined textual data.

ID3v2.3 Web Frames
^^^^^^^^^^^^^^^^^^

.. class:: ID3v23_W___Frame(frame_id, url)

   This :class:`ID3v23_Frame`-compatible object is a container
   for web links.
   ``frame_id`` is a 4 character string, ``url`` is a binary string.

.. class:: ID3v23_WXXX_Frame(encoding, description, url)

   This subclass of :class:`ID3v23_W___Frame` contains
   an additional ``description`` binary string field
   to hold user-defined web link data.

ID3v2.3 COMM Frame
^^^^^^^^^^^^^^^^^^

.. class:: ID3v23_COMM_Frame(encoding, language, short_description, data)

   This :class:`ID3v23_Frame`-compatible object is for holding
   a potentially large block of comment data.
   ``encoding`` is the same as in text frames:

   = ========
   0 Latin-1_
   1 UCS-2_
   = ========

   ``language`` is a 3 character string, such as ``"eng"`` for English.
   ``short_description`` is a :class:`C_string` object.
   ``data`` is a binary string.

.. classmethod:: ID3v23_COMM_Frame.converted(frame_id, unicode_string)

   Given a 4 byte ``"COMM"`` frame ID and Unicode string,
   returns a new :class:`ID3v23_COMM_Frame` object.

ID3v2.3 APIC Frame
^^^^^^^^^^^^^^^^^^

.. class:: ID3v23_APIC_Frame(mime_type, picture_type, description, data)

   This is a subclass of :class:`audiotools.Image`, in addition
   to being a :class:`ID3v23_Frame`-compatible object.
   ``mime_type`` is a :class:`C_string`.
   ``picture_type`` is an integer representing one of the following:

   == ======================================
   0  Other
   1  32x32 pixels 'file icon' (PNG only)
   2  Other file icon
   3  Cover (front)
   4  Cover (back)
   5  Leaflet page
   6  Media (e.g. label side of CD)
   7  Lead artist / Lead performer / Soloist
   8  Artist / Performer
   9  Conductor
   10 Band / Orchestra
   11 Composer
   12 Lyricist / Text writer
   13 Recording Location
   14 During recording
   15 During performance
   16 Movie / Video screen capture
   17 A bright colored fish
   18 Illustration
   19 Band / Artist logotype
   20 Publisher / Studio logotype
   == ======================================

   ``description`` is a :class:`C_string`.
   ``data`` is a string of binary image data.

.. method:: ID3v23_APIC_Frame.type_string()

   Returns the ``picture_type`` as a plain string.

.. classmethod:: ID3v23_APIC_Frame.converted(frame_id, image)

   Given an :class:`audiotools.Image` object,
   returns a new :class:`ID3v23_APIC_Frame` object.

ID3v2.4
-------

.. class:: ID3v24Comment(frames[, total_size])

   This is an ID3v2.4_ tag, one of the three ID3v2 variants used by MP3 files.
   During initialization, it takes a list of :class:`ID3v24_Frame`-compatible
   objects.
   It can then be manipulated like a regular Python dict with keys
   as 4 character frame identifiers and values as lists of :class:`ID3v24_Frame`
   objects - since each frame identifier may occur multiple times.

   For example:

   >>> tag = ID3v24Comment([ID3v24_T___Frame('TIT2', 0, u'Track Title')])
   >>> tag.track_name
   u'Track Title'
   >>> tag['TIT2']
   [ID3v24_T___Frame('TIT2', 0, u'Track Title')]
   >>> tag['TIT2'] = [ID3v24_T___Frame('TIT2', 0, u'New Track Title')]
   >>> tag.track_name
   u'New Track Title'

   Fields are mapped between ID3v2.4 frame identifiers,
   :class:`audiotools.MetaData` and :class:`ID3v24_Frame` objects as follows:

   ========== ================================ ==========================
   Identifier MetaData                         Object
   ---------- -------------------------------- --------------------------
   ``TIT2``   ``track_name``                   :class:`ID3v24_T___Frame`
   ``TRCK``   ``track_number``/``track_total`` :class:`ID3v24_T___Frame`
   ``TPOS``   ``album_number``/``album_total`` :class:`ID3v24_T___Frame`
   ``TALB``   ``album_name``                   :class:`ID3v24_T___Frame`
   ``TPE1``   ``artist_name``                  :class:`ID3v24_T___Frame`
   ``TPE2``   ``performer_name``               :class:`ID3v24_T___Frame`
   ``TPE3``   ``conductor_name``               :class:`ID3v24_T___Frame`
   ``TCOM``   ``composer_name``                :class:`ID3v24_T___Frame`
   ``TMED``   ``media``                        :class:`ID3v24_T___Frame`
   ``TSRC``   ``ISRC``                         :class:`ID3v24_T___Frame`
   ``TCOP``   ``copyright``                    :class:`ID3v24_T___Frame`
   ``TPUB``   ``publisher``                    :class:`ID3v24_T___Frame`
   ``TYER``   ``year``                         :class:`ID3v24_T___Frame`
   ``TRDA``   ``date``                         :class:`ID3v24_T___Frame`
   ``COMM``   ``comment``                      :class:`ID3v24_COMM_Frame`
   ``APIC``   ``images()``                     :class:`ID3v24_APIC_Frame`
   ========== ================================ ==========================

   The optional ``total_size`` field is the total size of the tag,
   not including the 6 byte ID3 header.
   If the total size is larger than the size of all tags,
   the remaining space will be padded with NULL bytes.

ID3v2.4 Frame
^^^^^^^^^^^^^

.. class:: ID3v24_Frame(frame_id, data)

   This is the base class for the various ID3v2.4 frames.
   ``frame_id`` is a 4 character string and ``data`` is
   the frame's contents as a string.

.. method:: ID3v24_Frame.copy()

   Returns a new copy of this frame.

.. method:: ID3v24_Frame.raw_info()

   Returns this frame as a human-readable Unicode string.

.. classmethod:: ID3v24_Frame.parse(frame_id, frame_size, reader)

   Given a 4 byte frame ID, frame size and
   :class:`audiotools.bitstream.BitstreamReader`
   (positioned past the 10 byte frame header)
   returns a parsed :class:`ID3v24_Frame` object.

.. method:: ID3v24_Frame.build(writer)

   Writes frame to the given
   :class:`audiotools.bitstream.BitstreamWriter`,
   not including its 10 byte frame header.

.. method:: ID3v24_Frame.size()

   Returns the frame's size, not including its 10 byte frame header.

.. method:: ID3v24_Frame.clean(fixes_performed)

   Returns a new :class:`ID3v24_Frame` object that's been cleaned
   of any problems.
   Any fixes performed are appended to ``fixes_performed``
   as Unicode strings.

ID3v2.4 Text Frames
^^^^^^^^^^^^^^^^^^^

.. class:: ID3v24_T___Frame(frame_id, encoding, data)

   This :class:`ID3v24_Frame`-compatible object is a container
   for textual data.
   ``frame_id`` is a 3 character string, ``data`` is a binary string
   and ``encoding`` is one of the following integers representing a
   text encoding:

   = =========
   0 Latin-1_
   1 UTF-16_
   2 UTF-16BE_
   3 UTF-8_
   = =========

.. method:: ID3v24_T___Frame.number()

   Returns the first integer portion of the frame data as an int
   if the frame is container for numerical data such as
   ``TRCK`` or ``TPOS``.

.. method:: ID3v24_T___Frame.total()

   Returns the second integer portion of the frame data as an int
   if the frame is a numerical container and has a "total" field.
   For example:

   >>> f = ID3v24_T___Frame('TRCK', 0, '1/2')
   >>> f.number()
   1
   >>> f.total()
   2

.. classmethod:: ID3v24_T___Frame.converted(frame_id, unicode_string)

   Given a 4 byte frame ID and Unicode string,
   returns a new :class:`ID3v24_T___Frame` object.

.. class:: ID3v24_TXXX_Frame(encoding, description, data)

   This subclass of :class:`ID3v24_T___Frame` contains
   an additional ``description`` binary string field
   to hold user-defined textual data.

ID3v2.4 Web Frames
^^^^^^^^^^^^^^^^^^

.. class:: ID3v24_W___Frame(frame_id, url)

   This :class:`ID3v24_Frame`-compatible object is a container
   for web links.
   ``frame_id`` is a 4 character string, ``url`` is a binary string.

.. class:: ID3v24_WXXX_Frame(encoding, description, url)

   This subclass of :class:`ID3v24_W___Frame` contains
   an additional ``description`` binary string field
   to hold user-defined web link data.

ID3v2.4 COMM Frame
^^^^^^^^^^^^^^^^^^

.. class:: ID3v24_COMM_Frame(encoding, language, short_description, data)

   This :class:`ID3v24_Frame`-compatible object is for holding
   a potentially large block of comment data.
   ``encoding`` is the same as in text frames:

   = =========
   0 Latin-1_
   1 UTF-16_
   2 UTF-16BE_
   3 UTF-8_
   = =========

   ``language`` is a 3 character string, such as ``"eng"`` for English.
   ``short_description`` is a :class:`C_string` object.
   ``data`` is a binary string.

.. classmethod:: ID3v24_COMM_Frame.converted(frame_id, unicode_string)

   Given a 4 byte ``"COMM"`` frame ID and Unicode string,
   returns a new :class:`ID3v23_COMM_Frame` object.

ID3v2.4 APIC Frame
^^^^^^^^^^^^^^^^^^

.. class:: ID3v24_APIC_Frame(mime_type, picture_type, description, data)

   This is a subclass of :class:`audiotools.Image`, in addition
   to being a :class:`ID3v24_Frame`-compatible object.
   ``mime_type`` is a :class:`C_string`.
   ``picture_type`` is an integer representing one of the following:

   == ======================================
   0  Other
   1  32x32 pixels 'file icon' (PNG only)
   2  Other file icon
   3  Cover (front)
   4  Cover (back)
   5  Leaflet page
   6  Media (e.g. label side of CD)
   7  Lead artist / Lead performer / Soloist
   8  Artist / Performer
   9  Conductor
   10 Band / Orchestra
   11 Composer
   12 Lyricist / Text writer
   13 Recording Location
   14 During recording
   15 During performance
   16 Movie / Video screen capture
   17 A bright colored fish
   18 Illustration
   19 Band / Artist logotype
   20 Publisher / Studio logotype
   == ======================================

   ``description`` is a :class:`C_string`.
   ``data`` is a string of binary image data.

.. method:: ID3v24_APIC_Frame.type_string()

   Returns the ``picture_type`` as a plain string.

.. classmethod:: ID3v24_APIC_Frame.converted(frame_id, image)

   Given an :class:`audiotools.Image` object,
   returns a new :class:`ID3v24_APIC_Frame` object.

ID3 Comment Pair
----------------

Often, MP3 files are tagged with both an ID3v2 comment and an ID3v1 comment
for maximum compatibility.
This class encapsulates both comments into a single class.

.. class:: ID3CommentPair(id3v2_comment, id3v1_comment)

   ``id3v2_comment`` is an :class:`ID3v22Comment`, :class:`ID3v23Comment`
   or :class:`ID3v24Comment`.
   ``id3v1_comment`` is an :class:`ID3v1Comment`.
   When getting :class:`audiotools.MetaData` attributes,
   the ID3v2 comment is used by default.
   Set attributes are propagated to both.
   For example:

   >>> tag = ID3CommentPair(ID3v23Comment([ID3v23_T___Frame('TIT2', 0, 'Title 1')]),
   ...                      ID3v1Comment(u'Title 2',u'',u'',u'',u'',1, 0))
   >>> tag.track_name
   u'Title 1'
   >>> tag.track_name = u'New Track Title'
   >>> unicode(tag.id3v2['TIT2'][0])
   u'New Track Title'
   >>> tag.id3v1.track_name
   u'New Track Title'

.. data:: ID3CommentPair.id3v2

   The embedded :class:`ID3v22Comment`, :class:`ID3v23Comment`
   or :class:`ID3v24Comment`

.. data:: ID3CommentPair.id3v1

   The embedded :class:`ID3v1Comment`

M4A
---

.. class:: M4A_META_Atom(version, flags, leaf_atoms)

   This is the metadata format used by QuickTime-compatible formats such as
   M4A and Apple Lossless.
   ``version`` and ``flags`` are integers (typically 0)
   and ``leaf_atoms`` is a list of atom objects,
   one of which should be an ``ilst`` tree atom.
   In addition to being a :class:`audiotools.MetaData` subclass,
   this is also a :class:`M4A_Tree_Atom` subclass
   with several methods specific for metadata.
   As an example:

   >>> tag = M4A_META_Atom(0, 0,
   ...                     [M4A_Tree_Atom('ilst',
   ...                                    [M4A_ILST_Leaf_Atom('\xa9nam',
   ...                                                        [M4A_ILST_Unicode_Data_Atom(0, 1, "Track Name")])])])
   >>> tag.track_name
   u'Track Name'
   >>> tag['ilst']['\xa9nam']
   ... M4A_ILST_Leaf_Atom('\xa9nam', [M4A_ILST_Unicode_Data_Atom(0, 1, 'Track Name')])
   >>> tag['ilst'].replace_child(M4A_ILST_Leaf_Atom('\xa9nam',
   ...                                              [M4A_ILST_Unicode_Data_Atom(0, 1, 'New Track Name')])
   >>> tag.track_name
   u'New Track Name'

   Fields are mapped between :class:`M4AMetaData`,
   :class:`audiotools.MetaData` and iTunes as follows:

   ============= ================================ ============
   M4AMetaData   MetaData                         iTunes
   ------------- -------------------------------- ------------
   ``"\xA9nam"`` ``track_name``                   Name
   ``"\xA9ART"`` ``artist_name``                  Artist
   ``"\xA9day"`` ``year``                         Year
   ``"trkn"``    ``track_number``/``track_total`` Track Number
   ``"disk"``    ``album_number``/``album_total`` Album Number
   ``"\xA9alb"`` ``album_name``                   Album
   ``"\xA9wrt"`` ``composer_name``                Composer
   ``"\xA9cmt"`` ``comment``                      Comment
   ``"cprt"``    ``copyright``
   ============= ================================ ============

   Note that several of the 4 character keys are prefixed by
   the non-ASCII byte ``0xA9``.

.. method:: M4A_META_Atom.has_ilst_atom()

   Returns ``True`` if the atom has an ``ilst`` child atom.

.. method:: M4A_META_Atom.ilst_atom()

   Returns the first ``ilst`` child atom,
   or ``None`` if no ``ilst`` atom is present.

.. method:: M4A_META_Atom.add_ilst_atom()

   Appends a new, empty ``ilst`` atom after
   the first ``hdlr`` atom, if any.

.. method:: M4A_META_Atom.raw_info()

   Returns atom data as a human-readable Unicode string.

.. classmethod:: M4A_META_Atom.parse(name, data_size, reader, parsers)

   Given a 4 byte atom name, size of the entire atom in bytes,
   a :class:`audiotools.bitstream.BitstreamReader`,
   and a dict of atom name strings -> atom classes,
   returns a new :class:`M4A_META_Atom` object.

   When parsing sub atoms, any atom name in the parsers dict
   is handled recursively via its value's ``parse`` classmethod.
   If the atom name is not present, the atom is treated as
   a generic :class:`M4A_Leaf_Atom`.

.. method:: M4A_META_Atom.build(writer)

   Writes the atom's contents to the given
   :class:`audiotools.bitstream.BitstreamWriter`
   recursively via its sub-atoms' ``build`` methods.
   This does *not* include the atom's 64-bit size / name header.

.. method:: M4A_META_Atom.size()

   Returns the size of the atom data in bytes,
   not including its 64-bit size / name header.

M4A Leaf Atom
^^^^^^^^^^^^^

.. class:: M4A_Leaf_Atom(name, data)

   ``name`` is a 4 byte atom name string.
   ``data`` is a string of raw atom data.

.. method:: M4A_Leaf_Atom.copy()

   Returns a duplicate copy of the atom.

.. method:: M4A_Leaf_Atom.raw_info()

   Returns atom data as a human-readable Unicode string.

.. method:: M4A_Leaf_Atom.parse(name, size, reader, parsers)

   Given a 4 byte atom name, size of the entire atom in bytes,
   a :class:`audiotools.bitstream.BitstreamReader`
   and an unused dict of atom name strings -> atom classes,
   returns a new :class:`M4A_Leaf_Atom` object.

.. method:: M4A_Leaf_Atom.build(writer)

   Writes the atom's contents to the given
   :class:`audiotools.bitstream.BitstreamWriter`.
   This does *not* include the atom's 64-bit size / name header.

.. method:: M4A_Leaf_Atom.size()

   Returns the size of the atom data in bytes,
   not including its 64-bit size / name header.

M4A Tree Atom
^^^^^^^^^^^^^

.. class:: M4A_Tree_Atom(name, leaf_atoms)

   ``name`` is a 4 byte atom name string.
   ``leaf_atoms`` is a list of atom-compatible objects,
   typically :class:`M4A_Leaf_Atom`, :class:`M4A_Tree_Atom`
   or subclasses of either.

.. method:: M4A_Tree_Atom.copy()

   Returns a duplicate copy of the atom.

.. method:: M4A_Tree_Atom.raw_info()

   Returns atom data as a human-readable Unicode string.

.. method:: M4A_Tree_Atom.parse(name, data_size, reader, parsers)

   Given a 4 byte atom name, size of the entire atom in bytes,
   a :class:`audiotools.bitstream.BitstreamReader`,
   and a dict of atom name strings -> atom classes,
   returns a new :class:`M4A_META_Atom` object.

   When parsing sub atoms, any atom name in the parsers dict
   is handled recursively via its value's ``parse`` classmethod.
   If the atom name is not present, the atom is treated as
   a generic :class:`M4A_Leaf_Atom`.

.. method:: M4A_Tree_Atom.build(writer)

   Writes the atom's contents to the given
   :class:`audiotools.bitstream.BitstreamWriter`
   recursively via its sub-atoms' ``build`` methods.
   This does *not* include the atom's 64-bit size / name header.

.. method:: M4A_Tree_Atom.size()

   Returns the size of the atom data in bytes,
   not including its 64-bit size / name header.

.. method:: M4A_Tree_Atom.get_child(atom_name)

   Given a 4 byte atom name string,
   returns the first instance of that child atom.
   Raises :exc:`KeyError` if the child is not found.

.. method:: M4A_Tree_Atom.has_child(atom_name)

   Given a 4 byte atom name string,
   returns ``True`` if that child atom is present.

.. method:: M4A_Tree_Atom.add_child(atom_object)

   Appends an atom-compatible object,
   to this atom's list of children.

.. method:: M4A_Tree_Atom.remove_child(atom_name)

   Given a 4 byte atom name string,
   removes the first instance of that child, if any.

.. method:: M4A_Tree_Atom.replace_child(atom_object)

   Replaces the first child atom with the same name
   with the given atom-compatible object.

.. method:: M4A_Tree_Atom.child_offset(*child_path)

   Given a list of 4 byte atom name strings,
   recursively searches :class:`M4A_Tree_Atom`-compatible
   sub-children and returns the byte offset
   of the final child atom.
   Raises :exc:`KeyError` if the child atom cannot be found.

M4A ILST Leaf Atom
^^^^^^^^^^^^^^^^^^

This atom is a subclass of :class:`M4A_Tree_Atom`
specialized for holding M4A metadata fields
and mapping them to Python values.

.. class:: M4A_ILST_Leaf_Atom(name, leaf_atoms)

   ``name`` is a 4 byte atom name string.
   ``leaf_atoms`` is a list of atom-compatible objects,

.. method:: M4A_ILST_Leaf_Atom.__unicode__()

   Returns the Unicode value of this leaf's first ``data`` child atom.

   >>> nam = M4A_ILST_Leaf_Atom("\xa9nam",
   ...                          [M4A_ILST_Unicode_Data_Atom(0, 1, "Track Name")])
   >>> unicode(nam)
   u"Track Name"

.. method:: M4A_ILST_Leaf_Atom.__int__()

   Returns the integer value of this leaf's first ``data`` child atom.

   >>> trkn = M4A_ILST_Leaf_Atom("trkn",
   ...                           [M4A_ILST_TRKN_Data_Atom(1, 2)])
   >>> int(trkn)
   1

.. method:: M4A_ILST_Leaf_Atom.total()

   Returns the total integer value of this leaf's first ``data`` child atom.

   >>> trkn = M4A_ILST_Leaf_Atom("trkn",
   ...                           [M4A_ILST_TRKN_Data_Atom(1, 2)])
   >>> trkn.total()
   2

M4A ILST Unicode Data Atom
^^^^^^^^^^^^^^^^^^^^^^^^^^

This atom is a subclass of :class:`M4A_Leaf_Atom`
specialized for holding Unicode data.

.. class:: M4A_ILST_Unicode_Data_Atom(type, flags, data)

   ``type`` is an integer (typically 0),
   ``flags`` is an integer (typically 1),
   ``data`` is a binary string of UTF-8-encoded data.
   The atom's ``name`` field is always ``"data"``.

.. method:: M4A_ILST_Unicode_Data_Atom.__unicode__()

   Returns the Unicode value of this atom's data.

   >>> nam = M4A_ILST_Unicode_Data_Atom(0, 1, "Track Name")
   >>> unicode(nam)
   u"Track Name"

M4A ILST Track Number Atom
^^^^^^^^^^^^^^^^^^^^^^^^^^

This atom is a subclass of :class:`M4A_Leaf_Atom`
specialized for holding track number data.

.. class:: M4A_ILST_TRKN_Data_Atom(track_number, track_total)

   ``track_number`` and ``track_total`` are both integers.
   Its ``name`` field is always ``"data"``.

.. method:: M4A_ILST_TRKN_Data_Atom.__int__()

   Returns this atom's ``track_number`` value.

   >>> trkn = M4A_ILST_TRKN_Data_Atom(1, 2)
   >>> int(trkn)
   1

.. method:: M4A_ILST_TRKN_Data_Atom.total()

   Returns this atom's ``track_total`` value.

   >>> trkn = M4A_ILST_TRKN_Data_Atom(1, 2)
   >>> trkn.total()
   2

M4A ILST Disc Number Atom
^^^^^^^^^^^^^^^^^^^^^^^^^

This atom is a subclass of :class:`M4A_Leaf_Atom`
specialized for holding disc number data.

.. class:: M4A_ILST_DISK_Data_Atom(disc_number, disc_total)

   ``disc_number`` and ``disc_total`` are both integers.
   Its ``name`` field is always ``"data"``.

.. method:: M4A_ILST_DISK_Data_Atom.__int__()

   Returns this atom's ``disc_number`` value.

   >>> disk = M4A_ILST_DISK_Data_Atom(3, 4)
   >>> int(disk)
   3

.. method:: M4A_ILST_DISK_Data_Atom.total()

   Returns this atom's ``disc_total`` value.

   >>> disk = M4A_ILST_DISK_Data_Atom(3, 4)
   >>> disk.total()
   4

M4A ILST Cover Art Atom
^^^^^^^^^^^^^^^^^^^^^^^

This atom is a subclass of :class:`M4A_Leaf_Atom` and of
:class:`audiotools.Image`.

.. class:: M4A_ILST_COVR_Data_Atom(version, flags, image_data)

   ``version`` is an integer (typically 0).
   ``flags`` is an integer (typically 0).
   ``image_data`` is a binary string of cover art data.
   Its ``name`` field is always ``"data"``.

   This atom parses its raw image data to populate
   its :class:`audiotools.Image`-specific methods and fields.

Vorbis Comment
--------------

.. class:: VorbisComment(comment_strings, vendor_string)

   This is a VorbisComment_ tag used by FLAC, Ogg FLAC, Ogg Vorbis,
   Ogg Speex and other formats in the Ogg family.
   ``comment_strings`` is a list of Unicode strings.
   ``vendor_string`` is a Unicode string.
   Once initialized, :class:`VorbisComment` can be manipulated like a
   regular Python dict in addition to its standard
   :class:`audiotools.MetaData` methods.

   For example:

   >>> tag = VorbisComment([u'TITLE=Track Title'], u'Vendor String')
   >>> tag.track_name
   u'Track Title'
   >>> tag[u'TITLE']
   [u'New Title']
   >>> tag[u'TITLE'] = [u'New Title']
   >>> tag.track_name
   u'New Title'

   Fields are mapped between :class:`VorbisComment` and
   :class:`audiotools.MetaData` as follows:

   ================= ==================
   VorbisComment     Metadata
   ----------------- ------------------
   ``TITLE``         ``track_name``
   ``TRACKNUMBER``   ``track_number``
   ``TRACKTOTAL``    ``track_total``
   ``DISCNUMBER``    ``album_number``
   ``DISCTOTAL``     ``album_total``
   ``ALBUM``         ``album_name``
   ``ARTIST``        ``artist_name``
   ``PERFORMER``     ``performer_name``
   ``COMPOSER``      ``composer_name``
   ``CONDUCTOR``     ``conductor_name``
   ``SOURCE MEDIUM`` ``media``
   ``ISRC``          ``ISRC``
   ``CATALOG``       ``catalog``
   ``COPYRIGHT``     ``copyright``
   ``PUBLISHER``     ``publisher``
   ``DATE``          ``year``
   ``COMMENT``       ``comment``
   ================= ==================

   Note that if the same key is used multiple times,
   the metadata attribute only indicates the first one:

   >>> tag = VorbisComment([u"TITLE=Title1", u"TITLE=Title2"], u"Vendor String")
   >>> tag.track_name
   u'Title1'

Opus Tags
---------

.. class:: OpusTags(comment_strings, vendor_string)

   OpusTags is a subclass of :class:`VorbisComment`
   which supports the same field/metadata mapping.

.. _APEv2: http://wiki.hydrogenaudio.org/index.php?title=APEv2

.. _ID3v1: http://www.id3.org/ID3v1

.. _FLAC: http://flac.sourceforge.net/format.html#metadata_block

.. _VorbisComment: http://www.xiph.org/vorbis/doc/v-comment.html

.. _ID3v2.2: http://www.id3.org/id3v2-00

.. _ID3v2.3: http://www.id3.org/d3v2.3.0

.. _ID3v2.4: http://www.id3.org/id3v2.4.0-structure

.. _Latin-1: http://en.wikipedia.org/wiki/Latin-1

.. _UCS-2: http://en.wikipedia.org/wiki/UTF-16

.. _UTF-16: http://en.wikipedia.org/wiki/UTF-16

.. _UTF-16BE: http://en.wikipedia.org/wiki/UTF-16

.. _UTF-8: http://en.wikipedia.org/wiki/UTF-8
