mirror of
https://github.com/fergalmoran/picard.git
synced 2026-01-06 00:23:58 +00:00
Merge branch 'image-class-2'
Conflicts: picard/ui/infodialog.py
This commit is contained in:
@@ -26,7 +26,7 @@ import re
|
||||
import traceback
|
||||
from functools import partial
|
||||
from picard import config, log
|
||||
from picard.metadata import is_front_image
|
||||
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
|
||||
@@ -84,12 +84,21 @@ def _coverart_downloaded(album, metadata, release, try_list, coverinfos, data, h
|
||||
QObject.tagger.window.set_statusbar_message(N_("Coverart %s downloaded"),
|
||||
http.url().toString())
|
||||
mime = mimetype.get_from_data(data, default="image/jpeg")
|
||||
filename = None
|
||||
if not is_front_image(coverinfos) and config.setting["caa_image_type_as_filename"]:
|
||||
filename = coverinfos['type']
|
||||
metadata.add_image(mime, data, filename, coverinfos)
|
||||
for track in album._new_tracks:
|
||||
track.metadata.add_image(mime, data, filename, coverinfos)
|
||||
|
||||
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), 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.
|
||||
|
||||
@@ -310,60 +310,13 @@ class File(QtCore.QObject, Item):
|
||||
shutil.move(encode_filename(old_filename), encode_filename(new_filename))
|
||||
return new_filename
|
||||
|
||||
def _make_image_filename(self, image_filename, dirname, metadata):
|
||||
image_filename = self._script_to_filename(image_filename, metadata)
|
||||
if not image_filename:
|
||||
image_filename = "cover"
|
||||
if os.path.isabs(image_filename):
|
||||
filename = image_filename
|
||||
else:
|
||||
filename = os.path.join(dirname, image_filename)
|
||||
if config.setting['windows_compatibility'] or sys.platform == 'win32':
|
||||
filename = filename.replace('./', '_/').replace('.\\', '_\\')
|
||||
return encode_filename(filename)
|
||||
|
||||
def _save_images(self, dirname, metadata):
|
||||
"""Save the cover images to disk."""
|
||||
if not metadata.images:
|
||||
return
|
||||
default_filename = self._make_image_filename(
|
||||
config.setting["cover_image_filename"], dirname, metadata)
|
||||
overwrite = config.setting["save_images_overwrite"]
|
||||
counters = defaultdict(lambda: 0)
|
||||
for image in metadata.images:
|
||||
filename = image["filename"]
|
||||
data = image["data"]
|
||||
mime = image["mime"]
|
||||
if filename is None:
|
||||
filename = default_filename
|
||||
else:
|
||||
filename = self._make_image_filename(filename, dirname, metadata)
|
||||
image_filename = filename
|
||||
ext = mimetype.get_extension(mime, ".jpg")
|
||||
if counters[filename] > 0:
|
||||
image_filename = "%s (%d)" % (filename, counters[filename])
|
||||
counters[filename] = counters[filename] + 1
|
||||
while os.path.exists(image_filename + ext) and not overwrite:
|
||||
if os.path.getsize(image_filename + ext) == len(data):
|
||||
log.debug("Identical file size, not saving %r", image_filename)
|
||||
break
|
||||
image_filename = "%s (%d)" % (filename, counters[filename])
|
||||
counters[filename] = counters[filename] + 1
|
||||
else:
|
||||
new_filename = image_filename + ext
|
||||
# Even if overwrite is enabled we don't need to write the same
|
||||
# image multiple times
|
||||
if (os.path.exists(new_filename) and
|
||||
os.path.getsize(new_filename) == len(data)):
|
||||
log.debug("Identical file size, not saving %r", image_filename)
|
||||
continue
|
||||
log.debug("Saving cover images to %r", image_filename)
|
||||
new_dirname = os.path.dirname(image_filename)
|
||||
if not os.path.isdir(new_dirname):
|
||||
os.makedirs(new_dirname)
|
||||
f = open(image_filename + ext, "wb")
|
||||
f.write(data)
|
||||
f.close()
|
||||
image.save(dirname, metadata, counters)
|
||||
|
||||
def _move_additional_files(self, old_filename, new_filename):
|
||||
"""Move extra files, like playlists..."""
|
||||
|
||||
@@ -63,7 +63,7 @@ class APEv2File(File):
|
||||
if '\0' in values.value:
|
||||
descr, data = values.value.split('\0', 1)
|
||||
mime = mimetype.get_from_data(data, descr, 'image/jpeg')
|
||||
metadata.add_image(mime, data)
|
||||
metadata.make_and_add_image(mime, data)
|
||||
# skip EXTERNAL and BINARY values
|
||||
if values.kind != mutagen.apev2.TEXT:
|
||||
continue
|
||||
@@ -150,8 +150,8 @@ class APEv2File(File):
|
||||
if not save_this_image_to_tags(image):
|
||||
continue
|
||||
cover_filename = 'Cover Art (Front)'
|
||||
cover_filename += mimetype.get_extension(image["mime"], '.jpg')
|
||||
tags['Cover Art (Front)'] = mutagen.apev2.APEValue(cover_filename + '\0' + image["data"], mutagen.apev2.BINARY)
|
||||
cover_filename += mimetype.get_extension(image.mimetype, '.jpg')
|
||||
tags['Cover Art (Front)'] = mutagen.apev2.APEValue(cover_filename + '\0' + image.data, mutagen.apev2.BINARY)
|
||||
break # can't save more than one item with the same name
|
||||
# (mp3tags does this, but it's against the specs)
|
||||
tags.save(encode_filename(filename))
|
||||
|
||||
@@ -141,11 +141,8 @@ class ASFFile(File):
|
||||
if name == 'WM/Picture':
|
||||
for image in values:
|
||||
(mime, data, type, description) = unpack_image(image.value)
|
||||
extras = {
|
||||
'desc': description,
|
||||
'type': image_type_from_id3_num(type)
|
||||
}
|
||||
metadata.add_image(mime, data, extras=extras)
|
||||
metadata.make_and_add_image(mime, data, comment=description,
|
||||
imagetype=image_type_from_id3_num(type))
|
||||
continue
|
||||
elif name not in self.__RTRANS:
|
||||
continue
|
||||
@@ -170,9 +167,9 @@ class ASFFile(File):
|
||||
for image in metadata.images:
|
||||
if not save_this_image_to_tags(image):
|
||||
continue
|
||||
tag_data = pack_image(image["mime"], image["data"],
|
||||
image_type_as_id3_num(image['type']),
|
||||
image['desc'])
|
||||
tag_data = pack_image(image.mimetype, image.data,
|
||||
image_type_as_id3_num(image.imagetype),
|
||||
image.description)
|
||||
cover.append(ASFByteArrayAttribute(tag_data))
|
||||
if cover:
|
||||
file.tags['WM/Picture'] = cover
|
||||
|
||||
@@ -259,11 +259,8 @@ class ID3File(File):
|
||||
else:
|
||||
log.error("Invalid %s value '%s' dropped in %r", frameid, frame.text[0], filename)
|
||||
elif frameid == 'APIC':
|
||||
extras = {
|
||||
'desc': frame.desc,
|
||||
'type': image_type_from_id3_num(frame.type)
|
||||
}
|
||||
metadata.add_image(frame.mime, frame.data, extras=extras)
|
||||
metadata.make_and_add_image(frame.mime, frame.data, comment=frame.desc,
|
||||
imagetype=image_type_from_id3_num(frame.type))
|
||||
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']:
|
||||
@@ -318,7 +315,7 @@ class ID3File(File):
|
||||
# any description.
|
||||
counters = defaultdict(lambda: 0)
|
||||
for image in metadata.images:
|
||||
desc = desctag = image['desc']
|
||||
desc = desctag = image.description
|
||||
if not save_this_image_to_tags(image):
|
||||
continue
|
||||
if counters[desc] > 0:
|
||||
@@ -328,10 +325,10 @@ class ID3File(File):
|
||||
desctag = "(%i)" % counters[desc]
|
||||
counters[desc] += 1
|
||||
tags.add(id3.APIC(encoding=0,
|
||||
mime=image["mime"],
|
||||
type=image_type_as_id3_num(image['type']),
|
||||
mime=image.mimetype,
|
||||
type=image_type_as_id3_num(image.imagetype),
|
||||
desc=desctag,
|
||||
data=image["data"]))
|
||||
data=image.data))
|
||||
|
||||
tmcl = mutagen.id3.TMCL(encoding=encoding, people=[])
|
||||
tipl = mutagen.id3.TIPL(encoding=encoding, people=[])
|
||||
|
||||
@@ -141,9 +141,9 @@ class MP4File(File):
|
||||
elif name == "covr":
|
||||
for value in values:
|
||||
if value.imageformat == value.FORMAT_JPEG:
|
||||
metadata.add_image("image/jpeg", value)
|
||||
metadata.make_and_add_image("image/jpeg", value)
|
||||
elif value.imageformat == value.FORMAT_PNG:
|
||||
metadata.add_image("image/png", value)
|
||||
metadata.make_and_add_image("image/png", value)
|
||||
|
||||
self._info(metadata, file)
|
||||
return metadata
|
||||
@@ -194,11 +194,11 @@ class MP4File(File):
|
||||
for image in metadata.images:
|
||||
if not save_this_image_to_tags(image):
|
||||
continue
|
||||
mime = image["mime"]
|
||||
mime = image.mimetype
|
||||
if mime == "image/jpeg":
|
||||
covr.append(MP4Cover(image["data"], MP4Cover.FORMAT_JPEG))
|
||||
covr.append(MP4Cover(image.data, MP4Cover.FORMAT_JPEG))
|
||||
elif mime == "image/png":
|
||||
covr.append(MP4Cover(image["data"], MP4Cover.FORMAT_PNG))
|
||||
covr.append(MP4Cover(image.data, MP4Cover.FORMAT_PNG))
|
||||
if covr:
|
||||
file.tags["covr"] = covr
|
||||
|
||||
|
||||
@@ -96,27 +96,22 @@ class VCommentFile(File):
|
||||
name = "totaldiscs"
|
||||
elif name == "metadata_block_picture":
|
||||
image = mutagen.flac.Picture(base64.standard_b64decode(value))
|
||||
extras = {
|
||||
'desc': image.desc,
|
||||
'type': image_type_from_id3_num(image.type)
|
||||
}
|
||||
metadata.add_image(image.mime, image.data, extras=extras)
|
||||
metadata.make_and_add_image(image.mime, image.data,
|
||||
comment=image.desc,
|
||||
imagetype=image_type_from_id3_num(image.type))
|
||||
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:
|
||||
extras = {
|
||||
'desc': image.desc,
|
||||
'type': image_type_from_id3_num(image.type)
|
||||
}
|
||||
metadata.add_image(image.mime, image.data, extras=extras)
|
||||
metadata.make_and_add_image(image.mime, image.data, comment=image.desc,
|
||||
imagetype=image_type_from_id3_num(image.type))
|
||||
# Read the unofficial COVERART tags, for backward compatibillity only
|
||||
if not "metadata_block_picture" in file.tags:
|
||||
try:
|
||||
for index, data in enumerate(file["COVERART"]):
|
||||
metadata.add_image(file["COVERARTMIME"][index],
|
||||
metadata.make_and_add_image(file["COVERARTMIME"][index],
|
||||
base64.standard_b64decode(data)
|
||||
)
|
||||
except KeyError:
|
||||
@@ -175,10 +170,10 @@ class VCommentFile(File):
|
||||
if not save_this_image_to_tags(image):
|
||||
continue
|
||||
picture = mutagen.flac.Picture()
|
||||
picture.data = image["data"]
|
||||
picture.mime = image["mime"]
|
||||
picture.desc = image['desc']
|
||||
picture.type = image_type_as_id3_num(image['type'])
|
||||
picture.data = image.data
|
||||
picture.mime = image.mimetype
|
||||
picture.desc = image.description
|
||||
picture.type = image_type_as_id3_num(image.imagetype)
|
||||
if self._File == mutagen.flac.FLAC:
|
||||
file.add_picture(picture)
|
||||
else:
|
||||
|
||||
@@ -15,13 +15,29 @@
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
||||
# USA.
|
||||
import os.path
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import traceback
|
||||
|
||||
|
||||
from hashlib import md5
|
||||
from os import fdopen, unlink
|
||||
from PyQt4.QtCore import QObject
|
||||
from picard import config
|
||||
from picard import config, log
|
||||
from picard.plugin import ExtensionPoint
|
||||
from picard.similarity import similarity2
|
||||
from picard.util import load_release_type_scores
|
||||
from picard.util import (
|
||||
encode_filename,
|
||||
load_release_type_scores,
|
||||
mimetype as mime,
|
||||
replace_non_ascii,
|
||||
replace_win32_incompat,
|
||||
unaccent,
|
||||
)
|
||||
from picard.mbxml import artist_credit_from_node
|
||||
|
||||
MULTI_VALUED_JOINER = '; '
|
||||
@@ -39,9 +55,105 @@ def is_front_image(image):
|
||||
def save_this_image_to_tags(image):
|
||||
if not config.setting["save_only_front_images_to_tags"]:
|
||||
return True
|
||||
if is_front_image(image):
|
||||
return True
|
||||
return False
|
||||
return image.is_front_image
|
||||
|
||||
|
||||
class Image(object):
|
||||
"""Wrapper around images. Instantiating an object of this class can raise
|
||||
an IOError or OSError due to the usage of tempfiles underneath.
|
||||
"""
|
||||
|
||||
def __init__(self, data, mimetype="image/jpeg", imagetype="front",
|
||||
comment="", filename=None, datahash=""):
|
||||
self.description = comment
|
||||
(fd, self._tempfile_filename) = tempfile.mkstemp(prefix="picard")
|
||||
with fdopen(fd, "wb") as imagefile:
|
||||
imagefile.write(data)
|
||||
log.debug("Saving image (hash=%s) to %r" % (datahash,
|
||||
self._tempfile_filename))
|
||||
self.datalength = len(data)
|
||||
self.extension = mime.get_extension(mime, ".jpg")
|
||||
self.filename = filename
|
||||
self.imagetype = imagetype
|
||||
self.is_front_image = imagetype == "front"
|
||||
self.mimetype = mimetype
|
||||
|
||||
def _make_image_filename(self, filename, dirname, metadata):
|
||||
if config.setting["ascii_filenames"]:
|
||||
if isinstance(filename, unicode):
|
||||
filename = unaccent(filename)
|
||||
filename = replace_non_ascii(filename)
|
||||
if not filename:
|
||||
filename = "cover"
|
||||
if not os.path.isabs(filename):
|
||||
filename = os.path.join(dirname, filename)
|
||||
# replace incompatible characters
|
||||
if config.setting["windows_compatibility"] or sys.platform == "win32":
|
||||
filename = replace_win32_incompat(filename)
|
||||
# remove null characters
|
||||
filename = filename.replace("\x00", "")
|
||||
return encode_filename(filename)
|
||||
|
||||
def save(self, dirname, metadata, counters):
|
||||
"""Saves this image.
|
||||
|
||||
:dirname: The name of the directory that contains the audio file
|
||||
:metadata: A metadata object
|
||||
:counters: A dictionary mapping filenames to the amount of how many
|
||||
images with that filename were already saved in `dirname`.
|
||||
"""
|
||||
if self.filename is not None:
|
||||
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
|
||||
else:
|
||||
log.debug("Using default file name %s",
|
||||
config.setting["cover_image_filename"])
|
||||
filename = config.setting["cover_image_filename"]
|
||||
filename = self._make_image_filename(filename, dirname, metadata)
|
||||
|
||||
overwrite = config.setting["save_images_overwrite"]
|
||||
ext = self.extension
|
||||
image_filename = filename
|
||||
if counters[filename] > 0:
|
||||
image_filename = "%s (%d)" % (filename, counters[filename])
|
||||
counters[filename] = counters[filename] + 1
|
||||
while os.path.exists(image_filename + ext) and not overwrite:
|
||||
if os.path.getsize(image_filename + ext) == self.datalength:
|
||||
log.debug("Identical file size, not saving %r", image_filename)
|
||||
break
|
||||
image_filename = "%s (%d)" % (filename, counters[filename])
|
||||
counters[filename] = counters[filename] + 1
|
||||
else:
|
||||
new_filename = image_filename + ext
|
||||
# Even if overwrite is enabled we don't need to write the same
|
||||
# image multiple times
|
||||
if (os.path.exists(new_filename) and
|
||||
os.path.getsize(new_filename) == self.datalength):
|
||||
log.debug("Identical file size, not saving %r", image_filename)
|
||||
return
|
||||
log.debug("Saving cover images to %r", image_filename)
|
||||
new_dirname = os.path.dirname(image_filename)
|
||||
if not os.path.isdir(new_dirname):
|
||||
os.makedirs(new_dirname)
|
||||
shutil.copyfile(self._tempfile_filename, new_filename)
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
"""Reads the data from the temporary file created for this image. May
|
||||
raise IOErrors or OSErrors.
|
||||
"""
|
||||
with open(self._tempfile_filename, "rb") as imagefile:
|
||||
return imagefile.read()
|
||||
|
||||
def _delete(self):
|
||||
log.debug("Unlinking %s", self._tempfile_filename)
|
||||
try:
|
||||
unlink(self._tempfile_filename)
|
||||
except OSError, e:
|
||||
log.error(traceback.format_exc())
|
||||
|
||||
|
||||
class Metadata(dict):
|
||||
@@ -61,26 +173,30 @@ class Metadata(dict):
|
||||
self.images = []
|
||||
self.length = 0
|
||||
|
||||
def add_image(self, mime, data, filename=None, extras=None):
|
||||
"""Adds the image ``data`` to this Metadata object.
|
||||
def make_and_add_image(self, mime, data, filename=None, comment="",
|
||||
imagetype="front"):
|
||||
"""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.
|
||||
|
||||
Arguments:
|
||||
mime -- The mimetype of the image
|
||||
data -- The image data
|
||||
filename -- The image filename, without an extension
|
||||
extras -- extra informations about image as dict
|
||||
'desc' : image description or comment, default to ''
|
||||
'type' : main type as a string, default to 'front'
|
||||
'front': if set, CAA front flag is true for this image
|
||||
comment -- image description or comment, default to ''
|
||||
imagetype -- main type as a string, default to 'front'
|
||||
"""
|
||||
imagedict = {'mime': mime,
|
||||
'data': data,
|
||||
'filename': filename,
|
||||
'type': 'front',
|
||||
'desc': ''}
|
||||
if extras is not None:
|
||||
imagedict.update(extras)
|
||||
self.images.append(imagedict)
|
||||
m = md5()
|
||||
m.update(data)
|
||||
datahash = m.hexdigest()
|
||||
QObject.tagger.images.lock()
|
||||
image = QObject.tagger.images[datahash]
|
||||
if image is None:
|
||||
image = Image(data, mime, imagetype, comment, filename,
|
||||
datahash=datahash)
|
||||
QObject.tagger.images[datahash] = image
|
||||
QObject.tagger.images.unlock()
|
||||
self.images.append(image)
|
||||
|
||||
def remove_image(self, index):
|
||||
self.images.pop(index)
|
||||
|
||||
@@ -30,7 +30,9 @@ import os.path
|
||||
import re
|
||||
import shutil
|
||||
import signal
|
||||
import socket
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from functools import partial
|
||||
from itertools import chain
|
||||
|
||||
@@ -76,6 +78,7 @@ from picard.util import (
|
||||
check_io_encoding,
|
||||
uniqify,
|
||||
is_hidden_path,
|
||||
LockableDefaultDict
|
||||
)
|
||||
from picard.webservice import XmlWebService
|
||||
|
||||
@@ -107,6 +110,25 @@ class Tagger(QtGui.QApplication):
|
||||
self.save_thread_pool = QtCore.QThreadPool(self)
|
||||
self.save_thread_pool.setMaxThreadCount(1)
|
||||
|
||||
if not sys.platform == "win32":
|
||||
# Set up signal handling
|
||||
# It's not possible to call all available functions from signal
|
||||
# handlers, therefore we need to set up a QSocketNotifier to listen
|
||||
# on a socket. Sending data through a socket can be done in a
|
||||
# signal handler, so we use the socket to notify the application of
|
||||
# the signal.
|
||||
# This code is adopted from
|
||||
# https://qt-project.org/doc/qt-4.8/unix-signals.html
|
||||
self.signalfd = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM, 0)
|
||||
|
||||
self.signalnotifier = QtCore.QSocketNotifier(self.signalfd[1].fileno(),
|
||||
QtCore.QSocketNotifier.Read, self)
|
||||
self.signalnotifier.activated.connect(self.sighandler)
|
||||
|
||||
signal.signal(signal.SIGHUP, self.signal)
|
||||
signal.signal(signal.SIGINT, self.signal)
|
||||
signal.signal(signal.SIGTERM, self.signal)
|
||||
|
||||
# Setup logging
|
||||
if debug or "PICARD_DEBUG" in os.environ:
|
||||
log.log_levels = log.log_levels | log.LOG_DEBUG
|
||||
@@ -165,6 +187,7 @@ class Tagger(QtGui.QApplication):
|
||||
self.albums = {}
|
||||
self.release_groups = {}
|
||||
self.mbid_redirects = {}
|
||||
self.images = LockableDefaultDict(lambda: None)
|
||||
self.unmatched_files = UnmatchedFiles()
|
||||
self.nats = None
|
||||
self.window = MainWindow()
|
||||
@@ -205,6 +228,8 @@ class Tagger(QtGui.QApplication):
|
||||
self.nats.update()
|
||||
|
||||
def exit(self):
|
||||
log.debug("exit")
|
||||
map(lambda i: i._delete(), self.images.itervalues())
|
||||
self.stopping = True
|
||||
self._acoustid.done()
|
||||
self.thread_pool.waitForDone()
|
||||
@@ -543,6 +568,18 @@ class Tagger(QtGui.QApplication):
|
||||
def instance(cls):
|
||||
return cls.__instance
|
||||
|
||||
def signal(self, signum, frame):
|
||||
log.debug("signal %i received", signum)
|
||||
# Send a notification about a received signal from the signal handler
|
||||
# to Qt.
|
||||
self.signalfd[0].sendall("a")
|
||||
|
||||
def sighandler(self):
|
||||
self.signalnotifier.setEnabled(False)
|
||||
self.exit()
|
||||
self.quit()
|
||||
self.signalnotifier.setEnabled(True)
|
||||
|
||||
|
||||
def help():
|
||||
print """Usage: %s [OPTIONS] [FILE] [FILE] ...
|
||||
@@ -577,4 +614,5 @@ def main(localedir=None, autoupdate=True):
|
||||
elif opt in ("-d", "--debug"):
|
||||
kwargs["debug"] = True
|
||||
tagger = Tagger(args, localedir, autoupdate, **kwargs)
|
||||
tagger.startTimer(1000)
|
||||
sys.exit(tagger.run())
|
||||
|
||||
@@ -23,7 +23,6 @@ from picard import config, log
|
||||
from picard.album import Album
|
||||
from picard.track import Track
|
||||
from picard.file import File
|
||||
from picard.metadata import is_front_image
|
||||
from picard.util import webbrowser2, encode_filename
|
||||
|
||||
|
||||
@@ -104,7 +103,7 @@ class CoverArtBox(QtGui.QGroupBox):
|
||||
if self.data:
|
||||
if pixmap is None:
|
||||
pixmap = QtGui.QPixmap()
|
||||
pixmap.loadFromData(self.data["data"])
|
||||
pixmap.loadFromData(self.data.data)
|
||||
if not pixmap.isNull():
|
||||
offx, offy, w, h = (1, 1, 121, 121)
|
||||
cover = QtGui.QPixmap(self.shadow)
|
||||
@@ -123,7 +122,7 @@ class CoverArtBox(QtGui.QGroupBox):
|
||||
data = None
|
||||
if metadata and metadata.images:
|
||||
for image in metadata.images:
|
||||
if is_front_image(image):
|
||||
if image.is_front_image:
|
||||
data = image
|
||||
break
|
||||
else:
|
||||
@@ -189,16 +188,16 @@ class CoverArtBox(QtGui.QGroupBox):
|
||||
self.__set_data([mime, data], pixmap=pixmap)
|
||||
if isinstance(self.item, Album):
|
||||
album = self.item
|
||||
album.metadata.add_image(mime, data)
|
||||
album.metadata.make_and_add_image(mime, data)
|
||||
for track in album.tracks:
|
||||
track.metadata.add_image(mime, data)
|
||||
track.metadata.make_and_add_image(mime, data)
|
||||
for file in album.iterfiles():
|
||||
file.metadata.add_image(mime, data)
|
||||
file.metadata.make_and_add_image(mime, data)
|
||||
elif isinstance(self.item, Track):
|
||||
track = self.item
|
||||
track.metadata.add_image(mime, data)
|
||||
track.metadata.make_and_add_image(mime, data)
|
||||
for file in track.iterfiles():
|
||||
file.metadata.add_image(mime, data)
|
||||
file.metadata.make_and_add_image(mime, data)
|
||||
elif isinstance(self.item, File):
|
||||
file = self.item
|
||||
file.metadata.add_image(mime, data)
|
||||
file.metadata.make_and_add_image(mime, data)
|
||||
|
||||
@@ -49,7 +49,11 @@ class InfoDialog(PicardDialog):
|
||||
return
|
||||
|
||||
for image in images:
|
||||
data = image["data"]
|
||||
try:
|
||||
data = image.data
|
||||
except (OSError, IOError), e:
|
||||
log.error(traceback.format_exc())
|
||||
continue
|
||||
size = len(data)
|
||||
item = QtGui.QListWidgetItem()
|
||||
pixmap = QtGui.QPixmap()
|
||||
@@ -105,8 +109,8 @@ class FileInfoDialog(InfoDialog):
|
||||
ch = str(ch)
|
||||
info.append((_('Channels:'), ch))
|
||||
text = '<br/>'.join(map(lambda i: '<b>%s</b><br/>%s' %
|
||||
(cgi.escape(i[0]),
|
||||
cgi.escape(i[1])), info))
|
||||
(QtCore.Qt.escape(i[0]),
|
||||
QtCore.Qt.escape(i[1])), info))
|
||||
self.ui.info.setText(text)
|
||||
|
||||
|
||||
@@ -124,7 +128,7 @@ class AlbumInfoDialog(InfoDialog):
|
||||
if album.errors:
|
||||
tabWidget.setTabText(tab_index, _("&Errors"))
|
||||
text = '<br />'.join(map(lambda s: '<font color="darkred">%s</font>' %
|
||||
'<br />'.join(unicode(cgi.escape(s))
|
||||
'<br />'.join(unicode(QtCore.Qt.escape(s))
|
||||
.replace('\t', ' ')
|
||||
.replace(' ', ' ')
|
||||
.splitlines()
|
||||
|
||||
@@ -27,6 +27,19 @@ from PyQt4 import QtCore
|
||||
from encodings import rot_13
|
||||
from string import Template
|
||||
from functools import partial
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
class LockableDefaultDict(defaultdict):
|
||||
def __init__(self, default):
|
||||
defaultdict.__init__(self, default)
|
||||
self.__lock = QtCore.QReadWriteLock()
|
||||
|
||||
def lock(self):
|
||||
self.__lock.lockForWrite()
|
||||
|
||||
def unlock(self):
|
||||
self.__lock.unlock()
|
||||
|
||||
|
||||
def asciipunct(s):
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import os.path
|
||||
import picard.formats
|
||||
import unittest
|
||||
import shutil
|
||||
from tempfile import mkstemp
|
||||
|
||||
|
||||
from PyQt4 import QtCore
|
||||
from picard.util import LockableDefaultDict
|
||||
from picard import config, log
|
||||
from picard.metadata import Metadata
|
||||
import picard.formats
|
||||
from PyQt4 import QtCore
|
||||
from tempfile import mkstemp
|
||||
|
||||
|
||||
settings = {
|
||||
@@ -33,6 +36,7 @@ class FakeTagger(QtCore.QObject):
|
||||
QtCore.QObject.config = config
|
||||
QtCore.QObject.log = log
|
||||
self.tagger_stats_changed.connect(self.emit)
|
||||
self.images = LockableDefaultDict(lambda: None)
|
||||
|
||||
def emit(self, *args):
|
||||
pass
|
||||
@@ -504,6 +508,7 @@ class TestCoverArt(unittest.TestCase):
|
||||
QtCore.QObject.tagger = FakeTagger()
|
||||
|
||||
def _tear_down(self):
|
||||
map(lambda i: i._delete(), QtCore.QObject.tagger.images.itervalues())
|
||||
os.unlink(self.filename)
|
||||
|
||||
def test_asf(self):
|
||||
@@ -544,13 +549,13 @@ class TestCoverArt(unittest.TestCase):
|
||||
f = picard.formats.open(self.filename)
|
||||
metadata = Metadata()
|
||||
imgdata = tests[t]['head'] + dummyload
|
||||
metadata.add_image(tests[t]['mime'], imgdata)
|
||||
metadata.make_and_add_image(tests[t]['mime'], imgdata)
|
||||
f._save(self.filename, metadata)
|
||||
|
||||
f = picard.formats.open(self.filename)
|
||||
loaded_metadata = f._load(self.filename)
|
||||
image = loaded_metadata.images[0]
|
||||
self.assertEqual(image["mime"], tests[t]['mime'])
|
||||
self.assertEqual(image["data"], imgdata)
|
||||
self.assertEqual(image.mimetype, tests[t]['mime'])
|
||||
self.assertEqual(image.data, imgdata)
|
||||
finally:
|
||||
self._tear_down()
|
||||
|
||||
Reference in New Issue
Block a user