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 from ..utils.utils import MPDSimaException
32 UUID_RE = r'^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[89AB][a-f0-9]{3}-[a-f0-9]{12}$'
33 #: The Track Object is collapsing multiple tags into a single string using this
34 # separator. It is used then to split back the string to tags list.
35 SEPARATOR = chr(0x1F) # ASCII Unit Separator
39 """Controls MusicBrainz UUID4 format
41 :param str uuid: String representing the UUID
44 regexp = re.compile(UUID_RE, re.IGNORECASE)
45 if regexp.match(uuid):
50 class MetaException(MPDSimaException):
51 """Generic Meta Exception"""
55 def wrapper(*args, **kwargs):
58 kwargs.pop('mbid', None)
59 kwargs.pop('musicbrainz_artistid', None)
60 kwargs.pop('musicbrainz_albumartistid', None)
66 def wrapper(*args, **kwargs):
67 ans = func(*args, **kwargs)
68 if isinstance(ans, set):
69 return {s.replace("'", r"\'").replace('"', r'\"') for s in ans}
70 return ans.replace("'", r"\'").replace('"', r'\"')
76 A generic Class to handle tracks metadata such as artist, album, albumartist
77 names and their associated MusicBrainz's ID.
80 Using generic kwargs in constructor for convenience but the actual signature is:
82 >>> Meta(name, mbid=None, **kwargs)
84 :param str name: set name attribute
85 :param str mbid: set MusicBrainz ID
88 """Class attribute to disable use of MusicBrainz IDs"""
90 def __init__(self, **kwargs):
91 """Meta(name=<str>[, mbid=UUID4])"""
92 self.__name = None # TODO: should be immutable
94 self.__aliases = set()
95 self.log = logging.getLogger(__name__)
96 if 'name' not in kwargs or not kwargs.get('name'):
97 raise MetaException('Need a "name" argument (str type)')
98 if not isinstance(kwargs.get('name'), str):
99 raise MetaException('"name" argument not a string')
101 self.__name = kwargs.pop('name').split(SEPARATOR)[0]
102 if 'mbid' in kwargs and kwargs.get('mbid'):
103 mbid = kwargs.get('mbid').lower().split(SEPARATOR)[0]
107 self.log.warning('Wrong mbid %s:%s', self.__name, mbid)
108 # mbid immutable as hash rests on
109 self.__dict__.update(**kwargs)
112 fmt = '{0}(name={1.name!r}, mbid={1.mbid!r})'
113 return fmt.format(self.__class__.__name__, self)
116 return self.__name.__str__()
118 def __eq__(self, other):
120 Perform mbid equality test
122 #if hasattr(other, 'mbid'): # better isinstance?
123 if isinstance(other, Meta) and self.mbid and other.mbid:
124 return self.mbid == other.mbid
125 if isinstance(other, Meta):
126 return bool(self.names & other.names)
127 if getattr(other, '__str__', None):
128 # is other.__str__() in self.__name or self.__aliases
129 return other.__str__() in self.names
134 return hash(self.mbid)
135 return hash(self.__name)
137 def add_alias(self, other):
138 """Add alternative name to `aliases` attibute.
140 `other` can be a :class:`sima.lib.meta.Meta` object in which case aliases are merged.
142 :param str other: Alias to add, could be any object with ``__str__`` method.
144 if isinstance(other, Meta):
145 self.__aliases |= other.__aliases
146 self.__aliases -= {self.name}
147 if getattr(other, '__str__', None):
148 if callable(other.__str__) and other.__str__() != self.name:
149 self.__aliases |= {other.__str__()}
151 raise MetaException('No __str__ method found in {!r}'.format(other))
168 return self.__aliases
172 def aliases_sz(self):
178 return self.__aliases | {self.__name, }
190 def __init__(self, name=None, mbid=None, **kwargs):
191 if kwargs.get('musicbrainz_albumid', False):
192 mbid = kwargs.get('musicbrainz_albumid')
193 super().__init__(name=name, mbid=mbid, **kwargs)
201 """Artist object deriving from :class:`Meta`.
203 :param str name: Artist name
204 :param str mbid: Musicbrainz artist ID
205 :param str artist: Overrides "name" argument
206 :param str albumartist: use "name" if not set
207 :param str musicbrainz_artistid: Overrides "mbid" argument
211 >>> trk = {'artist':'Art Name',
212 >>> 'albumartist': 'Alb Art Name', # optional
213 >>> 'musicbrainz_artistid': '<UUID4>', # optional
215 >>> artobj0 = Artist(**trk)
216 >>> artobj1 = Artist(name='Tool')
220 def __init__(self, name=None, mbid=None, **kwargs):
221 if kwargs.get('artist', False):
222 name = kwargs.get('artist')
223 if kwargs.get('musicbrainz_artistid', False):
224 mbid = kwargs.get('musicbrainz_artistid')
225 if name and not kwargs.get('albumartist', False):
226 kwargs['albumartist'] = name.split(SEPARATOR)[0]
227 super().__init__(name=name, mbid=mbid,
228 albumartist=kwargs.get('albumartist'))
231 class MetaContainer(Set):
233 def __init__(self, iterable):
234 self.elements = lst = []
235 for value in iterable:
241 inlst.add_alias(value)
244 return iter(self.elements)
246 def __contains__(self, value):
247 return value in self.elements
250 return len(self.elements)
253 return repr(self.elements)
256 # vim: ai ts=4 sw=4 sts=4 expandtab