Skip to content

Commit

Permalink
Merge pull request metabrainz#360 from phw/deezerart-astrcmp
Browse files Browse the repository at this point in the history
Deezerart: Use astrcmp and make matching threshold configurable
  • Loading branch information
phw authored Sep 15, 2023
2 parents 340178b + bfb0b83 commit 5a18c78
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 33 deletions.
29 changes: 20 additions & 9 deletions plugins/deezerart/__init__.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,33 @@
PLUGIN_NAME = "Deezer cover art"
PLUGIN_AUTHOR = "Fabio Forni <livingsilver94>"
PLUGIN_DESCRIPTION = "Fetch cover arts from Deezer"
PLUGIN_VERSION = '1.1.1'
PLUGIN_VERSION = '1.2'
PLUGIN_API_VERSIONS = ['2.5']
PLUGIN_LICENSE = "GPL-3.0-or-later"
PLUGIN_LICENSE_URL = "https://www.gnu.org/licenses/gpl-3.0.html"

from difflib import SequenceMatcher
from typing import Any, List, Optional
from urllib.parse import urlsplit

import picard
from picard import config
from picard.coverart import providers
from picard.coverart.image import CoverArtImage
from picard.util.astrcmp import astrcmp
from PyQt5 import QtNetwork as QtNet

from .deezer import Client, SearchOptions, obj
from .options import Ui_Form

__version__ = PLUGIN_VERSION

DEFAULT_SIMILARITY_THRESHOLD = 0.6

def is_similar(str1: str, str2: str) -> bool:

def is_similar(str1: str, str2: str, min_similarity: float = DEFAULT_SIMILARITY_THRESHOLD) -> bool:
if str1 in str2:
return True
# Python doc considers a ratio equal to 0.6 a good match.
return SequenceMatcher(None, str1, str2).quick_ratio() >= 0.65
return astrcmp(str1, str2) >= min_similarity


def is_deezer_url(url: str) -> bool:
Expand All @@ -36,16 +37,21 @@ def is_deezer_url(url: str) -> bool:
class OptionsPage(providers.ProviderOptions):
NAME = 'Deezer'
TITLE = 'Deezer'
options = [config.TextOption('setting', 'deezerart_size', obj.CoverSize.BIG.value)]
options = [
config.TextOption('setting', 'deezerart_size', obj.CoverSize.BIG.value),
config.FloatOption('setting', 'deezerart_min_similarity', DEFAULT_SIMILARITY_THRESHOLD),
]
_options_ui = Ui_Form

def load(self):
for s in obj.CoverSize:
self.ui.size.addItem(str(s.name).title(), userData=s.value)
self.ui.size.setCurrentIndex(self.ui.size.findData(config.setting['deezerart_size']))
self.ui.min_similarity.setValue(int(config.setting["deezerart_min_similarity"] * 100))

def save(self):
config.setting['deezerart_size'] = self.ui.size.currentData()
config.setting['deezerart_min_similarity'] = float(self.ui.min_similarity.value()) / 100.0


class Provider(providers.CoverArtProvider):
Expand Down Expand Up @@ -81,8 +87,8 @@ def queue_images(self):
def error(self, msg):
super().error(self._log_prefix + msg)

def log_debug(self, msg: Any):
picard.log.debug('%s%s', self._log_prefix, msg)
def log_debug(self, msg: Any, *args):
picard.log.debug(self._log_prefix + msg, *args)

def _url_callback(self, url: str):
if is_deezer_url(url):
Expand Down Expand Up @@ -119,10 +125,15 @@ def _queue_from_search(self, results: List[obj.APIObject], error: Optional[QtNet
return
artist = self._artist()
album = self.metadata['album']
min_similarity = config.setting['deezerart_min_similarity']
for result in results:
if not isinstance(result, obj.Track):
continue
if not is_similar(artist, result.artist.name) or not is_similar(album, result.album.title):
if not is_similar(artist, result.artist.name, min_similarity):
self.log_debug('artist similarity below threshold: %r ~ %r', artist, result.artist.title)
continue
if not is_similar(album, result.album.title, min_similarity):
self.log_debug('album similarity below threshold: %r ~ %r', album, result.album.title)
continue
cover_url = result.album.cover_url(obj.CoverSize(config.setting['deezerart_size']))
self.queue_put(CoverArtImage(cover_url))
Expand Down
35 changes: 26 additions & 9 deletions plugins/deezerart/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# Form implementation generated from reading ui file 'plugins/deezerart/options.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
# Created by: PyQt5 UI code generator 5.15.9
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
Expand All @@ -15,19 +15,33 @@ class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(420, 320)
self.verticalLayout = QtWidgets.QVBoxLayout(Form)
self.verticalLayout.setObjectName("verticalLayout")
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.gridLayout = QtWidgets.QGridLayout(Form)
self.gridLayout.setObjectName("gridLayout")
self.sizeLabel = QtWidgets.QLabel(Form)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.sizeLabel.sizePolicy().hasHeightForWidth())
self.sizeLabel.setSizePolicy(sizePolicy)
self.sizeLabel.setObjectName("sizeLabel")
self.horizontalLayout.addWidget(self.sizeLabel)
self.gridLayout.addWidget(self.sizeLabel, 0, 0, 1, 1)
self.size = QtWidgets.QComboBox(Form)
self.size.setObjectName("size")
self.horizontalLayout.addWidget(self.size)
self.verticalLayout.addLayout(self.horizontalLayout)
self.gridLayout.addWidget(self.size, 0, 2, 1, 1)
self.min_similarity_label = QtWidgets.QLabel(Form)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.min_similarity_label.sizePolicy().hasHeightForWidth())
self.min_similarity_label.setSizePolicy(sizePolicy)
self.min_similarity_label.setObjectName("min_similarity_label")
self.gridLayout.addWidget(self.min_similarity_label, 1, 0, 1, 2)
self.min_similarity = QtWidgets.QSpinBox(Form)
self.min_similarity.setMaximum(100)
self.min_similarity.setObjectName("min_similarity")
self.gridLayout.addWidget(self.min_similarity, 1, 2, 1, 1)
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem)
self.gridLayout.addItem(spacerItem, 2, 1, 1, 1)

self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
Expand All @@ -36,3 +50,6 @@ def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))
self.sizeLabel.setText(_translate("Form", "Cover size:"))
self.min_similarity_label.setText(_translate("Form", "Minimal similarity for matches:"))
self.min_similarity.setSuffix(_translate("Form", " %"))
self.min_similarity.setPrefix(_translate("Form", " "))
58 changes: 43 additions & 15 deletions plugins/deezerart/options.ui
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,50 @@
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="sizeLabel">
<property name="text">
<string>Cover size:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="size"/>
</item>
</layout>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="sizeLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Cover size:</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QComboBox" name="size"/>
</item>
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="min_similarity_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Minimal similarity for matches:</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QSpinBox" name="min_similarity">
<property name="suffix">
<string> %</string>
</property>
<property name="prefix">
<string> </string>
</property>
<property name="maximum">
<number>100</number>
</property>
</widget>
</item>
<item>
<item row="2" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
Expand Down

0 comments on commit 5a18c78

Please sign in to comment.