1 # -*- coding: utf-8 -*-
2 # Copyright (c) 2013, 2014, 2015 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 # python >= 3.3
27 from collections import Set # python 3.2
31 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}$'
32 # The Track Object is collapsing multiple tags into a single string using this
33 # separator. It is used then to split back the string to tags list.
34 SEPARATOR = chr(0x1F) # ASCII Unit Separator
37 """Controls MusicBrainz UUID4 format
39 :param str uuid: String representing the UUID
42 regexp = re.compile(UUID_RE, re.IGNORECASE)
43 if regexp.match(uuid):
47 class MetaException(Exception):
48 """Generic Meta Exception"""
52 def wrapper(*args, **kwargs):
55 kwargs.pop('mbid', None)
56 kwargs.pop('musicbrainz_artistid', None)
57 kwargs.pop('musicbrainz_albumartistid', None)
62 def wrapper(*args, **kwargs):
63 ans = func(*args, **kwargs)
64 if isinstance(ans, set):
65 return {s.replace("'", r"\'") for s in ans}
66 return ans.replace("'", r"\'")
72 A generic Class to handle tracks metadata such as artist, album, albumartist
73 names and their associated MusicBrainz's ID.
76 Using generic kwargs in constructor for convenience but the actual signature is:
78 >>> Meta(name, mbid=None, **kwargs)
80 :param str name: set name attribute
81 :param str mbid: set MusicBrainz ID
84 """Class attribute to disable use of MusicBrainz IDs"""
86 def __init__(self, **kwargs):
87 """Meta(name=<str>[, mbid=UUID4])"""
88 self.__name = None # TODO: should be immutable
90 self.__aliases = set()
91 self.log = logging.getLogger(__name__)
92 if 'name' not in kwargs or not kwargs.get('name'):
93 raise MetaException('Need a "name" argument (str type)')
94 if not isinstance(kwargs.get('name'), str):
95 raise MetaException('"name" argument not a string')
97 self.__name = kwargs.pop('name')
98 if 'mbid' in kwargs and kwargs.get('mbid'):
99 if is_uuid4(kwargs.get('mbid')):
100 self.__mbid = kwargs.pop('mbid').lower()
102 self.log.warning('Wrong mbid %s:%s', self.__name,
104 # mbid immutable as hash rests on
105 self.__dict__.update(**kwargs)
108 fmt = '{0}(name={1.name!r}, mbid={1.mbid!r})'
109 return fmt.format(self.__class__.__name__, self)
112 return self.__name.__str__()
114 def __eq__(self, other):
116 Perform mbid equality test
118 #if hasattr(other, 'mbid'): # better isinstance?
119 if isinstance(other, Meta) and self.mbid and other.mbid:
120 return self.mbid == other.mbid
121 if isinstance(other, Meta):
122 return bool(self.names & other.names)
123 if getattr(other, '__str__', None):
124 # is other.__str__() in self.__name or self.__aliases
125 return other.__str__() in self.names
130 return hash(self.mbid)
131 return hash(self.__name)
133 def add_alias(self, other):
134 """Add alternative name to `aliases` attibute.
136 `other` can be a :class:`sima.lib.meta.Meta` object in which case aliases are merged.
138 :param str other: Alias to add, could be any object with ``__str__`` method.
140 if getattr(other, '__str__', None):
141 if callable(other.__str__) and other.__str__() != self.name:
142 self.__aliases |= {other.__str__()}
143 elif isinstance(other, Meta):
144 if other.name != self.name:
145 self.__aliases |= other.__aliases
147 raise MetaException('No __str__ method found in {!r}'.format(other))
164 return self.__aliases
168 def aliases_sz(self):
174 return self.__aliases | {self.__name,}
186 def __init__(self, name=None, mbid=None, **kwargs):
187 super().__init__(name=name, mbid=mbid, **kwargs)
195 """Artist object deriving from :class:`Meta`.
197 :param str name: Artist name
198 :param str mbid: Musicbrainz artist ID
199 :param str artist: Overrides "name" argument
200 :param str albumartist: Overrides "name" and "artist" argument
201 :param str musicbrainz_artistid: Overrides "mbid" argument
202 :param str musicbrainz_albumartistid: Overrides "musicbrainz_artistid" argument
206 >>> trk = {'artist':'Art Name',
207 >>> 'albumartist': 'Alb Art Name', # optional
208 >>> 'musicbrainz_artistid': '<UUID4>', # optional
209 >>> 'musicbrainz_albumartistid': '<UUID4>', # optional
211 >>> artobj0 = Artist(**trk)
212 >>> artobj1 = Artist(name='Tool')
216 def __init__(self, name=None, mbid=None, **kwargs):
217 if kwargs.get('artist', False):
218 name = kwargs.get('artist').split(SEPARATOR)[0]
219 if kwargs.get('musicbrainz_artistid', False):
220 mbid = kwargs.get('musicbrainz_artistid').split(SEPARATOR)[0]
221 if (kwargs.get('albumartist', False) and
222 kwargs.get('albumartist') != 'Various Artists'):
223 name = kwargs.get('albumartist').split(SEPARATOR)[0]
224 if (kwargs.get('musicbrainz_albumartistid', False) and
225 kwargs.get('musicbrainz_albumartistid') != '89ad4ac3-39f7-470e-963a-56509c546377'):
226 mbid = kwargs.get('musicbrainz_albumartistid').split(SEPARATOR)[0]
227 super().__init__(name=name, mbid=mbid)
229 class MetaContainer(Set):
231 def __init__(self, iterable):
232 self.elements = lst = []
233 for value in iterable:
239 inlst.add_alias(value)
242 return iter(self.elements)
244 def __contains__(self, value):
245 return value in self.elements
248 return len(self.elements)
251 return repr(self.elements)
254 # vim: ai ts=4 sw=4 sts=4 expandtab