mirror of
https://github.com/rembo10/headphones.git
synced 2026-05-15 16:19:28 +01:00
Artwork pp
Post processing Min/Max artwork size
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user