Repository: devsnd/tinytag

Issue:
[BUG] Can't read `extra` tags from .m4a files
**Describe the bug**
I added a custom field (via mp3tag) to audio files of various types, but that custom field only appears in tinytag's `extra` collection for the mp3 files and not the m4a files. 

**To Reproduce**
1. Unzip the Songs.zip attachment to find two files - both have a custom tag called `KPL_DATE` with the value `TEST_DATE`
2. Dump the tags for the two contained files using tinytag: `python3 -m tinytag --format json Symphony1.m4a Symphony2.mp3`
3. See that the custom tag is listed in `extra` for the mp3 file but not for the m4a file.  
<img width="548" alt="image" src="https://user-images.githubusercontent.com/19976093/205199217-c8ea2c3c-a67b-474b-b85a-859547ec015e.png">


**Expected behavior**
The custom tag is listed under `extra` for both files

**Sample File**
[Songs.zip](https://github.com/devsnd/tinytag/files/10137371/Songs.zip)


Discussion:
coveralls: 
[![Coverage Status](https://coveralls.io/builds/62007596/badge)](https://coveralls.io/builds/62007596)

coverage: 96.646% (+0.1%) from 96.498% when pulling **8836bcf78a84d2172d298c903989ddcabf17c039 on mathiascode:custom-fields** into **925402c79f2e9c3394d10bdc7f6d7e53f0642823 on devsnd:master**.

mathiascode: @aw-was-here You might be interested in this.

Are there fields you still need that aren't provided as custom fields? We could probably add them to the `extra` dict separately.
aw-was-here: > @aw-was-here You might be interested in this.
> 
> Are there fields you still need that aren't provided as custom fields? We could probably add them to the `extra` dict separately.

Awesome! This PR looks like it fills in a lot of the gaps I've been using another library for in a tremendous way.  Thanks!

Patch:
diff --git a/README.md b/README.md
index 47f3d08..7ce1b72 100644
--- a/README.md
+++ b/README.md
@@ -74,12 +74,14 @@ List of possible attributes you can get with TinyTag:
     tag.track_total   # total number of tracks as string
     tag.year          # year or date as string
 
-For non-common fields and fields specific to single file formats, use `extra`:
+For non-common fields and fields specific to certain file formats, use `extra`:
 
     tag.extra         # a dict of additional data
 
-The `extra` dict currently *may* contain the following data:
-   `url`, `isrc`, `text`, `initial_key`, `lyrics`, `copyright`
+The following standard `extra` field names are used when file formats provide relevant data:
+   `url`, `isrc`, `initial_key`, `lyrics`, `copyright`
+
+Any other `extra` field names are not guaranteed to be consistent across audio formats.
 
 Additionally you can also get cover images from ID3 tags:
 
diff --git a/tinytag/tinytag.py b/tinytag/tinytag.py
index 032f9ef..63fb1cf 100644
--- a/tinytag/tinytag.py
+++ b/tinytag/tinytag.py
@@ -289,7 +289,7 @@ class TinyTag(object):
         for key in ['track', 'track_total', 'title', 'artist',
                     'album', 'albumartist', 'year', 'duration',
                     'genre', 'disc', 'disc_total', 'comment', 'composer',
-                    '_image_data']:
+                    'extra', '_image_data']:
             if not getattr(self, key) and getattr(other, key):
                 setattr(self, key, getattr(other, key))
         if other.extra:
@@ -308,15 +308,15 @@ class MP4(TinyTag):
     class Parser:
         # https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW34
         ATOM_DECODER_BY_TYPE = {
-            0: lambda x: x,  # 'reserved',
+            # 0: 'reserved'
             1: lambda x: codecs.decode(x, 'utf-8', 'replace'),   # UTF-8
             2: lambda x: codecs.decode(x, 'utf-16', 'replace'),  # UTF-16
             3: lambda x: codecs.decode(x, 's/jis', 'replace'),   # S/JIS
             # 16: duration in millis
             13: lambda x: x,  # JPEG
             14: lambda x: x,  # PNG
-            21: lambda x: struct.unpack('>b', x)[0],  # BE Signed int
-            22: lambda x: struct.unpack('>B', x)[0],  # BE Unsigned int
+            # 21: BE Signed int
+            # 22: BE Unsigned int
             23: lambda x: struct.unpack('>f', x)[0],  # BE Float32
             24: lambda x: struct.unpack('>d', x)[0],  # BE Float64
             # 27: lambda x: x,  # BMP
@@ -366,6 +366,29 @@ class MP4(TinyTag):
                 if esds_atom.read(1) != b'\x80':
                     break
 
+        @classmethod
+        def parse_custom_field(cls, data):
+            fh = BytesIO(data)
+            header_size = 8
+            field_name = None
+            data_atom = b''
+            atom_header = fh.read(header_size)
+            while len(atom_header) == header_size:
+                atom_size = struct.unpack('>I', atom_header[:4])[0] - header_size
+                atom_type = atom_header[4:]
+                if atom_type == b'name':
+                    atom_value = fh.read(atom_size)[4:].lower()
+                    field_name = 'extra.' + codecs.decode(atom_value, 'utf-8', 'replace')
+                elif atom_type == b'data':
+                    data_atom = fh.read(atom_size)
+                else:
+                    fh.seek(atom_size, os.SEEK_CUR)
+                atom_header = fh.read(header_size)  # read next atom
+            if len(data_atom) < 8:
+                return {}
+            parser = cls.make_data_atom_parser(field_name)
+            return parser(data_atom)
+
         @classmethod
         def parse_audio_sample_entry_mp4a(cls, data):
             # this atom also contains the esds atom:
@@ -453,6 +476,7 @@ class MP4(TinyTag):
         b'disk': {b'data': Parser.make_number_parser('disc', 'disc_total')},
         b'gnre': {b'data': Parser.parse_id3v1_genre},
         b'trkn': {b'data': Parser.make_number_parser('track', 'track_total')},
+        b'----': Parser.parse_custom_field,
         # need test-data for this
         # b'tmpo': {b'data': Parser.make_data_atom_parser('extra.bmp')},
     }}}}}
@@ -538,13 +562,13 @@ class ID3(TinyTag):
         'TPE2': 'albumartist', 'TCOM': 'composer',
         'WXXX': 'extra.url',
         'TSRC': 'extra.isrc',
-        'TXXX': 'extra.text',
         'TKEY': 'extra.initial_key',
         'USLT': 'extra.lyrics',
         'TCOP': 'extra.copyright', 'TCR': 'extra.copyright',
     }
     IMAGE_FRAME_IDS = {'APIC', 'PIC'}
-    PARSABLE_FRAME_IDS = set(FRAME_ID_TO_FIELD.keys()).union(IMAGE_FRAME_IDS)
+    CUSTOM_FRAME_IDS = {'TXXX', 'TXX'}
+    PARSABLE_FRAME_IDS = set(FRAME_ID_TO_FIELD.keys()).union(IMAGE_FRAME_IDS, CUSTOM_FRAME_IDS)
     _MAX_ESTIMATION_SEC = 30
     _CBR_DETECTION_FRAME_COUNT = 5
     _USE_XING_HEADER = True  # much faster, but can be deactivated for testing
@@ -826,6 +850,12 @@ class ID3(TinyTag):
             if fieldname:
                 language = fieldname in ("comment", "extra.lyrics")
                 self._set_field(fieldname, self._decode_string(content, language))
+            elif frame_id in self.CUSTOM_FRAME_IDS:
+                # custom fields
+                custom_text = self._decode_string(content)
+                custom_field_name, _separator, value = custom_text.partition('\x00')
+                if custom_field_name:
+                    self._set_field('extra.' + custom_field_name.lower(), value.lstrip(u'\ufeff'))
             elif frame_id in self.IMAGE_FRAME_IDS and self._load_image:
                 # See section 4.14: http://id3.org/id3v2.4.0-frames
                 encoding = content[0:1]
@@ -1005,10 +1035,8 @@ class Ogg(TinyTag):
             'genre': 'genre',
             'description': 'comment',
             'comment': 'comment',
+            'comments': 'comment',
             'composer': 'composer',
-            'copyright': 'extra.copyright',
-            'isrc': 'extra.isrc',
-            'lyrics': 'extra.lyrics',
         }
         if contains_vendor:
             vendor_length = struct.unpack('I', fh.read(4))[0]
@@ -1031,9 +1059,9 @@ class Ogg(TinyTag):
                 else:
                     if DEBUG:
                         stderr('Found Vorbis Comment', key, value[:64])
-                    fieldname = comment_type_to_attr_mapping.get(key_lowercase)
-                    if fieldname:
-                        self._set_field(fieldname, value)
+                    fieldname = comment_type_to_attr_mapping.get(
+                        key_lowercase, 'extra.' + key_lowercase)  # custom fields go in 'extra'
+                    self._set_field(fieldname, value)
 
     def _parse_pages(self, fh):
         # for the spec, see: https://wiki.xiph.org/Ogg
@@ -1337,11 +1365,16 @@ class Wma(TinyTag):
                     name = self.__decode_string(fh.read(name_len))
                     value_type = _bytes_to_int_le(fh.read(2))
                     value_len = _bytes_to_int_le(fh.read(2))
-                    value = fh.read(value_len)
-                    field_name = mapping.get(name)
-                    if field_name:
-                        field_value = self.__decode_ext_desc(value_type, value)
-                        self._set_field(field_name, field_value)
+                    if value_type == 1:
+                        fh.seek(value_len, os.SEEK_CUR)  # skip byte values
+                        continue
+                    field_name = mapping.get(name)  # try to get normalized field name
+                    if field_name is None:  # custom field
+                        if name.startswith('WM/'):
+                            name = name[3:]
+                        field_name = 'extra.' + name.lower()
+                    field_value = self.__decode_ext_desc(value_type, fh.read(value_len))
+                    self._set_field(field_name, field_value)
             elif object_id == Wma.ASF_FILE_PROPERTY_OBJECT:
                 blocks = self.read_blocks(fh, [
                     ('file_id', 16, False),


Test patch:
diff --git a/tinytag/tests/test_all.py b/tinytag/tests/test_all.py
index 4f68a4a..d412c20 100644
--- a/tinytag/tests/test_all.py
+++ b/tinytag/tests/test_all.py
@@ -66,8 +66,14 @@ testfiles = OrderedDict([
          'album': 'The Young Americans', 'title': 'Play Dead', 'filesize': 256, 'track': '12',
          'artist': 'Björk', 'year': '1993', 'comment': '                            '}),
     ('samples/UTF16.mp3',
-        {'extra': {'text': 'MusicBrainz Artist Id\x00664c3e0e-42d8-48c1-b209-1efca19c0325',
-         'url': 'WIKIPEDIA_RELEASE\x00http://en.wikipedia.org/wiki/High_Violet'},
+        {'extra': {'musicbrainz artist id': '664c3e0e-42d8-48c1-b209-1efca19c0325',
+                   'musicbrainz album id': '25322466-a29b-417b-b560-399687b91ddd',
+                   'musicbrainz album artist id': '664c3e0e-42d8-48c1-b209-1efca19c0325',
+                   'musicbrainz disc id': 'p.5xoyYRtCVFe2gt0mfTfsXrO9U-',
+                   'musicip puid': '6ff97581-1c73-fc05-b4e4-a4ccee12ec84', 'asin': 'B003KVNV4S',
+                   'musicbrainz album status': 'Official', 'musicbrainz album type': 'Album',
+                   'musicbrainz album release country': 'United States',
+                   'url': 'WIKIPEDIA_RELEASE\u0000http://en.wikipedia.org/wiki/High_Violet'},
          'track_total': '11', 'track': '07', 'artist': 'The National',
          'year': '2010', 'album': 'High Violet', 'title': 'Lemonworld', 'filesize': 20480,
          'genre': 'Indie', 'comment': 'Track 7'}),
@@ -109,7 +115,7 @@ testfiles = OrderedDict([
          'disc': '1', 'disc_total': '2', 'title': '1 Ghosts I', 'track': '1', 'track_total': '36',
          'year': '2008', 'comment': '3/4 time'}),
     ('samples/id3_comment_utf_16_double_bom.mp3',
-        {'extra': {'text': 'LABEL\x00\ufeffUnclear'}, 'filesize': 512, 'album': 'The Embrace',
+        {'extra': {'label': 'Unclear'}, 'filesize': 512, 'album': 'The Embrace',
          'artist': 'Johannes Heil & D.Diggler', 'comment': 'Unclear',
          'title': 'The Embrace (Romano Alfieri Remix)',
          'track': '04-johannes_heil_and_d.diggler-the_embrace_(romano_alfieri_remix)',
@@ -124,7 +130,7 @@ testfiles = OrderedDict([
          'duration': 1.0438932496075353}),
     ('samples/id3v1_does_not_overwrite_id3v2.mp3',
         {'filesize': 1130, 'album': 'Somewhere Far Beyond', 'albumartist': 'Blind Guardian',
-         'artist': 'Blind Guardian', 'comment': '', 'extra': {'text': 'LOVE RATING\x00L'},
+         'artist': 'Blind Guardian', 'comment': '', 'extra': {'love rating': 'L'},
          'genre': 'Power Metal', 'title': 'Time What Is Time', 'track': '01', 'year': '1992'}),
     ('samples/nicotinetestdata.mp3',
         {'extra': {}, 'filesize': 80919, 'audio_offset': 45, 'channels': 2,
@@ -139,8 +145,22 @@ testfiles = OrderedDict([
          'audio_offset': 194, 'bitrate': 192.0, 'channels': 2, 'duration': 0.052244897959183675,
          'samplerate': 44100, 'title': 'Tony Hawk VS Wayne Gretzky'}),
     ('samples/id3_xxx_lang.mp3',
-        {'extra': {'isrc': 'USVI20400513', 'lyrics': "Don't fret, precious",
-                   'text': 'SCRIPT\x00\ufeffLatn'},
+        {'extra': {'script': 'Latn', 'originalyear': '2004',
+                   'acoustid id': '2dc0b571-a633-45b0-aa5e-f3d25e4e0020',
+                   'musicbrainz album type': 'album',
+                   'musicbrainz album artist id': '078a9376-3c04-4280-b7d7-b20e158f345d',
+                   'musicbrainz artist id': '078a9376-3c04-4280-b7d7-b20e158f345d',
+                   'barcode': '724386668721',
+                   'musicbrainz album id': '38b555fe-24c7-37b3-ad1b-f6dea9f1aafa',
+                   'artists': 'A Perfect Circle',
+                   'musicbrainz release track id': '7f7c31a5-0905-39ba-ba72-68db91d3b9da',
+                   'catalognumber': '7243 8 66687 2 1',
+                   'musicbrainz release group id': '0f21095a-e629-389c-981a-d9569e9673c9',
+                   'musicbrainz album status': 'official',
+                   'asin': 'B000641ZIQ', 'musicbrainz album release country': 'US',
+                   'isrc': 'USVI20400513', 'lyrics': 'Don\'t fret, precious',
+                   'replaygain_track_gain': '-3.95 dB', 'replaygain_track_peak': '0.999969',
+                   'replaygain_album_gain': '-8.26 dB'},
          'filesize': 6943, 'album': 'eMOTIVe', 'albumartist': 'A Perfect Circle',
          'artist': 'A Perfect Circle', 'audio_offset': 3647, 'bitrate': 192.0, 'channels': 2,
          'duration': 0.13198711063372717, 'genre': 'Rock',
@@ -203,12 +223,11 @@ testfiles = OrderedDict([
         {'extra': {}, 'duration': 3.684716553287982,
          'filesize': 4328, 'audio_offset': 0, 'bitrate': 112.0,
          'samplerate': 44100}),
-    ('samples/multipagecomment.ogg',
-        {'extra': {}, 'duration': 3.684716553287982,
-         'filesize': 135694, 'audio_offset': 0, 'bitrate': 112,
-         'samplerate': 44100}),
     ('samples/multipage-setup.ogg',
-        {'extra': {}, 'genre': 'JRock', 'duration': 4.128798185941043,
+        {'extra': {'transcoded': 'mp3;241', 'replaygain_album_gain': '-10.29 dB',
+                   'replaygain_album_peak': '1.50579047', 'replaygain_track_peak': '1.17979193',
+                   'replaygain_track_gain': '-10.02 dB'},
+         'genre': 'JRock', 'duration': 4.128798185941043,
          'album': 'Timeless', 'year': '2006', 'title': 'Burst', 'artist': 'UVERworld', 'track': '7',
          'filesize': 76983, 'audio_offset': 0, 'bitrate': 160.0,
          'samplerate': 44100, 'comment': 'SRCL-6240'}),
@@ -226,13 +245,19 @@ testfiles = OrderedDict([
          'genre': 'Some Genre', 'samplerate': 44100, 'title': 'A Title', 'track': '2',
          'year': '2007', 'composer': 'some composer', 'comment': 'A Comment'}),
     ('samples/test.opus',
-        {'extra': {}, 'albumartist': 'Alstroemeria Records', 'samplerate': 48000, 'channels': 2,
+        {'extra': {'encoder': 'Lavc57.24.102 libopus', 'arrange': '\u6771\u65b9',
+                   'catalogid': 'ARCD0024', 'discid': 'A212230D', 'event': '\u4f8b\u5927\u796d5',
+                   'lyricist': 'Haruka', 'mastering': 'Hedonist',
+                   'origin': '\u6771\u65b9\u5e7b\u60f3\u90f7', 'originaltitle': 'Bad Apple!!',
+                   'performer': 'Masayoshi Minoshima', 'vocal': 'nomico'},
+         'albumartist': 'Alstroemeria Records', 'samplerate': 48000, 'channels': 2,
          'track': '1', 'disc': '1', 'title': 'Bad Apple!!', 'duration': 2.0, 'year': '2008.05.25',
          'filesize': 10000, 'artist': 'nomico',
          'album': 'Exserens - A selection of Alstroemeria Records',
          'comment': 'ARCD0018 - Lovelight', 'disc_total': '1', 'track_total': '13'}),
     ('samples/8khz_5s.opus',
-        {'extra': {}, 'filesize': 7251, 'channels': 1, 'samplerate': 48000, 'duration': 5.0}),
+        {'extra': {'encoder': 'opusenc from opus-tools 0.2'}, 'filesize': 7251, 'channels': 1,
+         'samplerate': 48000, 'duration': 5.0}),
     ('samples/test_flac.oga',
         {'extra': {'copyright': 'test3', 'isrc': 'test4', 'lyrics': 'test7'},
          'filesize': 9273, 'album': 'test2', 'artist': 'test6', 'comment': 'test5',
@@ -288,7 +313,8 @@ testfiles = OrderedDict([
     ('samples/flac1sMono.flac',
         {'extra': {}, 'genre': 'Avantgarde', 'album': 'alb', 'year': '2014',
          'duration': 1.0, 'title': 'track', 'track': '23', 'artist': 'art', 'channels': 1,
-         'filesize': 26632, 'bitrate': 213.056, 'samplerate': 44100, 'bitdepth': 16}),
+         'filesize': 26632, 'bitrate': 213.056, 'samplerate': 44100, 'bitdepth': 16,
+         'comment': 'hello'}),
     ('samples/flac453sStereo.flac',
         {'extra': {}, 'channels': 2, 'duration': 453.51473922902494, 'filesize': 84236,
          'bitrate': 1.4859230399999999, 'samplerate': 44100, 'bitdepth': 16}),
@@ -296,9 +322,16 @@ testfiles = OrderedDict([
         {'extra': {}, 'channels': 2, 'album': 'alb', 'year': '2014',
          'duration': 1.4995238095238095, 'title': 'track', 'track': '23', 'artist': 'art',
          'filesize': 59868, 'bitrate': 319.39739599872973, 'genre': 'Avantgarde',
-         'samplerate': 44100, 'bitdepth': 16}),
+         'samplerate': 44100, 'bitdepth': 16, 'comment': 'hello'}),
     ('samples/flac_application.flac',
-        {'extra': {}, 'channels': 2, 'track_total': '11',
+        {'extra': {'replaygain_track_peak': '0.9976',
+                   'musicbrainz_albumartistid': 'e5c7b94f-e264-473c-bb0f-37c85d4d5c70',
+                   'musicbrainz_trackid': 'e65fb332-0c1e-4172-85e0-59cd37e5669e',
+                   'replaygain_album_gain': '-8.14 dB', 'labelid': 'RTRADLP480',
+                   'musicbrainz_albumid': '359a91e9-3bb3-4b60-a823-8aaa4bad1e36',
+                   'artistsort': 'Belle and Sebastian', 'replaygain_track_gain': '-8.08 dB',
+                   'replaygain_album_peak': '1.0000'},
+         'channels': 2, 'track_total': '11',
          'album': 'Belle and Sebastian Write About Love', 'year': '2010-10-11', 'duration': 273.64,
          'title': 'I Want the World to Stop', 'track': '4', 'artist': 'Belle and Sebastian',
          'filesize': 13000, 'bitrate': 0.38006139453296306, 'samplerate': 44100, 'bitdepth': 16}),
@@ -306,7 +339,15 @@ testfiles = OrderedDict([
         {'extra': {}, 'channels': 2, 'duration': 3.684716553287982, 'filesize': 4692,
          'bitrate': 10.186943678613627, 'samplerate': 44100, 'bitdepth': 16}),
     ('samples/variable-block.flac',
-        {'extra': {}, 'channels': 2, 'album': 'Appleseed Original Soundtrack', 'year': '2004',
+        {'extra': {'discid': 'AA0B360B',
+                   'japanese title': ('\u30a2\u30c3\u30d7\u30eb\u30b7\u30fc\u30c9 '
+                                      '\u30aa\u30ea\u30b8\u30ca\u30eb\u30fb\u30b5\u30a6'
+                                      '\u30f3\u30c9\u30c8\u30e9\u30c3\u30af'),
+                   'organization': 'Sony Music Records (SRCP-371)',
+                   'ripper': 'Exact Audio Copy 0.99pb5',
+                   'replaygain_album_gain': '-8.68 dB', 'replaygain_album_peak': '1.000000',
+                   'replaygain_track_gain': '-9.61 dB', 'replaygain_track_peak': '1.000000'},
+         'channels': 2, 'album': 'Appleseed Original Soundtrack', 'year': '2004',
          'duration': 261.68, 'title': 'DIVE FOR YOU', 'track': '01', 'track_total': '11',
          'artist': 'Boom Boom Satellites', 'filesize': 10240, 'bitrate': 0.31305411189238763,
          'disc': '1', 'genre': 'Anime Soundtrack', 'samplerate': 44100, 'bitdepth': 16,
@@ -318,31 +359,38 @@ testfiles = OrderedDict([
         {'extra': {}, 'filesize': 4692, 'bitrate': 10.186943678613627, 'channels': 2,
          'duration': 3.68, 'samplerate': 44100, 'bitdepth': 16}),
     ('samples/with_id3_header.flac',
-        {'extra': {'text': 'ID\x00\ufeff8591671910'}, 'filesize': 64837, 'album': '   ',
+        {'extra': {'id': '8591671910'}, 'filesize': 64837, 'album': '   ',
          'artist': '群星', 'disc': '0',
          'title': 'A 梦 哆啦 机器猫 短信铃声', 'track': '0', 'bitrate': 1143.72468, 'channels': 1,
          'duration': 0.45351473922902497, 'genre': 'genre', 'samplerate': 44100, 'bitdepth': 16,
-         'year': '2018'}),
+         'year': '2018', 'comment': 'comment'}),
     ('samples/with_padded_id3_header.flac',
         {'extra': {}, 'filesize': 16070, 'album': 'album', 'artist': 'artist',
          'bitrate': 283.4748, 'channels': 1,
          'duration': 0.45351473922902497, 'genre': 'genre', 'samplerate': 44100, 'bitdepth': 16,
-         'title': 'title', 'track': '1', 'year': '2018'}),
+         'title': 'title', 'track': '1', 'year': '2018', 'comment': 'comment'}),
     ('samples/with_padded_id3_header2.flac',
         {'extra': {}, 'filesize': 19522, 'album': 'Unbekannter Titel',
          'artist': 'Unbekannter Künstler', 'bitrate': 344.36807999999996,
          'channels': 1, 'disc': '1', 'disc_total': '1',
          'duration': 0.45351473922902497, 'genre': 'genre', 'samplerate': 44100, 'bitdepth': 16,
-         'title': 'Track01', 'track': '01', 'track_total': '05', 'year': '2018'}),
+         'title': 'Track01', 'track': '01', 'track_total': '05', 'year': '2018',
+         'comment': 'comment'}),
     ('samples/flac_with_image.flac',
-        {'extra': {}, 'filesize': 80000, 'album': 'smilin´ in circles', 'artist': 'Andreas Kümmert',
+        {'extra': {'band': ''}, 'filesize': 80000, 'album': 'smilin´ in circles',
+         'artist': 'Andreas Kümmert',
          'bitrate': 7.6591670655816175, 'channels': 2, 'disc': '1', 'disc_total': '1',
          'duration': 83.56, 'genre': 'Blues', 'samplerate': 44100, 'bitdepth': 16, 'title': 'intro',
          'track': '01', 'track_total': '8'}),
 
     # WMA
     ('samples/test2.wma',
-        {'extra': {}, 'samplerate': 44100, 'album': 'The Colour and the Shape', 'title': 'Doll',
+        {'extra': {'track': '0', 'lyrics': '',
+                   'mediaprimaryclassid': '{D1607DBC-E323-4BE2-86A1-48A42A28441E}',
+                   'encodingtime': 128861118183900000, 'wmfsdkversion': '11.0.5721.5145',
+                   'wmfsdkneeded': '0.0.0.0000', 'isvbr': 1, 'peakvalue': 30369,
+                   'averagelevel': 7291},
+         'samplerate': 44100, 'album': 'The Colour and the Shape', 'title': 'Doll',
          'bitrate': 64.04, 'filesize': 5800, 'track': '1', 'albumartist': 'Foo Fighters',
          'artist': 'Foo Fighters', 'duration': 83.406, 'year': '1997',
          'genre': 'Alternative', 'comment': '', 'composer': 'Foo Fighters'}),
@@ -352,11 +400,27 @@ testfiles = OrderedDict([
 
     # ALAC/M4A/MP4
     ('samples/test.m4a',
-        {'extra': {}, 'samplerate': 44100, 'duration': 314.97, 'bitrate': 256.0, 'channels': 2,
+        {'extra': {'itunsmpb': (' 00000000 00000840 000001DC 0000000000D3E9E4 00000000 00000000 '
+                                '00000000 00000000 00000000 00000000 00000000 00000000'),
+                   'itunnorm': (' 00000358 0000032E 000020AE 000020D9 0003A228 00032A28 00007E20 '
+                                '00007E90 00007BFD 00009293'),
+                   'itunes_cddb_ids': '11++', 'ufidhttp://www.cddb.com/id3/taginfo1.html':
+                   '3CD3N48Q241232290U3387DD249F72E6B082B283425ADB9B0F324P1'},
+         'samplerate': 44100, 'duration': 314.97, 'bitrate': 256.0, 'channels': 2,
          'genre': 'Pop', 'year': '2011', 'title': 'Nothing', 'album': 'Only Our Hearts To Lose',
          'track_total': '11', 'track': '11', 'artist': 'Marian', 'filesize': 61432}),
     ('samples/test2.m4a',
-        {'extra': {'copyright': '℗ 1992 Ace Records'}, 'bitrate': 256.0, 'track': '1',
+        {'extra': {'copyright': '℗ 1992 Ace Records',
+                   'itunnorm': (' 00000371 00000481 00002E90 00002EA6 00000099 00000058 000073F3 '
+                                '0000768E 00000092 00000092'),
+                   'itunsmpb': (' 00000000 00000840 00000110 000000000070DEB0 00000000 00000000 '
+                                '00000000 00000000 00000000 00000000 00000000 00000000'),
+                   'itunmovi': ('<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC '
+                                '"-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs'
+                                '/PropertyList-1.0.dtd">\n<plist version="1.0">\n<dict>\n\t<key>'
+                                'asset-info</key>\n\t<dict>\n\t\t<key>flavor</key>\n\t\t<string>'
+                                '2:256</string>\n\t</dict>\n</dict>\n</plist>\n')},
+         'bitrate': 256.0, 'track': '1',
          'albumartist': "Millie Jackson - Get It Out 'cha System - 1978",
          'duration': 167.78739229024944, 'filesize': 223365, 'channels': 2, 'year': '1978',
          'artist': 'Millie Jackson', 'track_total': '9', 'disc_total': '1', 'genre': 'R&B/Soul',
@@ -372,7 +436,8 @@ testfiles = OrderedDict([
          'albumartist': 'Major Lazer', 'channels': 2, 'bitrate': 125.584,
          'comment': '? 2016 Mad Decent'}),
     ('samples/alac_file.m4a',
-        {'extra': {'copyright': '© Hyperion Records Ltd, London', 'lyrics': 'Album notes:'},
+        {'extra': {'copyright': '© Hyperion Records Ltd, London', 'lyrics': 'Album notes:',
+         'upc': '0034571177380'},
          'artist': 'Howard Shelley', 'composer': 'Clementi, Muzio (1752-1832)',
          'filesize': 20000,
          'title': 'Clementi: Piano Sonata in D major, Op 25 No 6 - Movement 2: Un poco andante',