1 # -*- coding: utf-8 -*-
2 # Copyright (c) 2013, 2014, 2015, 2021 kaliko <kaliko@azylum.org>
4 # This file is part of sima
6 # sima is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # sima is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with sima. If not, see <http://www.gnu.org/licenses/>.
21 Defines some object to handle audio file metadata
25 from collections.abc import Set
29 UUID_RE = r'^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}$'
30 # The Track Object is collapsing multiple tags into a single string using this
31 # separator. It is used then to split back the string to tags list.
32 SEPARATOR = chr(0x1F) # ASCII Unit Separator
35 """Controls MusicBrainz UUID4 format
37 :param str uuid: String representing the UUID
40 regexp = re.compile(UUID_RE, re.IGNORECASE)
41 if regexp.match(uuid):
45 class MetaException(Exception):
46 """Generic Meta Exception"""
50 def wrapper(*args, **kwargs):
53 kwargs.pop('mbid', None)
54 kwargs.pop('musicbrainz_artistid', None)
55 kwargs.pop('musicbrainz_albumartistid', None)
60 def wrapper(*args, **kwargs):
61 ans = func(*args, **kwargs)
62 if isinstance(ans, set):
63 return {s.replace("'", r"\'") for s in ans}
64 return ans.replace("'", r"\'")
70 A generic Class to handle tracks metadata such as artist, album, albumartist
71 names and their associated MusicBrainz's ID.
74 Using generic kwargs in constructor for convenience but the actual signature is:
76 >>> Meta(name, mbid=None, **kwargs)
78 :param str name: set name attribute
79 :param str mbid: set MusicBrainz ID
82 """Class attribute to disable use of MusicBrainz IDs"""
84 def __init__(self, **kwargs):
85 """Meta(name=<str>[, mbid=UUID4])"""
86 self.__name = None # TODO: should be immutable
88 self.__aliases = set()
89 self.log = logging.getLogger(__name__)
90 if 'name' not in kwargs or not kwargs.get('name'):
91 raise MetaException('Need a "name" argument (str type)')
92 if not isinstance(kwargs.get('name'), str):
93 raise MetaException('"name" argument not a string')
95 self.__name = kwargs.pop('name')
96 if 'mbid' in kwargs and kwargs.get('mbid'):
97 if is_uuid4(kwargs.get('mbid')):
98 self.__mbid = kwargs.pop('mbid').lower()
100 self.log.warning('Wrong mbid %s:%s', self.__name,
102 # mbid immutable as hash rests on
103 self.__dict__.update(**kwargs)
106 fmt = '{0}(name={1.name!r}, mbid={1.mbid!r})'
107 return fmt.format(self.__class__.__name__, self)
110 return self.__name.__str__()
112 def __eq__(self, other):
114 Perform mbid equality test
116 #if hasattr(other, 'mbid'): # better isinstance?
117 if isinstance(other, Meta) and self.mbid and other.mbid:
118 return self.mbid == other.mbid
119 if isinstance(other, Meta):
120 return bool(self.names & other.names)
121 if getattr(other, '__str__', None):
122 # is other.__str__() in self.__name or self.__aliases
123 return other.__str__() in self.names
128 return hash(self.mbid)
129 return hash(self.__name)
131 def add_alias(self, other):
132 """Add alternative name to `aliases` attibute.
134 `other` can be a :class:`sima.lib.meta.Meta` object in which case aliases are merged.
136 :param str other: Alias to add, could be any object with ``__str__`` method.
138 if getattr(other, '__str__', None):
139 if callable(other.__str__) and other.__str__() != self.name:
140 self.__aliases |= {other.__str__()}
141 elif isinstance(other, Meta):
142 if other.name != self.name:
143 self.__aliases |= other.__aliases
145 raise MetaException('No __str__ method found in {!r}'.format(other))
162 return self.__aliases
166 def aliases_sz(self):
172 return self.__aliases | {self.__name,}
184 def __init__(self, name=None, mbid=None, **kwargs):
185 super().__init__(name=name, mbid=mbid, **kwargs)
193 """Artist object deriving from :class:`Meta`.
195 :param str name: Artist name
196 :param str mbid: Musicbrainz artist ID
197 :param str artist: Overrides "name" argument
198 :param str albumartist: Overrides "name" and "artist" argument
199 :param str musicbrainz_artistid: Overrides "mbid" argument
200 :param str musicbrainz_albumartistid: Overrides "musicbrainz_artistid" argument
204 >>> trk = {'artist':'Art Name',
205 >>> 'albumartist': 'Alb Art Name', # optional
206 >>> 'musicbrainz_artistid': '<UUID4>', # optional
207 >>> 'musicbrainz_albumartistid': '<UUID4>', # optional
209 >>> artobj0 = Artist(**trk)
210 >>> artobj1 = Artist(name='Tool')
214 def __init__(self, name=None, mbid=None, **kwargs):
215 if kwargs.get('artist', False):
216 name = kwargs.get('artist').split(SEPARATOR)[0]
217 if kwargs.get('musicbrainz_artistid', False):
218 mbid = kwargs.get('musicbrainz_artistid').split(SEPARATOR)[0]
219 if (kwargs.get('albumartist', False) and
220 kwargs.get('albumartist') != 'Various Artists'):
221 name = kwargs.get('albumartist').split(SEPARATOR)[0]
222 if (kwargs.get('musicbrainz_albumartistid', False) and
223 kwargs.get('musicbrainz_albumartistid') != '89ad4ac3-39f7-470e-963a-56509c546377'):
224 mbid = kwargs.get('musicbrainz_albumartistid').split(SEPARATOR)[0]
225 super().__init__(name=name, mbid=mbid)
227 class MetaContainer(Set):
229 def __init__(self, iterable):
230 self.elements = lst = []
231 for value in iterable:
237 inlst.add_alias(value)
240 return iter(self.elements)
242 def __contains__(self, value):
243 return value in self.elements
246 return len(self.elements)
249 return repr(self.elements)
252 # vim: ai ts=4 sw=4 sts=4 expandtab