diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index 6db6bf86..f5f9ea78 100644 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -980,8 +980,8 @@
- - + Album art min width \ + Album art max width
diff --git a/headphones/albumart.py b/headphones/albumart.py index 4c2f716b..c35658da 100644 --- a/headphones/albumart.py +++ b/headphones/albumart.py @@ -13,11 +13,9 @@ # You should have received a copy of the GNU General Public License # along with Headphones. If not, see . -import StringIO import struct -from contextlib import closing from six.moves.urllib.parse import urlencode -import requests as requests +from io import BytesIO import headphones from headphones import db, request, logger @@ -39,10 +37,11 @@ def getAlbumArt(albumid): # Amazon logger.info("Searching for artwork at Amazon") myDB = db.DBConnection() - asin = myDB.action( - 'SELECT AlbumASIN from albums WHERE AlbumID=?', [albumid]).fetchone()[0] - if asin: - artwork_path = 'http://ec1.images-amazon.com/images/P/%s.01.LZZZZZZZ.jpg' % asin + dbalbum = myDB.action( + 'SELECT ArtistName, AlbumTitle, ReleaseID, AlbumASIN FROM albums WHERE AlbumID=?', + [albumid]).fetchone() + if dbalbum['AlbumASIN']: + artwork_path = 'http://ec1.images-amazon.com/images/P/%s.01.LZZZZZZZ.jpg' % dbalbum['AlbumASIN'] artwork = getartwork(artwork_path) if artwork: logger.info("Artwork found at Amazon") @@ -50,12 +49,7 @@ def getAlbumArt(albumid): # last.fm from headphones import lastfm - logger.info("Searching for artwork at last.fm") - myDB = db.DBConnection() - dbalbum = myDB.action( - 'SELECT ArtistName, AlbumTitle, ReleaseID FROM albums WHERE AlbumID=?', - [albumid]).fetchone() if dbalbum['ReleaseID'] != albumid: data = lastfm.request_lastfm("album.getinfo", mbid=dbalbum['ReleaseID']) if not data: @@ -87,6 +81,94 @@ def getAlbumArt(albumid): return None, None +def jpeg(bites): + fhandle = BytesIO(bites) + try: + fhandle.seek(0) + size = 2 + ftype = 0 + while not 0xc0 <= ftype <= 0xcf: + fhandle.seek(size, 1) + byte = fhandle.read(1) + while ord(byte) == 0xff: + byte = fhandle.read(1) + ftype = ord(byte) + size = struct.unpack('>H', fhandle.read(2))[0] - 2 + fhandle.seek(1, 1) + height, width = struct.unpack('>HH', fhandle.read(4)) + return width, height + except struct.error: + return None, None + except TypeError: + return None, None + + +def png(bites): + try: + check = struct.unpack('>i', bites[4:8])[0] + if check != 0x0d0a1a0a: + return None, None + return struct.unpack('>ii', bites[16:24]) + except struct.error: + return None, None + + +def get_image_data(bites): + type = None + width = None + height = None + if len(bites) < 24: + return None, None, None + + peek = bites[0:2] + if peek == b'\xff\xd8': + width, height = jpeg(bites) + type = 'jpg' + elif peek == b'\x89P': + width, height = png(bites) + type = 'png' + return type, width, height + + +def getartwork(artwork_path): + artwork = bytes() + minwidth = int(headphones.CONFIG.ALBUM_ART_MIN_WIDTH) + maxwidth = int(headphones.CONFIG.ALBUM_ART_MAX_WIDTH) + + resp = request.request_response(artwork_path, timeout=20, stream=True, whitelist_status_code=404) + + if resp: + img_width = None + for chunk in resp.iter_content(chunk_size=1024): + artwork += chunk + if not img_width and (minwidth or maxwidth): + img_type, img_width, img_height = get_image_data(artwork) + # Check min/max + if img_width and (minwidth or maxwidth): + if minwidth and img_width < minwidth: + logger.info("Artwork is too small. Type: %s. Width: %s. Height: %s", + img_type, img_width, img_height) + artwork = None + break + elif maxwidth and img_width > maxwidth: + # Downsize using proxy service to max width + logger.info("Artwork is greater than the maximum width, downsizing using proxy service") + artwork_path = '{0}?{1}'.format('http://images.weserv.nl/', urlencode({ + 'url': artwork_path.replace('http://', ''), + 'w': maxwidth, + })) + artwork = bytes() + r = request.request_response(artwork_path, timeout=20, stream=True, whitelist_status_code=404) + if r: + for chunk in r.iter_content(chunk_size=1024): + artwork += chunk + r.close() + break + resp.close() + + return artwork + + def getCachedArt(albumid): from headphones import cache @@ -104,114 +186,4 @@ def getCachedArt(albumid): return else: with open(artwork_path, "r") as fp: - return fp.read() - - -def getartwork(artwork_path): - - artwork = None - minwidth = headphones.CONFIG.ALBUM_ART_MIN_WIDTH - maxwidth = headphones.CONFIG.ALBUM_ART_MAX_WIDTH - useproxy = False - - with closing(requests.get(artwork_path, stream=True)) as resp: - - # Get 1st block of artwork - data = resp.iter_content(chunk_size=1024) - artwork = b'' - for chunk in data: - artwork += chunk - if len(artwork) >= 32: - break - else: - artwork = None - - # Check image and size - if artwork: - - - #TODO not working well - #img_type, img_width, img_height = getImageInfo(artwork) - - #if not img_type or minwidth and img_width < minwidth: - # logger.info("Artwork is not suitable or too small. Type: %s. Width: %s. Height: %s", - # img_type, img_width, img_height) - # artwork = None - #elif maxwidth and img_width > maxwidth: - # useproxy = True - #else: - # Get rest of artwork - for chunk in data: - artwork += chunk - - # Downsize using proxy service to max width - # if useproxy: - # logger.info("Artwork is greater than the maximum width, downsizing using proxy service") - # artwork_path = '{0}?{1}'.format('http://images.weserv.nl/', urlencode({ - # 'url': artwork_path.replace('http://', ''), - # 'w': maxwidth, - # })) - # artwork = request.request_content(artwork_path, timeout=20) - - return artwork - - -def getImageInfo(data): - data = str(data) - size = len(data) - height = -1 - width = -1 - content_type = None - - # handle GIFs - if size >= 10 and data[:6] in ('GIF87a', 'GIF89a'): - # Check to see if content_type is correct - content_type = 'image/gif' - w, h = struct.unpack("= 24 and data.startswith('\211PNG\r\n\032\n') and data[12:16] == 'IHDR': - content_type = 'image/png' - w, h = struct.unpack(">LL", data[16:24]) - width = int(w) - height = int(h) - - # Maybe this is for an older PNG version. - elif size >= 16 and data.startswith('\211PNG\r\n\032\n'): - # Check to see if we have the right content type - content_type = 'image/png' - w, h = struct.unpack(">LL", data[8:16]) - width = int(w) - height = int(h) - - # handle JPEGs - elif size >= 2 and data.startswith('\377\330'): - content_type = 'image/jpeg' - jpeg = StringIO.StringIO(data) - jpeg.read(2) - b = jpeg.read(1) - try: - while b and ord(b) != 0xDA: - while ord(b) != 0xFF: - b = jpeg.read(1) - while ord(b) == 0xFF: - b = jpeg.read(1) - if ord(b) >= 0xC0 and ord(b) <= 0xC3: - jpeg.read(3) - h, w = struct.unpack(">HH", jpeg.read(4)) - break - else: - jpeg.read(int(struct.unpack(">H", jpeg.read(2))[0]) - 2) - b = jpeg.read(1) - width = int(w) - height = int(h) - except struct.error: - pass - except ValueError: - pass - - return content_type, width, height + return fp.read() \ No newline at end of file diff --git a/headphones/config.py b/headphones/config.py index 69f33b58..07b6678f 100644 --- a/headphones/config.py +++ b/headphones/config.py @@ -34,8 +34,8 @@ _CONFIG_DEFINITIONS = { 'ADD_ALBUM_ART': (int, 'General', 0), 'ADVANCEDENCODER': (str, 'General', ''), 'ALBUM_ART_FORMAT': (str, 'General', 'folder'), - 'ALBUM_ART_MIN_WIDTH': (int, 'General', 0), - 'ALBUM_ART_MAX_WIDTH': (int, 'General', 0), + 'ALBUM_ART_MIN_WIDTH': (str, 'General', ''), + 'ALBUM_ART_MAX_WIDTH': (str, 'General', ''), # This is used in importer.py to determine how complete an album needs to # be - to be considered "downloaded". Percentage from 0-100 'ALBUM_COMPLETION_PCT': (int, 'Advanced', 80),