mirror of
https://github.com/fergalmoran/picard.git
synced 2026-02-28 10:33:59 +00:00
Merge pull request #313 from zas/coverart_cleanup
Coverart cleanup, mostly code rewrite + minor fixes
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
# Copyright (C) 2007, 2010, 2011 Lukáš Lalinský
|
||||
# Copyright (C) 2011 Michael Wiencek
|
||||
# Copyright (C) 2011-2012 Wieland Hoffmann
|
||||
# Copyright (C) 2013-2014 Laurent Monin
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -26,7 +27,6 @@ import re
|
||||
import traceback
|
||||
from functools import partial
|
||||
from picard import config, log
|
||||
from picard.metadata import Image, is_front_image
|
||||
from picard.util import mimetype, parse_amazon_url
|
||||
from picard.const import CAA_HOST, CAA_PORT
|
||||
from PyQt4.QtCore import QUrl, QObject
|
||||
@@ -67,84 +67,22 @@ AMAZON_SERVER = {
|
||||
},
|
||||
}
|
||||
|
||||
AMAZON_IMAGE_PATH = '/images/P/%s.%s.%sZZZZZZZ.jpg'
|
||||
AMAZON_IMAGE_PATH = '/images/P/%(asin)s.%(serverid)s.%(size)s.jpg'
|
||||
|
||||
|
||||
def _coverart_http_error(album, http):
|
||||
album.error_append(u'Coverart error: %s' % (unicode(http.errorString())))
|
||||
|
||||
|
||||
def _coverart_downloaded(album, metadata, release, try_list, coverinfos, data, http, error):
|
||||
album._requests -= 1
|
||||
|
||||
if error or len(data) < 1000:
|
||||
if error:
|
||||
_coverart_http_error(album, http)
|
||||
else:
|
||||
QObject.tagger.window.set_statusbar_message(
|
||||
N_("Cover art of type '%(type)s' downloaded for %(albumid)s from %(host)s"),
|
||||
{
|
||||
'type': coverinfos['type'].title(),
|
||||
'albumid': album.id,
|
||||
'host': coverinfos['host']
|
||||
}
|
||||
)
|
||||
mime = mimetype.get_from_data(data, default="image/jpeg")
|
||||
|
||||
try:
|
||||
metadata.make_and_add_image(mime, data,
|
||||
imagetype=coverinfos['type'],
|
||||
comment=coverinfos['desc'])
|
||||
for track in album._new_tracks:
|
||||
track.metadata.make_and_add_image(mime, data,
|
||||
imagetype=coverinfos['type'],
|
||||
comment=coverinfos['desc'])
|
||||
except (IOError, OSError) as e:
|
||||
album.error_append(e.message)
|
||||
album._finalize_loading(error=True)
|
||||
# It doesn't make sense to store/download more images if we can't
|
||||
# save them in the temporary folder, abort.
|
||||
return
|
||||
|
||||
# If the image already was a front image, there might still be some
|
||||
# other front images in the try_list - remove them.
|
||||
if is_front_image(coverinfos):
|
||||
for item in try_list[:]:
|
||||
if is_front_image(item) and 'archive.org' not in item['host']:
|
||||
# Hosts other than archive.org only provide front images
|
||||
try_list.remove(item)
|
||||
_walk_try_list(album, metadata, release, try_list)
|
||||
|
||||
|
||||
def _caa_json_downloaded(album, metadata, release, try_list, data, http, error):
|
||||
album._requests -= 1
|
||||
caa_front_found = False
|
||||
if error:
|
||||
_coverart_http_error(album, http)
|
||||
else:
|
||||
try:
|
||||
caa_data = json.loads(data)
|
||||
except ValueError:
|
||||
log.debug("Invalid JSON: %s", http.url().toString())
|
||||
else:
|
||||
caa_types = config.setting["caa_image_types"]
|
||||
caa_types = map(unicode.lower, caa_types)
|
||||
for image in caa_data["images"]:
|
||||
if config.setting["caa_approved_only"] and not image["approved"]:
|
||||
continue
|
||||
if not image["types"] and "unknown" in caa_types:
|
||||
image["types"] = [u"Unknown"]
|
||||
imagetypes = map(unicode.lower, image["types"])
|
||||
for imagetype in imagetypes:
|
||||
if imagetype == "front":
|
||||
caa_front_found = True
|
||||
if imagetype in caa_types:
|
||||
_caa_append_image_to_trylist(try_list, image)
|
||||
break
|
||||
|
||||
if error or not caa_front_found:
|
||||
_fill_try_list(album, release, try_list)
|
||||
_walk_try_list(album, metadata, release, try_list)
|
||||
# First item in the list will be tried first
|
||||
AMAZON_SIZES = (
|
||||
# huge size option is only available for items
|
||||
# that have a ZOOMing picture on its amazon web page
|
||||
# and it doesn't work for all of the domain names
|
||||
#'_SCRM_', # huge size
|
||||
'LZZZZZZZ', # large size, option format 1
|
||||
#'_SCLZZZZZZZ_', # large size, option format 3
|
||||
'MZZZZZZZ', # default image size, format 1
|
||||
#'_SCMZZZZZZZ_', # medium size, option format 3
|
||||
#'TZZZZZZZ', # medium image size, option format 1
|
||||
#'_SCTZZZZZZZ_', # small size, option format 3
|
||||
#'THUMBZZZ', # small size, option format 1
|
||||
)
|
||||
|
||||
_CAA_THUMBNAIL_SIZE_MAP = {
|
||||
0: "small",
|
||||
@@ -152,146 +90,376 @@ _CAA_THUMBNAIL_SIZE_MAP = {
|
||||
}
|
||||
|
||||
|
||||
def _caa_append_image_to_trylist(try_list, imagedata):
|
||||
"""Adds URLs to `try_list` depending on the users CAA image size settings."""
|
||||
imagesize = config.setting["caa_image_size"]
|
||||
thumbsize = _CAA_THUMBNAIL_SIZE_MAP.get(imagesize, None)
|
||||
if thumbsize is None:
|
||||
url = QUrl(imagedata["image"])
|
||||
else:
|
||||
url = QUrl(imagedata["thumbnails"][thumbsize])
|
||||
extras = {
|
||||
'type': imagedata["types"][0].lower(), # FIXME: we pass only 1 type
|
||||
'desc': imagedata["comment"],
|
||||
'front': imagedata['front'], # front image indicator from CAA
|
||||
}
|
||||
_try_list_append_image_url(try_list, url, extras)
|
||||
class CoverArtImage:
|
||||
|
||||
support_types = False
|
||||
# consider all images as front if types aren't supported by provider
|
||||
is_front = True
|
||||
|
||||
def __init__(self, url=None, types=[u'front'], comment=''):
|
||||
if url is not None:
|
||||
self.parse_url(url)
|
||||
else:
|
||||
self.url = None
|
||||
self.types = types
|
||||
self.comment = comment
|
||||
|
||||
def parse_url(self, url):
|
||||
self.url = QUrl(url)
|
||||
self.host = str(self.url.host())
|
||||
self.port = self.url.port(80)
|
||||
self.path = str(self.url.encodedPath())
|
||||
if self.url.hasQuery():
|
||||
self.path += '?' + str(self.url.encodedQuery())
|
||||
|
||||
def is_front_image(self):
|
||||
# CAA has a flag for "front" image, use it in priority
|
||||
if self.is_front:
|
||||
return True
|
||||
# no caa front flag, use type instead
|
||||
return u'front' in self.types
|
||||
|
||||
def __repr__(self):
|
||||
p = []
|
||||
if self.url is not None:
|
||||
p.append("url=%r" % self.url.toString())
|
||||
p.append("types=%r" % self.types)
|
||||
if self.comment:
|
||||
p.append("comment=%r" % self.comment)
|
||||
return "%s(%s)" % (self.__class__.__name__, ", ".join(p))
|
||||
|
||||
def __unicode__(self):
|
||||
p = [u'Image']
|
||||
if self.url is not None:
|
||||
p.append(u"from %s" % self.url.toString())
|
||||
p.append(u"of type %s" % u','.join(self.types))
|
||||
if self.comment:
|
||||
p.append(u"and comment '%s'" % self.comment)
|
||||
return u' '.join(p)
|
||||
|
||||
def __str__(self):
|
||||
return unicode(self).encode('utf-8')
|
||||
|
||||
|
||||
def coverart(album, metadata, release, try_list=None):
|
||||
""" Gets all cover art URLs from the metadata and then attempts to
|
||||
download the album art. """
|
||||
class CaaCoverArtImage(CoverArtImage):
|
||||
|
||||
# try_list will be None for the first call
|
||||
if try_list is None:
|
||||
try_list = []
|
||||
is_front = False
|
||||
support_types = True
|
||||
|
||||
|
||||
class CoverArt:
|
||||
|
||||
def __init__(self, album, metadata, release):
|
||||
self._queue_new()
|
||||
self.album = album
|
||||
self.metadata = metadata
|
||||
self.release = release
|
||||
self.caa_types = map(unicode.lower, config.setting["caa_image_types"])
|
||||
self.len_caa_types = len(self.caa_types)
|
||||
self.front_image_found = False
|
||||
|
||||
def __repr__(self):
|
||||
return "CoverArt for %r" % (self.album)
|
||||
|
||||
def retrieve(self):
|
||||
"""Retrieve available cover art images for the release"""
|
||||
|
||||
if self._caa_has_suitable_artwork():
|
||||
self._xmlws_download(
|
||||
CAA_HOST,
|
||||
CAA_PORT,
|
||||
"/release/%s/" % self.metadata["musicbrainz_albumid"],
|
||||
self._caa_json_downloaded,
|
||||
priority=True,
|
||||
important=False
|
||||
)
|
||||
else:
|
||||
self._queue_from_relationships()
|
||||
self._download_next_in_queue()
|
||||
|
||||
def _caa_has_suitable_artwork(self):
|
||||
"""Check if CAA artwork has to be downloaded"""
|
||||
if not config.setting['ca_provider_use_caa']:
|
||||
log.debug("Cover Art Archive disabled by user")
|
||||
return False
|
||||
if not self.len_caa_types:
|
||||
log.debug("User disabled all Cover Art Archive types")
|
||||
return False
|
||||
|
||||
# MB web service indicates if CAA has artwork
|
||||
# http://tickets.musicbrainz.org/browse/MBS-4536
|
||||
has_caa_artwork = False
|
||||
caa_types = map(unicode.lower, config.setting["caa_image_types"])
|
||||
if 'cover_art_archive' not in self.release.children:
|
||||
log.debug("No Cover Art Archive information for %s"
|
||||
% self.release.id)
|
||||
return False
|
||||
|
||||
if 'cover_art_archive' in release.children:
|
||||
caa_node = release.children['cover_art_archive'][0]
|
||||
has_caa_artwork = (caa_node.artwork[0].text == 'true')
|
||||
caa_node = self.release.children['cover_art_archive'][0]
|
||||
caa_has_suitable_artwork = caa_node.artwork[0].text == 'true'
|
||||
|
||||
if len(caa_types) == 2 and ('front' in caa_types or 'back' in caa_types):
|
||||
# The OR cases are there to still download and process the CAA
|
||||
# JSON file if front or back is enabled but not in the CAA and
|
||||
# another type (that's neither front nor back) is enabled.
|
||||
# For example, if both front and booklet are enabled and the
|
||||
# CAA only has booklet images, the front element in the XML
|
||||
# from the webservice will be false (thus front_in_caa is False
|
||||
# as well) but it's still necessary to download the booklet
|
||||
# images by using the fact that back is enabled but there are
|
||||
# no back images in the CAA.
|
||||
front_in_caa = caa_node.front[0].text == 'true' or 'front' not in caa_types
|
||||
back_in_caa = caa_node.back[0].text == 'true' or 'back' not in caa_types
|
||||
has_caa_artwork = has_caa_artwork and (front_in_caa or back_in_caa)
|
||||
if not caa_has_suitable_artwork:
|
||||
log.debug("There are no images in the Cover Art Archive for %s"
|
||||
% self.release.id)
|
||||
return False
|
||||
|
||||
elif len(caa_types) == 1 and ('front' in caa_types or 'back' in caa_types):
|
||||
front_in_caa = caa_node.front[0].text == 'true' and 'front' in caa_types
|
||||
back_in_caa = caa_node.back[0].text == 'true' and 'back' in caa_types
|
||||
has_caa_artwork = has_caa_artwork and (front_in_caa or back_in_caa)
|
||||
want_front = 'front' in self.caa_types
|
||||
want_back = 'back' in self.caa_types
|
||||
caa_has_front = caa_node.front[0].text == 'true'
|
||||
caa_has_back = caa_node.back[0].text == 'true'
|
||||
|
||||
if config.setting['ca_provider_use_caa'] and has_caa_artwork\
|
||||
and len(caa_types) > 0:
|
||||
log.debug("There are suitable images in the cover art archive for %s"
|
||||
% release.id)
|
||||
album._requests += 1
|
||||
album.tagger.xmlws.download(
|
||||
CAA_HOST, CAA_PORT, "/release/%s/" %
|
||||
metadata["musicbrainz_albumid"],
|
||||
partial(_caa_json_downloaded, album, metadata, release, try_list),
|
||||
priority=True, important=False)
|
||||
if self.len_caa_types == 2 and (want_front or want_back):
|
||||
# The OR cases are there to still download and process the CAA
|
||||
# JSON file if front or back is enabled but not in the CAA and
|
||||
# another type (that's neither front nor back) is enabled.
|
||||
# For example, if both front and booklet are enabled and the
|
||||
# CAA only has booklet images, the front element in the XML
|
||||
# from the webservice will be false (thus front_in_caa is False
|
||||
# as well) but it's still necessary to download the booklet
|
||||
# images by using the fact that back is enabled but there are
|
||||
# no back images in the CAA.
|
||||
front_in_caa = caa_has_front or not want_front
|
||||
back_in_caa = caa_has_back or not want_back
|
||||
caa_has_suitable_artwork = front_in_caa or back_in_caa
|
||||
|
||||
elif self.len_caa_types == 1 and (want_front or want_back):
|
||||
front_in_caa = caa_has_front and want_front
|
||||
back_in_caa = caa_has_back and want_back
|
||||
caa_has_suitable_artwork = front_in_caa or back_in_caa
|
||||
|
||||
if not caa_has_suitable_artwork:
|
||||
log.debug("There are no suitable images in the Cover Art Archive for %s"
|
||||
% self.release.id)
|
||||
else:
|
||||
log.debug("There are no suitable images in the cover art archive for %s"
|
||||
% release.id)
|
||||
_fill_try_list(album, release, try_list)
|
||||
_walk_try_list(album, metadata, release, try_list)
|
||||
log.debug("There are suitable images in the Cover Art Archive for %s"
|
||||
% self.release.id)
|
||||
|
||||
return caa_has_suitable_artwork
|
||||
|
||||
def _fill_try_list(album, release, try_list):
|
||||
"""Fills ``try_list`` by looking at the relationships in ``release``."""
|
||||
try:
|
||||
if 'relation_list' in release.children:
|
||||
for relation_list in release.relation_list:
|
||||
if relation_list.target_type == 'url':
|
||||
for relation in relation_list.relation:
|
||||
# Use the URL of a cover art link directly
|
||||
if config.setting['ca_provider_use_whitelist']\
|
||||
and (relation.type == 'cover art link' or
|
||||
relation.type == 'has_cover_art_at'):
|
||||
_try_list_append_image_url(try_list, QUrl(relation.target[0].text))
|
||||
elif config.setting['ca_provider_use_amazon']\
|
||||
and (relation.type == 'amazon asin' or
|
||||
relation.type == 'has_Amazon_ASIN'):
|
||||
_process_asin_relation(try_list, relation)
|
||||
except AttributeError:
|
||||
album.error_append(traceback.format_exc())
|
||||
def _coverart_http_error(self, http):
|
||||
"""Append http error to album errors"""
|
||||
self.album.error_append(u'Coverart error: %s' %
|
||||
(unicode(http.errorString())))
|
||||
|
||||
def _coverart_downloaded(self, coverartimage, data, http, error):
|
||||
"""Handle finished download, save it to metadata"""
|
||||
self.album._requests -= 1
|
||||
|
||||
def _walk_try_list(album, metadata, release, try_list):
|
||||
"""Downloads each item in ``try_list``. If there are none left, loading of
|
||||
``album`` will be finalized."""
|
||||
if len(try_list) == 0:
|
||||
album._finalize_loading(None)
|
||||
elif album.id not in album.tagger.albums:
|
||||
return
|
||||
else:
|
||||
# We still have some items to try!
|
||||
album._requests += 1
|
||||
coverinfos = try_list.pop(0)
|
||||
QObject.tagger.window.set_statusbar_message(
|
||||
N_("Downloading cover art of type '%(type)s' for %(albumid)s from %(host)s ..."),
|
||||
{
|
||||
'type': coverinfos['type'],
|
||||
'albumid': album.id,
|
||||
'host': coverinfos['host']
|
||||
}
|
||||
if error:
|
||||
self._coverart_http_error(http)
|
||||
elif len(data) < 1000:
|
||||
log.warning("Not enough data, skipping %s" % coverartimage)
|
||||
else:
|
||||
self._message(
|
||||
N_("Cover art of type '%(type)s' downloaded for %(albumid)s from %(host)s"),
|
||||
{
|
||||
'type': ','.join(coverartimage.types),
|
||||
'albumid': self.album.id,
|
||||
'host': coverartimage.host
|
||||
}
|
||||
)
|
||||
mime = mimetype.get_from_data(data, default="image/jpeg")
|
||||
|
||||
try:
|
||||
self.metadata.make_and_add_image(
|
||||
mime,
|
||||
data,
|
||||
types=coverartimage.types,
|
||||
comment=coverartimage.comment,
|
||||
is_front=coverartimage.is_front
|
||||
)
|
||||
for track in self.album._new_tracks:
|
||||
track.metadata.make_and_add_image(
|
||||
mime,
|
||||
data,
|
||||
types=coverartimage.types,
|
||||
comment=coverartimage.comment,
|
||||
is_front=coverartimage.is_front
|
||||
)
|
||||
# If the image already was a front image,
|
||||
# there might still be some other non-CAA front
|
||||
# images in the queue - ignore them.
|
||||
if not self.front_image_found:
|
||||
self.front_image_found = coverartimage.is_front_image()
|
||||
|
||||
except (IOError, OSError) as e:
|
||||
self.album.error_append(e.message)
|
||||
self.album._finalize_loading(error=True)
|
||||
# It doesn't make sense to store/download more images if we can't
|
||||
# save them in the temporary folder, abort.
|
||||
return
|
||||
|
||||
self._download_next_in_queue()
|
||||
|
||||
def _caa_json_downloaded(self, data, http, error):
|
||||
"""Parse CAA JSON file and queue CAA cover art images for download"""
|
||||
self.album._requests -= 1
|
||||
caa_front_found = False
|
||||
if error:
|
||||
self._coverart_http_error(http)
|
||||
else:
|
||||
try:
|
||||
caa_data = json.loads(data)
|
||||
except ValueError:
|
||||
self.album.error_append(
|
||||
"Invalid JSON: %s", http.url().toString())
|
||||
else:
|
||||
for image in caa_data["images"]:
|
||||
if config.setting["caa_approved_only"] and not image["approved"]:
|
||||
continue
|
||||
# if image has no type set, we still want it to match
|
||||
# pseudo type 'unknown'
|
||||
if not image["types"]:
|
||||
image["types"] = [u"unknown"]
|
||||
else:
|
||||
image["types"] = map(unicode.lower, image["types"])
|
||||
# only keep enabled caa types
|
||||
types = set(image["types"]).intersection(
|
||||
set(self.caa_types))
|
||||
if types:
|
||||
if not caa_front_found:
|
||||
caa_front_found = u'front' in types
|
||||
self._queue_from_caa(image)
|
||||
|
||||
if error or not caa_front_found:
|
||||
self._queue_from_relationships()
|
||||
self._download_next_in_queue()
|
||||
|
||||
def _queue_from_caa(self, image):
|
||||
"""Queue images depending on the CAA image size settings."""
|
||||
imagesize = config.setting["caa_image_size"]
|
||||
thumbsize = _CAA_THUMBNAIL_SIZE_MAP.get(imagesize, None)
|
||||
if thumbsize is None:
|
||||
url = image["image"]
|
||||
else:
|
||||
url = image["thumbnails"][thumbsize]
|
||||
coverartimage = CaaCoverArtImage(
|
||||
url,
|
||||
types=image["types"],
|
||||
comment=image["comment"],
|
||||
)
|
||||
album.tagger.xmlws.download(
|
||||
coverinfos['host'], coverinfos['port'], coverinfos['path'],
|
||||
partial(_coverart_downloaded, album, metadata, release, try_list, coverinfos),
|
||||
priority=True, important=False)
|
||||
# front image indicator from CAA
|
||||
coverartimage.is_front = bool(image['front'])
|
||||
self._queue_put(coverartimage)
|
||||
|
||||
def _queue_from_relationships(self):
|
||||
"""Queue images by looking at the release's relationships.
|
||||
"""
|
||||
use_whitelist = config.setting['ca_provider_use_whitelist']
|
||||
use_amazon = config.setting['ca_provider_use_amazon']
|
||||
if not (use_whitelist or use_amazon):
|
||||
return
|
||||
log.debug("Trying to get cover art from release relationships ...")
|
||||
try:
|
||||
if 'relation_list' in self.release.children:
|
||||
for relation_list in self.release.relation_list:
|
||||
if relation_list.target_type == 'url':
|
||||
for relation in relation_list.relation:
|
||||
# Use the URL of a cover art link directly
|
||||
if use_whitelist \
|
||||
and (relation.type == 'cover art link' or
|
||||
relation.type == 'has_cover_art_at'):
|
||||
self._queue_from_cover_art_relation(relation)
|
||||
elif use_amazon \
|
||||
and (relation.type == 'amazon asin' or
|
||||
relation.type == 'has_Amazon_ASIN'):
|
||||
self._queue_from_asin_relation(relation)
|
||||
except AttributeError:
|
||||
self.album.error_append(traceback.format_exc())
|
||||
|
||||
def _process_asin_relation(try_list, relation):
|
||||
amz = parse_amazon_url(relation.target[0].text)
|
||||
if amz is not None:
|
||||
def _queue_from_cover_art_relation(self, relation):
|
||||
"""Queue from cover art relationships"""
|
||||
log.debug("Found cover art link in whitelist")
|
||||
url = relation.target[0].text
|
||||
self._queue_put(CoverArtImage(url))
|
||||
|
||||
def _queue_from_asin_relation(self, relation):
|
||||
"""Queue cover art images from Amazon"""
|
||||
amz = parse_amazon_url(relation.target[0].text)
|
||||
if amz is None:
|
||||
return
|
||||
log.debug("Found ASIN relation : %s %s", amz['host'], amz['asin'])
|
||||
if amz['host'] in AMAZON_SERVER:
|
||||
serverInfo = AMAZON_SERVER[amz['host']]
|
||||
else:
|
||||
serverInfo = AMAZON_SERVER['amazon.com']
|
||||
host = serverInfo['server']
|
||||
path_l = AMAZON_IMAGE_PATH % (amz['asin'], serverInfo['id'], 'L')
|
||||
path_m = AMAZON_IMAGE_PATH % (amz['asin'], serverInfo['id'], 'M')
|
||||
_try_list_append_image_url(try_list, QUrl("http://%s:%s" % (host, path_l)))
|
||||
_try_list_append_image_url(try_list, QUrl("http://%s:%s" % (host, path_m)))
|
||||
for size in AMAZON_SIZES:
|
||||
path = AMAZON_IMAGE_PATH % {
|
||||
'asin': amz['asin'],
|
||||
'serverid': serverInfo['id'],
|
||||
'size': size
|
||||
}
|
||||
url = "http://%s:%s" % (host, path)
|
||||
self._queue_put(CoverArtImage(url))
|
||||
|
||||
def _download_next_in_queue(self):
|
||||
"""Downloads next item in queue.
|
||||
If there are none left, loading of album will be finalized.
|
||||
"""
|
||||
if self._queue_empty():
|
||||
self.album._finalize_loading(None)
|
||||
return
|
||||
|
||||
if self.album.id not in self.album.tagger.albums:
|
||||
return
|
||||
|
||||
# We still have some items to try!
|
||||
coverartimage = self._queue_get()
|
||||
if not coverartimage.support_types and self.front_image_found:
|
||||
# we already have one front image, no need to try other type-less
|
||||
# sources
|
||||
log.debug("Skipping %r, one front image is already available",
|
||||
coverartimage)
|
||||
self._download_next_in_queue()
|
||||
return
|
||||
|
||||
self._message(
|
||||
N_("Downloading cover art of type '%(type)s' for %(albumid)s from %(host)s ..."),
|
||||
{
|
||||
'type': ','.join(coverartimage.types),
|
||||
'albumid': self.album.id,
|
||||
'host': coverartimage.host
|
||||
}
|
||||
)
|
||||
self._xmlws_download(
|
||||
coverartimage.host,
|
||||
coverartimage.port,
|
||||
coverartimage.path,
|
||||
partial(self._coverart_downloaded, coverartimage),
|
||||
priority=True,
|
||||
important=False
|
||||
)
|
||||
|
||||
def _queue_put(self, coverartimage):
|
||||
"Add an image to queue"
|
||||
log.debug("Queing %r for download", coverartimage)
|
||||
self.__queue.append(coverartimage)
|
||||
|
||||
def _queue_get(self):
|
||||
"Get next image and remove it from queue"
|
||||
return self.__queue.pop(0)
|
||||
|
||||
def _queue_empty(self):
|
||||
"Returns True if the queue is empty"
|
||||
return not self.__queue
|
||||
|
||||
def _queue_new(self):
|
||||
"Initialize the queue"
|
||||
self.__queue = []
|
||||
|
||||
def _message(self, *args, **kwargs):
|
||||
"""Display message to status bar"""
|
||||
QObject.tagger.window.set_statusbar_message(*args, **kwargs)
|
||||
|
||||
def _xmlws_download(self, *args, **kwargs):
|
||||
"""xmlws.download wrapper"""
|
||||
self.album._requests += 1
|
||||
self.album.tagger.xmlws.download(*args, **kwargs)
|
||||
|
||||
|
||||
def _try_list_append_image_url(try_list, parsedUrl, extras=None):
|
||||
path = str(parsedUrl.encodedPath())
|
||||
if parsedUrl.hasQuery():
|
||||
path += '?' + parsedUrl.encodedQuery()
|
||||
coverinfos = {
|
||||
'host': str(parsedUrl.host()),
|
||||
'port': parsedUrl.port(80),
|
||||
'path': str(path),
|
||||
'type': 'front',
|
||||
'desc': ''
|
||||
}
|
||||
if extras is not None:
|
||||
coverinfos.update(extras)
|
||||
log.debug("Adding %s image %s", coverinfos['type'], parsedUrl.toString())
|
||||
try_list.append(coverinfos)
|
||||
def coverart(album, metadata, release):
|
||||
"""Gets all cover art URLs from the metadata and then attempts to
|
||||
download the album art. """
|
||||
|
||||
coverart = CoverArt(album, metadata, release)
|
||||
coverart.retrieve()
|
||||
log.debug("New %r", coverart)
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
from picard import config, log
|
||||
from picard.file import File
|
||||
from picard.formats.id3 import image_type_from_id3_num, image_type_as_id3_num
|
||||
from picard.formats.id3 import types_and_front, image_type_as_id3_num
|
||||
from picard.util import encode_filename
|
||||
from picard.metadata import Metadata, save_this_image_to_tags
|
||||
from mutagen.asf import ASF, ASFByteArrayAttribute
|
||||
@@ -141,8 +141,9 @@ class ASFFile(File):
|
||||
if name == 'WM/Picture':
|
||||
for image in values:
|
||||
(mime, data, type, description) = unpack_image(image.value)
|
||||
types, is_front = types_and_front(type)
|
||||
metadata.make_and_add_image(mime, data, comment=description,
|
||||
imagetype=image_type_from_id3_num(type))
|
||||
types=types, is_front=is_front)
|
||||
continue
|
||||
elif name not in self.__RTRANS:
|
||||
continue
|
||||
@@ -168,7 +169,7 @@ class ASFFile(File):
|
||||
if not save_this_image_to_tags(image):
|
||||
continue
|
||||
tag_data = pack_image(image.mimetype, image.data,
|
||||
image_type_as_id3_num(image.imagetype),
|
||||
image_type_as_id3_num(image.maintype()),
|
||||
image.description)
|
||||
cover.append(ASFByteArrayAttribute(tag_data))
|
||||
if cover:
|
||||
|
||||
@@ -92,6 +92,12 @@ def image_type_as_id3_num(texttype):
|
||||
return __ID3_IMAGE_TYPE_MAP.get(texttype, 0)
|
||||
|
||||
|
||||
def types_and_front(id3type):
|
||||
imgtype = image_type_from_id3_num(id3type)
|
||||
is_front = imgtype == 'front'
|
||||
return [unicode(imgtype)], is_front
|
||||
|
||||
|
||||
class ID3File(File):
|
||||
|
||||
"""Generic ID3-based file."""
|
||||
@@ -255,8 +261,9 @@ class ID3File(File):
|
||||
else:
|
||||
log.error("Invalid %s value '%s' dropped in %r", frameid, frame.text[0], filename)
|
||||
elif frameid == 'APIC':
|
||||
types, is_front = types_and_front(frame.type)
|
||||
metadata.make_and_add_image(frame.mime, frame.data, comment=frame.desc,
|
||||
imagetype=image_type_from_id3_num(frame.type))
|
||||
types=types, is_front=is_front)
|
||||
elif frameid == 'POPM':
|
||||
# Rating in ID3 ranges from 0 to 255, normalize this to the range 0 to 5
|
||||
if frame.email == config.setting['rating_user_email']:
|
||||
@@ -322,7 +329,7 @@ class ID3File(File):
|
||||
counters[desc] += 1
|
||||
tags.add(id3.APIC(encoding=0,
|
||||
mime=image.mimetype,
|
||||
type=image_type_as_id3_num(image.imagetype),
|
||||
type=image_type_as_id3_num(image.maintype()),
|
||||
desc=desctag,
|
||||
data=image.data))
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ except ImportError:
|
||||
with_opus = False
|
||||
from picard import config, log
|
||||
from picard.file import File
|
||||
from picard.formats.id3 import image_type_from_id3_num, image_type_as_id3_num
|
||||
from picard.formats.id3 import types_and_front, image_type_as_id3_num
|
||||
from picard.metadata import Metadata, save_this_image_to_tags
|
||||
from picard.util import encode_filename, sanitize_date
|
||||
|
||||
@@ -96,17 +96,20 @@ class VCommentFile(File):
|
||||
name = "totaldiscs"
|
||||
elif name == "metadata_block_picture":
|
||||
image = mutagen.flac.Picture(base64.standard_b64decode(value))
|
||||
types, is_front = types_and_front(image.type)
|
||||
metadata.make_and_add_image(image.mime, image.data,
|
||||
comment=image.desc,
|
||||
imagetype=image_type_from_id3_num(image.type))
|
||||
types=types,
|
||||
is_front=is_front)
|
||||
continue
|
||||
elif name in self.__translate:
|
||||
name = self.__translate[name]
|
||||
metadata.add(name, value)
|
||||
if self._File == mutagen.flac.FLAC:
|
||||
for image in file.pictures:
|
||||
types, is_front = types_and_front(image.type)
|
||||
metadata.make_and_add_image(image.mime, image.data, comment=image.desc,
|
||||
imagetype=image_type_from_id3_num(image.type))
|
||||
types=types, is_front=is_front)
|
||||
# Read the unofficial COVERART tags, for backward compatibillity only
|
||||
if not "metadata_block_picture" in file.tags:
|
||||
try:
|
||||
@@ -173,7 +176,7 @@ class VCommentFile(File):
|
||||
picture.data = image.data
|
||||
picture.mime = image.mimetype
|
||||
picture.desc = image.description
|
||||
picture.type = image_type_as_id3_num(image.imagetype)
|
||||
picture.type = image_type_as_id3_num(image.maintype())
|
||||
if self._File == mutagen.flac.FLAC:
|
||||
file.add_picture(picture)
|
||||
else:
|
||||
|
||||
@@ -45,19 +45,10 @@ from picard.mbxml import artist_credit_from_node
|
||||
MULTI_VALUED_JOINER = '; '
|
||||
|
||||
|
||||
def is_front_image(image):
|
||||
# CAA has a flag for "front" image, use it in priority
|
||||
caa_front = image.get('front', None)
|
||||
if caa_front is None:
|
||||
# no caa front flag, use type instead
|
||||
return (image['type'] == 'front')
|
||||
return caa_front
|
||||
|
||||
|
||||
def save_this_image_to_tags(image):
|
||||
if not config.setting["save_only_front_images_to_tags"]:
|
||||
return True
|
||||
return image.is_front_image
|
||||
return image.is_front
|
||||
|
||||
|
||||
class Image(object):
|
||||
@@ -66,8 +57,8 @@ class Image(object):
|
||||
an IOError or OSError due to the usage of tempfiles underneath.
|
||||
"""
|
||||
|
||||
def __init__(self, data, mimetype="image/jpeg", imagetype="front",
|
||||
comment="", filename=None, datahash=""):
|
||||
def __init__(self, data, mimetype="image/jpeg", types=[u"front"],
|
||||
comment="", filename=None, datahash="", is_front=True):
|
||||
self.description = comment
|
||||
(fd, self._tempfile_filename) = tempfile.mkstemp(prefix="picard")
|
||||
with fdopen(fd, "wb") as imagefile:
|
||||
@@ -77,10 +68,13 @@ class Image(object):
|
||||
self.datalength = len(data)
|
||||
self.extension = mime.get_extension(mime, ".jpg")
|
||||
self.filename = filename
|
||||
self.imagetype = imagetype
|
||||
self.is_front_image = imagetype == "front"
|
||||
self.types = types
|
||||
self.is_front = is_front
|
||||
self.mimetype = mimetype
|
||||
|
||||
def maintype(self):
|
||||
return self.types[0]
|
||||
|
||||
def _make_image_filename(self, filename, dirname, metadata):
|
||||
if config.setting["ascii_filenames"]:
|
||||
if isinstance(filename, unicode):
|
||||
@@ -110,8 +104,8 @@ class Image(object):
|
||||
log.debug("Using the custom file name %s", self.filename)
|
||||
filename = self.filename
|
||||
elif config.setting["caa_image_type_as_filename"]:
|
||||
log.debug("Using image type %s", self.imagetype)
|
||||
filename = self.imagetype
|
||||
filename = self.maintype()
|
||||
log.debug("Make filename from types: %r -> %r", self.types, filename)
|
||||
else:
|
||||
log.debug("Using default file name %s",
|
||||
config.setting["cover_image_filename"])
|
||||
@@ -180,7 +174,7 @@ class Metadata(dict):
|
||||
self.length = 0
|
||||
|
||||
def make_and_add_image(self, mime, data, filename=None, comment="",
|
||||
imagetype="front"):
|
||||
types=[u"front"], is_front=True):
|
||||
"""Build a new image object from ``data`` and adds it to this Metadata
|
||||
object. If an image with the same MD5 hash has already been added to
|
||||
any Metadata object, that file will be reused.
|
||||
@@ -190,7 +184,8 @@ class Metadata(dict):
|
||||
data -- The image data
|
||||
filename -- The image filename, without an extension
|
||||
comment -- image description or comment, default to ''
|
||||
imagetype -- main type as a string, default to 'front'
|
||||
types -- list of types, default to [u'front']
|
||||
is_front -- mark image as front image
|
||||
"""
|
||||
m = md5()
|
||||
m.update(data)
|
||||
@@ -198,8 +193,9 @@ class Metadata(dict):
|
||||
QObject.tagger.images.lock()
|
||||
image = QObject.tagger.images[datahash]
|
||||
if image is None:
|
||||
image = Image(data, mime, imagetype, comment, filename,
|
||||
datahash=datahash)
|
||||
image = Image(data, mime, types, comment, filename,
|
||||
datahash=datahash,
|
||||
is_front=is_front)
|
||||
QObject.tagger.images[datahash] = image
|
||||
QObject.tagger.images.unlock()
|
||||
self.images.append(image)
|
||||
|
||||
@@ -122,7 +122,7 @@ class CoverArtBox(QtGui.QGroupBox):
|
||||
data = None
|
||||
if metadata and metadata.images:
|
||||
for image in metadata.images:
|
||||
if image.is_front_image:
|
||||
if image.is_front:
|
||||
data = image
|
||||
break
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user