mirror of
https://github.com/fergalmoran/picard.git
synced 2026-02-20 22:54:10 +00:00
Merge pull request #1263 from phw/PICARD-1586-replaygain-tags
PICARD-1586: ReplayGain 2.0 tags
This commit is contained in:
@@ -55,6 +55,8 @@ UNSUPPORTED_TAGS = [
|
||||
'podcasturl',
|
||||
'show',
|
||||
'showsort',
|
||||
'r128_album_gain',
|
||||
'r128_track_gain',
|
||||
]
|
||||
|
||||
|
||||
@@ -97,6 +99,13 @@ class APEv2File(File):
|
||||
"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",
|
||||
}
|
||||
__rtranslate = dict([(v, k) for k, v in __translate.items()])
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ from picard.formats.id3 import (
|
||||
image_type_as_id3_num,
|
||||
types_from_id3,
|
||||
)
|
||||
from picard.formats.mutagenext import delall_ci
|
||||
from picard.metadata import Metadata
|
||||
from picard.util import encode_filename
|
||||
|
||||
@@ -161,8 +162,25 @@ class ASFFile(File):
|
||||
}
|
||||
__RTRANS = dict([(b, a) for a, b in __TRANS.items()])
|
||||
|
||||
# Tags to load case insensitive
|
||||
__TRANS_CI = {
|
||||
'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',
|
||||
}
|
||||
__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 = {}
|
||||
file = ASF(encode_filename(filename))
|
||||
metadata = Metadata()
|
||||
for name, values in file.tags.items():
|
||||
@@ -184,8 +202,6 @@ class ASFFile(File):
|
||||
else:
|
||||
metadata.images.append(coverartimage)
|
||||
|
||||
continue
|
||||
elif name not in self.__RTRANS:
|
||||
continue
|
||||
elif name == 'WM/SharedUserRating':
|
||||
# Rating in WMA ranges from 0 to 99, normalize this to the range 0 to 5
|
||||
@@ -195,7 +211,15 @@ class ASFFile(File):
|
||||
if len(disc) > 1:
|
||||
metadata["totaldiscs"] = disc[1]
|
||||
values[0] = disc[0]
|
||||
name = self.__RTRANS[name]
|
||||
name_lower = name.lower()
|
||||
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]
|
||||
if values:
|
||||
metadata[name] = values
|
||||
@@ -224,9 +248,16 @@ class ASFFile(File):
|
||||
values = [int(values[0]) * 99 // (config.setting['rating_steps'] - 1)]
|
||||
elif name == 'discnumber' and 'totaldiscs' in metadata:
|
||||
values = ['%s/%s' % (metadata['discnumber'], metadata['totaldiscs'])]
|
||||
if name not in self.__TRANS:
|
||||
if name in self.__TRANS:
|
||||
name = self.__TRANS[name]
|
||||
elif name in self.__TRANS_CI:
|
||||
if name in self.__casemap:
|
||||
name = self.__casemap[name]
|
||||
else:
|
||||
name = self.__TRANS_CI[name]
|
||||
delall_ci(tags, name)
|
||||
else:
|
||||
continue
|
||||
name = self.__TRANS[name]
|
||||
tags[name] = values
|
||||
|
||||
self._remove_deleted_tags(metadata, tags)
|
||||
@@ -243,6 +274,7 @@ class ASFFile(File):
|
||||
@classmethod
|
||||
def supports_tag(cls, name):
|
||||
return (name in cls.__TRANS
|
||||
or name in cls.__TRANS_CI
|
||||
or name in ('~rating', '~length', 'totaldiscs')
|
||||
or name.startswith('lyrics'))
|
||||
|
||||
|
||||
@@ -37,7 +37,10 @@ from picard.coverart.image import (
|
||||
TagCoverArtImage,
|
||||
)
|
||||
from picard.file import File
|
||||
from picard.formats.mutagenext import compatid3
|
||||
from picard.formats.mutagenext import (
|
||||
compatid3,
|
||||
delall_ci,
|
||||
)
|
||||
from picard.metadata import Metadata
|
||||
from picard.util import (
|
||||
encode_filename,
|
||||
@@ -177,6 +180,18 @@ class ID3File(File):
|
||||
__rtranslate_freetext = dict([(v, k) for k, v in __translate_freetext.items()])
|
||||
__translate_freetext['writer'] = 'writer' # For backward compatibility of case
|
||||
|
||||
# Freetext fields that are loaded case-insensitive
|
||||
__rtranslate_freetext_ci = {
|
||||
'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',
|
||||
}
|
||||
__translate_freetext_ci = dict([(b.lower(), a) for a, b in __rtranslate_freetext_ci.items()])
|
||||
|
||||
# Obsolete tag names which will still be loaded, but will get renamed on saving
|
||||
__rename_freetext = {
|
||||
'Artists': 'ARTISTS',
|
||||
@@ -202,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
|
||||
@@ -214,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
|
||||
@@ -256,9 +276,14 @@ class ID3File(File):
|
||||
metadata.add('performer:%s' % role, name)
|
||||
elif frameid == 'TXXX':
|
||||
name = frame.desc
|
||||
name_lower = name.lower()
|
||||
if name in self.__rename_freetext:
|
||||
name = self.__rename_freetext[name]
|
||||
if name in self.__translate_freetext:
|
||||
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)
|
||||
!= (name in self.__rtranslate_freetext)):
|
||||
@@ -370,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)
|
||||
@@ -451,6 +475,13 @@ class ID3File(File):
|
||||
tags.delall('XSOP')
|
||||
elif frameid == 'TSO2':
|
||||
tags.delall('TXXX:ALBUMARTISTSORT')
|
||||
elif name in self.__rtranslate_freetext_ci:
|
||||
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:
|
||||
description = self.__rtranslate_freetext[name]
|
||||
if description in self.__rrename_freetext:
|
||||
@@ -542,7 +573,7 @@ class ID3File(File):
|
||||
|
||||
@classmethod
|
||||
def supports_tag(cls, name):
|
||||
unsupported_tags = {}
|
||||
unsupported_tags = ['r128_album_gain', 'r128_track_gain']
|
||||
return ((name and not name.startswith("~") and name not in unsupported_tags)
|
||||
or name in ("~rating", "~length")
|
||||
or name.startswith("~id3"))
|
||||
|
||||
@@ -31,6 +31,7 @@ from picard.coverart.image import (
|
||||
TagCoverArtImage,
|
||||
)
|
||||
from picard.file import File
|
||||
from picard.formats.mutagenext import delall_ci
|
||||
from picard.metadata import Metadata
|
||||
from picard.util import encode_filename
|
||||
|
||||
@@ -124,15 +125,33 @@ class MP4File(File):
|
||||
}
|
||||
__r_freeform_tags = dict([(v, k) for k, v in __freeform_tags.items()])
|
||||
|
||||
# Tags to load case insensitive
|
||||
__r_freeform_tags_ci = {
|
||||
"replaygain_album_gain": "----:com.apple.iTunes:REPLAYGAIN_ALBUM_GAIN",
|
||||
"replaygain_album_peak": "----:com.apple.iTunes:REPLAYGAIN_ALBUM_PEAK",
|
||||
"replaygain_album_range": "----:com.apple.iTunes:REPLAYGAIN_ALBUM_RANGE",
|
||||
"replaygain_track_gain": "----:com.apple.iTunes:REPLAYGAIN_TRACK_GAIN",
|
||||
"replaygain_track_peak": "----:com.apple.iTunes:REPLAYGAIN_TRACK_PEAK",
|
||||
"replaygain_track_range": "----:com.apple.iTunes:REPLAYGAIN_TRACK_RANGE",
|
||||
"replaygain_reference_loudness": "----:com.apple.iTunes:REPLAYGAIN_REFERENCE_LOUDNESS",
|
||||
}
|
||||
__freeform_tags_ci = dict([(b.lower(), a) for a, b in __r_freeform_tags_ci.items()])
|
||||
|
||||
__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 = {}
|
||||
file = MP4(encode_filename(filename))
|
||||
tags = file.tags or {}
|
||||
metadata = Metadata()
|
||||
for name, values in tags.items():
|
||||
name_lower = name.lower()
|
||||
if name in self.__text_tags:
|
||||
for value in values:
|
||||
metadata.add(self.__text_tags[name], value)
|
||||
@@ -145,6 +164,12 @@ class MP4File(File):
|
||||
for value in values:
|
||||
value = value.decode("utf-8", "replace").strip("\x00")
|
||||
metadata.add(self.__freeform_tags[name], value)
|
||||
elif name_lower in self.__freeform_tags_ci:
|
||||
for value in values:
|
||||
value = value.decode("utf-8", "replace").strip("\x00")
|
||||
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")
|
||||
@@ -201,6 +226,14 @@ class MP4File(File):
|
||||
elif name in self.__r_freeform_tags:
|
||||
values = [v.encode("utf-8") for v in values]
|
||||
tags[self.__r_freeform_tags[name]] = values
|
||||
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])
|
||||
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]
|
||||
|
||||
@@ -244,6 +277,7 @@ class MP4File(File):
|
||||
return (name in cls.__r_text_tags
|
||||
or name in cls.__r_bool_tags
|
||||
or name in cls.__r_freeform_tags
|
||||
or name in cls.__r_freeform_tags_ci
|
||||
or name in cls.__r_int_tags
|
||||
or name in cls.__other_supported_tags
|
||||
or name.startswith('lyrics:')
|
||||
@@ -260,6 +294,8 @@ class MP4File(File):
|
||||
return self.__r_int_tags[name]
|
||||
elif name in self.__r_freeform_tags:
|
||||
return self.__r_freeform_tags[name]
|
||||
elif name in self.__r_freeform_tags_ci:
|
||||
return self.__r_freeform_tags_ci[name]
|
||||
elif name == "musicip_fingerprint":
|
||||
return "----:com.apple.iTunes:fingerprint"
|
||||
elif name in ("tracknumber", "totaltracks"):
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Picard, the next-generation MusicBrainz tagger
|
||||
# Copyright (C) 2019 Philipp Wolfer
|
||||
#
|
||||
# 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.
|
||||
|
||||
|
||||
def delall_ci(tags, key):
|
||||
"""Delete all tags with given key, case-insensitive"""
|
||||
key = key.lower()
|
||||
for k in list(tags.keys()):
|
||||
if k.lower() == key:
|
||||
del tags[k]
|
||||
|
||||
@@ -304,7 +304,7 @@ class VCommentFile(File):
|
||||
|
||||
@classmethod
|
||||
def supports_tag(cls, name):
|
||||
unsupported_tags = {}
|
||||
unsupported_tags = ['r128_album_gain', 'r128_track_gain']
|
||||
return (bool(name) and name not in unsupported_tags
|
||||
and is_valid_key(name))
|
||||
|
||||
@@ -360,6 +360,14 @@ class OggOpusFile(VCommentFile):
|
||||
NAME = "Ogg Opus"
|
||||
_File = mutagen.oggopus.OggOpus
|
||||
|
||||
@classmethod
|
||||
def supports_tag(cls, name):
|
||||
if name.startswith('replaygain_'):
|
||||
return False
|
||||
elif name.startswith('r128_'):
|
||||
return True
|
||||
return VCommentFile.supports_tag(name)
|
||||
|
||||
|
||||
def OggAudioFile(filename):
|
||||
"""Generic Ogg audio file."""
|
||||
|
||||
@@ -96,6 +96,15 @@ TAG_NAMES = {
|
||||
'musicbrainz_originalartistid': N_('MusicBrainz Original Artist Id'),
|
||||
'originalalbum': N_('Original Album'),
|
||||
'musicbrainz_originalalbumid': N_('MusicBrainz Original Release Id'),
|
||||
'replaygain_album_gain': N_('ReplayGain Album Gain'),
|
||||
'replaygain_album_peak': N_('ReplayGain Album Peak'),
|
||||
'replaygain_album_range': N_('ReplayGain Album Range'),
|
||||
'replaygain_track_gain': N_('ReplayGain Track Gain'),
|
||||
'replaygain_track_peak': N_('ReplayGain Track Peak'),
|
||||
'replaygain_track_range': N_('ReplayGain Track Range'),
|
||||
'replaygain_reference_loudness': N_('ReplayGain Reference Loudness'),
|
||||
'r128_album_gain': N_('R128 Album Gain'),
|
||||
'r128_track_gain': N_('R128 Track Gain'),
|
||||
}
|
||||
|
||||
PRESERVED_TAGS = [
|
||||
|
||||
@@ -11,6 +11,7 @@ from test.picardtestcase import PicardTestCase
|
||||
from picard import config
|
||||
import picard.formats
|
||||
from picard.formats import ext_to_format
|
||||
from picard.formats.mutagenext.tak import TAK
|
||||
from picard.metadata import Metadata
|
||||
|
||||
|
||||
@@ -34,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)
|
||||
|
||||
|
||||
@@ -52,7 +55,17 @@ def save_and_load_metadata(filename, metadata):
|
||||
|
||||
|
||||
def load_raw(filename):
|
||||
return mutagen.File(filename)
|
||||
f = mutagen.File(filename)
|
||||
if f is None:
|
||||
f = mutagen.File(filename, [TAK])
|
||||
return f
|
||||
|
||||
|
||||
def save_raw(filename, tags):
|
||||
f = load_raw(filename)
|
||||
for k, v in tags.items():
|
||||
f[k] = v
|
||||
f.save()
|
||||
|
||||
|
||||
TAGS = {
|
||||
@@ -130,6 +143,16 @@ TAGS = {
|
||||
'work': 'Foo'
|
||||
}
|
||||
|
||||
REPLAYGAIN_TAGS = {
|
||||
'replaygain_album_gain': '-6.48 dB',
|
||||
'replaygain_album_peak': '0.978475',
|
||||
'replaygain_album_range': '7.84 dB',
|
||||
'replaygain_track_gain': '-6.16 dB',
|
||||
'replaygain_track_peak': '0.976991',
|
||||
'replaygain_track_range': '8.22 dB',
|
||||
'replaygain_reference_loudness': '-18.00 LUFS',
|
||||
}
|
||||
|
||||
|
||||
def skipUnlessTestfile(func):
|
||||
def _decorator(self, *args, **kwargs):
|
||||
@@ -190,6 +213,8 @@ class CommonTests:
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.tags = TAGS.copy()
|
||||
self.replaygain_tags = REPLAYGAIN_TAGS.copy()
|
||||
self.unsupported_tags = {}
|
||||
self.setup_tags()
|
||||
|
||||
def setup_tags(self):
|
||||
@@ -208,9 +233,26 @@ class CommonTests:
|
||||
|
||||
@skipUnlessTestfile
|
||||
def test_simple_tags(self):
|
||||
metadata = Metadata(self.tags)
|
||||
loaded_metadata = save_and_load_metadata(self.filename, metadata)
|
||||
for (key, value) in self.tags.items():
|
||||
self._test_supported_tags(self.tags)
|
||||
|
||||
@skipUnlessTestfile
|
||||
def test_replaygain_tags(self):
|
||||
self._test_supported_tags(self.replaygain_tags)
|
||||
|
||||
@skipUnlessTestfile
|
||||
def test_replaygain_tags_case_insensitive(self):
|
||||
tags = {
|
||||
'replaygain_album_gain': '-6.48 dB',
|
||||
'Replaygain_Album_Peak': '0.978475',
|
||||
'replaygain_album_range': '7.84 dB',
|
||||
'replaygain_track_gain': '-6.16 dB',
|
||||
'replaygain_track_peak': '0.976991',
|
||||
'replaygain_track_range': '8.22 dB',
|
||||
'replaygain_reference_loudness': '-18.00 LUFS',
|
||||
}
|
||||
save_raw(self.filename, tags)
|
||||
loaded_metadata = load_metadata(self.filename)
|
||||
for (key, value) in self.replaygain_tags.items():
|
||||
self.assertEqual(loaded_metadata[key], value, '%s: %r != %r' % (key, loaded_metadata[key], value))
|
||||
|
||||
@skipUnlessTestfile
|
||||
@@ -225,10 +267,7 @@ class CommonTests:
|
||||
|
||||
@skipUnlessTestfile
|
||||
def test_unsupported_tags(self):
|
||||
metadata = Metadata(self.unsupported_tags)
|
||||
loaded_metadata = save_and_load_metadata(self.filename, metadata)
|
||||
for tag in self.unsupported_tags:
|
||||
self.assertTrue(tag not in loaded_metadata, '%s: %r != None' % (tag, loaded_metadata[tag]))
|
||||
self._test_unsupported_tags(self.unsupported_tags)
|
||||
|
||||
@skipUnlessTestfile
|
||||
def test_preserve_unchanged_tags(self):
|
||||
@@ -328,3 +367,15 @@ class CommonTests:
|
||||
self.assertEqual(f._fixed_splitext(f.EXTENSIONS[0]), ('', f.EXTENSIONS[0]))
|
||||
self.assertEqual(f._fixed_splitext('.test'), os.path.splitext('.test'))
|
||||
self.assertNotEqual(f._fixed_splitext(f.EXTENSIONS[0]), os.path.splitext(f.EXTENSIONS[0]))
|
||||
|
||||
def _test_supported_tags(self, tags):
|
||||
metadata = Metadata(tags)
|
||||
loaded_metadata = save_and_load_metadata(self.filename, metadata)
|
||||
for (key, value) in tags.items():
|
||||
self.assertEqual(loaded_metadata[key], value, '%s: %r != %r' % (key, loaded_metadata[key], value))
|
||||
|
||||
def _test_unsupported_tags(self, tags):
|
||||
metadata = Metadata(tags)
|
||||
loaded_metadata = save_and_load_metadata(self.filename, metadata)
|
||||
for tag in tags:
|
||||
self.assertTrue(tag not in loaded_metadata, '%s: %r != None' % (tag, loaded_metadata[tag]))
|
||||
|
||||
@@ -36,6 +36,11 @@ SUPPORTED_TAGS = list(set(TAGS.keys()) - set(apev2.UNSUPPORTED_TAGS))
|
||||
class CommonApeTests:
|
||||
|
||||
class ApeTestCase(CommonTests.TagFormatsTestCase):
|
||||
def setup_tags(self):
|
||||
super().setup_tags()
|
||||
self.unsupported_tags['r128_album_gain'] = '-2857'
|
||||
self.unsupported_tags['r128_track_gain'] = '-2857'
|
||||
|
||||
def test_supports_tags(self):
|
||||
supports_tag = self.format.supports_tag
|
||||
for key in VALID_KEYS + SUPPORTED_TAGS:
|
||||
|
||||
@@ -3,13 +3,58 @@ from test.picardtestcase import (
|
||||
create_fake_png,
|
||||
)
|
||||
|
||||
from picard.formats import asf
|
||||
from picard.formats import (
|
||||
asf,
|
||||
ext_to_format,
|
||||
)
|
||||
|
||||
from .common import CommonTests
|
||||
from .common import (
|
||||
CommonTests,
|
||||
load_metadata,
|
||||
load_raw,
|
||||
save_metadata,
|
||||
save_raw,
|
||||
skipUnlessTestfile,
|
||||
)
|
||||
from .coverart import CommonCoverArtTests
|
||||
|
||||
|
||||
class ASFTest(CommonTests.TagFormatsTestCase):
|
||||
# prevent unittest to run tests in those classes
|
||||
class CommonAsfTests:
|
||||
|
||||
class AsfTestCase(CommonTests.TagFormatsTestCase):
|
||||
|
||||
def test_supports_tag(self):
|
||||
fmt = ext_to_format(self.testfile_ext[1:])
|
||||
self.assertTrue(fmt.supports_tag('copyright'))
|
||||
self.assertTrue(fmt.supports_tag('compilation'))
|
||||
self.assertTrue(fmt.supports_tag('bpm'))
|
||||
self.assertTrue(fmt.supports_tag('djmixer'))
|
||||
self.assertTrue(fmt.supports_tag('discnumber'))
|
||||
self.assertTrue(fmt.supports_tag('lyrics:lead'))
|
||||
self.assertTrue(fmt.supports_tag('~length'))
|
||||
for tag in self.replaygain_tags.keys():
|
||||
self.assertTrue(fmt.supports_tag(tag))
|
||||
|
||||
@skipUnlessTestfile
|
||||
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.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):
|
||||
testfile = 'test.asf'
|
||||
supports_ratings = True
|
||||
expected_info = {
|
||||
@@ -20,7 +65,7 @@ class ASFTest(CommonTests.TagFormatsTestCase):
|
||||
}
|
||||
|
||||
|
||||
class WMATest(CommonTests.TagFormatsTestCase):
|
||||
class WMATest(CommonAsfTests.AsfTestCase):
|
||||
testfile = 'test.wma'
|
||||
supports_ratings = True
|
||||
expected_info = {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import os.path
|
||||
|
||||
import mutagen
|
||||
|
||||
from test.picardtestcase import PicardTestCase
|
||||
|
||||
from picard import config
|
||||
@@ -12,6 +14,7 @@ from .common import (
|
||||
load_raw,
|
||||
save_and_load_metadata,
|
||||
save_metadata,
|
||||
save_raw,
|
||||
skipUnlessTestfile,
|
||||
)
|
||||
from .coverart import CommonCoverArtTests
|
||||
@@ -28,6 +31,8 @@ class CommonId3Tests:
|
||||
self.set_tags({
|
||||
'originaldate': '1980'
|
||||
})
|
||||
self.unsupported_tags['r128_album_gain'] = '-2857'
|
||||
self.unsupported_tags['r128_track_gain'] = '-2857'
|
||||
|
||||
@skipUnlessTestfile
|
||||
def test_id3_freeform_delete(self):
|
||||
@@ -191,6 +196,39 @@ class CommonId3Tests:
|
||||
config.setting['write_id3v23'] = True
|
||||
self.test_preserve_unchanged_tags()
|
||||
|
||||
@skipUnlessTestfile
|
||||
def test_replaygain_tags_case_insensitive(self):
|
||||
tags = mutagen.id3.ID3Tags()
|
||||
tags.add(mutagen.id3.TXXX(desc='replaygain_album_gain', text='-6.48 dB'))
|
||||
tags.add(mutagen.id3.TXXX(desc='Replaygain_Album_Peak', text='0.978475'))
|
||||
tags.add(mutagen.id3.TXXX(desc='replaygain_album_range', text='7.84 dB'))
|
||||
tags.add(mutagen.id3.TXXX(desc='replaygain_track_gain', text='-6.16 dB'))
|
||||
tags.add(mutagen.id3.TXXX(desc='REPLAYGAIN_track_peak', text='0.976991'))
|
||||
tags.add(mutagen.id3.TXXX(desc='REPLAYGAIN_TRACK_RANGE', text='8.22 dB'))
|
||||
tags.add(mutagen.id3.TXXX(desc='replaygain_reference_loudness', text='-18.00 LUFS'))
|
||||
save_raw(self.filename, tags)
|
||||
loaded_metadata = load_metadata(self.filename)
|
||||
for (key, value) in self.replaygain_tags.items():
|
||||
self.assertEqual(loaded_metadata[key], value, '%s: %r != %r' % (key, loaded_metadata[key], value))
|
||||
|
||||
@skipUnlessTestfile
|
||||
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.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):
|
||||
testfile = 'test.mp3'
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import mutagen
|
||||
|
||||
from picard.formats import ext_to_format
|
||||
|
||||
from .common import (
|
||||
CommonTests,
|
||||
load_metadata,
|
||||
load_raw,
|
||||
save_metadata,
|
||||
save_raw,
|
||||
skipUnlessTestfile,
|
||||
)
|
||||
from .coverart import CommonCoverArtTests
|
||||
|
||||
@@ -27,11 +33,46 @@ class MP4Test(CommonTests.TagFormatsTestCase):
|
||||
self.assertTrue(fmt.supports_tag('discnumber'))
|
||||
self.assertTrue(fmt.supports_tag('lyrics:lead'))
|
||||
self.assertTrue(fmt.supports_tag('~length'))
|
||||
for tag in self.replaygain_tags.keys():
|
||||
self.assertTrue(fmt.supports_tag(tag))
|
||||
|
||||
def test_format(self):
|
||||
metadata = load_metadata(self.filename)
|
||||
self.assertIn('AAC LC', metadata['~format'])
|
||||
|
||||
@skipUnlessTestfile
|
||||
def test_replaygain_tags_case_insensitive(self):
|
||||
tags = mutagen.mp4.MP4Tags()
|
||||
tags['----:com.apple.iTunes:replaygain_album_gain'] = [b'-6.48 dB']
|
||||
tags['----:com.apple.iTunes:Replaygain_Album_Peak'] = [b'0.978475']
|
||||
tags['----:com.apple.iTunes:replaygain_album_range'] = [b'7.84 dB']
|
||||
tags['----:com.apple.iTunes:replaygain_track_gain'] = [b'-6.16 dB']
|
||||
tags['----:com.apple.iTunes:REPLAYGAIN_track_peak'] = [b'0.976991']
|
||||
tags['----:com.apple.iTunes:REPLAYGAIN_TRACK_RANGE'] = [b'8.22 dB']
|
||||
tags['----:com.apple.iTunes:replaygain_reference_loudness'] = [b'-18.00 LUFS']
|
||||
save_raw(self.filename, tags)
|
||||
loaded_metadata = load_metadata(self.filename)
|
||||
for (key, value) in self.replaygain_tags.items():
|
||||
self.assertEqual(loaded_metadata[key], value, '%s: %r != %r' % (key, loaded_metadata[key], value))
|
||||
|
||||
@skipUnlessTestfile
|
||||
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.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):
|
||||
testfile = 'test.m4a'
|
||||
|
||||
17
test/formats/test_mutagenext.py
Normal file
17
test/formats/test_mutagenext.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from test.picardtestcase import PicardTestCase
|
||||
|
||||
from picard.formats import mutagenext
|
||||
|
||||
|
||||
class MutagenExtTest(PicardTestCase):
|
||||
|
||||
def test_delall_ci(self):
|
||||
tags = {
|
||||
'TAGNAME:ABC': 'a',
|
||||
'tagname:abc': 'a',
|
||||
'TagName:Abc': 'a',
|
||||
'OtherTag': 'a'
|
||||
}
|
||||
mutagenext.delall_ci(tags, 'tagname:Abc')
|
||||
self.assertEqual({'OtherTag': 'a'}, tags)
|
||||
@@ -11,6 +11,7 @@ from picard.coverart.image import CoverArtImage
|
||||
from picard.formats import vorbis
|
||||
|
||||
from .common import (
|
||||
REPLAYGAIN_TAGS,
|
||||
TAGS,
|
||||
CommonTests,
|
||||
load_metadata,
|
||||
@@ -56,6 +57,15 @@ class CommonVorbisTests:
|
||||
for key in INVALID_KEYS + ['']:
|
||||
self.assertFalse(supports_tag(key), '%r should be unsupported' % key)
|
||||
|
||||
@skipUnlessTestfile
|
||||
def test_r128_replaygain_tags(self):
|
||||
# Vorbis files other then Opus must not support the r128_* tags
|
||||
tags = {
|
||||
'r128_album_gain': '-2857',
|
||||
'r128_track_gain': '-2857',
|
||||
}
|
||||
self._test_unsupported_tags(tags)
|
||||
|
||||
|
||||
class FLACTest(CommonVorbisTests.VorbisTestCase):
|
||||
testfile = 'test.flac'
|
||||
@@ -103,6 +113,20 @@ class OggOpusTest(CommonVorbisTests.VorbisTestCase):
|
||||
'~channels': '2',
|
||||
}
|
||||
|
||||
@skipUnlessTestfile
|
||||
def test_replaygain_tags(self):
|
||||
# The normal replaygain tags are not supported by Opus
|
||||
tags = REPLAYGAIN_TAGS.copy()
|
||||
self._test_unsupported_tags(tags)
|
||||
|
||||
@skipUnlessTestfile
|
||||
def test_r128_replaygain_tags(self):
|
||||
tags = {
|
||||
'r128_album_gain': '-2857',
|
||||
'r128_track_gain': '-2857',
|
||||
}
|
||||
self._test_supported_tags(tags)
|
||||
|
||||
|
||||
class VorbisUtilTest(PicardTestCase):
|
||||
def test_sanitize_key(self):
|
||||
|
||||
Reference in New Issue
Block a user