Merge pull request #313 from zas/coverart_cleanup

Coverart cleanup, mostly code rewrite + minor fixes
This commit is contained in:
Laurent Monin
2014-05-10 22:03:52 +02:00
6 changed files with 401 additions and 226 deletions

View File

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

View File

@@ -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:

View File

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

View File

@@ -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:

View File

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

View File

@@ -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: