]> kaliko git repositories - mpd-sima.git/blob - sima/lib/track.py
MPD client: Tries to resolve MusicBrainzArtistID when possible.
[mpd-sima.git] / sima / lib / track.py
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2009-2021 kaliko <kaliko@azylum.org>
4 # Copyright (c) 2009 J. Alexander Treuman (Tag collapse method)
5 # Copyright (c) 2008 Rick van Hattem
6 #
7 #  This file is part of sima
8 #
9 #  sima is free software: you can redistribute it and/or modify
10 #  it under the terms of the GNU General Public License as published by
11 #  the Free Software Foundation, either version 3 of the License, or
12 #  (at your option) any later version.
13 #
14 #  sima is distributed in the hope that it will be useful,
15 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
16 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 #  GNU General Public License for more details.
18 #
19 #  You should have received a copy of the GNU General Public License
20 #  along with sima.  If not, see <http://www.gnu.org/licenses/>.
21 #
22 #
23
24 import time
25
26 from .meta import Artist, Album, SEPARATOR
27
28
29 class Track:
30     """
31     Track object.
32     Instantiate with Player replies.
33
34     :param str file: media file, defaults to ``None``
35     :param int duration: duration in second, defaults to 0
36     :param int pos: position in queue, defaults to -1
37     :param str title|artist|album|albumartist: defaults to ""
38     :param str musicbrainz_{artistid|albumartistid|albumid|trackid}: MusicBrainz IDs, defaults to ``None``
39     """
40
41     def __init__(self, file=None, duration=0, pos=-1, **kwargs):
42         self.title = self.artist = self.album = self.albumartist = ''
43         self.musicbrainz_artistid = self.musicbrainz_albumartistid = None
44         self.musicbrainz_albumid = self.musicbrainz_trackid = None
45         self.pos = int(pos)
46         self._file = file
47         self._empty = False
48         self.duration = float(duration)
49         if not kwargs:
50             self._empty = True
51         self.__dict__.update(**kwargs)
52         self.tags_to_collapse = ['artist', 'album', 'title', 'date',
53                                  'genre', 'albumartist',
54                                  'musicbrainz_artistid',
55                                  'musicbrainz_albumartistid']
56         # Which tags have been collapsed?
57         self.collapsed_tags = list()
58         # Needed for multiple tags which returns a list instead of a string
59         self._collapse_tags()
60
61     def _collapse_tags(self):
62         """
63         Necessary to deal with tags defined multiple times.
64         These entries are set as lists instead of strings.
65         """
66         for tag, value in self.__dict__.items():
67             if tag not in self.tags_to_collapse:
68                 continue
69             if isinstance(value, list):
70                 self.collapsed_tags.append(tag)
71                 self.__dict__.update({tag: SEPARATOR.join(value)})
72
73     def __repr__(self):
74         return '%s(artist="%s", album="%s", title="%s", file="%s")' % (
75             self.__class__.__name__,
76             self.artist,
77             self.album,
78             self.title,
79             self.file,
80         )
81
82     def __str__(self):
83         return '{artist} - {album} - {title} ({length})'.format(
84             length=self.length,
85             **self.__dict__
86         )
87
88     def __int__(self):
89         return int(self.duration)
90
91     def __add__(self, other):
92         return Track(duration=self.duration + other.duration)
93
94     def __sub__(self, other):
95         return Track(duration=self.duration - other.duration)
96
97     def __hash__(self):
98         if self.file:
99             return hash(self.file)
100         return id(self)
101
102     def __eq__(self, other):
103         return hash(self) == hash(other)
104
105     def __ne__(self, other):
106         return hash(self) != hash(other)
107
108     def __bool__(self):
109         if not self._file:
110             return False
111         return not self._empty
112
113     @property
114     def file(self):
115         """file is an immutable attribute that's used for the hash method"""
116         return self._file
117
118     @property
119     def length(self):
120         """Get a fancy duration as ``%H:%M:%S`` (use :attr:`duration` to get duration in second only)"""
121         temps = time.gmtime(self.duration)  #TODO: returns a date not a duration
122         if temps.tm_hour:
123             fmt = '%H:%M:%S'
124         else:
125             fmt = '%M:%S'
126         return time.strftime(fmt, temps)
127
128     @property
129     def genres(self):
130         """Fetches Genres for the track
131         Multivalue genre are dealt with:
132
133         * when genre tag is multivalued
134         * when single tag uses coma or semi-colon separator
135         """
136         if 'genre' not in self.__dict__:
137             return []
138         genres = self.genre.split(SEPARATOR)
139         for sep in [',', ';']:
140             if sep in self.genre:
141                 genres = [g for multi in genres for g in multi.split(sep) if g]
142         return list(map(str.strip, genres))
143
144     @property
145     def Artist(self):
146         """Get the :class:`sima.lib.meta.Artist` associated to this track"""
147         if not self.artist:
148             if not self.musicbrainz_artistid:
149                 return Artist(name='[unknown]',
150                               mbid='125ec42a-7229-4250-afc5-e057484327fe')
151             return Artist(name='[unknown]', **self.__dict__)
152         return Artist(**self.__dict__)
153
154     @property
155     def Album(self):
156         """Get the :class:`sima.lib.meta.Album` associated to this track"""
157         if not self.album:
158             return Album(name='[unknown]', **self.__dict__)
159         return Album(name=self.album, **self.__dict__)
160
161 # VIM MODLINE
162 # vim: ai ts=4 sw=4 sts=4 expandtab