From 659c2a3a5bfbf684f677db8f2a5bac454d1ae1b3 Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Thu, 16 Feb 2017 11:47:02 +0100 Subject: [PATCH 01/35] Store cover images in the album original metadata --- picard/album.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/picard/album.py b/picard/album.py index 6cc225553..e332ee2aa 100644 --- a/picard/album.py +++ b/picard/album.py @@ -58,6 +58,7 @@ class Album(DataObject, Item): def __init__(self, id, discid=None): DataObject.__init__(self, id) self.metadata = Metadata() + self.orig_metadata = Metadata() self.tracks = [] self.loaded = False self.load_task = None @@ -382,6 +383,10 @@ class Album(DataObject, Item): def _add_file(self, track, file): self._files += 1 self.update(update_tracks=False) + # Fixme: The next lines can probably be moved to update() + for image in file.orig_metadata.images: + if image not in self.orig_metadata.images: + self.orig_metadata.append_image(image) def _remove_file(self, track, file): self._files -= 1 From d5e91efb94d0ef4fbaa3b4a96886798b2bbfdad7 Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Thu, 16 Feb 2017 11:47:54 +0100 Subject: [PATCH 02/35] Store cover images in the cluster metadata --- picard/cluster.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/picard/cluster.py b/picard/cluster.py index 290ec2a41..546cfe834 100644 --- a/picard/cluster.py +++ b/picard/cluster.py @@ -69,6 +69,9 @@ class Cluster(QtCore.QObject, Item): self.metadata.length += file.metadata.length file._move(self) file.update(signal=False) + cover = file.metadata.get_single_front_image() + if cover and cover[0] not in self.metadata.images: + self.metadata.append_image(cover[0]) self.files.extend(files) self.metadata['totaltracks'] = len(self.files) self.item.add_files(files) @@ -79,6 +82,9 @@ class Cluster(QtCore.QObject, Item): self.metadata['totaltracks'] = len(self.files) file._move(self) file.update(signal=False) + cover = file.metadata.get_single_front_image() + if cover and cover[0] not in self.metadata.images: + self.metadata.append_image(cover[0]) self.item.add_file(file) def remove_file(self, file): From 876a3055b0b6a8d6a8397fe460c137a992f8409c Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Thu, 16 Feb 2017 11:49:33 +0100 Subject: [PATCH 03/35] Add debug information This will be removed later, but it's useful to see the actual metadata arriving to CoverArtBox. --- picard/ui/coverartbox.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/picard/ui/coverartbox.py b/picard/ui/coverartbox.py index 766f2d7ff..8205c3bfe 100644 --- a/picard/ui/coverartbox.py +++ b/picard/ui/coverartbox.py @@ -83,9 +83,10 @@ class ActiveLabel(QtGui.QLabel): class CoverArtThumbnail(ActiveLabel): - def __init__(self, active=False, drops=False, *args, **kwargs): + def __init__(self, active=False, drops=False, name=None, *args, **kwargs): super(CoverArtThumbnail, self).__init__(active, drops, *args, **kwargs) self.data = None + self.name = name self.shadow = QtGui.QPixmap(":/images/CoverArtShadow.png") self.release = None self.setPixmap(self.shadow) @@ -134,6 +135,8 @@ class CoverArtThumbnail(ActiveLabel): self.related_images = [] if metadata and metadata.images: self.related_images = metadata.images + print("%s using images:" % (self.name), metadata.images) + # TODO: Combine all images to show there are different images in use instead of getting the first one for image in metadata.images: if image.is_front_image(): data = image @@ -174,10 +177,10 @@ class CoverArtBox(QtGui.QGroupBox): self.item = None self.cover_art_label = QtGui.QLabel('') self.cover_art_label.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignHCenter) - self.cover_art = CoverArtThumbnail(False, True, parent) + self.cover_art = CoverArtThumbnail(False, True, "new cover", parent) spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.orig_cover_art_label = QtGui.QLabel('') - self.orig_cover_art = CoverArtThumbnail(False, False, parent) + self.orig_cover_art = CoverArtThumbnail(False, False, "original cover", parent) self.orig_cover_art_label.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignHCenter) self.orig_cover_art.setHidden(True) self.show_details_button = QtGui.QPushButton(_(u'Show more details'), self) From 3dab58c22e703e2de0b205ce7c700c634c540f47 Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Thu, 16 Feb 2017 22:59:26 +0100 Subject: [PATCH 04/35] Draw stack of covers when metadata contains multiple images Now CoverArtThumbnails uses all images in metadata to draw a stack of covers so when a release contains files with different original metadata covers that will be overwritten, the user can intuitively see that. --- picard/ui/coverartbox.py | 65 +++++++++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 21 deletions(-) diff --git a/picard/ui/coverartbox.py b/picard/ui/coverartbox.py index 8205c3bfe..a6be172f6 100644 --- a/picard/ui/coverartbox.py +++ b/picard/ui/coverartbox.py @@ -104,6 +104,19 @@ class CoverArtThumbnail(ActiveLabel): def show(self): self.set_data(self.data, True) + def decorate_cover(self, pixmap): + offx, offy, w, h = (1, 1, 121, 121) + cover = QtGui.QPixmap(self.shadow) + pixmap = pixmap.scaled(w, h, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) + painter = QtGui.QPainter(cover) + bgcolor = QtGui.QColor.fromRgb(0, 0, 0, 128) + painter.fillRect(QtCore.QRectF(offx, offy, w, h), bgcolor) + x = offx + (w - pixmap.width()) / 2 + y = offy + (h - pixmap.height()) / 2 + painter.drawPixmap(x, y, pixmap) + painter.end() + return cover + def set_data(self, data, force=False, pixmap=None): if not force and self.data == data: return @@ -114,36 +127,46 @@ class CoverArtThumbnail(ActiveLabel): cover = self.shadow if self.data: + w, h, displacements = (121, 121, 20) if pixmap is None: - pixmap = QtGui.QPixmap() - pixmap.loadFromData(self.data.data) - if not pixmap.isNull(): - offx, offy, w, h = (1, 1, 121, 121) - cover = QtGui.QPixmap(self.shadow) - pixmap = pixmap.scaled(w, h, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) - painter = QtGui.QPainter(cover) - bgcolor = QtGui.QColor.fromRgb(0, 0, 0, 128) - painter.fillRect(QtCore.QRectF(offx, offy, w, h), bgcolor) - x = offx + (w - pixmap.width()) / 2 - y = offy + (h - pixmap.height()) / 2 - painter.drawPixmap(x, y, pixmap) - painter.end() - self.setPixmap(cover) + if len(self.data) == 1: + pixmap = QtGui.QPixmap() + pixmap.loadFromData(self.data[0].data) + else: + stack_width, stack_height = (w+displacements*(len(self.data)-1), h+displacements*(len(self.data)-1)) + pixmap = QtGui.QPixmap(stack_width, stack_height) + bgcolor = self.palette().color(QtGui.QPalette.Window) + painter = QtGui.QPainter(pixmap) + painter.fillRect(QtCore.QRectF(0, 0, stack_width, stack_height), bgcolor) + x = w / 2 + y = h / 2 + for image in self.data: + thumb = QtGui.QPixmap() + thumb.loadFromData(image.data) + thumb = self.decorate_cover( thumb ) + painter.drawPixmap(x - thumb.width()/2, y - thumb.height()/2, thumb) + x += displacements + y += displacements + painter.end() + pixmap = pixmap.scaled(w, h, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) + self.setPixmap(pixmap) + pixmap = None + + if pixmap and not pixmap.isNull(): + cover = self.decorate_cover( pixmap ) + self.setPixmap(cover) def set_metadata(self, metadata): data = None self.related_images = [] if metadata and metadata.images: self.related_images = metadata.images - print("%s using images:" % (self.name), metadata.images) + log.debug("%s using images:" % (self.name), metadata.images) # TODO: Combine all images to show there are different images in use instead of getting the first one - for image in metadata.images: - if image.is_front_image(): - data = image - break - else: + data = [ image for image in metadata.images if image.is_front_image() ] + if not data: # There's no front image, choose the first one available - data = metadata.images[0] + data = [ metadata.images[0] ] self.set_data(data) release = None if metadata: From 14e2639e1a5d491959e452fb9f4b825a037740f4 Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Thu, 16 Feb 2017 23:31:11 +0100 Subject: [PATCH 05/35] Replace front images instead of appending them When an image is dropped, replace the front image with the dropped one. Appending front covers is counterintuitive --- picard/metadata.py | 5 +++++ picard/ui/coverartbox.py | 16 +++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/picard/metadata.py b/picard/metadata.py index da7fd8133..afbb51026 100644 --- a/picard/metadata.py +++ b/picard/metadata.py @@ -55,6 +55,11 @@ class Metadata(dict): def append_image(self, coverartimage): self.images.append(coverartimage) + def set_front_image(self, coverartimage): + # First remove all front images + self.images[:] = [ img for img in self.images if not img.is_front_image() ] + self.images.append(coverartimage) + @property def images_to_be_saved_to_tags(self): if not config.setting["save_images_to_tags"]: diff --git a/picard/ui/coverartbox.py b/picard/ui/coverartbox.py index a6be172f6..555bdf39d 100644 --- a/picard/ui/coverartbox.py +++ b/picard/ui/coverartbox.py @@ -302,6 +302,7 @@ class CoverArtBox(QtGui.QGroupBox): try: coverartimage = CoverArtImage( url=url.toString(), + types=[u'front'], data=data ) except CoverArtImageError as e: @@ -309,21 +310,18 @@ class CoverArtBox(QtGui.QGroupBox): return if isinstance(self.item, Album): album = self.item - album.metadata.append_image(coverartimage) + album.metadata.set_front_image(coverartimage) for track in album.tracks: - track.metadata.append_image(coverartimage) + track.metadata.set_front_image(coverartimage) for file in album.iterfiles(): - file.metadata.append_image(coverartimage) - file.update() + file.metadata.set_front_image(coverartimage) elif isinstance(self.item, Track): track = self.item - track.metadata.append_image(coverartimage) + track.metadata.set_front_image(coverartimage) for file in track.iterfiles(): - file.metadata.append_image(coverartimage) - file.update() + file.metadata.set_front_image(coverartimage) elif isinstance(self.item, File): file = self.item - file.metadata.append_image(coverartimage) - file.update() + file.metadata.set_front_image(coverartimage) self.cover_art.set_metadata(self.item.metadata) self.show() From b4bfaed65fd2668a877f1c0e7d54e9fc4f883d96 Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Thu, 16 Feb 2017 23:50:09 +0100 Subject: [PATCH 06/35] Update album images when an album is to be shown When a user drops different images to be used as covers of different tracks of the same album, the album's metadata.images list didn't reflect these changes. This commit updates the list of images so CoverArtBox always shows the correct values --- picard/album.py | 13 +++++++++++++ picard/ui/mainwindow.py | 4 ++++ 2 files changed, 17 insertions(+) diff --git a/picard/album.py b/picard/album.py index e332ee2aa..4d85ba9b0 100644 --- a/picard/album.py +++ b/picard/album.py @@ -557,6 +557,19 @@ class Album(DataObject, Item): self.tagger.albums[mbid] = self self.load(priority=True, refresh=True) + def update_metadata_images(self): + new_images = [] + for track in self.tracks: + for file in list(track.linked_files): + for image in file.metadata.images: + if image not in new_images: + new_images.append(image) + for file in list(self.unmatched_files.files): + for image in file.metadata.images: + if image not in new_images: + new_images.append(image) + self.metadata.images = new_images + class NatAlbum(Album): diff --git a/picard/ui/mainwindow.py b/picard/ui/mainwindow.py index 24a1b2b9b..a9f23a0ae 100644 --- a/picard/ui/mainwindow.py +++ b/picard/ui/mainwindow.py @@ -952,6 +952,10 @@ class MainWindow(QtGui.QMainWindow): } self.set_statusbar_message(msg, mparms, echo=None, history=None) + elif isinstance(obj, Album): + obj.update_metadata_images() + metadata = obj.metadata + orig_metadata = obj.orig_metadata elif obj.can_edit_tags(): metadata = obj.metadata From d86d7f205b2b35af396edf429d77a57047522d08 Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Sun, 5 Mar 2017 19:06:18 +0100 Subject: [PATCH 07/35] Fix update of album original images When the tracks of an album change their original images (for example, because the tracks are saved and the original images change) then them album also has to update its original images --- picard/album.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/picard/album.py b/picard/album.py index 4d85ba9b0..d2dbb276a 100644 --- a/picard/album.py +++ b/picard/album.py @@ -383,10 +383,6 @@ class Album(DataObject, Item): def _add_file(self, track, file): self._files += 1 self.update(update_tracks=False) - # Fixme: The next lines can probably be moved to update() - for image in file.orig_metadata.images: - if image not in self.orig_metadata.images: - self.orig_metadata.append_image(image) def _remove_file(self, track, file): self._files -= 1 @@ -559,16 +555,24 @@ class Album(DataObject, Item): def update_metadata_images(self): new_images = [] + orig_images = [] for track in self.tracks: for file in list(track.linked_files): for image in file.metadata.images: if image not in new_images: new_images.append(image) + for image in file.orig_metadata.images: + if image not in orig_images: + orig_images.append(image) for file in list(self.unmatched_files.files): for image in file.metadata.images: if image not in new_images: new_images.append(image) + for image in file.orig_metadata.images: + if image not in orig_images: + orig_images.append(image) self.metadata.images = new_images + self.orig_metadata.images = orig_images class NatAlbum(Album): From 0c7e2a0ecd27ec78e2c81b0010932871ff5f13af Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Sun, 5 Mar 2017 19:09:21 +0100 Subject: [PATCH 08/35] PEP8 fixes --- picard/ui/coverartbox.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/picard/ui/coverartbox.py b/picard/ui/coverartbox.py index 555bdf39d..1fca3dea8 100644 --- a/picard/ui/coverartbox.py +++ b/picard/ui/coverartbox.py @@ -133,7 +133,7 @@ class CoverArtThumbnail(ActiveLabel): pixmap = QtGui.QPixmap() pixmap.loadFromData(self.data[0].data) else: - stack_width, stack_height = (w+displacements*(len(self.data)-1), h+displacements*(len(self.data)-1)) + stack_width, stack_height = (w + displacements * (len(self.data) - 1), h + displacements * (len(self.data) - 1)) pixmap = QtGui.QPixmap(stack_width, stack_height) bgcolor = self.palette().color(QtGui.QPalette.Window) painter = QtGui.QPainter(pixmap) @@ -143,8 +143,8 @@ class CoverArtThumbnail(ActiveLabel): for image in self.data: thumb = QtGui.QPixmap() thumb.loadFromData(image.data) - thumb = self.decorate_cover( thumb ) - painter.drawPixmap(x - thumb.width()/2, y - thumb.height()/2, thumb) + thumb = self.decorate_cover(thumb) + painter.drawPixmap(x - thumb.width() / 2, y - thumb.height() / 2, thumb) x += displacements y += displacements painter.end() @@ -153,7 +153,7 @@ class CoverArtThumbnail(ActiveLabel): pixmap = None if pixmap and not pixmap.isNull(): - cover = self.decorate_cover( pixmap ) + cover = self.decorate_cover(pixmap) self.setPixmap(cover) def set_metadata(self, metadata): @@ -162,11 +162,10 @@ class CoverArtThumbnail(ActiveLabel): if metadata and metadata.images: self.related_images = metadata.images log.debug("%s using images:" % (self.name), metadata.images) - # TODO: Combine all images to show there are different images in use instead of getting the first one - data = [ image for image in metadata.images if image.is_front_image() ] + data = [image for image in metadata.images if image.is_front_image()] if not data: # There's no front image, choose the first one available - data = [ metadata.images[0] ] + data = [metadata.images[0]] self.set_data(data) release = None if metadata: From cb07b7874e6aa5b4e9d22f2b48f505c8d476a327 Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Sun, 5 Mar 2017 20:00:52 +0100 Subject: [PATCH 09/35] Set the default empty cover art when there's no cover art to show When there's no data, we should show the empty cover art box --- picard/ui/coverartbox.py | 59 +++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/picard/ui/coverartbox.py b/picard/ui/coverartbox.py index 1fca3dea8..5622cb7dc 100644 --- a/picard/ui/coverartbox.py +++ b/picard/ui/coverartbox.py @@ -126,35 +126,38 @@ class CoverArtThumbnail(ActiveLabel): return cover = self.shadow - if self.data: - w, h, displacements = (121, 121, 20) - if pixmap is None: - if len(self.data) == 1: - pixmap = QtGui.QPixmap() - pixmap.loadFromData(self.data[0].data) - else: - stack_width, stack_height = (w + displacements * (len(self.data) - 1), h + displacements * (len(self.data) - 1)) - pixmap = QtGui.QPixmap(stack_width, stack_height) - bgcolor = self.palette().color(QtGui.QPalette.Window) - painter = QtGui.QPainter(pixmap) - painter.fillRect(QtCore.QRectF(0, 0, stack_width, stack_height), bgcolor) - x = w / 2 - y = h / 2 - for image in self.data: - thumb = QtGui.QPixmap() - thumb.loadFromData(image.data) - thumb = self.decorate_cover(thumb) - painter.drawPixmap(x - thumb.width() / 2, y - thumb.height() / 2, thumb) - x += displacements - y += displacements - painter.end() - pixmap = pixmap.scaled(w, h, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) - self.setPixmap(pixmap) - pixmap = None + if not self.data: + self.setPixmap(self.shadow) + return - if pixmap and not pixmap.isNull(): - cover = self.decorate_cover(pixmap) - self.setPixmap(cover) + w, h, displacements = (121, 121, 20) + if pixmap is None: + if len(self.data) == 1: + pixmap = QtGui.QPixmap() + pixmap.loadFromData(self.data[0].data) + else: + stack_width, stack_height = (w + displacements * (len(self.data) - 1), h + displacements * (len(self.data) - 1)) + pixmap = QtGui.QPixmap(stack_width, stack_height) + bgcolor = self.palette().color(QtGui.QPalette.Window) + painter = QtGui.QPainter(pixmap) + painter.fillRect(QtCore.QRectF(0, 0, stack_width, stack_height), bgcolor) + x = w / 2 + y = h / 2 + for image in self.data: + thumb = QtGui.QPixmap() + thumb.loadFromData(image.data) + thumb = self.decorate_cover(thumb) + painter.drawPixmap(x - thumb.width() / 2, y - thumb.height() / 2, thumb) + x += displacements + y += displacements + painter.end() + pixmap = pixmap.scaled(w, h, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) + self.setPixmap(pixmap) + pixmap = None + + if pixmap and not pixmap.isNull(): + cover = self.decorate_cover(pixmap) + self.setPixmap(cover) def set_metadata(self, metadata): data = None From c67decd47e84bf0d67b3fee407b912ee5be22f1b Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Fri, 17 Feb 2017 10:16:29 +0100 Subject: [PATCH 10/35] Update file status after dropping images --- picard/ui/coverartbox.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/picard/ui/coverartbox.py b/picard/ui/coverartbox.py index 5622cb7dc..4aac2d1a0 100644 --- a/picard/ui/coverartbox.py +++ b/picard/ui/coverartbox.py @@ -317,13 +317,16 @@ class CoverArtBox(QtGui.QGroupBox): track.metadata.set_front_image(coverartimage) for file in album.iterfiles(): file.metadata.set_front_image(coverartimage) + file.update() elif isinstance(self.item, Track): track = self.item track.metadata.set_front_image(coverartimage) for file in track.iterfiles(): file.metadata.set_front_image(coverartimage) + file.update() elif isinstance(self.item, File): file = self.item file.metadata.set_front_image(coverartimage) + file.update() self.cover_art.set_metadata(self.item.metadata) self.show() From 42dad8031a729470b80dcb19bddd007feb6a3947 Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Tue, 7 Mar 2017 09:05:00 +0100 Subject: [PATCH 11/35] Move duplicate code to a common private function --- picard/album.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/picard/album.py b/picard/album.py index d2dbb276a..6315c926c 100644 --- a/picard/album.py +++ b/picard/album.py @@ -556,21 +556,21 @@ class Album(DataObject, Item): def update_metadata_images(self): new_images = [] orig_images = [] - for track in self.tracks: - for file in list(track.linked_files): - for image in file.metadata.images: - if image not in new_images: - new_images.append(image) - for image in file.orig_metadata.images: - if image not in orig_images: - orig_images.append(image) - for file in list(self.unmatched_files.files): + + def process_file_images(file): for image in file.metadata.images: if image not in new_images: new_images.append(image) for image in file.orig_metadata.images: if image not in orig_images: orig_images.append(image) + + for track in self.tracks: + for file in list(track.linked_files): + process_file_images(file) + for file in list(self.unmatched_files.files): + process_file_images(file) + self.metadata.images = new_images self.orig_metadata.images = orig_images From fdb3ae4e4437a88a39ba31bb54aa6c83ea2fa6c4 Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Tue, 7 Mar 2017 10:19:29 +0100 Subject: [PATCH 12/35] Implement CoverArtImage.__hash__ This make CoverArtImage objects hashable --- picard/coverart/image.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/picard/coverart/image.py b/picard/coverart/image.py index 69fdd1157..cb036cd47 100644 --- a/picard/coverart/image.py +++ b/picard/coverart/image.py @@ -70,6 +70,9 @@ class DataHash: def __eq__(self, other): return self._hash == other._hash + def hash(self): + return self._hash + def delete_file(self): if self._filename: try: @@ -211,6 +214,11 @@ class CoverArtImage: else: return False + def __hash__(self): + if self.datahash is None: + return 0 + return hash(self.datahash.hash()) + def set_data(self, data): """Store image data in a file, if data already exists in such file it will be re-used and no file write occurs From b1520c155f74da001448418e5bd47525fc8b093c Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Tue, 7 Mar 2017 10:22:19 +0100 Subject: [PATCH 13/35] Cache generated pixmaps in CoverArtThumbnail This commit implements a LRU cache for generated pixmaps so they're not generated each time the function is called (saving a lot of I/O and CPU). The cache is shared by cover_art and orig_cover_art objects and currently is configured to contain a maximum of 40 pixmaps. --- picard/ui/coverartbox.py | 54 ++++++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/picard/ui/coverartbox.py b/picard/ui/coverartbox.py index 4aac2d1a0..b46bbd1d5 100644 --- a/picard/ui/coverartbox.py +++ b/picard/ui/coverartbox.py @@ -37,6 +37,36 @@ if sys.platform == 'darwin': log.warning("Unable to import NSURL, file drag'n'drop might not work correctly") +class LRUCache(dict): + + def __init__(self, max_size): + self._ordered_keys = [] + self._max_size = max_size + + def __getitem__(self, key): + if key in self: + self._ordered_keys.remove(key) + self._ordered_keys.insert(0, key) + return super(LRUCache, self).__getitem__(key) + + def __setitem__(self, key, value): + if key in self: + self._ordered_keys.remove(key) + self._ordered_keys.insert(0, key) + + r = super(LRUCache, self).__setitem__(key, value) + + if len(self) > self._max_size: + item = self._ordered_keys.pop() + super(LRUCache, self).__delitem__(item) + + return r + + def __delitem__(self, key): + self._ordered_keys.remove(key) + super(LRUCache, self).__delitem__(key) + + class ActiveLabel(QtGui.QLabel): """Clickable QLabel.""" @@ -83,7 +113,7 @@ class ActiveLabel(QtGui.QLabel): class CoverArtThumbnail(ActiveLabel): - def __init__(self, active=False, drops=False, name=None, *args, **kwargs): + def __init__(self, active=False, drops=False, name=None, pixmap_cache=None, *args, **kwargs): super(CoverArtThumbnail, self).__init__(active, drops, *args, **kwargs) self.data = None self.name = name @@ -94,6 +124,7 @@ class CoverArtThumbnail(ActiveLabel): self.clicked.connect(self.open_release_page) self.image_dropped.connect(self.fetch_remote_image) self.related_images = list() + self._pixmap_cache = pixmap_cache def __eq__(self, other): if len(self.related_images) or len(other.related_images): @@ -117,7 +148,7 @@ class CoverArtThumbnail(ActiveLabel): painter.end() return cover - def set_data(self, data, force=False, pixmap=None): + def set_data(self, data, force=False): if not force and self.data == data: return @@ -125,16 +156,19 @@ class CoverArtThumbnail(ActiveLabel): if not force and self.parent().isHidden(): return - cover = self.shadow if not self.data: self.setPixmap(self.shadow) return w, h, displacements = (121, 121, 20) - if pixmap is None: + key = hash(tuple(sorted(self.data))) + try: + pixmap = self._pixmap_cache[key] + except KeyError: if len(self.data) == 1: pixmap = QtGui.QPixmap() pixmap.loadFromData(self.data[0].data) + pixmap = self.decorate_cover(pixmap) else: stack_width, stack_height = (w + displacements * (len(self.data) - 1), h + displacements * (len(self.data) - 1)) pixmap = QtGui.QPixmap(stack_width, stack_height) @@ -152,12 +186,9 @@ class CoverArtThumbnail(ActiveLabel): y += displacements painter.end() pixmap = pixmap.scaled(w, h, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) - self.setPixmap(pixmap) - pixmap = None + self._pixmap_cache[key] = pixmap - if pixmap and not pixmap.isNull(): - cover = self.decorate_cover(pixmap) - self.setPixmap(cover) + self.setPixmap(pixmap) def set_metadata(self, metadata): data = None @@ -200,12 +231,13 @@ class CoverArtBox(QtGui.QGroupBox): self.setStyleSheet('''QGroupBox{background-color:none;border:1px;}''') self.setFlat(True) self.item = None + self.pixmap_cache = LRUCache(40) self.cover_art_label = QtGui.QLabel('') self.cover_art_label.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignHCenter) - self.cover_art = CoverArtThumbnail(False, True, "new cover", parent) + self.cover_art = CoverArtThumbnail(False, True, "new cover", self.pixmap_cache, parent) spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.orig_cover_art_label = QtGui.QLabel('') - self.orig_cover_art = CoverArtThumbnail(False, False, "original cover", parent) + self.orig_cover_art = CoverArtThumbnail(False, False, "original cover", self.pixmap_cache, parent) self.orig_cover_art_label.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignHCenter) self.orig_cover_art.setHidden(True) self.show_details_button = QtGui.QPushButton(_(u'Show more details'), self) From b3726f255abcdd95e74cc62b8da1822c44be32dd Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Tue, 7 Mar 2017 10:36:50 +0100 Subject: [PATCH 14/35] Remove unuseful/broken debug message --- picard/ui/coverartbox.py | 1 - 1 file changed, 1 deletion(-) diff --git a/picard/ui/coverartbox.py b/picard/ui/coverartbox.py index b46bbd1d5..8916754f3 100644 --- a/picard/ui/coverartbox.py +++ b/picard/ui/coverartbox.py @@ -195,7 +195,6 @@ class CoverArtThumbnail(ActiveLabel): self.related_images = [] if metadata and metadata.images: self.related_images = metadata.images - log.debug("%s using images:" % (self.name), metadata.images) data = [image for image in metadata.images if image.is_front_image()] if not data: # There's no front image, choose the first one available From a6614eec77505b9c972fb88a3da2917160bd45a0 Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Tue, 7 Mar 2017 16:47:28 +0100 Subject: [PATCH 15/35] Allow to open an info dialog of a track with no linked file Instead of raising an exception, now it's possible to see all images of tracks with no linked file. --- picard/ui/infodialog.py | 17 ++++++++++++++++- picard/ui/mainwindow.py | 10 +++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/picard/ui/infodialog.py b/picard/ui/infodialog.py index b4a90a2c0..0414b90df 100644 --- a/picard/ui/infodialog.py +++ b/picard/ui/infodialog.py @@ -104,7 +104,8 @@ class InfoDialog(PicardDialog): isinstance(obj, Track): # Display existing artwork only if selected object is track object # or linked to a track object - self.display_existing_artwork = True + if getattr(obj, 'orig_metadata', None) is not None: + self.display_existing_artwork = True self.ui.setupUi(self) self.ui.buttonBox.accepted.connect(self.accept) @@ -296,6 +297,20 @@ class AlbumInfoDialog(InfoDialog): tabWidget.setTabText(tab_index, _("&Info")) self.tab_hide(tab) +class TrackInfoDialog(InfoDialog): + + def __init__(self, track, parent=None): + InfoDialog.__init__(self, track, parent) + self.setWindowTitle(_("Track Info")) + + def _display_info_tab(self): + tab = self.ui.info_tab + track = self.obj + tabWidget = self.ui.tabWidget + tab_index = tabWidget.indexOf(tab) + tabWidget.setTabText(tab_index, _("&Info")) + self.tab_hide(tab) + class ClusterInfoDialog(InfoDialog): diff --git a/picard/ui/mainwindow.py b/picard/ui/mainwindow.py index a9f23a0ae..16eafca6a 100644 --- a/picard/ui/mainwindow.py +++ b/picard/ui/mainwindow.py @@ -33,7 +33,7 @@ from picard.ui.metadatabox import MetadataBox from picard.ui.filebrowser import FileBrowser from picard.ui.tagsfromfilenames import TagsFromFileNamesDialog from picard.ui.options.dialog import OptionsDialog -from picard.ui.infodialog import FileInfoDialog, AlbumInfoDialog, ClusterInfoDialog +from picard.ui.infodialog import FileInfoDialog, AlbumInfoDialog, TrackInfoDialog, ClusterInfoDialog from picard.ui.infostatus import InfoStatus from picard.ui.passworddialog import PasswordDialog, ProxyDialog from picard.ui.logview import LogView, HistoryView @@ -842,8 +842,12 @@ class MainWindow(QtGui.QMainWindow): cluster = self.selected_objects[0] dialog = ClusterInfoDialog(cluster, self) else: - file = self.tagger.get_files_from_objects(self.selected_objects)[0] - dialog = FileInfoDialog(file, self) + files = self.tagger.get_files_from_objects(self.selected_objects) + if not files and isinstance(self.selected_objects[0], Track): + track = self.selected_objects[0] + dialog = TrackInfoDialog(track, self) + else: + dialog = FileInfoDialog(files[0], self) dialog.ui.tabWidget.setCurrentIndex(default_tab) dialog.exec_() From 53afbb51be90010b899c33de945d48b02097338f Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Tue, 7 Mar 2017 16:59:25 +0100 Subject: [PATCH 16/35] Fix album cover art for album with no files Retrieve the images from tracks, since an album that doesn't have any linked file, can still have images defined in its tracks. --- picard/album.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/picard/album.py b/picard/album.py index 6315c926c..5934dc85b 100644 --- a/picard/album.py +++ b/picard/album.py @@ -561,11 +561,15 @@ class Album(DataObject, Item): for image in file.metadata.images: if image not in new_images: new_images.append(image) - for image in file.orig_metadata.images: - if image not in orig_images: - orig_images.append(image) + try: + for image in file.orig_metadata.images: + if image not in orig_images: + orig_images.append(image) + except AttributeError: + pass for track in self.tracks: + process_file_images(track) for file in list(track.linked_files): process_file_images(file) for file in list(self.unmatched_files.files): From 9ff5434dd73fa561f1fa464d5731aa3bb0994333 Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Tue, 7 Mar 2017 17:25:42 +0100 Subject: [PATCH 17/35] Remove CoverArtThumbnail.name variable which is not used anymore --- picard/ui/coverartbox.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/picard/ui/coverartbox.py b/picard/ui/coverartbox.py index 8916754f3..a637f3ff7 100644 --- a/picard/ui/coverartbox.py +++ b/picard/ui/coverartbox.py @@ -113,10 +113,9 @@ class ActiveLabel(QtGui.QLabel): class CoverArtThumbnail(ActiveLabel): - def __init__(self, active=False, drops=False, name=None, pixmap_cache=None, *args, **kwargs): + def __init__(self, active=False, drops=False, pixmap_cache=None, *args, **kwargs): super(CoverArtThumbnail, self).__init__(active, drops, *args, **kwargs) self.data = None - self.name = name self.shadow = QtGui.QPixmap(":/images/CoverArtShadow.png") self.release = None self.setPixmap(self.shadow) @@ -233,10 +232,10 @@ class CoverArtBox(QtGui.QGroupBox): self.pixmap_cache = LRUCache(40) self.cover_art_label = QtGui.QLabel('') self.cover_art_label.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignHCenter) - self.cover_art = CoverArtThumbnail(False, True, "new cover", self.pixmap_cache, parent) + self.cover_art = CoverArtThumbnail(False, True, self.pixmap_cache, parent) spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.orig_cover_art_label = QtGui.QLabel('') - self.orig_cover_art = CoverArtThumbnail(False, False, "original cover", self.pixmap_cache, parent) + self.orig_cover_art = CoverArtThumbnail(False, False, self.pixmap_cache, parent) self.orig_cover_art_label.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignHCenter) self.orig_cover_art.setHidden(True) self.show_details_button = QtGui.QPushButton(_(u'Show more details'), self) From 7370819ddba028d19c7077df3c5cfe2aebb4c95c Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Tue, 7 Mar 2017 17:49:10 +0100 Subject: [PATCH 18/35] Move LRUCache to picard/utils/lrucache.py Moved the LRUCache class to its own module in utils, in case it's useful in other parts of the code. --- picard/ui/coverartbox.py | 34 +---------------- picard/util/lrucache.py | 79 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 32 deletions(-) create mode 100644 picard/util/lrucache.py diff --git a/picard/ui/coverartbox.py b/picard/ui/coverartbox.py index a637f3ff7..1c593c676 100644 --- a/picard/ui/coverartbox.py +++ b/picard/ui/coverartbox.py @@ -26,7 +26,8 @@ from picard.album import Album from picard.coverart.image import CoverArtImage, CoverArtImageError from picard.track import Track from picard.file import File -from picard.util import encode_filename, imageinfo +from picard.util import encode_filename, imageinfo, get_file_path +from picard.util.lrucache import LRUCache if sys.platform == 'darwin': try: @@ -37,38 +38,7 @@ if sys.platform == 'darwin': log.warning("Unable to import NSURL, file drag'n'drop might not work correctly") -class LRUCache(dict): - - def __init__(self, max_size): - self._ordered_keys = [] - self._max_size = max_size - - def __getitem__(self, key): - if key in self: - self._ordered_keys.remove(key) - self._ordered_keys.insert(0, key) - return super(LRUCache, self).__getitem__(key) - - def __setitem__(self, key, value): - if key in self: - self._ordered_keys.remove(key) - self._ordered_keys.insert(0, key) - - r = super(LRUCache, self).__setitem__(key, value) - - if len(self) > self._max_size: - item = self._ordered_keys.pop() - super(LRUCache, self).__delitem__(item) - - return r - - def __delitem__(self, key): - self._ordered_keys.remove(key) - super(LRUCache, self).__delitem__(key) - - class ActiveLabel(QtGui.QLabel): - """Clickable QLabel.""" clicked = QtCore.pyqtSignal() diff --git a/picard/util/lrucache.py b/picard/util/lrucache.py new file mode 100644 index 000000000..72261b8cf --- /dev/null +++ b/picard/util/lrucache.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +# +# Picard, the next-generation MusicBrainz tagger +# Copyright (C) 2017 Antonio Larrosa +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# 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. + +""" +Helper class to cache items using a Least Recently Used policy. + +It's originally used to cache generated pixmaps in the CoverArtBox object +but it's generic enough to be used for other purposes if necessary. +The cache will never hold more than max_size items and the item least +recently used will be discarded. + +>>> cache = LRUCache(3) +>>> cache['item1'] = 'some value' +>>> cache['item2'] = 'some other value' +>>> cache['item3'] = 'yet another value' +>>> cache['item1'] +'some value' +>>> cache['item4'] = 'This will push item 2 out of the cache' +>>> cache['item2'] +Traceback (most recent call last): + File "", line 1, in + File "lrucache.py", line 48, in __getitem__ + return super(LRUCache, self).__getitem__(key) +KeyError: 'item2' +>>> cache['item5'] = 'This will push item3 out of the cache' +>>> cache['item3'] +Traceback (most recent call last): + File "", line 1, in + File "lrucache.py", line 48, in __getitem__ + return super(LRUCache, self).__getitem__(key) +KeyError: 'item3' +>>> cache['item1'] +'some value' +""" + + +class LRUCache(dict): + def __init__(self, max_size): + self._ordered_keys = [] + self._max_size = max_size + + def __getitem__(self, key): + if key in self: + self._ordered_keys.remove(key) + self._ordered_keys.insert(0, key) + return super(LRUCache, self).__getitem__(key) + + def __setitem__(self, key, value): + if key in self: + self._ordered_keys.remove(key) + self._ordered_keys.insert(0, key) + + r = super(LRUCache, self).__setitem__(key, value) + + if len(self) > self._max_size: + item = self._ordered_keys.pop() + super(LRUCache, self).__delitem__(item) + + return r + + def __delitem__(self, key): + self._ordered_keys.remove(key) + super(LRUCache, self).__delitem__(key) From 77fbaaee43ac2e4727cee624324a52f7ec104016 Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Tue, 7 Mar 2017 18:36:20 +0100 Subject: [PATCH 19/35] Cleanup the calculations --- picard/ui/coverartbox.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/picard/ui/coverartbox.py b/picard/ui/coverartbox.py index 1c593c676..57c050feb 100644 --- a/picard/ui/coverartbox.py +++ b/picard/ui/coverartbox.py @@ -139,7 +139,8 @@ class CoverArtThumbnail(ActiveLabel): pixmap.loadFromData(self.data[0].data) pixmap = self.decorate_cover(pixmap) else: - stack_width, stack_height = (w + displacements * (len(self.data) - 1), h + displacements * (len(self.data) - 1)) + offset = displacements * (len(self.data) - 1) + stack_width, stack_height = (w + offset, h + offset) pixmap = QtGui.QPixmap(stack_width, stack_height) bgcolor = self.palette().color(QtGui.QPalette.Window) painter = QtGui.QPainter(pixmap) From 269b7f07e38483d51b136f4b5eb8b5cf250e98e8 Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Tue, 7 Mar 2017 18:56:40 +0100 Subject: [PATCH 20/35] PEP8 fixes Remove spaces at start/end of braces --- picard/metadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/picard/metadata.py b/picard/metadata.py index afbb51026..09c0dda4f 100644 --- a/picard/metadata.py +++ b/picard/metadata.py @@ -57,7 +57,7 @@ class Metadata(dict): def set_front_image(self, coverartimage): # First remove all front images - self.images[:] = [ img for img in self.images if not img.is_front_image() ] + self.images[:] = [img for img in self.images if not img.is_front_image()] self.images.append(coverartimage) @property From 1982d0353580121b2cfd980e1b3dabeac89f76d0 Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Tue, 7 Mar 2017 19:51:07 +0100 Subject: [PATCH 21/35] Enable cover artwork diff for albums And remove an unused variable in TrackInfoDialog --- picard/ui/infodialog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/picard/ui/infodialog.py b/picard/ui/infodialog.py index 0414b90df..86a04dbb3 100644 --- a/picard/ui/infodialog.py +++ b/picard/ui/infodialog.py @@ -24,6 +24,7 @@ from PyQt4 import QtGui, QtCore from picard import log from picard.file import File from picard.track import Track +from picard.album import Album from picard.coverart.image import CoverArtImageIOError from picard.util import format_time, encode_filename, bytes2human, webbrowser2, union_sorted_lists from picard.ui import PicardDialog @@ -101,7 +102,7 @@ class InfoDialog(PicardDialog): self.ui = Ui_InfoDialog() self.display_existing_artwork = False if isinstance(obj, File) and isinstance(obj.parent, Track) or \ - isinstance(obj, Track): + isinstance(obj, Track) or isinstance(obj, Album): # Display existing artwork only if selected object is track object # or linked to a track object if getattr(obj, 'orig_metadata', None) is not None: @@ -305,7 +306,6 @@ class TrackInfoDialog(InfoDialog): def _display_info_tab(self): tab = self.ui.info_tab - track = self.obj tabWidget = self.ui.tabWidget tab_index = tabWidget.indexOf(tab) tabWidget.setTabText(tab_index, _("&Info")) From 5e14e084bfcb7a46bd487cf5a1e415a2f8e40bef Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Tue, 7 Mar 2017 20:04:04 +0100 Subject: [PATCH 22/35] Slightly improve the readability of the code --- picard/ui/infodialog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/picard/ui/infodialog.py b/picard/ui/infodialog.py index 86a04dbb3..8a2b86df3 100644 --- a/picard/ui/infodialog.py +++ b/picard/ui/infodialog.py @@ -101,8 +101,8 @@ class InfoDialog(PicardDialog): self.obj = obj self.ui = Ui_InfoDialog() self.display_existing_artwork = False - if isinstance(obj, File) and isinstance(obj.parent, Track) or \ - isinstance(obj, Track) or isinstance(obj, Album): + if (isinstance(obj, File) and isinstance(obj.parent, Track) or + isinstance(obj, (Track, Album))): # Display existing artwork only if selected object is track object # or linked to a track object if getattr(obj, 'orig_metadata', None) is not None: From f9befdd0af0cf6325f5462a9cb84d8cdf6b5638c Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Wed, 8 Mar 2017 07:05:48 +0100 Subject: [PATCH 23/35] Move documentation inside class definition --- picard/util/lrucache.py | 64 ++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/picard/util/lrucache.py b/picard/util/lrucache.py index 72261b8cf..42c147970 100644 --- a/picard/util/lrucache.py +++ b/picard/util/lrucache.py @@ -17,40 +17,40 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -""" -Helper class to cache items using a Least Recently Used policy. - -It's originally used to cache generated pixmaps in the CoverArtBox object -but it's generic enough to be used for other purposes if necessary. -The cache will never hold more than max_size items and the item least -recently used will be discarded. - ->>> cache = LRUCache(3) ->>> cache['item1'] = 'some value' ->>> cache['item2'] = 'some other value' ->>> cache['item3'] = 'yet another value' ->>> cache['item1'] -'some value' ->>> cache['item4'] = 'This will push item 2 out of the cache' ->>> cache['item2'] -Traceback (most recent call last): - File "", line 1, in - File "lrucache.py", line 48, in __getitem__ - return super(LRUCache, self).__getitem__(key) -KeyError: 'item2' ->>> cache['item5'] = 'This will push item3 out of the cache' ->>> cache['item3'] -Traceback (most recent call last): - File "", line 1, in - File "lrucache.py", line 48, in __getitem__ - return super(LRUCache, self).__getitem__(key) -KeyError: 'item3' ->>> cache['item1'] -'some value' -""" - class LRUCache(dict): + """ + Helper class to cache items using a Least Recently Used policy. + + It's originally used to cache generated pixmaps in the CoverArtBox object + but it's generic enough to be used for other purposes if necessary. + The cache will never hold more than max_size items and the item least + recently used will be discarded. + + >>> cache = LRUCache(3) + >>> cache['item1'] = 'some value' + >>> cache['item2'] = 'some other value' + >>> cache['item3'] = 'yet another value' + >>> cache['item1'] + 'some value' + >>> cache['item4'] = 'This will push item 2 out of the cache' + >>> cache['item2'] + Traceback (most recent call last): + File "", line 1, in + File "lrucache.py", line 48, in __getitem__ + return super(LRUCache, self).__getitem__(key) + KeyError: 'item2' + >>> cache['item5'] = 'This will push item3 out of the cache' + >>> cache['item3'] + Traceback (most recent call last): + File "", line 1, in + File "lrucache.py", line 48, in __getitem__ + return super(LRUCache, self).__getitem__(key) + KeyError: 'item3' + >>> cache['item1'] + 'some value' + """ + def __init__(self, max_size): self._ordered_keys = [] self._max_size = max_size From 0710a0d7a14a80d9ac7cd486b03e6339f2bcbc11 Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Wed, 8 Mar 2017 08:02:59 +0100 Subject: [PATCH 24/35] Rename file variable to obj I changed it in e9fa6c887f94306c1aee3f3eb90f9db05951cb97 so a track is passed to that function some times, so better reflect that in the variable name too. --- picard/album.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/picard/album.py b/picard/album.py index 5934dc85b..d7151f8e7 100644 --- a/picard/album.py +++ b/picard/album.py @@ -557,23 +557,23 @@ class Album(DataObject, Item): new_images = [] orig_images = [] - def process_file_images(file): - for image in file.metadata.images: + def process_images(obj): + for image in obj.metadata.images: if image not in new_images: new_images.append(image) try: - for image in file.orig_metadata.images: + for image in obj.orig_metadata.images: if image not in orig_images: orig_images.append(image) except AttributeError: pass for track in self.tracks: - process_file_images(track) + process_images(track) for file in list(track.linked_files): - process_file_images(file) + process_images(file) for file in list(self.unmatched_files.files): - process_file_images(file) + process_images(file) self.metadata.images = new_images self.orig_metadata.images = orig_images From cba798505afa26503b2586881cb077b8709aaa12 Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Wed, 8 Mar 2017 09:57:33 +0100 Subject: [PATCH 25/35] Small refactorization of TrackInfoDialog In order to fix tracks without files not having the info button available, I changed it to allow 0, 1 or multiple files. If the track doesn't have any linked file, only the cover art is shown (if available). If the track has n files, the info tab shows information from the all the files. --- picard/track.py | 2 +- picard/ui/coverartbox.py | 2 +- picard/ui/infodialog.py | 28 ++++++++++++++++++++++------ 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/picard/track.py b/picard/track.py index 8cfbb1956..3130f3f5f 100644 --- a/picard/track.py +++ b/picard/track.py @@ -110,7 +110,7 @@ class Track(DataObject, Item): return True def can_view_info(self): - return self.num_linked_files == 1 + return self.num_linked_files == 1 or self.metadata.images def column(self, column): m = self.metadata diff --git a/picard/ui/coverartbox.py b/picard/ui/coverartbox.py index 57c050feb..80226a4ce 100644 --- a/picard/ui/coverartbox.py +++ b/picard/ui/coverartbox.py @@ -227,7 +227,7 @@ class CoverArtBox(QtGui.QGroupBox): # We want to show the 2 coverarts only if they are different # and orig_cover_art data is set and not the default cd shadow if self.orig_cover_art.data is None or self.cover_art == self.orig_cover_art: - self.show_details_button.setHidden(len(self.cover_art.related_images) <= 1) + self.show_details_button.setHidden(len(self.cover_art.related_images) == 0) self.orig_cover_art.setHidden(True) self.cover_art_label.setText('') self.orig_cover_art_label.setText('') diff --git a/picard/ui/infodialog.py b/picard/ui/infodialog.py index 8a2b86df3..f5db09e46 100644 --- a/picard/ui/infodialog.py +++ b/picard/ui/infodialog.py @@ -102,11 +102,13 @@ class InfoDialog(PicardDialog): self.ui = Ui_InfoDialog() self.display_existing_artwork = False if (isinstance(obj, File) and isinstance(obj.parent, Track) or - isinstance(obj, (Track, Album))): + isinstance(obj, Track)): # Display existing artwork only if selected object is track object # or linked to a track object if getattr(obj, 'orig_metadata', None) is not None: self.display_existing_artwork = True + elif isinstance(obj, Album) and obj.get_num_total_files() > 0: + self.display_existing_artwork = True self.ui.setupUi(self) self.ui.buttonBox.accepted.connect(self.accept) @@ -238,8 +240,8 @@ class FileInfoDialog(InfoDialog): InfoDialog.__init__(self, file, parent) self.setWindowTitle(_("Info") + " - " + file.base_filename) - def _display_info_tab(self): - file = self.obj + @staticmethod + def format_file_info(file): info = [] info.append((_('Filename:'), file.filename)) if '~format' in file.orig_metadata: @@ -267,9 +269,13 @@ class FileInfoDialog(InfoDialog): else: ch = str(ch) info.append((_('Channels:'), ch)) - text = '
'.join(map(lambda i: '%s
%s' % + return '
'.join(map(lambda i: '%s
%s' % (cgi.escape(i[0]), cgi.escape(i[1])), info)) + + def _display_info_tab(self): + file = self.obj + text = FileInfoDialog.format_file_info(file) self.ui.info.setText(text) @@ -298,18 +304,28 @@ class AlbumInfoDialog(InfoDialog): tabWidget.setTabText(tab_index, _("&Info")) self.tab_hide(tab) -class TrackInfoDialog(InfoDialog): +class TrackInfoDialog(FileInfoDialog): def __init__(self, track, parent=None): InfoDialog.__init__(self, track, parent) self.setWindowTitle(_("Track Info")) def _display_info_tab(self): + track = self.obj tab = self.ui.info_tab tabWidget = self.ui.tabWidget tab_index = tabWidget.indexOf(tab) + if track.num_linked_files == 0: + tabWidget.setTabText(tab_index, _("&Info")) + self.tab_hide(tab) + return + tabWidget.setTabText(tab_index, _("&Info")) - self.tab_hide(tab) + text = ungettext("%i file in this track", "%i files in this track", + track.num_linked_files) % track.num_linked_files + info_files = [FileInfoDialog.format_file_info(file) for file in track.linked_files] + text += '
' + '
'.join(info_files) + self.ui.info.setText(text) class ClusterInfoDialog(InfoDialog): From 0dd1e8d6aa078f6c55cd5a2dcb0b8e0495339c95 Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Wed, 8 Mar 2017 10:13:31 +0100 Subject: [PATCH 26/35] Start the correct InfoDialog I forgot to add the changes from this file to the previous commit (654490053ab038c27a628510c9f921ba2bf1be02) --- picard/ui/mainwindow.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/picard/ui/mainwindow.py b/picard/ui/mainwindow.py index 16eafca6a..1b4a72ec9 100644 --- a/picard/ui/mainwindow.py +++ b/picard/ui/mainwindow.py @@ -841,13 +841,12 @@ class MainWindow(QtGui.QMainWindow): elif isinstance(self.selected_objects[0], Cluster): cluster = self.selected_objects[0] dialog = ClusterInfoDialog(cluster, self) + elif isinstance(self.selected_objects[0], Track): + track = self.selected_objects[0] + dialog = TrackInfoDialog(track, self) else: - files = self.tagger.get_files_from_objects(self.selected_objects) - if not files and isinstance(self.selected_objects[0], Track): - track = self.selected_objects[0] - dialog = TrackInfoDialog(track, self) - else: - dialog = FileInfoDialog(files[0], self) + file = self.tagger.get_files_from_objects(self.selected_objects)[0] + dialog = FileInfoDialog(file, self) dialog.ui.tabWidget.setCurrentIndex(default_tab) dialog.exec_() From e2e2ca39a69f735d190b01fff2e25149c5089e35 Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Wed, 8 Mar 2017 11:03:03 +0100 Subject: [PATCH 27/35] Update album metadata images when Files/Tracks emit signal Add a signal to Files/Tracks that gets emitted when the images change (or might have changed), and update the album's metadata images on such signals instead of each time the album is selected in the user interface. --- picard/album.py | 11 +++++++++++ picard/file.py | 3 +++ picard/track.py | 3 +++ picard/ui/coverartbox.py | 5 +++++ picard/ui/mainwindow.py | 1 - 5 files changed, 22 insertions(+), 1 deletion(-) diff --git a/picard/album.py b/picard/album.py index d7151f8e7..81da90d25 100644 --- a/picard/album.py +++ b/picard/album.py @@ -72,6 +72,7 @@ class Album(DataObject, Item): self.errors = [] self.status = None self._album_artists = [] + self.update_metadata_images_enabled = True def __repr__(self): return '' % (self.id, self.metadata[u"album"]) @@ -255,6 +256,7 @@ class Album(DataObject, Item): self._tracks_loaded = True if not self._requests: + self.update_metadata_images_enabled = False # Prepare parser for user's script if config.setting["enable_tagger_scripts"]: for s_pos, s_name, s_enabled, s_text in config.setting["list_of_scripts"]: @@ -276,6 +278,7 @@ class Album(DataObject, Item): self._new_metadata.strip_whitespace() for track in self.tracks: + track.metadata_images_changed.connect(self.update_metadata_images) for file in list(track.linked_files): file.move(self.unmatched_files) self.metadata = self._new_metadata @@ -285,6 +288,7 @@ class Album(DataObject, Item): self.loaded = True self.status = None self.match_files(self.unmatched_files.files) + self.update_metadata_images_enabled = True self.update() self.tagger.window.set_statusbar_message( N_('Album %(id)s loaded: %(artist)s - %(album)s'), @@ -379,14 +383,19 @@ class Album(DataObject, Item): def update(self, update_tracks=True): if self.item: self.item.update(update_tracks) + self.update_metadata_images() def _add_file(self, track, file): self._files += 1 self.update(update_tracks=False) + file.metadata_images_changed.connect(self.update_metadata_images) + self.update_metadata_images() def _remove_file(self, track, file): self._files -= 1 self.update(update_tracks=False) + file.metadata_images_changed.disconnect(self.update_metadata_images) + self.update_metadata_images() def match_files(self, files, use_recordingid=True): """Match files to tracks on this album, based on metadata similarity or recordingid.""" @@ -554,6 +563,8 @@ class Album(DataObject, Item): self.load(priority=True, refresh=True) def update_metadata_images(self): + if not self.update_metadata_images_enabled: + return new_images = [] orig_images = [] diff --git a/picard/file.py b/picard/file.py index cf20ed8cf..d2097259b 100644 --- a/picard/file.py +++ b/picard/file.py @@ -54,6 +54,8 @@ from picard.const import QUERY_LIMIT class File(QtCore.QObject, Item): + metadata_images_changed = QtCore.pyqtSignal() + UNDEFINED = -1 PENDING = 0 NORMAL = 1 @@ -156,6 +158,7 @@ class File(QtCore.QObject, Item): if acoustid: self.metadata["acoustid_id"] = acoustid + self.metadata_images_changed.emit() def has_error(self): return self.state == File.ERROR diff --git a/picard/track.py b/picard/track.py index 3130f3f5f..c1e33d1d4 100644 --- a/picard/track.py +++ b/picard/track.py @@ -19,6 +19,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from functools import partial +from PyQt4 import QtCore from picard import config, log from picard.metadata import Metadata, run_track_metadata_processors from picard.dataobj import DataObject @@ -44,6 +45,8 @@ class TrackArtist(DataObject): class Track(DataObject, Item): + metadata_images_changed = QtCore.pyqtSignal() + def __init__(self, id, album=None): DataObject.__init__(self, id) self.album = album diff --git a/picard/ui/coverartbox.py b/picard/ui/coverartbox.py index 80226a4ce..f7e7580b4 100644 --- a/picard/ui/coverartbox.py +++ b/picard/ui/coverartbox.py @@ -316,18 +316,23 @@ class CoverArtBox(QtGui.QGroupBox): album.metadata.set_front_image(coverartimage) for track in album.tracks: track.metadata.set_front_image(coverartimage) + track.metadata_images_changed.emit() for file in album.iterfiles(): file.metadata.set_front_image(coverartimage) + file.metadata_images_changed.emit() file.update() elif isinstance(self.item, Track): track = self.item track.metadata.set_front_image(coverartimage) + track.metadata_images_changed.emit() for file in track.iterfiles(): file.metadata.set_front_image(coverartimage) + file.metadata_images_changed.emit() file.update() elif isinstance(self.item, File): file = self.item file.metadata.set_front_image(coverartimage) + file.metadata_images_changed.emit() file.update() self.cover_art.set_metadata(self.item.metadata) self.show() diff --git a/picard/ui/mainwindow.py b/picard/ui/mainwindow.py index 1b4a72ec9..2ae7bf465 100644 --- a/picard/ui/mainwindow.py +++ b/picard/ui/mainwindow.py @@ -956,7 +956,6 @@ class MainWindow(QtGui.QMainWindow): self.set_statusbar_message(msg, mparms, echo=None, history=None) elif isinstance(obj, Album): - obj.update_metadata_images() metadata = obj.metadata orig_metadata = obj.orig_metadata elif obj.can_edit_tags(): From 270b07d08a634cffdd9cc3bfa2199efce4fdd741 Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Wed, 8 Mar 2017 11:18:23 +0100 Subject: [PATCH 28/35] Reduce the number of metadata image updates when dropping an image Disable/enable the update of images also before/after setting a dropped image --- picard/album.py | 9 +++++++-- picard/ui/coverartbox.py | 5 ++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/picard/album.py b/picard/album.py index 81da90d25..42ffde220 100644 --- a/picard/album.py +++ b/picard/album.py @@ -85,6 +85,11 @@ class Album(DataObject, Item): for file in self.unmatched_files.iterfiles(): yield file + def enable_update_metadata_images(self, enabled): + self.update_metadata_images_enabled = enabled + if enabled: + self.update_metadata_images() + def append_album_artist(self, id): """Append artist id to the list of album artists and return an AlbumArtist instance""" @@ -256,7 +261,7 @@ class Album(DataObject, Item): self._tracks_loaded = True if not self._requests: - self.update_metadata_images_enabled = False + self.enable_update_metadata_images(False) # Prepare parser for user's script if config.setting["enable_tagger_scripts"]: for s_pos, s_name, s_enabled, s_text in config.setting["list_of_scripts"]: @@ -288,8 +293,8 @@ class Album(DataObject, Item): self.loaded = True self.status = None self.match_files(self.unmatched_files.files) - self.update_metadata_images_enabled = True self.update() + self.enable_update_metadata_images(True) self.tagger.window.set_statusbar_message( N_('Album %(id)s loaded: %(artist)s - %(album)s'), { diff --git a/picard/ui/coverartbox.py b/picard/ui/coverartbox.py index f7e7580b4..c44b13852 100644 --- a/picard/ui/coverartbox.py +++ b/picard/ui/coverartbox.py @@ -313,7 +313,7 @@ class CoverArtBox(QtGui.QGroupBox): return if isinstance(self.item, Album): album = self.item - album.metadata.set_front_image(coverartimage) + album.enable_update_metadata_images(False) for track in album.tracks: track.metadata.set_front_image(coverartimage) track.metadata_images_changed.emit() @@ -321,14 +321,17 @@ class CoverArtBox(QtGui.QGroupBox): file.metadata.set_front_image(coverartimage) file.metadata_images_changed.emit() file.update() + album.enable_update_metadata_images(True) elif isinstance(self.item, Track): track = self.item + track.album.enable_update_metadata_images(False) track.metadata.set_front_image(coverartimage) track.metadata_images_changed.emit() for file in track.iterfiles(): file.metadata.set_front_image(coverartimage) file.metadata_images_changed.emit() file.update() + track.album.enable_update_metadata_images(True) elif isinstance(self.item, File): file = self.item file.metadata.set_front_image(coverartimage) From 3599f74f1634dcf9c369d4b7b43e31113e808907 Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Wed, 8 Mar 2017 12:36:18 +0100 Subject: [PATCH 29/35] Show cover stack from front-to-bottom and from bottom-left to top-right --- picard/ui/coverartbox.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/picard/ui/coverartbox.py b/picard/ui/coverartbox.py index c44b13852..c51cbe35f 100644 --- a/picard/ui/coverartbox.py +++ b/picard/ui/coverartbox.py @@ -145,14 +145,14 @@ class CoverArtThumbnail(ActiveLabel): bgcolor = self.palette().color(QtGui.QPalette.Window) painter = QtGui.QPainter(pixmap) painter.fillRect(QtCore.QRectF(0, 0, stack_width, stack_height), bgcolor) - x = w / 2 + x = stack_width - w / 2 y = h / 2 - for image in self.data: + for image in reversed(self.data): thumb = QtGui.QPixmap() thumb.loadFromData(image.data) thumb = self.decorate_cover(thumb) painter.drawPixmap(x - thumb.width() / 2, y - thumb.height() / 2, thumb) - x += displacements + x -= displacements y += displacements painter.end() pixmap = pixmap.scaled(w, h, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) From 63908e861933adc7bc6e302e589f2fe44e4a8931 Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Wed, 8 Mar 2017 18:44:46 +0100 Subject: [PATCH 30/35] Make front cover glow when there are no common images among tracks If all tracks/files have a common set of images, show the stack of cover art images as usual. If any track/file has a different set of images, the front image is painted with a darkgoldenrod glow (the same color used for the MetadataBox for tags with different values). Also, fixed the draw of the stack so the bottom cover is drawn correctly. --- picard/album.py | 53 +++++++++++++++++++++++++++------------- picard/ui/coverartbox.py | 34 ++++++++++++++++++-------- 2 files changed, 60 insertions(+), 27 deletions(-) diff --git a/picard/album.py b/picard/album.py index 42ffde220..273fd5870 100644 --- a/picard/album.py +++ b/picard/album.py @@ -570,29 +570,48 @@ class Album(DataObject, Item): def update_metadata_images(self): if not self.update_metadata_images_enabled: return - new_images = [] - orig_images = [] - def process_images(obj): - for image in obj.metadata.images: - if image not in new_images: - new_images.append(image) - try: - for image in obj.orig_metadata.images: - if image not in orig_images: - orig_images.append(image) - except AttributeError: - pass + class State: + new_images = [] + orig_images = [] + has_common_new_images = True + has_common_orig_images = True + first_new_obj = True + first_orig_obj = True + + state = State() + + def process_images(state, obj): + # Check new images + if state.first_new_obj: + state.new_images = obj.metadata.images[:] + state.first_new_obj = False + else: + if state.new_images != obj.metadata.images: + state.has_common_new_images = False + state.new_images.extend([image for image in obj.metadata.images if image not in state.new_images]) + if isinstance(obj, Track): + return + # Check orig images, but not for Tracks (which don't have orig_metadata) + if state.first_orig_obj: + state.orig_images = obj.orig_metadata.images[:] + state.first_orig_obj = False + else: + if state.orig_images != obj.orig_metadata.images: + state.has_common_orig_images = False + state.orig_images.extend([image for image in obj.orig_metadata.images if image not in state.orig_images]) for track in self.tracks: - process_images(track) + process_images(state, track) for file in list(track.linked_files): - process_images(file) + process_images(state, file) for file in list(self.unmatched_files.files): - process_images(file) + process_images(state, file) - self.metadata.images = new_images - self.orig_metadata.images = orig_images + self.metadata.images = state.new_images + self.metadata.has_common_images = state.has_common_new_images + self.orig_metadata.images = state.orig_images + self.orig_metadata.has_common_images = state.has_common_orig_images class NatAlbum(Album): diff --git a/picard/ui/coverartbox.py b/picard/ui/coverartbox.py index c51cbe35f..da13646d9 100644 --- a/picard/ui/coverartbox.py +++ b/picard/ui/coverartbox.py @@ -117,7 +117,7 @@ class CoverArtThumbnail(ActiveLabel): painter.end() return cover - def set_data(self, data, force=False): + def set_data(self, data, force=False, has_common_images=True): if not force and self.data == data: return @@ -129,8 +129,8 @@ class CoverArtThumbnail(ActiveLabel): self.setPixmap(self.shadow) return - w, h, displacements = (121, 121, 20) - key = hash(tuple(sorted(self.data))) + w, h, displacements = (128, 128, 20) + key = hash(tuple(sorted(self.data)) + (has_common_images,)) try: pixmap = self._pixmap_cache[key] except KeyError: @@ -145,15 +145,28 @@ class CoverArtThumbnail(ActiveLabel): bgcolor = self.palette().color(QtGui.QPalette.Window) painter = QtGui.QPainter(pixmap) painter.fillRect(QtCore.QRectF(0, 0, stack_width, stack_height), bgcolor) - x = stack_width - w / 2 - y = h / 2 + cx = stack_width - w / 2 + cy = h / 2 for image in reversed(self.data): thumb = QtGui.QPixmap() thumb.loadFromData(image.data) - thumb = self.decorate_cover(thumb) - painter.drawPixmap(x - thumb.width() / 2, y - thumb.height() / 2, thumb) - x -= displacements - y += displacements + thumb = self.decorate_cover(thumb) # QtGui.QColor("darkgoldenrod") + x, y = (cx - thumb.width() / 2, cy - thumb.height() / 2) + painter.drawPixmap(x, y, thumb) + cx -= displacements + cy += displacements + if not has_common_images: + color = QtGui.QColor("darkgoldenrod") + border_length = 10 + for k in range(border_length): + color.setAlpha(255 - k * 255 / border_length) + painter.setPen(color) + painter.drawLine(x, y - k - 1, x + 121 + k + 1, y - k - 1) + painter.drawLine(x + 121 + k + 2, y - 1 - k, x + 121 + k + 2, y + 121 + 4) + for k in range(5): + bgcolor.setAlpha(80 + k * 255 / 7) + painter.setPen(bgcolor) + painter.drawLine(x + 121 + 2, y + 121 + 2 + k, x + 121 + border_length + 2, y + 121 + 2 + k) painter.end() pixmap = pixmap.scaled(w, h, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) self._pixmap_cache[key] = pixmap @@ -169,7 +182,8 @@ class CoverArtThumbnail(ActiveLabel): if not data: # There's no front image, choose the first one available data = [metadata.images[0]] - self.set_data(data) + has_common_images = getattr(metadata, 'has_common_images', True) + self.set_data(data, has_common_images=has_common_images) release = None if metadata: release = metadata.get("musicbrainz_albumid", None) From 8057f2bed02f15ca95b44051b8c665479ea8db85 Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Wed, 8 Mar 2017 19:01:16 +0100 Subject: [PATCH 31/35] Add information about common/different images on tooltip --- picard/ui/coverartbox.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/picard/ui/coverartbox.py b/picard/ui/coverartbox.py index da13646d9..a5897b75c 100644 --- a/picard/ui/coverartbox.py +++ b/picard/ui/coverartbox.py @@ -189,7 +189,14 @@ class CoverArtThumbnail(ActiveLabel): release = metadata.get("musicbrainz_albumid", None) if release: self.setActive(True) - self.setToolTip(_(u"View release on MusicBrainz")) + text = _(u"View release on MusicBrainz") + if hasattr(metadata, 'has_common_images'): + text += '
' + if has_common_images: + text += _(u'Common images on all tracks') + else: + text += _(u'Tracks contain different images') + self.setToolTip(text) else: self.setActive(False) self.setToolTip("") From f5c82adfbda26eab0367b4941a088b1c47f7b80e Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Wed, 8 Mar 2017 21:22:39 +0100 Subject: [PATCH 32/35] Limit the number of images to draw in a stack to 4 If there are more than 4 images, draw only three and a kind of "grey stack of covers" that can be easily identified with "and more covers" --- picard/ui/coverartbox.py | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/picard/ui/coverartbox.py b/picard/ui/coverartbox.py index a5897b75c..462f5ba7c 100644 --- a/picard/ui/coverartbox.py +++ b/picard/ui/coverartbox.py @@ -139,7 +139,13 @@ class CoverArtThumbnail(ActiveLabel): pixmap.loadFromData(self.data[0].data) pixmap = self.decorate_cover(pixmap) else: - offset = displacements * (len(self.data) - 1) + limited = len(self.data) > 4 + if limited: + data_to_paint = data[:3] + offset = displacements * len(data_to_paint) + else: + data_to_paint = data + offset = displacements * (len(data_to_paint) - 1) stack_width, stack_height = (w + offset, h + offset) pixmap = QtGui.QPixmap(stack_width, stack_height) bgcolor = self.palette().color(QtGui.QPalette.Window) @@ -147,9 +153,23 @@ class CoverArtThumbnail(ActiveLabel): painter.fillRect(QtCore.QRectF(0, 0, stack_width, stack_height), bgcolor) cx = stack_width - w / 2 cy = h / 2 - for image in reversed(self.data): - thumb = QtGui.QPixmap() - thumb.loadFromData(image.data) + if limited: + x, y = (cx - self.shadow.width() / 2, cy - self.shadow.height() / 2) + for i in range(3): + painter.drawPixmap(x, y, self.shadow) + x -= displacements / 3 + y += displacements / 3 + cx -= displacements + cy += displacements + else: + cx = stack_width - w / 2 + cy = h / 2 + for image in reversed(data_to_paint): + if isinstance(image, QtGui.QPixmap): + thumb = image + else: + thumb = QtGui.QPixmap() + thumb.loadFromData(image.data) thumb = self.decorate_cover(thumb) # QtGui.QColor("darkgoldenrod") x, y = (cx - thumb.width() / 2, cy - thumb.height() / 2) painter.drawPixmap(x, y, thumb) From 65499261eccc6433aa9b5e26cc1a30492b5ba59b Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Thu, 9 Mar 2017 12:28:11 +0100 Subject: [PATCH 33/35] Small fixes Add a constant to hold the maximum number of covers to draw in a stack. Extract html tags out of a translated message. Reformat an if expression to move overators to front. Remove an unneeded comment. --- picard/ui/coverartbox.py | 13 +++++++------ picard/ui/infodialog.py | 5 +++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/picard/ui/coverartbox.py b/picard/ui/coverartbox.py index 462f5ba7c..1b9cb7864 100644 --- a/picard/ui/coverartbox.py +++ b/picard/ui/coverartbox.py @@ -82,6 +82,7 @@ class ActiveLabel(QtGui.QLabel): class CoverArtThumbnail(ActiveLabel): + MAX_COVERS_TO_STACK = 4 def __init__(self, active=False, drops=False, pixmap_cache=None, *args, **kwargs): super(CoverArtThumbnail, self).__init__(active, drops, *args, **kwargs) @@ -139,9 +140,9 @@ class CoverArtThumbnail(ActiveLabel): pixmap.loadFromData(self.data[0].data) pixmap = self.decorate_cover(pixmap) else: - limited = len(self.data) > 4 + limited = len(self.data) > self.MAX_COVERS_TO_STACK if limited: - data_to_paint = data[:3] + data_to_paint = data[:self.MAX_COVERS_TO_STACK - 1] offset = displacements * len(data_to_paint) else: data_to_paint = data @@ -170,7 +171,7 @@ class CoverArtThumbnail(ActiveLabel): else: thumb = QtGui.QPixmap() thumb.loadFromData(image.data) - thumb = self.decorate_cover(thumb) # QtGui.QColor("darkgoldenrod") + thumb = self.decorate_cover(thumb) x, y = (cx - thumb.width() / 2, cy - thumb.height() / 2) painter.drawPixmap(x, y, thumb) cx -= displacements @@ -211,11 +212,11 @@ class CoverArtThumbnail(ActiveLabel): self.setActive(True) text = _(u"View release on MusicBrainz") if hasattr(metadata, 'has_common_images'): - text += '
' if has_common_images: - text += _(u'Common images on all tracks') + note = _(u'Common images on all tracks') else: - text += _(u'Tracks contain different images') + note = _(u'Tracks contain different images') + text += '
%s' % note self.setToolTip(text) else: self.setActive(False) diff --git a/picard/ui/infodialog.py b/picard/ui/infodialog.py index f5db09e46..5496f205b 100644 --- a/picard/ui/infodialog.py +++ b/picard/ui/infodialog.py @@ -101,8 +101,9 @@ class InfoDialog(PicardDialog): self.obj = obj self.ui = Ui_InfoDialog() self.display_existing_artwork = False - if (isinstance(obj, File) and isinstance(obj.parent, Track) or - isinstance(obj, Track)): + if (isinstance(obj, File) + and isinstance(obj.parent, Track) + or isinstance(obj, Track)): # Display existing artwork only if selected object is track object # or linked to a track object if getattr(obj, 'orig_metadata', None) is not None: From 0992a6cb7ea45934c34fdab5c583d2e7cca3e3ac Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Thu, 9 Mar 2017 12:59:01 +0100 Subject: [PATCH 34/35] Move MAX_COVERS_TO_STACK to picard.const --- picard/const/__init__.py | 3 +++ picard/ui/coverartbox.py | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/picard/const/__init__.py b/picard/const/__init__.py index 5f22eb3dc..878765398 100644 --- a/picard/const/__init__.py +++ b/picard/const/__init__.py @@ -113,3 +113,6 @@ PLUGINS_API = { # Default query limit QUERY_LIMIT = 25 + +# Maximum number of covers to draw in a stack in CoverArtThumbnail +MAX_COVERS_TO_STACK = 4 diff --git a/picard/ui/coverartbox.py b/picard/ui/coverartbox.py index 1b9cb7864..bb359fef9 100644 --- a/picard/ui/coverartbox.py +++ b/picard/ui/coverartbox.py @@ -28,6 +28,7 @@ from picard.track import Track from picard.file import File from picard.util import encode_filename, imageinfo, get_file_path from picard.util.lrucache import LRUCache +from picard.const import MAX_COVERS_TO_STACK if sys.platform == 'darwin': try: @@ -82,7 +83,6 @@ class ActiveLabel(QtGui.QLabel): class CoverArtThumbnail(ActiveLabel): - MAX_COVERS_TO_STACK = 4 def __init__(self, active=False, drops=False, pixmap_cache=None, *args, **kwargs): super(CoverArtThumbnail, self).__init__(active, drops, *args, **kwargs) @@ -140,9 +140,9 @@ class CoverArtThumbnail(ActiveLabel): pixmap.loadFromData(self.data[0].data) pixmap = self.decorate_cover(pixmap) else: - limited = len(self.data) > self.MAX_COVERS_TO_STACK + limited = len(self.data) > MAX_COVERS_TO_STACK if limited: - data_to_paint = data[:self.MAX_COVERS_TO_STACK - 1] + data_to_paint = data[:MAX_COVERS_TO_STACK - 1] offset = displacements * len(data_to_paint) else: data_to_paint = data From 879a86e51c974626abb51afd5110bf52ea24c952 Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Sun, 12 Mar 2017 19:07:52 +0100 Subject: [PATCH 35/35] Fix unneeded broken import from a bad rebase merge --- picard/ui/coverartbox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/picard/ui/coverartbox.py b/picard/ui/coverartbox.py index bb359fef9..40fb800cd 100644 --- a/picard/ui/coverartbox.py +++ b/picard/ui/coverartbox.py @@ -26,7 +26,7 @@ from picard.album import Album from picard.coverart.image import CoverArtImage, CoverArtImageError from picard.track import Track from picard.file import File -from picard.util import encode_filename, imageinfo, get_file_path +from picard.util import encode_filename, imageinfo from picard.util.lrucache import LRUCache from picard.const import MAX_COVERS_TO_STACK