From 222e2bcae9260f28710b0ba5c2d48dde896e5309 Mon Sep 17 00:00:00 2001 From: Laurent Monin Date: Fri, 3 Jul 2015 17:40:03 +0200 Subject: [PATCH] Add Local Files cover art provider and its options --- picard/coverart/providers/__init__.py | 2 + picard/coverart/providers/local.py | 99 ++++++++++++++++++++++++++ picard/ui/options/cover.py | 1 + picard/ui/ui_provider_options_local.py | 67 +++++++++++++++++ ui/provider_options_local.ui | 83 +++++++++++++++++++++ 5 files changed, 252 insertions(+) create mode 100644 picard/coverart/providers/local.py create mode 100644 picard/ui/ui_provider_options_local.py create mode 100644 ui/provider_options_local.ui diff --git a/picard/coverart/providers/__init__.py b/picard/coverart/providers/__init__.py index a94b70c9b..088a4b2f8 100644 --- a/picard/coverart/providers/__init__.py +++ b/picard/coverart/providers/__init__.py @@ -164,12 +164,14 @@ class CoverArtProvider(object): self.error(traceback.format_exc()) +from picard.coverart.providers.local import CoverArtProviderLocal from picard.coverart.providers.caa import CoverArtProviderCaa from picard.coverart.providers.amazon import CoverArtProviderAmazon from picard.coverart.providers.whitelist import CoverArtProviderWhitelist from picard.coverart.providers.caa_release_group import CoverArtProviderCaaReleaseGroup __providers = [ + CoverArtProviderLocal, CoverArtProviderCaa, CoverArtProviderAmazon, CoverArtProviderWhitelist, diff --git a/picard/coverart/providers/local.py b/picard/coverart/providers/local.py new file mode 100644 index 000000000..48e353bf9 --- /dev/null +++ b/picard/coverart/providers/local.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +# +# Picard, the next-generation MusicBrainz tagger +# Copyright (C) 2015 Laurent Monin +# +# 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. + +import os +import re +import traceback +from PyQt4 import QtCore, QtGui +from picard import config, log +from picard.coverart.providers import CoverArtProvider, ProviderOptions +from picard.coverart.image import CoverArtImageFromFile +from picard.coverart.utils import CAA_TYPES +from picard.ui.ui_provider_options_local import Ui_LocalOptions + + +class ProviderOptionsLocal(ProviderOptions): + """ + Options for Local Files cover art provider + """ + + _DEFAULT_LOCAL_COVER_ART_REGEX = '^(?:cover|folder|albumart)(.*)\.(?:jpe?g|png|gif|tiff?)$' + + options = [ + config.TextOption("setting", "local_cover_regex", + _DEFAULT_LOCAL_COVER_ART_REGEX), + ] + + _options_ui = Ui_LocalOptions + + def __init__(self, parent=None): + super(ProviderOptionsLocal, self).__init__(parent) + self.init_regex_checker(self.ui.local_cover_regex_edit, self.ui.local_cover_regex_error) + self.ui.local_cover_regex_default.clicked.connect(self.set_local_cover_regex_default) + + def set_local_cover_regex_default(self): + self.ui.local_cover_regex_edit.setText(self._DEFAULT_LOCAL_COVER_ART_REGEX) + + def load(self): + self.ui.local_cover_regex_edit.setText(config.setting["local_cover_regex"]) + + def save(self): + config.setting["local_cover_regex"] = unicode(self.ui.local_cover_regex_edit.text()) + + +class CoverArtProviderLocal(CoverArtProvider): + + """Get cover art from local files""" + + NAME = "Local" + TITLE = N_(u"Local Files") + OPTIONS = ProviderOptionsLocal + + _types_split_re = re.compile('[^a-z0-9]', re.IGNORECASE) + _known_types = set([t['name'] for t in CAA_TYPES]) + + def enabled(self): + enabled = CoverArtProvider.enabled(self) + return enabled and not self.coverart.front_image_found + + def queue_images(self): + _match_re = re.compile(config.setting['local_cover_regex'], re.IGNORECASE) + dirs_done = {} + + for file in self.album.iterfiles(): + current_dir = os.path.dirname(file.filename) + if current_dir in dirs_done: + continue + dirs_done[current_dir] = True + for root, dirs, files in os.walk(current_dir): + for filename in files: + m = _match_re.search(filename) + if not m: + continue + filepath = os.path.join(current_dir, root, filename) + if os.path.exists(filepath): + types = self.get_types(m.group(1)) or [ u'front' ] + self.queue_put(CoverArtImageFromFile(filepath, + types=types, + support_types=True)) + return CoverArtProvider.FINISHED + + def get_types(self, string): + found = set([x.lower() for x in self._types_split_re.split(string) if x]) + return list(found.intersection(self._known_types)) diff --git a/picard/ui/options/cover.py b/picard/ui/options/cover.py index 294079d42..7bc61a49e 100644 --- a/picard/ui/options/cover.py +++ b/picard/ui/options/cover.py @@ -47,6 +47,7 @@ class CoverOptionsPage(OptionsPage): ('Amazon', True), ('Whitelist', True), ('CaaReleaseGroup', False), + ('Local', False), ]), ] diff --git a/picard/ui/ui_provider_options_local.py b/picard/ui/ui_provider_options_local.py new file mode 100644 index 000000000..8d7a770b9 --- /dev/null +++ b/picard/ui/ui_provider_options_local.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- + +# Automatically generated - don't edit. +# Use `python setup.py build_ui` to update it. + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + def _fromUtf8(s): + return s + +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig, _encoding) +except AttributeError: + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig) + +class Ui_LocalOptions(object): + def setupUi(self, LocalOptions): + LocalOptions.setObjectName(_fromUtf8("LocalOptions")) + LocalOptions.resize(472, 215) + self.verticalLayout = QtGui.QVBoxLayout(LocalOptions) + self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) + self.local_cover_regex_label = QtGui.QLabel(LocalOptions) + self.local_cover_regex_label.setObjectName(_fromUtf8("local_cover_regex_label")) + self.verticalLayout.addWidget(self.local_cover_regex_label) + self.local_cover_regex_edit = QtGui.QLineEdit(LocalOptions) + self.local_cover_regex_edit.setObjectName(_fromUtf8("local_cover_regex_edit")) + self.verticalLayout.addWidget(self.local_cover_regex_edit) + self.horizontalLayout_2 = QtGui.QHBoxLayout() + self.horizontalLayout_2.setObjectName(_fromUtf8("horizontalLayout_2")) + self.local_cover_regex_error = QtGui.QLabel(LocalOptions) + self.local_cover_regex_error.setText(_fromUtf8("")) + self.local_cover_regex_error.setObjectName(_fromUtf8("local_cover_regex_error")) + self.horizontalLayout_2.addWidget(self.local_cover_regex_error) + self.local_cover_regex_default = QtGui.QPushButton(LocalOptions) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.local_cover_regex_default.sizePolicy().hasHeightForWidth()) + self.local_cover_regex_default.setSizePolicy(sizePolicy) + self.local_cover_regex_default.setObjectName(_fromUtf8("local_cover_regex_default")) + self.horizontalLayout_2.addWidget(self.local_cover_regex_default) + self.verticalLayout.addLayout(self.horizontalLayout_2) + self.note = QtGui.QLabel(LocalOptions) + font = QtGui.QFont() + font.setItalic(True) + self.note.setFont(font) + self.note.setWordWrap(True) + self.note.setObjectName(_fromUtf8("note")) + self.verticalLayout.addWidget(self.note) + spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.verticalLayout.addItem(spacerItem) + + self.retranslateUi(LocalOptions) + QtCore.QMetaObject.connectSlotsByName(LocalOptions) + + def retranslateUi(self, LocalOptions): + LocalOptions.setWindowTitle(_("Form")) + self.local_cover_regex_label.setText(_("Local cover art files match the following regular expression:")) + self.local_cover_regex_default.setText(_("Default")) + self.note.setText(_("First group in the regular expression, if any, will be used as type, ie. cover-back-spine.jpg will be set as types Back + Spine. If no type is found, it will default to Front type.")) + diff --git a/ui/provider_options_local.ui b/ui/provider_options_local.ui new file mode 100644 index 000000000..ee17b875f --- /dev/null +++ b/ui/provider_options_local.ui @@ -0,0 +1,83 @@ + + + LocalOptions + + + + 0 + 0 + 472 + 215 + + + + Form + + + + + + Local cover art files match the following regular expression: + + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + Default + + + + + + + + + + true + + + + First group in the regular expression, if any, will be used as type, ie. cover-back-spine.jpg will be set as types Back + Spine. If no type is found, it will default to Front type. + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + +