Artwork pp

Post processing Min/Max artwork size
This commit is contained in:
Ade
2017-03-20 10:18:09 +13:00
parent 1cee19be74
commit f3020207c0
3 changed files with 99 additions and 127 deletions

View File

@@ -980,8 +980,8 @@
<div id="album_art_size_options" style="padding-left: 20px">
<div class="row">
<!--Album art min width <input type="text" class="override-float" name="album_art_min_width" value="${config['album_art_min_width']}" size="4" title="Only images with a pixel width greater or equal are considered as valid album art candidates. Default: 0.">\-->
<!--Album art max width <input type="text" class="override-float" name="album_art_max_width" value="${config['album_art_max_width']}" size="4" title="A maximum image width to downscale fetched images if they are too big.">-->
Album art min width <input type="text" class="override-float" name="album_art_min_width" value="${config['album_art_min_width']}" size="4" title="Only images with a pixel width greater or equal are considered as valid album art candidates. Default: 0.">\
Album art max width <input type="text" class="override-float" name="album_art_max_width" value="${config['album_art_max_width']}" size="4" title="A maximum image width to downscale fetched images if they are too big.">
</div>
</div>

View File

@@ -13,11 +13,9 @@
# You should have received a copy of the GNU General Public License
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
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("<HH", data[6:10])
width = int(w)
height = int(h)
# See PNG 2. Edition spec (http://www.w3.org/TR/PNG/)
# Bytes 0-7 are below, 4-byte chunk length, then 'IHDR'
# and finally the 4-byte width, height
elif size >= 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()

View File

@@ -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),