mirror of
https://github.com/fergalmoran/picard.git
synced 2025-12-22 09:18:18 +00:00
It doesn't really conflict with variable name but it confuses syntax highlighters (if they are Python 3.10+ aware ofc)
793 lines
31 KiB
Python
793 lines
31 KiB
Python
# -*- coding: utf-8 -*-
|
|
#
|
|
# Picard, the next-generation MusicBrainz tagger
|
|
#
|
|
# Copyright (C) 2017 Sophist-UK
|
|
# Copyright (C) 2018, 2020 Wieland Hoffmann
|
|
# Copyright (C) 2018-2021 Laurent Monin
|
|
# Copyright (C) 2018-2021, 2023 Philipp Wolfer
|
|
# Copyright (C) 2020 dukeyin
|
|
#
|
|
# 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.
|
|
|
|
|
|
from test.picardtestcase import (
|
|
PicardTestCase,
|
|
create_fake_png,
|
|
load_test_json,
|
|
)
|
|
from test.test_coverart_image import create_image
|
|
|
|
from picard.acoustid.json_helpers import (
|
|
parse_recording as acoustid_parse_recording,
|
|
)
|
|
from picard.cluster import CLUSTER_COMPARISON_WEIGHTS
|
|
from picard.coverart.image import CoverArtImage
|
|
from picard.file import FILE_COMPARISON_WEIGHTS
|
|
from picard.mbjson import (
|
|
release_to_metadata,
|
|
track_to_metadata,
|
|
)
|
|
from picard.metadata import (
|
|
MULTI_VALUED_JOINER,
|
|
Metadata,
|
|
MultiMetadataProxy,
|
|
trackcount_score,
|
|
weights_from_preferred_countries,
|
|
weights_from_preferred_formats,
|
|
weights_from_release_type_scores,
|
|
)
|
|
from picard.track import Track
|
|
from picard.util.imagelist import ImageList
|
|
from picard.util.tags import PRESERVED_TAGS
|
|
|
|
|
|
settings = {
|
|
'write_id3v23': False,
|
|
'id3v23_join_with': '/',
|
|
'preferred_release_countries': [],
|
|
'preferred_release_formats': [],
|
|
'standardize_artists': False,
|
|
'standardize_instruments': False,
|
|
'translate_artist_names': False,
|
|
'release_ars': True,
|
|
'release_type_scores': [
|
|
('Album', 1.0),
|
|
('Other', 1.0),
|
|
],
|
|
}
|
|
|
|
|
|
class CommonTests:
|
|
|
|
class CommonMetadataTestCase(PicardTestCase):
|
|
|
|
original = None
|
|
tags = []
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.set_config_values(settings)
|
|
self.metadata = self.get_metadata_object()
|
|
self.metadata.length = 242
|
|
self.metadata["single1"] = "single1-value"
|
|
self.metadata.add_unique("single2", "single2-value")
|
|
self.metadata.add_unique("single2", "single2-value")
|
|
self.multi1 = ["multi1-value1", "multi1-value1"]
|
|
self.metadata.add("multi1", self.multi1[0])
|
|
self.metadata.add("multi1", self.multi1[1])
|
|
self.multi2 = ["multi2-value1", "multi2-value2"]
|
|
self.metadata["multi2"] = self.multi2
|
|
self.multi3 = ["multi3-value1", "multi3-value2"]
|
|
self.metadata.set("multi3", self.multi3)
|
|
self.metadata["~hidden"] = "hidden-value"
|
|
|
|
self.metadata_d1 = Metadata({'a': 'b', 'c': 2, 'd': ['x', 'y'], 'x': ''})
|
|
self.metadata_d2 = Metadata({'a': 'b', 'c': 2, 'd': ['x', 'y'], 'x': 'z'})
|
|
self.metadata_d3 = Metadata({'c': 3, 'd': ['u', 'w'], 'x': 'p'})
|
|
|
|
@staticmethod
|
|
def get_metadata_object():
|
|
pass
|
|
|
|
def tearDown(self):
|
|
pass
|
|
|
|
def test_metadata_setitem(self):
|
|
self.assertEqual(["single1-value"], self.metadata.getraw("single1"))
|
|
self.assertEqual(["single2-value"], self.metadata.getraw("single2"))
|
|
self.assertEqual(self.multi1, self.metadata.getraw("multi1"))
|
|
self.assertEqual(self.multi2, self.metadata.getraw("multi2"))
|
|
self.assertEqual(self.multi3, self.metadata.getraw("multi3"))
|
|
self.assertEqual(["hidden-value"], self.metadata.getraw("~hidden"))
|
|
|
|
def test_metadata_set_all_values_as_string(self):
|
|
for val in (0, 2, True):
|
|
str_val = str(val)
|
|
self.metadata.set('val1', val)
|
|
self.assertEqual([str_val], self.metadata.getraw("val1"))
|
|
self.metadata['val2'] = val
|
|
self.assertEqual([str_val], self.metadata.getraw("val2"))
|
|
del self.metadata['val3']
|
|
self.metadata.add('val3', val)
|
|
self.assertEqual([str_val], self.metadata.getraw("val3"))
|
|
del self.metadata['val4']
|
|
self.metadata.add_unique('val4', val)
|
|
self.assertEqual([str_val], self.metadata.getraw("val4"))
|
|
|
|
def test_metadata_get(self):
|
|
self.assertEqual("single1-value", self.metadata["single1"])
|
|
self.assertEqual("single1-value", self.metadata.get("single1"))
|
|
self.assertEqual(["single1-value"], self.metadata.getall("single1"))
|
|
self.assertEqual(["single1-value"], self.metadata.getraw("single1"))
|
|
|
|
self.assertEqual(MULTI_VALUED_JOINER.join(self.multi1), self.metadata["multi1"])
|
|
self.assertEqual(MULTI_VALUED_JOINER.join(self.multi1), self.metadata.get("multi1"))
|
|
self.assertEqual(self.multi1, self.metadata.getall("multi1"))
|
|
self.assertEqual(self.multi1, self.metadata.getraw("multi1"))
|
|
|
|
self.assertEqual("", self.metadata["nonexistent"])
|
|
self.assertEqual(None, self.metadata.get("nonexistent"))
|
|
self.assertEqual([], self.metadata.getall("nonexistent"))
|
|
self.assertRaises(KeyError, self.metadata.getraw, "nonexistent")
|
|
|
|
self.assertEqual(self.metadata._store.items(), self.metadata.rawitems())
|
|
metadata_items = [(x, z) for (x, y) in self.metadata.rawitems() for z in y]
|
|
self.assertEqual(metadata_items, list(self.metadata.items()))
|
|
|
|
def test_metadata_unset(self):
|
|
self.metadata.unset("single1")
|
|
self.assertNotIn("single1", self.metadata)
|
|
self.assertNotIn("single1", self.metadata.deleted_tags)
|
|
self.metadata.unset('unknown_tag')
|
|
|
|
def test_metadata_pop(self):
|
|
self.metadata.pop("single1")
|
|
self.assertNotIn("single1", self.metadata)
|
|
self.assertIn("single1", self.metadata.deleted_tags)
|
|
self.metadata.pop('unknown_tag')
|
|
|
|
def test_metadata_delete(self):
|
|
del self.metadata["single1"]
|
|
self.assertNotIn("single1", self.metadata)
|
|
self.assertIn("single1", self.metadata.deleted_tags)
|
|
|
|
def test_metadata_legacy_delete(self):
|
|
self.metadata.delete("single1")
|
|
self.assertNotIn("single1", self.metadata)
|
|
self.assertIn("single1", self.metadata.deleted_tags)
|
|
|
|
def test_metadata_implicit_delete(self):
|
|
self.metadata["single2"] = ""
|
|
self.assertNotIn("single2", self.metadata)
|
|
self.assertIn("single2", self.metadata.deleted_tags)
|
|
|
|
self.metadata["unknown"] = ""
|
|
self.assertNotIn("unknown", self.metadata)
|
|
self.assertNotIn("unknown", self.metadata.deleted_tags)
|
|
|
|
def test_metadata_undelete(self):
|
|
self.metadata.delete("single1")
|
|
self.assertNotIn("single1", self.metadata)
|
|
self.assertIn("single1", self.metadata.deleted_tags)
|
|
|
|
self.metadata["single1"] = "value1"
|
|
self.assertIn("single1", self.metadata)
|
|
self.assertNotIn("single1", self.metadata.deleted_tags)
|
|
|
|
def test_normalize_tag(self):
|
|
self.assertEqual('sometag', Metadata.normalize_tag('sometag'))
|
|
self.assertEqual('sometag', Metadata.normalize_tag('sometag:'))
|
|
self.assertEqual('sometag', Metadata.normalize_tag('sometag::'))
|
|
self.assertEqual('sometag:desc', Metadata.normalize_tag('sometag:desc'))
|
|
|
|
def test_metadata_tag_trailing_colon(self):
|
|
self.metadata['tag:'] = 'Foo'
|
|
self.assertIn('tag', self.metadata)
|
|
self.assertIn('tag:', self.metadata)
|
|
self.assertEqual('Foo', self.metadata['tag'])
|
|
self.assertEqual('Foo', self.metadata['tag:'])
|
|
del self.metadata['tag']
|
|
self.assertNotIn('tag', self.metadata)
|
|
self.assertNotIn('tag:', self.metadata)
|
|
|
|
def test_metadata_copy(self):
|
|
m = Metadata()
|
|
m["old"] = "old-value"
|
|
self.metadata.delete("single1")
|
|
m.copy(self.metadata)
|
|
self.assertEqual(self.metadata._store, m._store)
|
|
self.assertEqual(self.metadata.deleted_tags, m.deleted_tags)
|
|
self.assertEqual(self.metadata.length, m.length)
|
|
self.assertEqual(self.metadata.images, m.images)
|
|
|
|
def test_metadata_copy_without_images(self):
|
|
m = Metadata()
|
|
m.copy(self.metadata, copy_images=False)
|
|
self.assertEqual(self.metadata._store, m._store)
|
|
self.assertEqual(self.metadata.deleted_tags, m.deleted_tags)
|
|
self.assertEqual(self.metadata.length, m.length)
|
|
self.assertEqual(ImageList(), m.images)
|
|
|
|
def test_metadata_init_with_existing_metadata(self):
|
|
self.metadata.delete("single1")
|
|
cover = CoverArtImage(url='file://file1', data=create_fake_png(b'a'))
|
|
self.metadata.images.append(cover)
|
|
m = Metadata(self.metadata)
|
|
self.assertEqual(self.metadata.length, m.length)
|
|
self.assertEqual(self.metadata.deleted_tags, m.deleted_tags)
|
|
self.assertEqual(self.metadata.images, m.images)
|
|
self.assertEqual(self.metadata._store, m._store)
|
|
|
|
def test_metadata_update(self):
|
|
m = Metadata()
|
|
m["old"] = "old-value"
|
|
self.metadata.delete("single1")
|
|
cover = CoverArtImage(url='file://file1', data=create_fake_png(b'a'))
|
|
self.metadata.images.append(cover)
|
|
m.update(self.metadata)
|
|
self.assertIn("old", m)
|
|
self.assertNotIn("single1", m)
|
|
self.assertIn("single1", m.deleted_tags)
|
|
self.assertEqual("single2-value", m["single2"])
|
|
self.assertEqual(self.metadata.length, m.length)
|
|
self.assertEqual(self.metadata.deleted_tags, m.deleted_tags)
|
|
self.assertEqual(self.metadata.images, m.images)
|
|
self.metadata["old"] = "old-value"
|
|
self.assertEqual(self.metadata._store, m._store)
|
|
|
|
def test_metadata_diff(self):
|
|
m1 = Metadata({
|
|
"foo1": "bar1",
|
|
"foo2": "bar2",
|
|
"foo3": "bar3",
|
|
})
|
|
m2 = Metadata(m1)
|
|
m1["foo1"] = "baz"
|
|
del m1["foo2"]
|
|
diff = m1.diff(m2)
|
|
self.assertEqual({"foo1": "baz"}, diff)
|
|
self.assertEqual(set(["foo2"]), diff.deleted_tags)
|
|
|
|
def test_metadata_clear(self):
|
|
self.metadata.clear()
|
|
self.assertEqual(0, len(self.metadata))
|
|
|
|
def test_metadata_clear_deleted(self):
|
|
self.metadata.delete("single1")
|
|
self.assertIn("single1", self.metadata.deleted_tags)
|
|
self.metadata.clear_deleted()
|
|
self.assertNotIn("single1", self.metadata.deleted_tags)
|
|
|
|
def test_metadata_applyfunc(self):
|
|
def func(x):
|
|
return x[1:]
|
|
self.metadata.apply_func(func)
|
|
|
|
self.assertEqual("ingle1-value", self.metadata["single1"])
|
|
self.assertEqual("ingle1-value", self.metadata.get("single1"))
|
|
self.assertEqual(["ingle1-value"], self.metadata.getall("single1"))
|
|
|
|
self.assertEqual(MULTI_VALUED_JOINER.join(map(func, self.multi1)), self.metadata["multi1"])
|
|
self.assertEqual(MULTI_VALUED_JOINER.join(map(func, self.multi1)), self.metadata.get("multi1"))
|
|
self.assertEqual(list(map(func, self.multi1)), self.metadata.getall("multi1"))
|
|
|
|
def test_metadata_applyfunc_preserve_tags(self):
|
|
self.assertTrue(len(PRESERVED_TAGS) > 0)
|
|
m = Metadata()
|
|
m[PRESERVED_TAGS[0]] = 'value1'
|
|
m['not_preserved'] = 'value2'
|
|
|
|
def func(x):
|
|
return x[1:]
|
|
m.apply_func(func)
|
|
|
|
self.assertEqual("value1", m[PRESERVED_TAGS[0]])
|
|
self.assertEqual("alue2", m['not_preserved'])
|
|
|
|
def test_metadata_applyfunc_delete_tags(self):
|
|
def func(x):
|
|
return None
|
|
metadata = Metadata(self.metadata)
|
|
metadata.apply_func(func)
|
|
self.assertEqual(0, len(metadata.rawitems()))
|
|
self.assertEqual(self.metadata.keys(), metadata.deleted_tags)
|
|
|
|
def test_length_score(self):
|
|
results = (
|
|
(20000, 0, 0.333333333333),
|
|
(20000, 10000, 0.666666666667),
|
|
(20000, 20000, 1.0),
|
|
(20000, 30000, 0.666666666667),
|
|
(20000, 40000, 0.333333333333),
|
|
(20000, 50000, 0.0),
|
|
(20000, None, 0.0),
|
|
(None, 2000, 0.0),
|
|
(None, None, 0.0),
|
|
)
|
|
for (a, b, expected) in results:
|
|
actual = Metadata.length_score(a, b)
|
|
self.assertAlmostEqual(expected, actual,
|
|
msg="a={a}, b={b}".format(a=a, b=b))
|
|
|
|
def test_compare_is_equal(self):
|
|
m1 = Metadata()
|
|
m1["title"] = "title1"
|
|
m1["tracknumber"] = "2"
|
|
m1.length = 360
|
|
m2 = Metadata()
|
|
m2["title"] = "title1"
|
|
m2["tracknumber"] = "2"
|
|
m2.length = 360
|
|
self.assertEqual(m1.compare(m2), m2.compare(m1))
|
|
self.assertEqual(m1.compare(m2), 1)
|
|
|
|
def test_compare_with_ignored(self):
|
|
m1 = Metadata()
|
|
m1["title"] = "title1"
|
|
m1["tracknumber"] = "2"
|
|
m1.length = 360
|
|
m2 = Metadata()
|
|
m2["title"] = "title1"
|
|
m2["tracknumber"] = "3"
|
|
m2.length = 300
|
|
self.assertNotEqual(m1.compare(m2), 1)
|
|
self.assertEqual(m1.compare(m2, ignored=['tracknumber', '~length']), 1)
|
|
|
|
def test_compare_lengths(self):
|
|
m1 = Metadata()
|
|
m1.length = 360
|
|
m2 = Metadata()
|
|
m2.length = 300
|
|
self.assertAlmostEqual(m1.compare(m2), 0.998)
|
|
|
|
def test_compare_tracknumber_difference(self):
|
|
m1 = Metadata()
|
|
m1["tracknumber"] = "1"
|
|
m2 = Metadata()
|
|
m2["tracknumber"] = "2"
|
|
m3 = Metadata()
|
|
m3["tracknumber"] = "2"
|
|
self.assertEqual(m1.compare(m2), 0)
|
|
self.assertEqual(m2.compare(m3), 1)
|
|
|
|
def test_compare_discnumber_difference(self):
|
|
m1 = Metadata()
|
|
m1["discnumber"] = "1"
|
|
m2 = Metadata()
|
|
m2["discnumber"] = "2"
|
|
m3 = Metadata()
|
|
m3["discnumber"] = "2"
|
|
self.assertEqual(m1.compare(m2), 0)
|
|
self.assertEqual(m2.compare(m3), 1)
|
|
|
|
def test_compare_deleted(self):
|
|
m1 = Metadata()
|
|
m1["artist"] = "TheArtist"
|
|
m1["title"] = "title1"
|
|
m2 = Metadata()
|
|
m2["artist"] = "TheArtist"
|
|
m2.delete("title")
|
|
self.assertTrue(m1.compare(m2) < 1)
|
|
|
|
def test_strip_whitespace(self):
|
|
m1 = Metadata()
|
|
m1["artist"] = " TheArtist "
|
|
m1["title"] = "\t\u00A0 tit le1 \r\n"
|
|
m1["genre"] = " \t"
|
|
m1.strip_whitespace()
|
|
self.assertEqual(m1["artist"], "TheArtist")
|
|
self.assertEqual(m1["title"], "tit le1")
|
|
|
|
def test_metadata_mapping_init(self):
|
|
d = {'a': 'b', 'c': 2, 'd': ['x', 'y'], 'x': '', 'z': {'u', 'w'}}
|
|
deleted_tags = set('c')
|
|
m = Metadata(d, deleted_tags=deleted_tags, length=1234)
|
|
self.assertIn('a', m)
|
|
self.assertEqual(m.getraw('a'), ['b'])
|
|
self.assertEqual(m['d'], MULTI_VALUED_JOINER.join(d['d']))
|
|
self.assertNotIn('c', m)
|
|
self.assertNotIn('length', m)
|
|
self.assertIn('c', m.deleted_tags)
|
|
self.assertEqual(m.length, 1234)
|
|
|
|
def test_metadata_mapping_init_zero(self):
|
|
m = Metadata(tag1='a', tag2=0, tag3='', tag4=None)
|
|
m['tag5'] = 0
|
|
m['tag1'] = ''
|
|
self.assertIn('tag1', m.deleted_tags)
|
|
self.assertEqual(m['tag2'], '0')
|
|
self.assertNotIn('tag3', m)
|
|
self.assertNotIn('tag4', m)
|
|
self.assertEqual(m['tag5'], '0')
|
|
|
|
def test_metadata_mapping_del(self):
|
|
m = self.metadata_d1
|
|
self.assertEqual(m.getraw('a'), ['b'])
|
|
self.assertNotIn('a', m.deleted_tags)
|
|
|
|
self.assertNotIn('x', m.deleted_tags)
|
|
self.assertRaises(KeyError, m.getraw, 'x')
|
|
|
|
del m['a']
|
|
self.assertRaises(KeyError, m.getraw, 'a')
|
|
self.assertIn('a', m.deleted_tags)
|
|
|
|
# NOTE: historic behavior of Metadata.delete()
|
|
# an attempt to delete an non-existing tag, will add it to the list
|
|
# of deleted tags
|
|
# so this will not raise a KeyError
|
|
# as is it differs from dict or even defaultdict behavior
|
|
del m['unknown']
|
|
self.assertIn('unknown', m.deleted_tags)
|
|
|
|
def test_metadata_mapping_iter(self):
|
|
self.assertEqual(set(self.metadata_d1), {'a', 'c', 'd'})
|
|
|
|
def test_metadata_mapping_keys(self):
|
|
self.assertEqual(set(self.metadata_d1.keys()), {'a', 'c', 'd'})
|
|
|
|
def test_metadata_mapping_values(self):
|
|
self.assertEqual(set(self.metadata_d1.values()), {'b', '2', 'x; y'})
|
|
|
|
def test_metadata_mapping_len(self):
|
|
m = self.metadata_d1
|
|
self.assertEqual(len(m), 3)
|
|
del m['x']
|
|
self.assertEqual(len(m), 3)
|
|
del m['c']
|
|
self.assertEqual(len(m), 2)
|
|
|
|
def _check_mapping_update(self, m):
|
|
self.assertEqual(m['a'], 'b')
|
|
self.assertEqual(m['c'], '3')
|
|
self.assertEqual(m.getraw('d'), ['u', 'w'])
|
|
self.assertEqual(m['x'], '')
|
|
self.assertIn('x', m.deleted_tags)
|
|
|
|
def test_metadata_mapping_update(self):
|
|
# update from Metadata
|
|
m = self.metadata_d2
|
|
m2 = self.metadata_d3
|
|
|
|
del m2['x']
|
|
m.update(m2)
|
|
self._check_mapping_update(m)
|
|
|
|
def test_metadata_mapping_update_dict(self):
|
|
# update from dict
|
|
m = self.metadata_d2
|
|
|
|
d2 = {'c': 3, 'd': ['u', 'w'], 'x': ''}
|
|
|
|
m.update(d2)
|
|
self._check_mapping_update(m)
|
|
|
|
def test_metadata_mapping_update_tuple(self):
|
|
# update from tuple
|
|
m = self.metadata_d2
|
|
|
|
d2 = (('c', 3), ('d', ['u', 'w']), ('x', ''))
|
|
|
|
m.update(d2)
|
|
self._check_mapping_update(m)
|
|
|
|
def test_metadata_mapping_update_dictlike(self):
|
|
# update from kwargs
|
|
m = self.metadata_d2
|
|
|
|
m.update(c=3, d=['u', 'w'], x='')
|
|
self._check_mapping_update(m)
|
|
|
|
def test_metadata_mapping_update_noparam(self):
|
|
# update without parameter
|
|
m = self.metadata_d2
|
|
|
|
self.assertRaises(TypeError, m.update)
|
|
self.assertEqual(m['a'], 'b')
|
|
|
|
def test_metadata_mapping_update_intparam(self):
|
|
# update without parameter
|
|
m = self.metadata_d2
|
|
|
|
self.assertRaises(TypeError, m.update, 123)
|
|
|
|
def test_metadata_mapping_update_strparam(self):
|
|
# update without parameter
|
|
m = self.metadata_d2
|
|
|
|
self.assertRaises(ValueError, m.update, 'abc')
|
|
|
|
def test_metadata_mapping_update_kw(self):
|
|
m = Metadata(tag1='a', tag2='b')
|
|
m.update(tag1='c')
|
|
self.assertEqual(m['tag1'], 'c')
|
|
self.assertEqual(m['tag2'], 'b')
|
|
m.update(tag2='')
|
|
self.assertIn('tag2', m.deleted_tags)
|
|
|
|
def test_metadata_mapping_update_kw_del(self):
|
|
m = Metadata(tag1='a', tag2='b')
|
|
del m['tag1']
|
|
|
|
m2 = Metadata(tag1='c', tag2='d')
|
|
del m2['tag2']
|
|
|
|
m.update(m2)
|
|
self.assertEqual(m['tag1'], 'c')
|
|
self.assertNotIn('tag2', m)
|
|
self.assertNotIn('tag1', m.deleted_tags)
|
|
self.assertIn('tag2', m.deleted_tags)
|
|
|
|
def test_metadata_mapping_images(self):
|
|
image1 = create_image(b'A', comment='A')
|
|
image2 = create_image(b'B', comment='B')
|
|
|
|
m1 = Metadata(a='b', length=1234, images=[image1])
|
|
self.assertEqual(m1.images[0], image1)
|
|
self.assertEqual(len(m1), 2) # one tag, one image
|
|
|
|
m1.images.append(image2)
|
|
self.assertEqual(m1.images[1], image2)
|
|
|
|
m1.images.pop(0)
|
|
self.assertEqual(m1.images[0], image2)
|
|
|
|
m2 = Metadata(a='c', length=4567, images=[image1])
|
|
m1.update(m2)
|
|
self.assertEqual(m1.images[0], image1)
|
|
|
|
m1.images.pop(0)
|
|
self.assertEqual(len(m1), 1) # one tag, zero image
|
|
self.assertFalse(m1.images)
|
|
|
|
def test_metadata_mapping_iterable(self):
|
|
m = Metadata(tag_tuple=('a', 0))
|
|
m['tag_set'] = {'c', 'd'}
|
|
m['tag_dict'] = {'e': 1, 'f': 2}
|
|
m['tag_str'] = 'gh'
|
|
self.assertIn('0', m.getraw('tag_tuple'))
|
|
self.assertIn('c', m.getraw('tag_set'))
|
|
self.assertIn('e', m.getraw('tag_dict'))
|
|
self.assertIn('gh', m.getraw('tag_str'))
|
|
|
|
def test_compare_to_release(self):
|
|
release = load_test_json('release.json')
|
|
metadata = Metadata()
|
|
release_to_metadata(release, metadata)
|
|
match_ = metadata.compare_to_release(release, CLUSTER_COMPARISON_WEIGHTS)
|
|
self.assertEqual(1.0, match_.similarity)
|
|
self.assertEqual(release, match_.release)
|
|
|
|
def test_compare_to_release_with_score(self):
|
|
release = load_test_json('release.json')
|
|
metadata = Metadata()
|
|
release_to_metadata(release, metadata)
|
|
for score, sim in ((42, 0.42), ('42', 0.42), ('foo', 1.0), (None, 1.0)):
|
|
release['score'] = score
|
|
match_ = metadata.compare_to_release(release, CLUSTER_COMPARISON_WEIGHTS)
|
|
self.assertEqual(sim, match_.similarity)
|
|
|
|
def test_compare_to_release_parts_totaltracks(self):
|
|
release = load_test_json('release_multidisc.json')
|
|
metadata = Metadata()
|
|
weights = {"totaltracks": 30}
|
|
release_to_metadata(release, metadata)
|
|
for totaltracks, sim in ((4, 1.0), (3, 1.0), (2, 0.3), (5, 0.0)):
|
|
metadata['totaltracks'] = totaltracks
|
|
parts = metadata.compare_to_release_parts(release, weights)
|
|
self.assertIn((sim, 30), parts)
|
|
|
|
def test_compare_to_release_parts_totalalbumtracks(self):
|
|
release = load_test_json('release_multidisc.json')
|
|
metadata = Metadata()
|
|
weights = {"totalalbumtracks": 30}
|
|
release_to_metadata(release, metadata)
|
|
for totaltracks, sim in ((7, 1.0), (6, 0.3), (8, 0.0)):
|
|
metadata['~totalalbumtracks'] = totaltracks
|
|
parts = metadata.compare_to_release_parts(release, weights)
|
|
self.assertIn((sim, 30), parts)
|
|
|
|
def test_compare_to_release_parts_totalalbumtracks_totaltracks_fallback(self):
|
|
release = load_test_json('release_multidisc.json')
|
|
metadata = Metadata()
|
|
weights = {"totalalbumtracks": 30}
|
|
release_to_metadata(release, metadata)
|
|
for totaltracks, sim in ((7, 1.0), (6, 0.3), (8, 0.0)):
|
|
metadata['totaltracks'] = totaltracks
|
|
parts = metadata.compare_to_release_parts(release, weights)
|
|
self.assertIn((sim, 30), parts)
|
|
|
|
def test_trackcount_score(self):
|
|
self.assertEqual(1.0, trackcount_score(5, 5))
|
|
self.assertEqual(0.0, trackcount_score(6, 5))
|
|
self.assertEqual(0.3, trackcount_score(4, 5))
|
|
|
|
def test_weights_from_release_type_scores(self):
|
|
release = load_test_json('release.json')
|
|
parts = []
|
|
weights_from_release_type_scores(parts, release, {'Album': 0.75}, 666)
|
|
self.assertEqual(
|
|
parts[0],
|
|
(0.75, 666)
|
|
)
|
|
weights_from_release_type_scores(parts, release, {}, 666)
|
|
self.assertEqual(
|
|
parts[1],
|
|
(0.5, 666)
|
|
)
|
|
|
|
def test_weights_from_release_type_scores_no_type(self):
|
|
release = load_test_json('release_no_type.json')
|
|
parts = []
|
|
weights_from_release_type_scores(parts, release, {'Other': 0.75}, 123)
|
|
self.assertEqual(
|
|
parts[0],
|
|
(0.75, 123)
|
|
)
|
|
weights_from_release_type_scores(parts, release, {}, 123)
|
|
self.assertEqual(
|
|
parts[1],
|
|
(0.5, 123)
|
|
)
|
|
|
|
def test_preferred_countries(self):
|
|
release = load_test_json('release.json')
|
|
parts = []
|
|
weights_from_preferred_countries(parts, release, [], 666)
|
|
self.assertFalse(parts)
|
|
weights_from_preferred_countries(parts, release, ['FR'], 666)
|
|
self.assertEqual(parts[0], (0.0, 666))
|
|
weights_from_preferred_countries(parts, release, ['GB'], 666)
|
|
self.assertEqual(parts[1], (1.0, 666))
|
|
|
|
def test_preferred_formats(self):
|
|
release = load_test_json('release.json')
|
|
parts = []
|
|
weights_from_preferred_formats(parts, release, [], 777)
|
|
self.assertFalse(parts)
|
|
weights_from_preferred_formats(parts, release, ['Digital Media'], 777)
|
|
self.assertEqual(parts[0], (0.0, 777))
|
|
weights_from_preferred_formats(parts, release, ['12" Vinyl'], 777)
|
|
self.assertEqual(parts[1], (1.0, 777))
|
|
|
|
def test_compare_to_track(self):
|
|
track_json = load_test_json('track.json')
|
|
track = Track(track_json['id'])
|
|
track_to_metadata(track_json, track)
|
|
match_ = track.metadata.compare_to_track(track_json, FILE_COMPARISON_WEIGHTS)
|
|
self.assertEqual(1.0, match_.similarity)
|
|
self.assertEqual(track_json, match_.track)
|
|
|
|
def test_compare_to_track_with_score(self):
|
|
track_json = load_test_json('track.json')
|
|
track = Track(track_json['id'])
|
|
track_to_metadata(track_json, track)
|
|
for score, sim in ((42, 0.42), ('42', 0.42), ('foo', 1.0), (None, 1.0)):
|
|
track_json['score'] = score
|
|
match_ = track.metadata.compare_to_track(track_json, FILE_COMPARISON_WEIGHTS)
|
|
self.assertEqual(sim, match_.similarity)
|
|
|
|
def test_compare_to_track_is_video(self):
|
|
recording = load_test_json('recording_video_null.json')
|
|
m = Metadata()
|
|
match_ = m.compare_to_track(recording, {'isvideo': 1})
|
|
self.assertEqual(1.0, match_.similarity)
|
|
m['~video'] = '1'
|
|
match_ = m.compare_to_track(recording, {'isvideo': 1})
|
|
self.assertEqual(0.0, match_.similarity)
|
|
recording['video'] = True
|
|
match_ = m.compare_to_track(recording, {'isvideo': 1})
|
|
self.assertEqual(1.0, match_.similarity)
|
|
|
|
def test_compare_to_track_full(self):
|
|
recording = load_test_json('recording_video_null.json')
|
|
m = Metadata({
|
|
'artist': 'Tim Green',
|
|
'release': 'Eastbound Silhouette',
|
|
'date': '2022',
|
|
'title': 'Lune',
|
|
'totaltracks': '6',
|
|
'albumartist': 'Tim Green',
|
|
'tracknumber': '4',
|
|
})
|
|
match_ = m.compare_to_track(recording, FILE_COMPARISON_WEIGHTS)
|
|
self.assertGreaterEqual(match_.similarity, 0.8)
|
|
self.assertEqual(recording, match_.track)
|
|
self.assertEqual(recording['releases'][0], match_.release)
|
|
|
|
def test_compare_to_track_without_releases(self):
|
|
self.set_config_values({
|
|
'release_type_scores': [('Compilation', 0.6), ('Other', 0.6)]
|
|
})
|
|
track_json = acoustid_parse_recording(load_test_json('acoustid.json'))
|
|
track = Track(track_json['id'])
|
|
track.metadata.update({
|
|
'album': 'x',
|
|
'artist': 'Ed Sheeran',
|
|
'title': 'Nina',
|
|
})
|
|
track.metadata.length = 225000
|
|
m1 = track.metadata.compare_to_track(track_json, FILE_COMPARISON_WEIGHTS)
|
|
del track_json['releases']
|
|
m2 = track.metadata.compare_to_track(track_json, FILE_COMPARISON_WEIGHTS)
|
|
self.assertGreater(m1.similarity, m2.similarity,
|
|
'Matching score for release with recordings must be higher then for release without')
|
|
|
|
|
|
class MetadataTest(CommonTests.CommonMetadataTestCase):
|
|
@staticmethod
|
|
def get_metadata_object():
|
|
return Metadata()
|
|
|
|
|
|
class MultiMetadataProxyAsMetadataTest(CommonTests.CommonMetadataTestCase):
|
|
@staticmethod
|
|
def get_metadata_object():
|
|
return MultiMetadataProxy(Metadata())
|
|
|
|
|
|
class MultiMetadataProxyTest(PicardTestCase):
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.m1 = Metadata({
|
|
"key1": "m1.val1",
|
|
"key2": "m1.val2",
|
|
})
|
|
self.m2 = Metadata({
|
|
"key2": "m2.val2",
|
|
"key3": "m2.val3",
|
|
})
|
|
self.m3 = Metadata({
|
|
"key2": "m3.val2",
|
|
"key4": "m3.val4",
|
|
})
|
|
|
|
def test_get_attribute(self):
|
|
mp = MultiMetadataProxy(self.m1, self.m2, self.m3)
|
|
self.assertEqual(mp.deleted_tags, self.m1.deleted_tags)
|
|
|
|
def test_gettitem(self):
|
|
mp = MultiMetadataProxy(self.m1, self.m2, self.m3)
|
|
self.assertEqual("m1.val1", mp["key1"])
|
|
self.assertEqual("m1.val2", mp["key2"])
|
|
self.assertEqual("m2.val3", mp["key3"])
|
|
self.assertEqual("m3.val4", mp["key4"])
|
|
|
|
def test_settitem(self):
|
|
orig_m2 = Metadata(self.m2)
|
|
mp = MultiMetadataProxy(self.m1, self.m2, self.m3)
|
|
mp["key1"] = "foo1"
|
|
mp["key2"] = "foo2"
|
|
mp["key3"] = "foo3"
|
|
mp["key4"] = "foo4"
|
|
mp["key5"] = "foo5"
|
|
self.assertEqual("foo1", self.m1["key1"])
|
|
self.assertEqual("foo2", self.m1["key2"])
|
|
self.assertEqual("foo3", self.m1["key3"])
|
|
self.assertEqual("foo4", self.m1["key4"])
|
|
self.assertEqual("foo5", self.m1["key5"])
|
|
self.assertEqual(orig_m2, self.m2)
|
|
|
|
def test_delitem(self):
|
|
orig_m2 = Metadata(self.m2)
|
|
mp = MultiMetadataProxy(self.m1, self.m2, self.m3)
|
|
del mp["key2"]
|
|
del mp["key3"]
|
|
self.assertIn("key2", self.m1.deleted_tags)
|
|
self.assertIn("key3", self.m1.deleted_tags)
|
|
self.assertEqual(orig_m2, self.m2)
|