diff --git a/picard/formats/apev2.py b/picard/formats/apev2.py
index 5102227ff..2d1f13fdf 100644
--- a/picard/formats/apev2.py
+++ b/picard/formats/apev2.py
@@ -191,12 +191,15 @@ class APEv2File(File):
tags = mutagen.apev2.APEv2()
images_to_save = list(metadata.images.to_be_saved_to_tags())
if config.setting["clear_existing_tags"]:
+ preserved = []
+ if config.setting['preserve_images']:
+ preserved = list(self._iter_cover_art_tags(tags))
tags.clear()
+ for name, value in preserved:
+ tags[name] = value
elif images_to_save:
- for name, value in tags.items():
- if (value.kind == mutagen.apev2.BINARY
- and name.lower().startswith('cover art')):
- del tags[name]
+ for name, value in self._iter_cover_art_tags(tags):
+ del tags[name]
temp = {}
for name, value in metadata.items():
if name.startswith("~") or not self.supports_tag(name):
@@ -273,6 +276,12 @@ class APEv2File(File):
else:
return name.title()
+ @staticmethod
+ def _iter_cover_art_tags(tags):
+ for name, value in tags.items():
+ if value.kind == mutagen.apev2.BINARY and name.lower().startswith('cover art'):
+ yield (name, value)
+
@classmethod
def supports_tag(cls, name):
return (bool(name) and name not in UNSUPPORTED_TAGS
diff --git a/picard/formats/asf.py b/picard/formats/asf.py
index 8e551a7a4..6371aaa06 100644
--- a/picard/formats/asf.py
+++ b/picard/formats/asf.py
@@ -267,7 +267,10 @@ class ASFFile(File):
tags = file.tags
if config.setting['clear_existing_tags']:
+ cover = tags['WM/Picture'] if config.setting['preserve_images'] else None
tags.clear()
+ if cover:
+ tags['WM/Picture'] = cover
cover = []
for image in metadata.images.to_be_saved_to_tags():
tag_data = pack_image(image.mimetype, image.data,
diff --git a/picard/formats/id3.py b/picard/formats/id3.py
index e84bcf425..d8f22d6e7 100644
--- a/picard/formats/id3.py
+++ b/picard/formats/id3.py
@@ -374,7 +374,10 @@ class ID3File(File):
tags = self._get_tags(filename)
config = get_config()
if config.setting['clear_existing_tags']:
+ cover = tags.getall('APIC') if config.setting["preserve_images"] else None
tags.clear()
+ if cover:
+ tags.setall('APIC', cover)
images_to_save = list(metadata.images.to_be_saved_to_tags())
if images_to_save:
tags.delall('APIC')
diff --git a/picard/formats/mp4.py b/picard/formats/mp4.py
index 939e6d1aa..382f1dcfe 100644
--- a/picard/formats/mp4.py
+++ b/picard/formats/mp4.py
@@ -253,7 +253,10 @@ class MP4File(File):
tags = file.tags
if config.setting["clear_existing_tags"]:
+ cover = tags['covr'] if config.setting['preserve_images'] else None
tags.clear()
+ if cover:
+ tags['covr'] = cover
for name, values in metadata.rawitems():
if name.startswith('lyrics:'):
diff --git a/picard/formats/vorbis.py b/picard/formats/vorbis.py
index c6f7d9f21..f6495ff24 100644
--- a/picard/formats/vorbis.py
+++ b/picard/formats/vorbis.py
@@ -4,7 +4,7 @@
#
# Copyright (C) 2006-2008, 2012 Lukáš Lalinský
# Copyright (C) 2008 Hendrik van Antwerpen
-# Copyright (C) 2008-2010, 2014-2015, 2018-2020 Philipp Wolfer
+# Copyright (C) 2008-2010, 2014-2015, 2018-2021 Philipp Wolfer
# Copyright (C) 2012-2013 Michael Wiencek
# Copyright (C) 2012-2014 Wieland Hoffmann
# Copyright (C) 2013 Calvin Walton
@@ -231,12 +231,22 @@ class VCommentFile(File):
if file.tags is None:
file.add_tags()
if config.setting["clear_existing_tags"]:
- channel_mask = file.tags.get('waveformatextensible_channel_mask', None)
+ preserve_tags = ['waveformatextensible_channel_mask']
+ if not is_flac and config.setting["preserve_images"]:
+ preserve_tags.append('METADATA_BLOCK_PICTURE')
+ preserve_tags.append('COVERART')
+ preserved_values = {}
+ for name in preserve_tags:
+ if name in file.tags:
+ preserved_values[name] = file.tags[name]
file.tags.clear()
- if channel_mask:
- file.tags['waveformatextensible_channel_mask'] = channel_mask
+ for name, value in preserved_values.items():
+ if value:
+ file.tags[name] = value
images_to_save = list(metadata.images.to_be_saved_to_tags())
- if is_flac and (config.setting["clear_existing_tags"] or images_to_save):
+ if is_flac and (
+ (config.setting["clear_existing_tags"] and not config.setting["preserve_images"])
+ or images_to_save):
file.clear_pictures()
tags = {}
for name, value in metadata.items():
diff --git a/picard/ui/options/tags.py b/picard/ui/options/tags.py
index e37cde065..a48b44f6b 100644
--- a/picard/ui/options/tags.py
+++ b/picard/ui/options/tags.py
@@ -53,6 +53,7 @@ class TagsOptionsPage(OptionsPage):
BoolOption("setting", "dont_write_tags", False),
BoolOption("setting", "preserve_timestamps", False),
BoolOption("setting", "clear_existing_tags", False),
+ BoolOption("setting", "preserve_images", False),
BoolOption("setting", "remove_id3_from_flac", False),
BoolOption("setting", "remove_ape_from_mp3", False),
ListOption("setting", "preserved_tags", []),
@@ -68,6 +69,7 @@ class TagsOptionsPage(OptionsPage):
self.ui.write_tags.setChecked(not config.setting["dont_write_tags"])
self.ui.preserve_timestamps.setChecked(config.setting["preserve_timestamps"])
self.ui.clear_existing_tags.setChecked(config.setting["clear_existing_tags"])
+ self.ui.preserve_images.setChecked(config.setting["preserve_images"])
self.ui.remove_ape_from_mp3.setChecked(config.setting["remove_ape_from_mp3"])
self.ui.remove_id3_from_flac.setChecked(config.setting["remove_id3_from_flac"])
self.ui.preserved_tags.update(config.setting["preserved_tags"])
@@ -81,6 +83,7 @@ class TagsOptionsPage(OptionsPage):
if clear_existing_tags != config.setting["clear_existing_tags"]:
config.setting["clear_existing_tags"] = clear_existing_tags
self.tagger.window.metadata_box.update()
+ config.setting["preserve_images"] = self.ui.preserve_images.isChecked()
config.setting["remove_ape_from_mp3"] = self.ui.remove_ape_from_mp3.isChecked()
config.setting["remove_id3_from_flac"] = self.ui.remove_id3_from_flac.isChecked()
config.setting["preserved_tags"] = list(self.ui.preserved_tags.tags)
diff --git a/picard/ui/ui_options_tags.py b/picard/ui/ui_options_tags.py
index f11d227c1..cf4ae2299 100644
--- a/picard/ui/ui_options_tags.py
+++ b/picard/ui/ui_options_tags.py
@@ -10,7 +10,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_TagsOptionsPage(object):
def setupUi(self, TagsOptionsPage):
TagsOptionsPage.setObjectName("TagsOptionsPage")
- TagsOptionsPage.resize(539, 525)
+ TagsOptionsPage.resize(567, 525)
self.vboxlayout = QtWidgets.QVBoxLayout(TagsOptionsPage)
self.vboxlayout.setObjectName("vboxlayout")
self.write_tags = QtWidgets.QCheckBox(TagsOptionsPage)
@@ -28,6 +28,10 @@ class Ui_TagsOptionsPage(object):
self.clear_existing_tags = QtWidgets.QCheckBox(self.before_tagging)
self.clear_existing_tags.setObjectName("clear_existing_tags")
self.vboxlayout1.addWidget(self.clear_existing_tags)
+ self.preserve_images = QtWidgets.QCheckBox(self.before_tagging)
+ self.preserve_images.setEnabled(False)
+ self.preserve_images.setObjectName("preserve_images")
+ self.vboxlayout1.addWidget(self.preserve_images)
self.remove_id3_from_flac = QtWidgets.QCheckBox(self.before_tagging)
self.remove_id3_from_flac.setObjectName("remove_id3_from_flac")
self.vboxlayout1.addWidget(self.remove_id3_from_flac)
@@ -50,12 +54,13 @@ class Ui_TagsOptionsPage(object):
self.vboxlayout.addWidget(self.before_tagging)
self.retranslateUi(TagsOptionsPage)
+ self.clear_existing_tags.toggled['bool'].connect(self.preserve_images.setEnabled)
QtCore.QMetaObject.connectSlotsByName(TagsOptionsPage)
TagsOptionsPage.setTabOrder(self.write_tags, self.preserve_timestamps)
TagsOptionsPage.setTabOrder(self.preserve_timestamps, self.clear_existing_tags)
- TagsOptionsPage.setTabOrder(self.clear_existing_tags, self.remove_id3_from_flac)
+ TagsOptionsPage.setTabOrder(self.clear_existing_tags, self.preserve_images)
+ TagsOptionsPage.setTabOrder(self.preserve_images, self.remove_id3_from_flac)
TagsOptionsPage.setTabOrder(self.remove_id3_from_flac, self.remove_ape_from_mp3)
- TagsOptionsPage.setTabOrder(self.remove_ape_from_mp3, self.preserved_tags)
def retranslateUi(self, TagsOptionsPage):
_translate = QtCore.QCoreApplication.translate
@@ -63,6 +68,7 @@ class Ui_TagsOptionsPage(object):
self.preserve_timestamps.setText(_("Preserve timestamps of tagged files"))
self.before_tagging.setTitle(_("Before Tagging"))
self.clear_existing_tags.setText(_("Clear existing tags"))
+ self.preserve_images.setText(_("Keep embedded images when clearing tags"))
self.remove_id3_from_flac.setText(_("Remove ID3 tags from FLAC files"))
self.remove_ape_from_mp3.setText(_("Remove APEv2 tags from MP3 files"))
self.preserved_tags_label.setText(_("Preserve these tags from being cleared or overwritten with MusicBrainz data:"))
diff --git a/test/formats/common.py b/test/formats/common.py
index da6f5835b..9f5830e91 100644
--- a/test/formats/common.py
+++ b/test/formats/common.py
@@ -40,6 +40,7 @@ from picard.metadata import Metadata
settings = {
'clear_existing_tags': False,
+ 'preserve_images': False,
'embed_only_one_front_image': False,
'enabled_plugins': '',
'id3v23_join_with': '/',
diff --git a/test/formats/coverart.py b/test/formats/coverart.py
index a3376f989..54e7b4903 100644
--- a/test/formats/coverart.py
+++ b/test/formats/coverart.py
@@ -2,7 +2,7 @@
#
# Picard, the next-generation MusicBrainz tagger
#
-# Copyright (C) 2019-2020 Philipp Wolfer
+# Copyright (C) 2019-2021 Philipp Wolfer
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -75,6 +75,10 @@ class CommonCoverArtTests:
def setUp(self):
super().setUp()
+ self.set_config_values({
+ 'clear_existing_tags': False,
+ 'preserve_images': False,
+ })
self.jpegdata = load_coverart_file('mb.jpg')
self.pngdata = load_coverart_file('mb.png')
@@ -118,6 +122,20 @@ class CommonCoverArtTests:
loaded_metadata = save_and_load_metadata(self.filename, metadata)
self.assertEqual(0, len(loaded_metadata.images))
+ @skipUnlessTestfile
+ def test_cover_art_clear_tags(self):
+ image = CoverArtImage(data=self.pngdata, types=['front'])
+ file_save_image(self.filename, image)
+ metadata = load_metadata(self.filename)
+ self.assertEqual(image, metadata.images[0])
+ config.setting['clear_existing_tags'] = True
+ config.setting['preserve_images'] = True
+ metadata = save_and_load_metadata(self.filename, Metadata())
+ self.assertEqual(image, metadata.images[0])
+ config.setting['preserve_images'] = False
+ metadata = save_and_load_metadata(self.filename, Metadata())
+ self.assertEqual(0, len(metadata.images))
+
def _cover_metadata(self):
imgdata = self.jpegdata
metadata = Metadata()
diff --git a/ui/options_tags.ui b/ui/options_tags.ui
index 8136d1670..5fd6079c6 100644
--- a/ui/options_tags.ui
+++ b/ui/options_tags.ui
@@ -6,7 +6,7 @@
0
0
- 539
+ 567
525
@@ -47,6 +47,16 @@
+ -
+
+
+ false
+
+
+ Keep embedded images when clearing tags
+
+
+
-
@@ -111,10 +121,27 @@
write_tags
preserve_timestamps
clear_existing_tags
+ preserve_images
remove_id3_from_flac
remove_ape_from_mp3
- preserved_tags
-
+
+
+ clear_existing_tags
+ toggled(bool)
+ preserve_images
+ setEnabled(bool)
+
+
+ 283
+ 107
+
+
+ 283
+ 132
+
+
+
+