diff --git a/picard/formats/apev2.py b/picard/formats/apev2.py index 777fd7c0f28..b9ef603af3d 100644 --- a/picard/formats/apev2.py +++ b/picard/formats/apev2.py @@ -263,6 +263,11 @@ def _remove_deleted_tags(self, metadata, tags): if real_name in tags: del tags[real_name] + if self.metadata.images._deleted: + for tag in tags: + if tag.lower().startswith('cover art'): + del tags[tag] + def _get_tag_name(self, name): if name in self.__casemap: return self.__casemap[name] diff --git a/picard/formats/asf.py b/picard/formats/asf.py index bf5784ee16f..bb37c49f1f1 100644 --- a/picard/formats/asf.py +++ b/picard/formats/asf.py @@ -304,6 +304,8 @@ def _remove_deleted_tags(self, metadata, tags): real_name = self._get_tag_name(tag) if real_name and real_name in tags: del tags[real_name] + if self.metadata.images._deleted: + del tags['WM/Picture'] @classmethod def supports_tag(cls, name): diff --git a/picard/formats/id3.py b/picard/formats/id3.py index 94a5b7b67e7..6f8e4468ad4 100644 --- a/picard/formats/id3.py +++ b/picard/formats/id3.py @@ -659,6 +659,11 @@ def _remove_deleted_tags(self, metadata, tags): except KeyError: pass + if self.metadata.images._deleted: + for key, frame in list(tags.items()): + if frame.FrameID == 'APIC': + del tags[key] + @classmethod def supports_tag(cls, name): return ((name and not name.startswith('~') and name not in UNSUPPORTED_TAGS) diff --git a/picard/formats/mp4.py b/picard/formats/mp4.py index ddfc5335399..f8e19400bb7 100644 --- a/picard/formats/mp4.py +++ b/picard/formats/mp4.py @@ -340,6 +340,9 @@ def _remove_deleted_tags(self, metadata, tags): if tag not in {'totaltracks', 'totaldiscs'}: del tags[real_name] + if self.metadata.images._deleted: + del tags['covr'] + @classmethod def supports_tag(cls, name): return (name diff --git a/picard/formats/vorbis.py b/picard/formats/vorbis.py index 25ac86d5175..2fa48c0d2d0 100644 --- a/picard/formats/vorbis.py +++ b/picard/formats/vorbis.py @@ -321,7 +321,7 @@ def _save(self, filename, metadata): base64.b64encode(picture.write()).decode('ascii')) file.tags.update(tags) - + self._clear_cover_art(file) self._remove_deleted_tags(metadata, file.tags) kwargs = {} @@ -358,6 +358,17 @@ def _remove_deleted_tags(self, metadata, tags): del tags[tag] del tags[real_name] + def _clear_cover_art(self, file): + is_flac = self._File == mutagen.flac.FLAC + + if self.metadata.images._deleted: + if is_flac: + file.clear_pictures() + else: + for tag in file: + if tag.lower().startswith('metadata_block_picture'): + del file.tags[tag] + def _get_tag_name(self, name): if name == '~rating': config = get_config() diff --git a/picard/ui/coverartbox.py b/picard/ui/coverartbox.py index c67b55a6b06..a5d88eef7c6 100644 --- a/picard/ui/coverartbox.py +++ b/picard/ui/coverartbox.py @@ -348,6 +348,11 @@ def open_release_page(self): lookup.album_lookup(self.release) +def image_delete(obj, image): + obj.metadata.images.strip_selected_image(image) + obj.metadata_images_changed.emit() + + def set_image_replace(obj, coverartimage): obj.metadata.images.strip_front_images() obj.metadata.images.append(coverartimage) @@ -545,6 +550,43 @@ def load_remote_image(self, url, data): log.warning("Can't load image: %s", e) return + def delete_cover_art(self): + if not self.item or not self.item.metadata.images: + return + + cover_art_list = [image.source or _("Unnamed Cover Art") for image in self.item.metadata.images] + + selected_item, ok_pressed = QtWidgets.QInputDialog.getItem(self, _("Delete Cover Art"), + _("Select the cover art image to delete:"), + cover_art_list, 0, False) + if ok_pressed: + selected_image_index = cover_art_list.index(selected_item) + selected_image = self.item.metadata.images[selected_image_index] + if not isinstance(self.item, File): + self.delete_cover_art_for_item(self.item, selected_image) + for file in self.get_files_to_process(): + self.delete_cover_art_for_item(file, selected_image) + self.update() + self.item.update() + + def get_files_to_process(self): + if isinstance(self.item, (Album, FileListItem)): + return self.item.iterfiles() + elif isinstance(self.item, File): + return [self.item] + + def delete_cover_art_for_item(self, item, selected_image): + metadata = item.metadata + if not metadata or not metadata.images: + return + + try: + image_delete(item, selected_image) + self.set_item(item) + self.cover_art.set_metadata(metadata) + except ValueError as e: + QtWidgets.QMessageBox.warning(self, _("Error"), str(e)) + def _try_load_remote_image(self, url, data): coverartimage = CoverArtImage( url=url.toString(), @@ -634,6 +676,12 @@ def contextMenuEvent(self, event): show_more_details_action.triggered.connect(self.show_cover_art_info) menu.addAction(show_more_details_action) + if self.item and self.item.can_show_coverart: + name = _("Delete cover art") + delete_cover_art_action = QtGui.QAction(name, self.parent) + delete_cover_art_action.triggered.connect(self.delete_cover_art) + menu.addAction(delete_cover_art_action) + if self.orig_cover_art.isVisible(): name = _("Keep original cover art") use_orig_value_action = QtGui.QAction(name, self.parent) diff --git a/picard/util/imagelist.py b/picard/util/imagelist.py index 6ba0b5f1fdb..555c92f7a8c 100644 --- a/picard/util/imagelist.py +++ b/picard/util/imagelist.py @@ -33,6 +33,7 @@ def __init__(self, iterable=()): self._images = list(iterable) self._hash_dict = {} self._changed = True + self._deleted = False def __len__(self): return len(self._images) @@ -50,6 +51,7 @@ def __delitem__(self, index): def insert(self, index, value): self._changed = True + self._deleted = False return self._images.insert(index, value) def __repr__(self): @@ -95,6 +97,12 @@ def strip_front_images(self): self._images = [image for image in self._images if not image.is_front_image()] self._changed = True + def strip_selected_image(self, image): + if image in self._images: + self._images.remove(image) + if len(self._images) == 0: + self._deleted = True + def hash_dict(self): if self._changed: self._hash_dict = {img.datahash.hash(): img for img in self._images}