]> kaliko git repositories - mpd-sima.git/blob - sima/lib/meta.py
Improved documentation
[mpd-sima.git] / sima / lib / meta.py
1 # -*- coding: utf-8 -*-
2 # Copyright (c) 2013, 2014 Jack Kaliko <kaliko@azylum.org>
3 #
4 #  This file is part of sima
5 #
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.
10 #
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.
15 #
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/>.
18 #
19 #
20 """
21 Defines some object to handle audio file metadata
22 """
23
24 try:
25     from collections.abc import Set # python >= 3.3
26 except ImportError:
27     from collections import Set # python 3.2
28 import logging
29 import re
30
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
33 def is_uuid4(uuid):
34     regexp = re.compile(UUID_RE, re.IGNORECASE)
35     if regexp.match(uuid):
36         return True
37     raise WrongUUID4(uuid)
38
39 class MetaException(Exception):
40     """Generic Meta Exception"""
41     pass
42
43 class WrongUUID4(MetaException):
44     pass
45
46 def mbidfilter(func):
47     def wrapper(*args, **kwargs):
48         cls = args[0]
49         if not cls.use_mbid:
50             kwargs.pop('mbid', None)
51             kwargs.pop('musicbrainz_artistid', None)
52             kwargs.pop('musicbrainz_albumartistid', None)
53         func(*args, **kwargs)
54     return wrapper
55
56
57 class Meta:
58     """Generic Class for Meta object
59     Meta(name=<str>[, mbid=UUID4])
60     """
61     use_mbid = True
62
63     def __init__(self, **kwargs):
64         self.__name = None #TODO: should be immutable
65         self.__mbid = None
66         self.__aliases = set()
67         self.log = logging.getLogger(__name__)
68         if 'name' not in kwargs or not kwargs.get('name'):
69             raise MetaException('Need a "name" argument')
70         else:
71             self.__name = kwargs.pop('name')
72         if 'mbid' in kwargs and kwargs.get('mbid'):
73             try:
74                 is_uuid4(kwargs.get('mbid'))
75                 self.__mbid = kwargs.pop('mbid').lower()
76             except WrongUUID4:
77                 self.log.warning('Wrong mbid {}:{}'.format(self.__name,
78                                                          kwargs.get('mbid')))
79             # mbid immutable as hash rests on
80         self.__dict__.update(**kwargs)
81
82     def __repr__(self):
83         fmt = '{0}(name={1.name!r}, mbid={1.mbid!r})'
84         return fmt.format(self.__class__.__name__, self)
85
86     def __str__(self):
87         return self.__name.__str__()
88
89     def __eq__(self, other):
90         """
91         Perform mbid equality test
92         """
93         #if hasattr(other, 'mbid'):  # better isinstance?
94         if isinstance(other, Meta) and self.mbid and other.mbid:
95             return self.mbid == other.mbid
96         elif isinstance(other, Meta):
97             return bool(self.names & other.names)
98         elif getattr(other, '__str__', None):
99             # is other.__str__() in self.__name or self.__aliases
100             return other.__str__() in self.names
101         return False
102
103     def __hash__(self):
104         if self.mbid:
105             return hash(self.mbid)
106         return hash(self.__name)
107
108     def add_alias(self, other):
109         if getattr(other, '__str__', None):
110             if callable(other.__str__) and other.__str__() != self.name:
111                 self.__aliases |= {other.__str__()}
112         elif isinstance(other, Meta):
113             if other.name != self.name:
114                 self.__aliases |= other.__aliases
115         else:
116             raise MetaException('No __str__ method found in {!r}'.format(other))
117
118     @property
119     def name(self):
120         return self.__name
121
122     @property
123     def mbid(self):
124         return self.__mbid
125
126     @property
127     def aliases(self):
128         return self.__aliases
129
130     @property
131     def names(self):
132         return self.__aliases | {self.__name,}
133
134
135 class Album(Meta):
136
137     @property
138     def album(self):
139         return self.name
140
141 class Artist(Meta):
142
143     @mbidfilter
144     def __init__(self, name=None, mbid=None, **kwargs):
145         """Artist object built from a mapping dict containing at least an
146         "artist" entry:
147             >>> trk = {'artist':'Art Name',
148             >>>        'albumartist': 'Alb Art Name',           # optional
149             >>>        'musicbrainz_artistid': '<UUID4>'    ,   # optional
150             >>>        'musicbrainz_albumartistid': '<UUID4>',  # optional
151             >>>       }
152             >>> artobj0 = Artist(**trk)
153             >>> artobj1 = Artist(name='Tool')
154         """
155         name = kwargs.get('artist', name).split(', ')[0]
156         mbid = kwargs.get('musicbrainz_artistid', mbid)
157         if (kwargs.get('albumartist', False) and
158                 kwargs.get('albumartist') != 'Various Artists'):
159             name = kwargs.get('albumartist').split(', ')[0]
160         if (kwargs.get('musicbrainz_albumartistid', False) and
161                 kwargs.get('musicbrainz_albumartistid') != '89ad4ac3-39f7-470e-963a-56509c546377'):
162             mbid = kwargs.get('musicbrainz_albumartistid').split(', ')[0]
163         super().__init__(name=name, mbid=mbid)
164
165 class MetaContainer(Set):
166
167     def __init__(self, iterable):
168         self.elements = lst = []
169         for value in iterable:
170             if value not in lst:
171                 lst.append(value)
172             else:
173                 for inlst in lst:
174                     if value == inlst:
175                         inlst.add_alias(value)
176
177     def __iter__(self):
178         return iter(self.elements)
179
180     def __contains__(self, value):
181         return value in self.elements
182
183     def __len__(self):
184         return len(self.elements)
185
186     def __repr__(self):
187         return repr(self.elements)
188
189 # VIM MODLINE
190 # vim: ai ts=4 sw=4 sts=4 expandtab