From 965ee9bbce54f5941b7d61a4a7ad5491d5fd4e19 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Fri, 6 Sep 2019 09:45:45 +0200 Subject: [PATCH] PICARD-1592: APEv2 tag case insensitive reading According to specification APEv2 tags should be read case insensitive. Previously Picard only read tags for which there was no explicit mapping case insensitive, but treated all explicitly mapped tags case sensitive. --- picard/formats/apev2.py | 84 +++++++++++++++++++------------------- test/formats/test_apev2.py | 22 ++++++++++ 2 files changed, 65 insertions(+), 41 deletions(-) diff --git a/picard/formats/apev2.py b/picard/formats/apev2.py index 0745229db..5a7ec4230 100644 --- a/picard/formats/apev2.py +++ b/picard/formats/apev2.py @@ -80,34 +80,34 @@ class APEv2File(File): _File = None __translate = { - "Album Artist": "albumartist", - "MixArtist": "remixer", - "Weblink": "website", - "DiscSubtitle": "discsubtitle", - "BPM": "bpm", - "ISRC": "isrc", - "CatalogNumber": "catalognumber", - "Barcode": "barcode", - "EncodedBy": "encodedby", - "Language": "language", - "MOVEMENT": "movementnumber", - "MOVEMENTNAME": "movement", - "MOVEMENTTOTAL": "movementtotal", - "SHOWMOVEMENT": "showmovement", - "MUSICBRAINZ_ALBUMSTATUS": "releasestatus", - "MUSICBRAINZ_ALBUMTYPE": "releasetype", - "musicbrainz_trackid": "musicbrainz_recordingid", - "musicbrainz_releasetrackid": "musicbrainz_trackid", - "Original Artist": "originalartist", - "REPLAYGAIN_ALBUM_GAIN": "replaygain_album_gain", - "REPLAYGAIN_ALBUM_PEAK": "replaygain_album_peak", - "REPLAYGAIN_ALBUM_RANGE": "replaygain_album_range", - "REPLAYGAIN_TRACK_GAIN": "replaygain_track_gain", - "REPLAYGAIN_TRACK_PEAK": "replaygain_track_peak", - "REPLAYGAIN_TRACK_RANGE": "replaygain_track_range", - "REPLAYGAIN_REFERENCE_LOUDNESS": "replaygain_reference_loudness", + "albumartist": "Album Artist", + "remixer": "MixArtist", + "website": "Weblink", + "discsubtitle": "DiscSubtitle", + "bpm": "BPM", + "isrc": "ISRC", + "catalognumber": "CatalogNumber", + "barcode": "Barcode", + "encodedby": "EncodedBy", + "language": "Language", + "movementnumber": "MOVEMENT", + "movement": "MOVEMENTNAME", + "movementtotal": "MOVEMENTTOTAL", + "showmovement": "SHOWMOVEMENT", + "releasestatus": "MUSICBRAINZ_ALBUMSTATUS", + "releasetype": "MUSICBRAINZ_ALBUMTYPE", + "musicbrainz_recordingid": "musicbrainz_trackid", + "musicbrainz_trackid": "musicbrainz_releasetrackid", + "originalartist": "Original Artist", + "replaygain_album_gain": "REPLAYGAIN_ALBUM_GAIN", + "replaygain_album_peak": "REPLAYGAIN_ALBUM_PEAK", + "replaygain_album_range": "REPLAYGAIN_ALBUM_RANGE", + "replaygain_track_gain": "REPLAYGAIN_TRACK_GAIN", + "replaygain_track_peak": "REPLAYGAIN_TRACK_PEAK", + "replaygain_track_range": "REPLAYGAIN_TRACK_RANGE", + "replaygain_reference_loudness": "REPLAYGAIN_REFERENCE_LOUDNESS", } - __rtranslate = dict([(v, k) for k, v in __translate.items()]) + __rtranslate = dict([(v.lower(), k) for k, v in __translate.items()]) def _load(self, filename): log.debug("Loading file %r", filename) @@ -115,7 +115,9 @@ class APEv2File(File): metadata = Metadata() if file.tags: for origname, values in file.tags.items(): - if origname.lower().startswith("cover art") and values.kind == mutagen.apev2.BINARY: + origname = origname.lower() + if (values.kind == mutagen.apev2.BINARY + and origname.startswith("cover art")): if b'\0' in values.value: descr, data = values.value.split(b'\0', 1) try: @@ -135,32 +137,30 @@ class APEv2File(File): continue for value in values: name = origname - if name == "Year": + if name == "year": name = "date" value = sanitize_date(value) - elif name == "Track": + elif name == "track": name = "tracknumber" track = value.split("/") if len(track) > 1: metadata["totaltracks"] = track[1] value = track[0] - elif name == "Disc": + elif name == "disc": name = "discnumber" disc = value.split("/") if len(disc) > 1: metadata["totaldiscs"] = disc[1] value = disc[0] - elif name == 'Performer' or name == 'Comment': - name = name.lower() + ':' + elif name == 'performer' or name == 'comment': + name = name + ':' if value.endswith(')'): start = value.rfind(' (') if start > 0: name += value[start + 2:-1] value = value[:start] - elif name in self.__translate: - name = self.__translate[name] - else: - name = name.lower() + elif name in self.__rtranslate: + name = self.__rtranslate[name] metadata.add(name, value) self._info(metadata, file) return metadata @@ -177,7 +177,8 @@ class APEv2File(File): tags.clear() elif images_to_save: for name, value in tags.items(): - if name.lower().startswith('cover art') and value.kind == mutagen.apev2.BINARY: + if (value.kind == mutagen.apev2.BINARY + and name.lower().startswith('cover art')): del tags[name] temp = {} for name, value in metadata.items(): @@ -205,7 +206,8 @@ class APEv2File(File): for image in images_to_save: cover_filename = 'Cover Art (Front)' cover_filename += image.extension - tags['Cover Art (Front)'] = mutagen.apev2.APEValue(cover_filename.encode('ascii') + b'\0' + image.data, mutagen.apev2.BINARY) + tags['Cover Art (Front)'] = mutagen.apev2.APEValue( + cover_filename.encode('ascii') + b'\0' + image.data, mutagen.apev2.BINARY) break # can't save more than one item with the same name # (mp3tags does this, but it's against the specs) @@ -243,8 +245,8 @@ class APEv2File(File): return 'Disc' elif name.startswith('performer:') or name.startswith('comment:'): return name.split(':', 1)[0].title() - elif name in self.__rtranslate: - return self.__rtranslate[name] + elif name in self.__translate: + return self.__translate[name] else: return name.title() diff --git a/test/formats/test_apev2.py b/test/formats/test_apev2.py index 33a3c962f..162f5cf0e 100644 --- a/test/formats/test_apev2.py +++ b/test/formats/test_apev2.py @@ -18,6 +18,8 @@ from .common import ( TAGS, CommonTests, load_metadata, + load_raw, + save_metadata, save_raw, skipUnlessTestfile, ) @@ -77,6 +79,26 @@ class CommonApeTests: self.assertTrue(self.format.supports_tag('lyrics:foó')) self.assertTrue(self.format.supports_tag('comment:foó')) + def test_case_insensitive_reading(self): + self._read_case_insensitive_tag('artist', 'Artist') + self._read_case_insensitive_tag('albumartist', 'Album Artist') + self._read_case_insensitive_tag('performer:', 'Performer') + self._read_case_insensitive_tag('tracknumber', 'Track') + self._read_case_insensitive_tag('discnumber', 'Disc') + + def _read_case_insensitive_tag(self, name, ape_name): + upper_ape_name = ape_name.upper() + metadata = { + upper_ape_name: 'Some value' + } + save_raw(self.filename, metadata) + loaded_metadata = load_metadata(self.filename) + self.assertEqual(metadata[upper_ape_name], loaded_metadata[name]) + save_metadata(self.filename, loaded_metadata) + raw_metadata = load_raw(self.filename) + self.assertIn(ape_name, raw_metadata.keys()) + self.assertEqual(metadata[upper_ape_name], raw_metadata[ape_name]) + class MonkeysAudioTest(CommonApeTests.ApeTestCase): testfile = 'test.ape'