1 # -*- coding: utf-8 -*-
2 # Copyright (c) 2013, 2014, 2015 Jack 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)
64 A generic Class to handle tracks metadata such as artist, album, albumartist
65 names and their associated MusicBrainz's ID.
68 Using generic kwargs in constructor for convenience but the actual signature is:
70 >>> Meta(name, mbid=None, **kwargs)
72 :param str name: set name attribute
73 :param str mbid: set MusicBrainz ID
76 """Class attribute to disable use of MusicBrainz IDs"""
78 def __init__(self, **kwargs):
79 """Meta(name=<str>[, mbid=UUID4])"""
80 self.__name = None #TODO: should be immutable
82 self.__aliases = set()
83 self.log = logging.getLogger(__name__)
84 if 'name' not in kwargs or not kwargs.get('name'):
85 raise MetaException('Need a "name" argument')
87 self.__name = kwargs.pop('name')
88 if 'mbid' in kwargs and kwargs.get('mbid'):
89 if is_uuid4(kwargs.get('mbid')):
90 self.__mbid = kwargs.pop('mbid').lower()
92 self.log.warning('Wrong mbid %s:%s', self.__name,
94 # mbid immutable as hash rests on
95 self.__dict__.update(**kwargs)
98 fmt = '{0}(name={1.name!r}, mbid={1.mbid!r})'
99 return fmt.format(self.__class__.__name__, self)
102 return self.__name.__str__()
104 def __eq__(self, other):
106 Perform mbid equality test
108 #if hasattr(other, 'mbid'): # better isinstance?
109 if isinstance(other, Meta) and self.mbid and other.mbid:
110 return self.mbid == other.mbid
111 elif isinstance(other, Meta):
112 return bool(self.names & other.names)
113 elif getattr(other, '__str__', None):
114 # is other.__str__() in self.__name or self.__aliases
115 return other.__str__() in self.names
120 return hash(self.mbid)
121 return hash(self.__name)
123 def add_alias(self, other):
124 """Add alternative name to `aliases` attibute.
126 `other` can be a :class:`sima.lib.meta.Meta` object in which case aliases are merged.
128 :param str other: Alias to add, could be any object with ``__str__`` method.
130 if getattr(other, '__str__', None):
131 if callable(other.__str__) and other.__str__() != self.name:
132 self.__aliases |= {other.__str__()}
133 elif isinstance(other, Meta):
134 if other.name != self.name:
135 self.__aliases |= other.__aliases
137 raise MetaException('No __str__ method found in {!r}'.format(other))
149 return self.__aliases
154 return self.__aliases | {self.__name,}
165 """Artist object deriving from :class:`Meta`.
167 :param str name: Artist name
168 :param str mbid: Musicbrainz artist ID
169 :param str artist: Overrides "name" argument
170 :param str albumartist: Overrides "name" and "artist" argument
171 :param str musicbrainz_artistid: Overrides "mbid" argument
172 :param str musicbrainz_albumartistid: Overrides "musicbrainz_artistid" argument
176 >>> trk = {'artist':'Art Name',
177 >>> 'albumartist': 'Alb Art Name', # optional
178 >>> 'musicbrainz_artistid': '<UUID4>', # optional
179 >>> 'musicbrainz_albumartistid': '<UUID4>', # optional
181 >>> artobj0 = Artist(**trk)
182 >>> artobj1 = Artist(name='Tool')
186 def __init__(self, name=None, mbid=None, **kwargs):
187 if kwargs.get('artist', False):
188 name = kwargs.get('artist').split(SEPARATOR)[0]
189 if kwargs.get('musicbrainz_artistid', False):
190 mbid = kwargs.get('musicbrainz_artistid').split(SEPARATOR)[0]
191 if (kwargs.get('albumartist', False) and
192 kwargs.get('albumartist') != 'Various Artists'):
193 name = kwargs.get('albumartist').split(SEPARATOR)[0]
194 if (kwargs.get('musicbrainz_albumartistid', False) and
195 kwargs.get('musicbrainz_albumartistid') != '89ad4ac3-39f7-470e-963a-56509c546377'):
196 mbid = kwargs.get('musicbrainz_albumartistid').split(SEPARATOR)[0]
197 super().__init__(name=name, mbid=mbid)
199 class MetaContainer(Set):
201 def __init__(self, iterable):
202 self.elements = lst = []
203 for value in iterable:
209 inlst.add_alias(value)
212 return iter(self.elements)
214 def __contains__(self, value):
215 return value in self.elements
218 return len(self.elements)
221 return repr(self.elements)
224 # vim: ai ts=4 sw=4 sts=4 expandtab