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