mirror of
https://github.com/fergalmoran/picard.git
synced 2025-12-22 09:18:18 +00:00
Add option to filter images by type
This commit is contained in:
@@ -85,6 +85,8 @@ DEFAULT_QUERY_LIMIT = 50
|
||||
|
||||
DEFAULT_DRIVES = get_default_cdrom_drives()
|
||||
|
||||
DEFAULT_CA_NEVER_REPLACE_TYPE_INCLUDE = ['front']
|
||||
DEFAULT_CA_NEVER_REPLACE_TYPE_EXCLUDE = ['matrix/runout', 'raw/unedited', 'watermark']
|
||||
DEFAULT_CA_PROVIDERS = [
|
||||
('Cover Art Archive', True),
|
||||
('UrlRelationships', True),
|
||||
|
||||
@@ -69,6 +69,20 @@ def bigger_previous_image_filter(data, info, album, coverartimage):
|
||||
|
||||
|
||||
def image_types_filter(data, info, album, coverartimage):
|
||||
config = get_config()
|
||||
if config.setting['dont_replace_cover_of_types'] and config.setting['save_images_to_tags']:
|
||||
downloaded_types = set(coverartimage.normalized_types())
|
||||
never_replace_types = config.setting['dont_replace_included_types']
|
||||
always_replace_types = config.setting['dont_replace_excluded_types']
|
||||
previous_image_types = album.orig_metadata.images.get_types_dict()
|
||||
if downloaded_types.intersection(always_replace_types):
|
||||
return True
|
||||
for previous_image_type in previous_image_types:
|
||||
type_already_embedded = downloaded_types.intersection(previous_image_type)
|
||||
should_not_replace = downloaded_types.intersection(never_replace_types)
|
||||
if type_already_embedded and should_not_replace:
|
||||
log.debug("Discarding cover art. An image with the same type is already embedded.")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ from picard.config import (
|
||||
from picard.const import MUSICBRAINZ_SERVERS
|
||||
from picard.const.defaults import (
|
||||
DEFAULT_AUTOBACKUP_DIRECTORY,
|
||||
DEFAULT_CA_NEVER_REPLACE_TYPE_EXCLUDE,
|
||||
DEFAULT_CA_NEVER_REPLACE_TYPE_INCLUDE,
|
||||
DEFAULT_CA_PROVIDERS,
|
||||
DEFAULT_CAA_IMAGE_SIZE,
|
||||
DEFAULT_CAA_IMAGE_TYPE_EXCLUDE,
|
||||
@@ -166,7 +168,10 @@ TextOption('setting', 'cd_lookup_device', ','.join(DEFAULT_DRIVES))
|
||||
ListOption('setting', 'ca_providers', DEFAULT_CA_PROVIDERS, title=N_("Cover art providers"))
|
||||
TextOption('setting', 'cover_image_filename', DEFAULT_COVER_IMAGE_FILENAME, title=N_("File name for images"))
|
||||
BoolOption('setting', 'embed_only_one_front_image', True, title=N_("Embed only a single front image"))
|
||||
BoolOption('setting', 'dont_replace_with_smaller_cover', False, title=N_("Never replace front images with smaller ones"))
|
||||
BoolOption('setting', 'dont_replace_with_smaller_cover', False, title=N_("Never replace cover images with smaller ones"))
|
||||
BoolOption('setting', 'dont_replace_cover_of_types', False, title=N_("Never replace cover images of the given types"))
|
||||
ListOption('setting', 'dont_replace_included_types', DEFAULT_CA_NEVER_REPLACE_TYPE_INCLUDE, title=N_("Never replace cover images of these types"))
|
||||
ListOption('setting', 'dont_replace_excluded_types', DEFAULT_CA_NEVER_REPLACE_TYPE_EXCLUDE, title=N_("Always replace cover images of these types"))
|
||||
BoolOption('setting', 'image_type_as_filename', False, title=N_("Always use the primary image type as the file name for non-front images"))
|
||||
BoolOption('setting', 'save_images_overwrite', False, title=N_("Overwrite existing image files"))
|
||||
BoolOption('setting', 'save_images_to_files', False, title=N_("Save cover images as separate files"))
|
||||
|
||||
@@ -34,6 +34,20 @@ class Ui_CoverOptionsPage(object):
|
||||
self.cb_dont_replace_with_smaller = QtWidgets.QCheckBox(parent=self.save_images_to_tags)
|
||||
self.cb_dont_replace_with_smaller.setObjectName("cb_dont_replace_with_smaller")
|
||||
self.vboxlayout.addWidget(self.cb_dont_replace_with_smaller)
|
||||
self.never_replace_types_layout = QtWidgets.QHBoxLayout()
|
||||
self.never_replace_types_layout.setObjectName("never_replace_types_layout")
|
||||
self.cb_never_replace_types = QtWidgets.QCheckBox(parent=self.save_images_to_tags)
|
||||
self.cb_never_replace_types.setObjectName("cb_never_replace_types")
|
||||
self.never_replace_types_layout.addWidget(self.cb_never_replace_types)
|
||||
self.select_types_button = QtWidgets.QPushButton(parent=self.save_images_to_tags)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.select_types_button.sizePolicy().hasHeightForWidth())
|
||||
self.select_types_button.setSizePolicy(sizePolicy)
|
||||
self.select_types_button.setObjectName("select_types_button")
|
||||
self.never_replace_types_layout.addWidget(self.select_types_button)
|
||||
self.vboxlayout.addLayout(self.never_replace_types_layout)
|
||||
self.verticalLayout.addWidget(self.save_images_to_tags)
|
||||
self.save_images_to_files = QtWidgets.QGroupBox(parent=CoverOptionsPage)
|
||||
self.save_images_to_files.setCheckable(True)
|
||||
@@ -104,6 +118,8 @@ class Ui_CoverOptionsPage(object):
|
||||
self.save_images_to_tags.setTitle(_("Embed cover images into tags"))
|
||||
self.cb_embed_front_only.setText(_("Embed only a single front image"))
|
||||
self.cb_dont_replace_with_smaller.setText(_("Never replace cover images with smaller ones"))
|
||||
self.cb_never_replace_types.setText(_("Never replace cover images matching selected types"))
|
||||
self.select_types_button.setText(_("Select Types..."))
|
||||
self.save_images_to_files.setTitle(_("Save cover images as separate files"))
|
||||
self.label_use_filename.setText(_("Use the following file name for images:"))
|
||||
self.save_images_overwrite.setText(_("Overwrite the file if it already exists"))
|
||||
|
||||
@@ -31,6 +31,10 @@ from picard.config import (
|
||||
Option,
|
||||
get_config,
|
||||
)
|
||||
from picard.const.defaults import (
|
||||
DEFAULT_CA_NEVER_REPLACE_TYPE_EXCLUDE,
|
||||
DEFAULT_CA_NEVER_REPLACE_TYPE_INCLUDE,
|
||||
)
|
||||
from picard.coverart.providers import cover_art_providers
|
||||
from picard.extension_points.options_pages import register_options_page
|
||||
from picard.i18n import (
|
||||
@@ -38,6 +42,7 @@ from picard.i18n import (
|
||||
gettext as _,
|
||||
)
|
||||
|
||||
from picard.ui.caa_types_selector import CAATypesSelectorDialog
|
||||
from picard.ui.forms.ui_options_cover import Ui_CoverOptionsPage
|
||||
from picard.ui.moveable_list_view import MoveableListView
|
||||
from picard.ui.options import OptionsPage
|
||||
@@ -62,12 +67,17 @@ class CoverOptionsPage(OptionsPage):
|
||||
self.ui.save_images_to_files.clicked.connect(self.update_ca_providers_groupbox_state)
|
||||
self.ui.save_images_to_tags.clicked.connect(self.update_ca_providers_groupbox_state)
|
||||
self.ui.save_only_one_front_image.toggled.connect(self.ui.image_type_as_filename.setDisabled)
|
||||
self.ui.cb_never_replace_types.toggled.connect(self.ui.select_types_button.setEnabled)
|
||||
self.ui.select_types_button.clicked.connect(self.select_never_replace_image_types)
|
||||
self.move_view = MoveableListView(self.ui.ca_providers_list, self.ui.up_button,
|
||||
self.ui.down_button)
|
||||
|
||||
self.register_setting('save_images_to_tags', ['save_images_to_tags'])
|
||||
self.register_setting('embed_only_one_front_image', ['cb_embed_front_only'])
|
||||
self.register_setting('dont_replace_with_smaller_cover', ['dont_replace_with_smaller_cover'])
|
||||
self.register_setting('dont_replace_cover_of_types', ['dont_replace_cover_of_types'])
|
||||
self.register_setting('dont_replace_included_types', ['dont_replace_included_types'])
|
||||
self.register_setting('dont_replace_excluded_types', ['dont_replace_excluded_types'])
|
||||
self.register_setting('save_images_to_files', ['save_images_to_files'])
|
||||
self.register_setting('cover_image_filename', ['cover_image_filename'])
|
||||
self.register_setting('save_images_overwrite', ['save_images_overwrite'])
|
||||
@@ -78,6 +88,8 @@ class CoverOptionsPage(OptionsPage):
|
||||
def restore_defaults(self):
|
||||
# Remove previous entries
|
||||
self.ui.ca_providers_list.clear()
|
||||
self.dont_replace_included_types = DEFAULT_CA_NEVER_REPLACE_TYPE_INCLUDE
|
||||
self.dont_replace_excluded_types = DEFAULT_CA_NEVER_REPLACE_TYPE_EXCLUDE
|
||||
super().restore_defaults()
|
||||
|
||||
def _load_cover_art_providers(self):
|
||||
@@ -94,6 +106,10 @@ class CoverOptionsPage(OptionsPage):
|
||||
self.ui.save_images_to_tags.setChecked(config.setting['save_images_to_tags'])
|
||||
self.ui.cb_embed_front_only.setChecked(config.setting['embed_only_one_front_image'])
|
||||
self.ui.cb_dont_replace_with_smaller.setChecked(config.setting['dont_replace_with_smaller_cover'])
|
||||
self.ui.cb_never_replace_types.setChecked(config.setting['dont_replace_cover_of_types'])
|
||||
self.ui.select_types_button.setEnabled(config.setting['dont_replace_cover_of_types'])
|
||||
self.dont_replace_included_types = config.setting['dont_replace_included_types']
|
||||
self.dont_replace_excluded_types = config.setting['dont_replace_excluded_types']
|
||||
self.ui.save_images_to_files.setChecked(config.setting['save_images_to_files'])
|
||||
self.ui.cover_image_filename.setText(config.setting['cover_image_filename'])
|
||||
self.ui.save_images_overwrite.setChecked(config.setting['save_images_overwrite'])
|
||||
@@ -112,6 +128,9 @@ class CoverOptionsPage(OptionsPage):
|
||||
config.setting['save_images_to_tags'] = self.ui.save_images_to_tags.isChecked()
|
||||
config.setting['embed_only_one_front_image'] = self.ui.cb_embed_front_only.isChecked()
|
||||
config.setting['dont_replace_with_smaller_cover'] = self.ui.cb_dont_replace_with_smaller.isChecked()
|
||||
config.setting['dont_replace_cover_of_types'] = self.ui.cb_never_replace_types.isChecked()
|
||||
config.setting['dont_replace_included_types'] = self.dont_replace_included_types
|
||||
config.setting['dont_replace_excluded_types'] = self.dont_replace_excluded_types
|
||||
config.setting['save_images_to_files'] = self.ui.save_images_to_files.isChecked()
|
||||
config.setting['cover_image_filename'] = self.ui.cover_image_filename.text()
|
||||
config.setting['save_images_overwrite'] = self.ui.save_images_overwrite.isChecked()
|
||||
@@ -124,5 +143,15 @@ class CoverOptionsPage(OptionsPage):
|
||||
tags_enabled = self.ui.save_images_to_tags.isChecked()
|
||||
self.ui.ca_providers_groupbox.setEnabled(files_enabled or tags_enabled)
|
||||
|
||||
def select_never_replace_image_types(self):
|
||||
(included_types, excluded_types, ok) = CAATypesSelectorDialog.display(
|
||||
types_include=self.dont_replace_included_types,
|
||||
types_exclude=self.dont_replace_excluded_types,
|
||||
parent=self,
|
||||
)
|
||||
if ok:
|
||||
self.dont_replace_included_types = included_types
|
||||
self.dont_replace_excluded_types = excluded_types
|
||||
|
||||
|
||||
register_options_page(CoverOptionsPage)
|
||||
|
||||
@@ -32,6 +32,7 @@ from picard.coverart.image import CoverArtImage
|
||||
from picard.coverart.processing import run_image_processors
|
||||
from picard.coverart.processing.filters import (
|
||||
bigger_previous_image_filter,
|
||||
image_types_filter,
|
||||
size_filter,
|
||||
size_metadata_filter,
|
||||
)
|
||||
@@ -69,6 +70,9 @@ class ImageFiltersTest(PicardTestCase):
|
||||
'cover_minimum_width': 500,
|
||||
'cover_minimum_height': 500,
|
||||
'dont_replace_with_smaller_cover': True,
|
||||
'dont_replace_cover_of_types': True,
|
||||
'dont_replace_included_types': ['front', 'booklet'],
|
||||
'dont_replace_excluded_types': ['back'],
|
||||
'save_images_to_tags': True,
|
||||
}
|
||||
self.set_config_values(settings)
|
||||
@@ -89,12 +93,16 @@ class ImageFiltersTest(PicardTestCase):
|
||||
self.assertTrue(size_metadata_filter(image_metadata2))
|
||||
self.assertTrue(size_metadata_filter(image_metadata3))
|
||||
|
||||
def test_filter_by_previous_image_size(self):
|
||||
def _create_fake_album(self):
|
||||
previous_coverartimage = CoverArtImage(types=['front'], support_types=True)
|
||||
previous_coverartimage.width = 1000
|
||||
previous_coverartimage.height = 1000
|
||||
album = Album(None)
|
||||
album.orig_metadata.images = ImageList([previous_coverartimage])
|
||||
return album
|
||||
|
||||
def test_filter_by_previous_image_size(self):
|
||||
album = self._create_fake_album()
|
||||
image1, info1 = create_fake_image(500, 500, 'jpg')
|
||||
image2, info2 = create_fake_image(2000, 2000, 'jpg')
|
||||
coverartimage = CoverArtImage(types=['front'], support_types=True)
|
||||
@@ -103,6 +111,20 @@ class ImageFiltersTest(PicardTestCase):
|
||||
coverartimage = CoverArtImage(types=['back'], support_types=True)
|
||||
self.assertTrue(bigger_previous_image_filter(image1, info1, album, coverartimage))
|
||||
|
||||
def test_filter_by_image_type(self):
|
||||
album = self._create_fake_album()
|
||||
image, info = create_fake_image(1000, 1000, 'jpg')
|
||||
coverartimage1 = CoverArtImage(types=['front'], support_types=True)
|
||||
coverartimage2 = CoverArtImage(types=['back'], support_types=True)
|
||||
coverartimage3 = CoverArtImage(types=['front', 'back'], support_types=True)
|
||||
coverartimage4 = CoverArtImage(types=['spine'], support_types=True)
|
||||
coverartimage5 = CoverArtImage(types=['booklet', 'spine'], support_types=True)
|
||||
self.assertFalse(image_types_filter(image, info, album, coverartimage1))
|
||||
self.assertTrue(image_types_filter(image, info, album, coverartimage2))
|
||||
self.assertTrue(image_types_filter(image, info, album, coverartimage3))
|
||||
self.assertTrue(image_types_filter(image, info, album, coverartimage4))
|
||||
self.assertTrue(image_types_filter(image, info, album, coverartimage5))
|
||||
|
||||
|
||||
class ImageProcessorsTest(PicardTestCase):
|
||||
def setUp(self):
|
||||
|
||||
@@ -52,6 +52,30 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="never_replace_types_layout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="cb_never_replace_types">
|
||||
<property name="text">
|
||||
<string>Never replace cover images matching selected types</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="select_types_button">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Select Types...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
Reference in New Issue
Block a user