PICARD-257: Add option to exclude embedded images from clear_existing_tags

This commit is contained in:
Philipp Wolfer
2021-06-15 20:34:28 +02:00
parent 59a1aa49c3
commit a3ebc1cb22
10 changed files with 99 additions and 16 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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': '/',

View File

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

View File

@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>539</width>
<width>567</width>
<height>525</height>
</rect>
</property>
@@ -47,6 +47,16 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="preserve_images">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Keep embedded images when clearing tags</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="remove_id3_from_flac">
<property name="text">
@@ -111,10 +121,27 @@
<tabstop>write_tags</tabstop>
<tabstop>preserve_timestamps</tabstop>
<tabstop>clear_existing_tags</tabstop>
<tabstop>preserve_images</tabstop>
<tabstop>remove_id3_from_flac</tabstop>
<tabstop>remove_ape_from_mp3</tabstop>
<tabstop>preserved_tags</tabstop>
</tabstops>
<resources/>
<connections/>
<connections>
<connection>
<sender>clear_existing_tags</sender>
<signal>toggled(bool)</signal>
<receiver>preserve_images</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>283</x>
<y>107</y>
</hint>
<hint type="destinationlabel">
<x>283</x>
<y>132</y>
</hint>
</hints>
</connection>
</connections>
</ui>