PICARD-1586: Preserve case for ReplayGain tags in ID3, MP4, ASF

This commit is contained in:
Philipp Wolfer
2019-09-05 23:09:45 +02:00
parent a7faed5cca
commit c07168f734
7 changed files with 63 additions and 17 deletions

View File

@@ -174,6 +174,10 @@ class ASFFile(File):
}
__RTRANS_CI = dict([(b.lower(), a) for a, b in __TRANS_CI.items()])
def __init__(self, filename):
super().__init__(filename)
self.__casemap = {}
def _load(self, filename):
log.debug("Loading file %r", filename)
self.__casemap = {}
@@ -211,7 +215,9 @@ class ASFFile(File):
if name in self.__RTRANS:
name = self.__RTRANS[name]
elif name_lower in self.__RTRANS_CI:
orig_name = name
name = self.__RTRANS_CI[name_lower]
self.__casemap[name] = orig_name
else:
continue
values = [str(value) for value in values if value]
@@ -245,7 +251,10 @@ class ASFFile(File):
if name in self.__TRANS:
name = self.__TRANS[name]
elif name in self.__TRANS_CI:
name = self.__TRANS_CI[name]
if name in self.__casemap:
name = self.__casemap[name]
else:
name = self.__TRANS_CI[name]
delall_ci(tags, name)
else:
continue

View File

@@ -217,6 +217,10 @@ class ID3File(File):
'MVIN': re.compile(r'^(?P<movementnumber>\d+)(?:/(?P<movementtotal>\d+))?$')
}
def __init__(self, filename):
super().__init__(filename)
self.__casemap = {}
def build_TXXX(self, encoding, desc, values):
"""Construct and return a TXXX frame."""
# This is here so that plugins can customize the behavior of TXXX
@@ -229,6 +233,7 @@ class ID3File(File):
def _load(self, filename):
log.debug("Loading file %r", filename)
self.__casemap = {}
file = self._get_file(encode_filename(filename))
tags = file.tags or {}
# upgrade custom 2.3 frames to 2.4
@@ -275,7 +280,9 @@ class ID3File(File):
if name in self.__rename_freetext:
name = self.__rename_freetext[name]
if name_lower in self.__translate_freetext_ci:
orig_name = name
name = self.__translate_freetext_ci[name_lower]
self.__casemap[name] = orig_name
elif name in self.__translate_freetext:
name = self.__translate_freetext[name]
elif ((name in self.__rtranslate)
@@ -388,7 +395,6 @@ class ID3File(File):
tmcl = mutagen.id3.TMCL(encoding=encoding, people=[])
tipl = mutagen.id3.TIPL(encoding=encoding, people=[])
for name, values in metadata.rawitems():
values = [id3text(v, encoding) for v in values]
name = id3text(name, encoding)
@@ -470,7 +476,10 @@ class ID3File(File):
elif frameid == 'TSO2':
tags.delall('TXXX:ALBUMARTISTSORT')
elif name in self.__rtranslate_freetext_ci:
description = self.__rtranslate_freetext_ci[name]
if name in self.__casemap:
description = self.__casemap[name]
else:
description = self.__rtranslate_freetext_ci[name]
delall_ci(tags, 'TXXX:' + description)
tags.add(self.build_TXXX(encoding, description, values))
elif name in self.__rtranslate_freetext:

View File

@@ -140,6 +140,10 @@ class MP4File(File):
__other_supported_tags = ("discnumber", "tracknumber",
"totaldiscs", "totaltracks")
def __init__(self, filename):
super().__init__(filename)
self.__casemap = {}
def _load(self, filename):
log.debug("Loading file %r", filename)
self.__casemap = {}
@@ -163,7 +167,9 @@ class MP4File(File):
elif name_lower in self.__freeform_tags_ci:
for value in values:
value = value.decode("utf-8", "replace").strip("\x00")
metadata.add(self.__freeform_tags_ci[name_lower], value)
tag_name = self.__freeform_tags_ci[name_lower]
metadata.add(tag_name, value)
self.__casemap[tag_name] = name
elif name == "----:com.apple.iTunes:fingerprint":
for value in values:
value = value.decode("utf-8", "replace").strip("\x00")
@@ -223,7 +229,11 @@ class MP4File(File):
elif name in self.__r_freeform_tags_ci:
values = [v.encode("utf-8") for v in values]
delall_ci(tags, self.__r_freeform_tags_ci[name])
tags[self.__r_freeform_tags_ci[name]] = values
if name in self.__casemap:
name = self.__casemap[name]
else:
name = self.__r_freeform_tags_ci[name]
tags[name] = values
elif name == "musicip_fingerprint":
tags["----:com.apple.iTunes:fingerprint"] = [b"MusicMagic Fingerprint%s" % v.encode('ascii') for v in values]

View File

@@ -35,6 +35,8 @@ settings = {
def save_metadata(filename, metadata):
f = picard.formats.open_(filename)
loaded_metadata = f._load(filename)
f._copy_loaded_metadata(loaded_metadata)
f._save(filename, metadata)

View File

@@ -37,17 +37,21 @@ class CommonAsfTests:
self.assertTrue(fmt.supports_tag(tag))
@skipUnlessTestfile
def test_replaygain_tags_not_duplicated(self):
# Ensure values are not duplicated on repeated save
def test_ci_tags_preserve_case(self):
# Ensure values are not duplicated on repeated save and are saved
# case preserving.
tags = {
'Replaygain_Album_Peak': '-6.48 dB'
}
save_raw(self.filename, tags)
loaded_metadata = load_metadata(self.filename)
loaded_metadata['replaygain_album_peak'] = '1.0'
save_metadata(self.filename, loaded_metadata)
raw_metadata = load_raw(self.filename)
self.assertFalse('Replaygain_Album_Peak' in raw_metadata)
self.assertTrue('REPLAYGAIN_ALBUM_PEAK' in raw_metadata)
self.assertIn('Replaygain_Album_Peak', raw_metadata)
self.assertEqual(raw_metadata['Replaygain_Album_Peak'][0], loaded_metadata['replaygain_album_peak'])
self.assertEqual(1, len(raw_metadata['Replaygain_Album_Peak']))
self.assertNotIn('REPLAYGAIN_ALBUM_PEAK', raw_metadata)
class ASFTest(CommonAsfTests.AsfTestCase):

View File

@@ -212,16 +212,22 @@ class CommonId3Tests:
self.assertEqual(loaded_metadata[key], value, '%s: %r != %r' % (key, loaded_metadata[key], value))
@skipUnlessTestfile
def test_replaygain_tags_not_duplicated(self):
# Ensure values are not duplicated on repeated save
def test_ci_tags_preserve_case(self):
# Ensure values are not duplicated on repeated save and are saved
# case preserving.
tags = mutagen.id3.ID3Tags()
tags.add(mutagen.id3.TXXX(desc='Replaygain_Album_Peak', text='0.978475'))
save_raw(self.filename, tags)
loaded_metadata = load_metadata(self.filename)
loaded_metadata['replaygain_album_peak'] = '1.0'
save_metadata(self.filename, loaded_metadata)
raw_metadata = load_raw(self.filename)
self.assertFalse('TXXX:Replaygain_Album_Peak' in raw_metadata)
self.assertTrue('TXXX:REPLAYGAIN_ALBUM_PEAK' in raw_metadata)
self.assertIn('TXXX:Replaygain_Album_Peak', raw_metadata)
self.assertEqual(
raw_metadata['TXXX:Replaygain_Album_Peak'].text[0],
loaded_metadata['replaygain_album_peak'])
self.assertEqual(1, len(raw_metadata['TXXX:Replaygain_Album_Peak'].text))
self.assertNotIn('TXXX:REPLAYGAIN_ALBUM_PEAK', raw_metadata)
class MP3Test(CommonId3Tests.Id3TestCase):

View File

@@ -56,16 +56,22 @@ class MP4Test(CommonTests.TagFormatsTestCase):
self.assertEqual(loaded_metadata[key], value, '%s: %r != %r' % (key, loaded_metadata[key], value))
@skipUnlessTestfile
def test_replaygain_tags_not_duplicated(self):
# Ensure values are not duplicated on repeated save
def test_ci_tags_preserve_case(self):
# Ensure values are not duplicated on repeated save and are saved
# case preserving.
tags = mutagen.mp4.MP4Tags()
tags['----:com.apple.iTunes:Replaygain_Album_Peak'] = [b'-6.48 dB']
save_raw(self.filename, tags)
loaded_metadata = load_metadata(self.filename)
loaded_metadata['replaygain_album_peak'] = '1.0'
save_metadata(self.filename, loaded_metadata)
raw_metadata = load_raw(self.filename)
self.assertFalse('----:com.apple.iTunes:Replaygain_Album_Peak' in raw_metadata)
self.assertTrue('----:com.apple.iTunes:REPLAYGAIN_ALBUM_PEAK' in raw_metadata)
self.assertIn('----:com.apple.iTunes:Replaygain_Album_Peak', raw_metadata)
self.assertEqual(
raw_metadata['----:com.apple.iTunes:Replaygain_Album_Peak'][0].decode('utf-8'),
loaded_metadata['replaygain_album_peak'])
self.assertEqual(1, len(raw_metadata['----:com.apple.iTunes:Replaygain_Album_Peak']))
self.assertNotIn('----:com.apple.iTunes:REPLAYGAIN_ALBUM_PEAK', raw_metadata)
class Mp4CoverArtTest(CommonCoverArtTests.CoverArtTestCase):