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 (str type)')
86 elif not isinstance(kwargs.get('name'), str):
87 raise MetaException('"name" argument not a string')
89 self.__name = kwargs.pop('name')
90 if 'mbid' in kwargs and kwargs.get('mbid'):
91 if is_uuid4(kwargs.get('mbid')):
92 self.__mbid = kwargs.pop('mbid').lower()
94 self.log.warning('Wrong mbid %s:%s', self.__name,
96 # mbid immutable as hash rests on
97 self.__dict__.update(**kwargs)
100 fmt = '{0}(name={1.name!r}, mbid={1.mbid!r})'
101 return fmt.format(self.__class__.__name__, self)
104 return self.__name.__str__()
106 def __eq__(self, other):
108 Perform mbid equality test
110 #if hasattr(other, 'mbid'): # better isinstance?
111 if isinstance(other, Meta) and self.mbid and other.mbid:
112 return self.mbid == other.mbid
113 elif isinstance(other, Meta):
114 return bool(self.names & other.names)
115 elif getattr(other, '__str__', None):
116 # is other.__str__() in self.__name or self.__aliases
117 return other.__str__() in self.names
122 return hash(self.mbid)
123 return hash(self.__name)
125 def add_alias(self, other):
126 """Add alternative name to `aliases` attibute.
128 `other` can be a :class:`sima.lib.meta.Meta` object in which case aliases are merged.
130 :param str other: Alias to add, could be any object with ``__str__`` method.
132 if getattr(other, '__str__', None):
133 if callable(other.__str__) and other.__str__() != self.name:
134 self.__aliases |= {other.__str__()}
135 elif isinstance(other, Meta):
136 if other.name != self.name:
137 self.__aliases |= other.__aliases
139 raise MetaException('No __str__ method found in {!r}'.format(other))
151 return self.__aliases
156 return self.__aliases | {self.__name,}
167 """Artist object deriving from :class:`Meta`.
169 :param str name: Artist name
170 :param str mbid: Musicbrainz artist ID
171 :param str artist: Overrides "name" argument
172 :param str albumartist: Overrides "name" and "artist" argument
173 :param str musicbrainz_artistid: Overrides "mbid" argument
174 :param str musicbrainz_albumartistid: Overrides "musicbrainz_artistid" argument
178 >>> trk = {'artist':'Art Name',
179 >>> 'albumartist': 'Alb Art Name', # optional
180 >>> 'musicbrainz_artistid': '<UUID4>', # optional
181 >>> 'musicbrainz_albumartistid': '<UUID4>', # optional
183 >>> artobj0 = Artist(**trk)
184 >>> artobj1 = Artist(name='Tool')
188 def __init__(self, name=None, mbid=None, **kwargs):
189 if kwargs.get('artist', False):
190 name = kwargs.get('artist').split(SEPARATOR)[0]
191 if kwargs.get('musicbrainz_artistid', False):
192 mbid = kwargs.get('musicbrainz_artistid').split(SEPARATOR)[0]
193 if (kwargs.get('albumartist', False) and
194 kwargs.get('albumartist') != 'Various Artists'):
195 name = kwargs.get('albumartist').split(SEPARATOR)[0]
196 if (kwargs.get('musicbrainz_albumartistid', False) and
197 kwargs.get('musicbrainz_albumartistid') != '89ad4ac3-39f7-470e-963a-56509c546377'):
198 mbid = kwargs.get('musicbrainz_albumartistid').split(SEPARATOR)[0]
199 super().__init__(name=name, mbid=mbid)
201 class MetaContainer(Set):
203 def __init__(self, iterable):
204 self.elements = lst = []
205 for value in iterable:
211 inlst.add_alias(value)
214 return iter(self.elements)
216 def __contains__(self, value):
217 return value in self.elements
220 return len(self.elements)
223 return repr(self.elements)
226 # vim: ai ts=4 sw=4 sts=4 expandtab