diff --git a/data/interfaces/default/index.html b/data/interfaces/default/index.html index 260c0a1a3..e7becc618 100644 --- a/data/interfaces/default/index.html +++ b/data/interfaces/default/index.html @@ -36,7 +36,7 @@ "aTargets": [0], "mData":"ArtistID", "mRender": function ( data, type, full ) { - return '
'; + return '
'; } }, { diff --git a/headphones/__init__.py b/headphones/__init__.py index 826658e26..9a0c4fc55 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -443,10 +443,9 @@ def dbcheck(): # General speed up c.execute('CREATE INDEX IF NOT EXISTS artist_artistsortname ON artists(ArtistSortName COLLATE NOCASE ASC)') - exists = c.execute('SELECT * FROM pragma_index_info("have_matched_artist_album")').fetchone() - if not exists: - c.execute('CREATE INDEX have_matched_artist_album ON have(Matched ASC, ArtistName COLLATE NOCASE ASC, AlbumTitle COLLATE NOCASE ASC)') - c.execute('DROP INDEX IF EXISTS have_matched') + c.execute( + """CREATE INDEX IF NOT EXISTS have_matched_artist_album ON have(Matched ASC, ArtistName COLLATE NOCASE ASC, AlbumTitle COLLATE NOCASE ASC)""") + c.execute('DROP INDEX IF EXISTS have_matched') try: c.execute('SELECT IncludeExtras from artists') diff --git a/headphones/albumart.py b/headphones/albumart.py index 77f19f535..e35d4edc4 100644 --- a/headphones/albumart.py +++ b/headphones/albumart.py @@ -139,6 +139,9 @@ def getartwork(artwork_path): if headphones.CONFIG.ALBUM_ART_MAX_WIDTH: maxwidth = int(headphones.CONFIG.ALBUM_ART_MAX_WIDTH) + if artwork_path is None: + return + resp = request.request_response(artwork_path, timeout=20, stream=True, whitelist_status_code=404) if resp: diff --git a/headphones/cache.py b/headphones/cache.py index 0e9466777..9cbd51f03 100644 --- a/headphones/cache.py +++ b/headphones/cache.py @@ -17,9 +17,13 @@ import headphones from headphones import db, helpers, logger, lastfm, request, mb +from fanart.music import Artist +from fanart.errors import ResponseFanartError LASTFM_API_KEY = "690e1ed3bc00bc91804cd8f7fe5ed6d4" +os.environ.setdefault('FANART_APIKEY', '1f081b32bcd780219f4e6d519f78e37e') + class Cache(object): """ @@ -106,22 +110,6 @@ def _is_current(self, filename=None, date=None): else: return False - def _get_thumb_url(self, data): - - thumb_url = None - - try: - images = data[self.id_type]['image'] - except KeyError: - return None - - for image in images: - if image['size'] == 'medium' and '#text' in image: - thumb_url = image['#text'] - break - - return thumb_url - def get_artwork_from_cache(self, ArtistID=None, AlbumID=None): """ Pass a musicbrainz id to this function (either ArtistID or AlbumID) @@ -211,39 +199,57 @@ def get_image_links(self, ArtistID=None, AlbumID=None): if ArtistID: self.id_type = 'artist' - data = lastfm.request_lastfm("artist.getinfo", mbid=ArtistID, api_key=LASTFM_API_KEY) - if not data: + try: + data = Artist.get(id=ArtistID) + except ResponseFanartError as e: + logger.debug('Fanart.tv lookup error for %s: %s', ArtistID, e) return - try: - image_url = data['artist']['image'][-1]['#text'] - except (KeyError, IndexError): - logger.debug('No artist image found') - image_url = None + logger.debug('Fanart.tv ArtistID: %s', ArtistID) - thumb_url = self._get_thumb_url(data) - if not thumb_url: - logger.debug('No artist thumbnail image found') + artist_url = None + thumb_url = None + image_url = None + + if data.thumbs: + for thumbs in data.thumbs[0:1]: + artist_url = str(thumbs.url) + + if artist_url: + thumb_url = artist_url.replace('fanart/', 'preview/') + image_url = thumb_url + logger.debug('Fanart.tv artist url: %s', thumb_url) + else: + logger.debug('Fanart.tv no artist image found for %s', ArtistID) else: self.id_type = 'album' - data = lastfm.request_lastfm("album.getinfo", mbid=AlbumID, api_key=LASTFM_API_KEY) - if not data: + try: + data = Artist.get(id="ArtistID") + except ResponseFanartError as e: + logger.debug('Fanart.tv lookup error for %s: %s', ArtistID, e) return - try: - image_url = data['album']['image'][-1]['#text'] - except (KeyError, IndexError): - logger.debug('No album image found on last.fm') - image_url = None + logger.debug('Fanart.tv AlbumID: %s', AlbumID) + + album_url = None + thumb_url = None + image_url = None - thumb_url = self._get_thumb_url(data) + if data.albums: + for x in data.albums: + if x.mbid == AlbumID: + album_url = str(x.covers[0]) - if not thumb_url: - logger.debug('No album thumbnail image found on last.fm') + if album_url: + thumb_url = album_url.replace('fanart/', 'preview/') + image_url = thumb_url + logger.debug('Fanart.tv album url: %s', thumb_url) + else: + logger.debug('Fanart.tv no album image found for %s', AlbumID) return {'artwork': image_url, 'thumbnail': thumb_url} @@ -284,22 +290,46 @@ def _update_cache(self): myDB = db.DBConnection() - # Since lastfm uses release ids rather than release group ids for albums, we have to do a artist + album search for albums - # Exception is when adding albums manually, then we should use release id if self.id_type == 'artist': + try: + data = Artist.get(id=self.id) + except Exception as e: + dbartist = myDB.action('SELECT ArtistName, Type FROM artists WHERE ArtistID=?', [self.id]).fetchone()[0] + if dbartist: + logger.debug('Fanart.tv artist lookup error for %s: %s', dbartist, e) + logger.debug('Stored id for %s is: %s', dbartist, self.id) + else: + logger.debug('Fanart.tv artist lookup error for %s: %s', self.id, e) + return + + artist_url = None + thumb_url = None + image_url = None + + if data.thumbs: + for thumbs in data.thumbs[0:1]: + artist_url = str(thumbs.url) + + if artist_url: + thumb_url = artist_url.replace('fanart/', 'preview/') + image_url = thumb_url + logger.debug('Fanart.tv artist image url: %s', thumb_url) + else: + logger.debug('Fanart.tv no artist image found for: %s', self.id) + data = lastfm.request_lastfm("artist.getinfo", mbid=self.id, api_key=LASTFM_API_KEY) # Try with name if not found if not data: - dbartist = myDB.action('SELECT ArtistName, Type FROM artists WHERE ArtistID=?', [self.id]).fetchone() + dbartist = myDB.action('SELECT ArtistName, Type FROM artists WHERE ArtistID=?', [self.id]).fetchone()[0] if dbartist: data = lastfm.request_lastfm("artist.getinfo", artist=helpers.clean_musicbrainz_name(dbartist['ArtistName']), api_key=LASTFM_API_KEY) if not data: - return + logger.debug('Last.fm connection cannot be made') try: self.info_summary = data['artist']['bio']['summary'] @@ -311,80 +341,39 @@ def _update_cache(self): except KeyError: logger.debug('No artist bio found') self.info_content = None - try: - image_url = data['artist']['image'][-1]['#text'] - except KeyError: - logger.debug('No artist image found') - image_url = None - - thumb_url = self._get_thumb_url(data) - if not thumb_url: - logger.debug('No artist thumbnail image found') else: - dbalbum = myDB.action( - 'SELECT ArtistName, AlbumTitle, ReleaseID, Type FROM albums WHERE AlbumID=?', - [self.id]).fetchone() - if dbalbum['ReleaseID'] != self.id: - data = lastfm.request_lastfm("album.getinfo", mbid=dbalbum['ReleaseID'], - api_key=LASTFM_API_KEY) - if not data: - data = lastfm.request_lastfm("album.getinfo", - artist=helpers.clean_musicbrainz_name(dbalbum['ArtistName']), - album=helpers.clean_musicbrainz_name(dbalbum['AlbumTitle']), - api_key=LASTFM_API_KEY) - else: - if dbalbum['Type'] != "part of": - data = lastfm.request_lastfm("album.getinfo", - artist=helpers.clean_musicbrainz_name(dbalbum['ArtistName']), - album=helpers.clean_musicbrainz_name(dbalbum['AlbumTitle']), - api_key=LASTFM_API_KEY) - else: - # Series, use actual artist for the release-group - artist = mb.getArtistForReleaseGroup(self.id) - if artist: - data = lastfm.request_lastfm("album.getinfo", - artist=helpers.clean_musicbrainz_name(artist), - album=helpers.clean_musicbrainz_name(dbalbum['AlbumTitle']), - api_key=LASTFM_API_KEY) - - if not data: - return + # get ArtistID from AlbumID lookup - ArtistID not passed into this function otherwise + myDB = db.DBConnection() + ArtistID = myDB.action('SELECT ArtistID FROM albums WHERE ReleaseID=?', [self.id]).fetchone()[0] try: - self.info_summary = data['album']['wiki']['summary'] - except KeyError: - logger.debug('No album summary found') - self.info_summary = None - try: - self.info_content = data['album']['wiki']['content'] - except KeyError: - logger.debug('No album infomation found') - self.info_content = None - try: - image_url = data['album']['image'][-1]['#text'] - except KeyError: - logger.debug('No album image link found') - image_url = None - - thumb_url = self._get_thumb_url(data) - - if not thumb_url: - logger.debug('No album thumbnail image found') + data = Artist.get(id=ArtistID) + except Exception as e: + dbartist = myDB.action('SELECT ArtistName, Type FROM artists WHERE ArtistID=?', [ArtistID]).fetchone()[0] + if dbartist: + logger.debug('Fanart.tv artist lookup error for %s: %s', dbartist, e) + logger.debug('Stored id for %s is: %s', dbartist, ArtistID) + else: + logger.debug('Fanart.tv artist lookup error for %s: %s', ArtistID, e) + return - # Save the content & summary to the database no matter what if we've - # opened up the url - if self.id_type == 'artist': - controlValueDict = {"ArtistID": self.id} - else: - controlValueDict = {"ReleaseGroupID": self.id} + album_url = None + thumb_url = None + image_url = None - newValueDict = {"Summary": self.info_summary, - "Content": self.info_content, - "LastUpdated": helpers.today()} + if data.albums: + for x in data.albums: + if x.mbid == self.id: + album_url = str(x.covers[0]) - myDB.upsert("descriptions", newValueDict, controlValueDict) + if album_url: + thumb_url = album_url.replace('fanart/', 'preview/') + image_url = thumb_url + logger.debug('Fanart.tv album url: %s', thumb_url) + else: + logger.debug('Fanart.tv no album image found for: %s', self.id) # Save the image URL to the database if image_url: @@ -403,9 +392,16 @@ def _update_cache(self): # Should we grab the artwork here if we're just grabbing thumbs or # info? Probably not since the files can be quite big - if image_url and self.query_type == 'artwork': + + # With fanart.tv only one url is used for both thumb_url and image_url - so only making one request + # If seperate ones are desired in the future, the artwork vars below will need to be uncommented + + if image_url is not None: artwork = request.request_content(image_url, timeout=20) + if image_url and self.query_type == 'artwork': + # artwork = request.request_content(image_url, timeout=20) + if artwork: # Make sure the artwork dir exists: if not os.path.isdir(self.path_to_art_cache): @@ -443,7 +439,7 @@ def _update_cache(self): # as it's missing/outdated. if thumb_url and self.query_type in ['thumb', 'artwork'] and not ( self.thumb_files and self._is_current(self.thumb_files[0])): - artwork = request.request_content(thumb_url, timeout=20) + # artwork = request.request_content(thumb_url, timeout=20) if artwork: # Make sure the artwork dir exists: @@ -478,6 +474,58 @@ def _update_cache(self): self.thumb_errors = True self.thumb_url = image_url + dbalbum = myDB.action('SELECT ArtistName, AlbumTitle, ReleaseID, Type FROM albums WHERE AlbumID=?', [self.id]).fetchone() + if dbalbum: + if dbalbum['ReleaseID'] != self.id: + data = lastfm.request_lastfm("album.getinfo", mbid=dbalbum['ReleaseID'], + api_key=LASTFM_API_KEY) + if not data: + data = lastfm.request_lastfm("album.getinfo", + artist=helpers.clean_musicbrainz_name(dbalbum['ArtistName']), + album=helpers.clean_musicbrainz_name(dbalbum['AlbumTitle']), + api_key=LASTFM_API_KEY) + else: + if dbalbum['Type'] != "part of": + data = lastfm.request_lastfm("album.getinfo", + artist=helpers.clean_musicbrainz_name(dbalbum['ArtistName']), + album=helpers.clean_musicbrainz_name(dbalbum['AlbumTitle']), + api_key=LASTFM_API_KEY) + else: + # Series, use actual artist for the release-group + artist = mb.getArtistForReleaseGroup(self.id) + if artist: + data = lastfm.request_lastfm("album.getinfo", + artist=helpers.clean_musicbrainz_name(artist), + album=helpers.clean_musicbrainz_name(dbalbum['AlbumTitle']), + api_key=LASTFM_API_KEY) + + if not data: + logger.debug('Last.fm connection cannot be made') + + try: + self.info_summary = data['album']['wiki']['summary'] + except KeyError: + logger.debug('No album summary found') + self.info_summary = None + try: + self.info_content = data['album']['wiki']['content'] + except KeyError: + logger.debug('No album infomation found') + self.info_content = None + + # Save the content & summary to the database no matter what if we've + # opened up the url + if self.id_type == 'artist': + controlValueDict = {"ArtistID": self.id} + else: + controlValueDict = {"ReleaseGroupID": self.id} + + newValueDict = {"Summary": self.info_summary, + "Content": self.info_content, + "LastUpdated": helpers.today()} + + myDB.upsert("descriptions", newValueDict, controlValueDict) + def getArtwork(ArtistID=None, AlbumID=None): c = Cache() @@ -486,7 +534,7 @@ def getArtwork(ArtistID=None, AlbumID=None): if not artwork_path: return None - if artwork_path.startswith('http://'): + if artwork_path.startswith('http://') or artwork_path.startswith('https://'): return artwork_path else: artwork_file = os.path.basename(artwork_path) @@ -500,7 +548,7 @@ def getThumb(ArtistID=None, AlbumID=None): if not artwork_path: return None - if artwork_path.startswith('http://'): + if artwork_path.startswith('http://') or artwork_path.startswith('https://'): return artwork_path else: thumbnail_file = os.path.basename(artwork_path) diff --git a/headphones/importer.py b/headphones/importer.py index 5595d37e2..57c087b85 100644 --- a/headphones/importer.py +++ b/headphones/importer.py @@ -540,7 +540,7 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False, type="artist"): try: cache.getThumb(ArtistID=artistid) except Exception as e: - logger.error("Error getting album art: %s", e) + logger.error("Error getting artist art: %s", e) logger.info(u"Fetching Metacritic reviews for: %s" % artist['artist_name']) try: diff --git a/headphones/lastfm.py b/headphones/lastfm.py index db3e1aec5..524f480a9 100644 --- a/headphones/lastfm.py +++ b/headphones/lastfm.py @@ -52,11 +52,13 @@ def request_lastfm(method, **kwargs): # Parse response and check for errors. if not data: logger.error("Error calling Last.FM method: %s", method) - return + # when there is a last.fm api fail, this return prevents artist artwork from loading + # return if "error" in data: logger.debug("Last.FM returned an error: %s", data["message"]) - return + # when there is a last.fm api fail, this return prevents artist artwork from loading + # return return data diff --git a/lib/fanart/__init__.py b/lib/fanart/__init__.py new file mode 100644 index 000000000..01d686b53 --- /dev/null +++ b/lib/fanart/__init__.py @@ -0,0 +1,118 @@ +__author__ = 'Andrea De Marco <24erre@gmail.com>' +__maintainer__ = 'Pol Canelles ' +__version__ = '2.0.0' +__classifiers__ = [ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: OS Independent', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Software Development :: Libraries', +] +__copyright__ = "2012, %s " % __author__ +__license__ = """ + Copyright %s. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied. + See the License for the specific language governing permissions and + limitations under the License. +""" % __copyright__ + +__docformat__ = 'restructuredtext en' + +__doc__ = """ +:abstract: Python interface to fanart.tv API (v3) +:version: %s +:author: %s +:contact: http://z4r.github.com/ +:date: 2012-04-04 +:copyright: %s +""" % (__version__, __author__, __license__) + + +def values(obj): + return [v for k, v in obj.__dict__.items() if not k.startswith('_')] + + +BASEURL = 'http://webservice.fanart.tv/v3' + + +class FORMAT(object): + JSON = 'JSON' + XML = 'XML' + PHP = 'PHP' + + +class WS(object): + MUSIC = 'music' + MOVIE = 'movies' + TV = 'tv' + + +class TYPE(object): + ALL = 'all' + + class TV(object): + ART = 'clearart' + LOGO = 'clearlogo' + CHARACTER = 'characterart' + THUMB = 'tvthumb' + SEASONTHUMB = 'seasonthumb' + SEASONBANNER = 'seasonbanner' + SEASONPOSTER = 'seasonposter' + BACKGROUND = 'showbackground' + HDLOGO = 'hdtvlogo' + HDART = 'hdclearart' + POSTER = 'tvposter' + BANNER = 'tvbanner' + + class MUSIC(object): + DISC = 'cdart' + LOGO = 'musiclogo' + BACKGROUND = 'artistbackground' + COVER = 'albumcover' + THUMB = 'artistthumb' + + class MOVIE(object): + ART = 'movieart' + LOGO = 'movielogo' + DISC = 'moviedisc' + POSTER = 'movieposter' + BACKGROUND = 'moviebackground' + HDLOGO = 'hdmovielogo' + HDART = 'hdmovieclearart' + BANNER = 'moviebanner' + THUMB = 'moviethumb' + + +class SORT(object): + POPULAR = 1 + NEWEST = 2 + OLDEST = 3 + + +class LIMIT(object): + ONE = 1 + ALL = 2 + + +FORMAT_LIST = values(FORMAT) +WS_LIST = values(WS) +TYPE_LIST = values(TYPE.MUSIC) + values(TYPE.TV) + values(TYPE.MOVIE) + [TYPE.ALL] +MUSIC_TYPE_LIST = values(TYPE.MUSIC) + [TYPE.ALL] +TV_TYPE_LIST = values(TYPE.TV) + [TYPE.ALL] +MOVIE_TYPE_LIST = values(TYPE.MOVIE) + [TYPE.ALL] +SORT_LIST = values(SORT) +LIMIT_LIST = values(LIMIT) diff --git a/lib/fanart/core.py b/lib/fanart/core.py new file mode 100644 index 000000000..2adda6043 --- /dev/null +++ b/lib/fanart/core.py @@ -0,0 +1,50 @@ +import requests +import fanart +from fanart.errors import RequestFanartError, ResponseFanartError + + +class Request(object): + def __init__(self, apikey, id, ws, type=None, sort=None, limit=None): + ''' + .. warning:: Since the migration to fanart.tv's api v3, we cannot use + the kwargs `type/sort/limit` as we did before, so for now this + kwargs will be ignored. + ''' + self._apikey = apikey + self._id = id + self._ws = ws + self._type = type or fanart.TYPE.ALL + self._sort = sort or fanart.SORT.POPULAR + self._limit = limit or fanart.LIMIT.ALL + self.validate() + self._response = None + + def validate(self): + for attribute_name in ('ws', 'type', 'sort', 'limit'): + attribute = getattr(self, '_' + attribute_name) + choices = getattr(fanart, attribute_name.upper() + '_LIST') + if attribute not in choices: + raise RequestFanartError( + 'Not allowed {}: {} [{}]'.format( + attribute_name, attribute, ', '.join(choices))) + + def __str__(self): + return '{base_url}/{ws}/{id}?api_key={apikey}'.format( + base_url=fanart.BASEURL, + ws=self._ws, + id=self._id, + apikey=self._apikey, + ) + + def response(self): + try: + response = requests.get(str(self)) + rjson = response.json() + if not isinstance(rjson, dict): + raise Exception(response.text) + if 'error message' in rjson: + #raise Exception(rjson['status'], rjson['error message']) + raise Exception(rjson['error message']) + return rjson + except Exception as e: + raise ResponseFanartError(str(e)) diff --git a/lib/fanart/errors.py b/lib/fanart/errors.py new file mode 100644 index 000000000..95a71e35e --- /dev/null +++ b/lib/fanart/errors.py @@ -0,0 +1,15 @@ +class FanartError(Exception): + def __str__(self): + return ', '.join(map(str, self.args)) + + def __repr__(self): + name = self.__class__.__name__ + return '%s%r' % (name, self.args) + + +class ResponseFanartError(FanartError): + pass + + +class RequestFanartError(FanartError): + pass diff --git a/lib/fanart/immutable.py b/lib/fanart/immutable.py new file mode 100644 index 000000000..d51ce1826 --- /dev/null +++ b/lib/fanart/immutable.py @@ -0,0 +1,46 @@ +class Immutable(object): + _mutable = False + + def __setattr__(self, name, value): + if self._mutable or name == '_mutable': + super(Immutable, self).__setattr__(name, value) + else: + raise TypeError("Can't modify immutable instance") + + def __delattr__(self, name): + if self._mutable: + super(Immutable, self).__delattr__(name) + else: + raise TypeError("Can't modify immutable instance") + + def __eq__(self, other): + return hash(self) == hash(other) + + def __hash__(self): + return hash(repr(self)) + + def __repr__(self): + return '%s(%s)' % ( + self.__class__.__name__, + ', '.join(['{0}={1}'.format(k, repr(v)) for k, v in self]) + ) + + def __iter__(self): + l = list(self.__dict__.keys()) + l.sort() + for k in l: + if not k.startswith('_'): + yield k, getattr(self, k) + + @staticmethod + def mutablemethod(f): + def func(self, *args, **kwargs): + if isinstance(self, Immutable): + old_mutable = self._mutable + self._mutable = True + res = f(self, *args, **kwargs) + self._mutable = old_mutable + else: + res = f(self, *args, **kwargs) + return res + return func diff --git a/lib/fanart/items.py b/lib/fanart/items.py new file mode 100644 index 000000000..77bd85306 --- /dev/null +++ b/lib/fanart/items.py @@ -0,0 +1,70 @@ +import json +import os +import requests +from fanart.core import Request +from fanart.immutable import Immutable + + +class LeafItem(Immutable): + KEY = NotImplemented + + @Immutable.mutablemethod + def __init__(self, id, url, likes): + self.id = int(id) + self.url = url + self.likes = int(likes) + self._content = None + + @classmethod + def from_dict(cls, resource): + return cls(**dict([(str(k), v) for k, v in resource.items()])) + + @classmethod + def extract(cls, resource): + return [cls.from_dict(i) for i in resource.get(cls.KEY, {})] + + @Immutable.mutablemethod + def content(self): + if not self._content: + self._content = requests.get(self.url).content + return self._content + + def __str__(self): + return self.url + + +class ResourceItem(Immutable): + WS = NotImplemented + request_cls = Request + + @classmethod + def from_dict(cls, map): + raise NotImplementedError + + @classmethod + def get(cls, id): + map = cls.request_cls( + apikey=os.environ.get('FANART_APIKEY'), + id=id, + ws=cls.WS + ).response() + return cls.from_dict(map) + + def json(self, **kw): + return json.dumps( + self, + default=lambda o: dict( + [(k, v) for k, v in o.__dict__.items() + if not k.startswith('_')]), + **kw + ) + + +class CollectableItem(Immutable): + @classmethod + def from_dict(cls, key, map): + raise NotImplementedError + + @classmethod + def collection_from_dict(cls, map): + return [cls.from_dict(k, v) for k, v in map.items()] diff --git a/lib/fanart/music.py b/lib/fanart/music.py new file mode 100644 index 000000000..61e23d231 --- /dev/null +++ b/lib/fanart/music.py @@ -0,0 +1,80 @@ +from fanart.items import Immutable, LeafItem, ResourceItem, CollectableItem +import fanart +__all__ = ( + 'BackgroundItem', + 'CoverItem', + 'LogoItem', + 'ThumbItem', + 'DiscItem', + 'Artist', + 'Album', +) + + +class BackgroundItem(LeafItem): + KEY = fanart.TYPE.MUSIC.BACKGROUND + + +class CoverItem(LeafItem): + KEY = fanart.TYPE.MUSIC.COVER + + +class LogoItem(LeafItem): + KEY = fanart.TYPE.MUSIC.LOGO + + +class ThumbItem(LeafItem): + KEY = fanart.TYPE.MUSIC.THUMB + + +class DiscItem(LeafItem): + KEY = fanart.TYPE.MUSIC.DISC + + @Immutable.mutablemethod + def __init__(self, id, url, likes, disc, size): + super(DiscItem, self).__init__(id, url, likes) + self.disc = int(disc) + self.size = int(size) + + +class Artist(ResourceItem): + WS = fanart.WS.MUSIC + + @Immutable.mutablemethod + def __init__(self, name, mbid, albums, backgrounds, logos, thumbs): + self.name = name + self.mbid = mbid + self.albums = albums + self.backgrounds = backgrounds + self.logos = logos + self.thumbs = thumbs + + @classmethod + def from_dict(cls, resource): + minimal_keys = {'name', 'mbid_id'} + assert all(k in resource for k in minimal_keys), 'Bad Format Map' + return cls( + name=resource['name'], + mbid=resource['mbid_id'], + albums=Album.collection_from_dict(resource.get('albums', {})), + backgrounds=BackgroundItem.extract(resource), + thumbs=ThumbItem.extract(resource), + logos=LogoItem.extract(resource), + ) + + +class Album(CollectableItem): + + @Immutable.mutablemethod + def __init__(self, mbid, covers, arts): + self.mbid = mbid + self.covers = covers + self.arts = arts + + @classmethod + def from_dict(cls, key, resource): + return cls( + mbid=key, + covers=CoverItem.extract(resource), + arts=DiscItem.extract(resource), + )