From 65c0ddc278416cffb6a664a3c48e536a10529820 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Wed, 11 May 2011 22:50:33 +0200 Subject: [PATCH 1/2] Added basic weights to prefer albums over compilations. --- picard/file.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/picard/file.py b/picard/file.py index ac4a55587..5d0a212f4 100644 --- a/picard/file.py +++ b/picard/file.py @@ -433,10 +433,11 @@ class File(LockableObject, Item): Weigths: * title = 13 - * artist name = 3 + * artist name = 4 * release name = 5 * length = 10 - * number of tracks = 3 + * number of tracks = 4 + * album type = 20 """ total = 0.0 @@ -467,7 +468,8 @@ class File(LockableObject, Item): parts.append((score, 10)) total += 10 - track_list = track.release_list[0].release[0].track_list[0] + first_release = track.release_list[0].release[0] + track_list = first_release.track_list[0] if 'totaltracks' in self.metadata and 'count' in track_list.attribs: try: a = int(self.metadata['totaltracks']) @@ -483,6 +485,19 @@ class File(LockableObject, Item): except ValueError: pass + if 'type' in first_release.attribs: + type = first_release.type + if type == 'Album': + score = 1.0 + elif type in ('EP', 'Single'): + score = 0.5 + else: + score = 0.0 + else: + score = 0.0 + parts.append((score, 20)) + total += 20 + return reduce(lambda x, y: x + y[0] * y[1] / total, parts, 0.0) def _lookup_finished(self, lookuptype, document, http, error): From 1567d802c39df8d3c1a3ff8678161018c4ad043e Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Fri, 13 May 2011 00:15:39 +0200 Subject: [PATCH 2/2] Added UI to set preferred release types. --- picard/file.py | 12 +- picard/ui/options/matching.py | 31 +++- picard/ui/ui_options_matching.py | 140 +++++++++++++++- picard/util/__init__.py | 12 ++ test/test_utils.py | 32 +++- ui/options_matching.ui | 263 ++++++++++++++++++++++++++++++- 6 files changed, 473 insertions(+), 17 deletions(-) diff --git a/picard/file.py b/picard/file.py index 5d0a212f4..f98dc4bb4 100644 --- a/picard/file.py +++ b/picard/file.py @@ -43,6 +43,7 @@ from picard.util import ( LockableObject, pathcmp, mimetype, + load_release_type_scores, ) @@ -485,14 +486,11 @@ class File(LockableObject, Item): except ValueError: pass + type_scores = load_release_type_scores(self.config.setting["release_type_scores"]) if 'type' in first_release.attribs: - type = first_release.type - if type == 'Album': - score = 1.0 - elif type in ('EP', 'Single'): - score = 0.5 - else: - score = 0.0 + release_type = first_release.type + score = type_scores.get(release_type, type_scores.get('Other', 0.5)) + print release_type, score else: score = 0.0 parts.append((score, 20)) diff --git a/picard/ui/options/matching.py b/picard/ui/options/matching.py index 673239a35..d5ef2ce3b 100644 --- a/picard/ui/options/matching.py +++ b/picard/ui/options/matching.py @@ -17,8 +17,9 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -from PyQt4 import QtGui -from picard.config import FloatOption +from PyQt4 import QtCore, QtGui +from picard.config import FloatOption, TextOption +from picard.util import load_release_type_scores, save_release_type_scores from picard.ui.options import OptionsPage, OptionsCheckError, register_options_page from picard.ui.ui_options_matching import Ui_MatchingOptionsPage @@ -35,22 +36,48 @@ class MatchingOptionsPage(OptionsPage): FloatOption("setting", "file_lookup_threshold", 0.7), FloatOption("setting", "cluster_lookup_threshold", 0.8), FloatOption("setting", "track_matching_threshold", 0.4), + TextOption("setting", "release_type_scores", "Album 0.2 Single 0.2 EP 0.2 Compilation 0.2 Soundtrack 0.2 Spokenword 0.2 Interview 0.2 Audiobook 0.2 Live 0.2 Remix 0.2 Other 0.5"), ] + _release_type_sliders = {} + def __init__(self, parent=None): super(MatchingOptionsPage, self).__init__(parent) self.ui = Ui_MatchingOptionsPage() self.ui.setupUi(self) + self.connect(self.ui.reset_preferred_types_btn, QtCore.SIGNAL("clicked()"), self.reset_preferred_types) + self._release_type_sliders["Album"] = self.ui.prefer_album_score + self._release_type_sliders["Single"] = self.ui.prefer_single_score + self._release_type_sliders["EP"] = self.ui.prefer_ep_score + self._release_type_sliders["Compilation"] = self.ui.prefer_compilation_score + self._release_type_sliders["Soundtrack"] = self.ui.prefer_soundtrack_score + self._release_type_sliders["Spokenword"] = self.ui.prefer_spokenword_score + self._release_type_sliders["Interview"] = self.ui.prefer_interview_score + self._release_type_sliders["Audiobook"] = self.ui.prefer_audiobook_score + self._release_type_sliders["Live"] = self.ui.prefer_live_score + self._release_type_sliders["Remix"] = self.ui.prefer_remix_score + self._release_type_sliders["Other"] = self.ui.prefer_other_score def load(self): self.ui.file_lookup_threshold.setValue(int(self.config.setting["file_lookup_threshold"] * 100)) self.ui.cluster_lookup_threshold.setValue(int(self.config.setting["cluster_lookup_threshold"] * 100)) self.ui.track_matching_threshold.setValue(int(self.config.setting["track_matching_threshold"] * 100)) + scores = load_release_type_scores(self.config.setting["release_type_scores"]) + for (release_type, release_type_slider) in self._release_type_sliders.iteritems(): + release_type_slider.setValue(int(scores.get(release_type, 0.5) * 100)) def save(self): self.config.setting["file_lookup_threshold"] = float(self.ui.file_lookup_threshold.value()) / 100.0 self.config.setting["cluster_lookup_threshold"] = float(self.ui.cluster_lookup_threshold.value()) / 100.0 self.config.setting["track_matching_threshold"] = float(self.ui.track_matching_threshold.value()) / 100.0 + scores = {} + for (release_type, release_type_slider) in self._release_type_sliders.iteritems(): + scores[release_type] = float(release_type_slider.value()) / 100.0 + self.config.setting["release_type_scores"] = save_release_type_scores(scores) + + def reset_preferred_types(self): + for release_type_slider in self._release_type_sliders.values(): + release_type_slider.setValue(50) register_options_page(MatchingOptionsPage) diff --git a/picard/ui/ui_options_matching.py b/picard/ui/ui_options_matching.py index 3ce995b19..7601f999f 100644 --- a/picard/ui/ui_options_matching.py +++ b/picard/ui/ui_options_matching.py @@ -2,8 +2,8 @@ # Form implementation generated from reading ui file 'ui/options_matching.ui' # -# Created: Thu Sep 3 00:39:04 2009 -# by: PyQt4 UI code generator 4.4.4 +# Created: Thu May 12 23:52:13 2011 +# by: PyQt4 UI code generator 4.7.2 # # WARNING! All changes made in this file will be lost! @@ -12,7 +12,7 @@ from PyQt4 import QtCore, QtGui class Ui_MatchingOptionsPage(object): def setupUi(self, MatchingOptionsPage): MatchingOptionsPage.setObjectName("MatchingOptionsPage") - MatchingOptionsPage.resize(383, 313) + MatchingOptionsPage.resize(382, 498) self.vboxlayout = QtGui.QVBoxLayout(MatchingOptionsPage) self.vboxlayout.setSpacing(6) self.vboxlayout.setMargin(9) @@ -60,8 +60,125 @@ class Ui_MatchingOptionsPage(object): self.label_5.setObjectName("label_5") self.gridlayout.addWidget(self.label_5, 1, 0, 1, 1) self.vboxlayout.addWidget(self.rename_files) - spacerItem = QtGui.QSpacerItem(20, 41, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.vboxlayout.addItem(spacerItem) + self.groupBox = QtGui.QGroupBox(MatchingOptionsPage) + self.groupBox.setObjectName("groupBox") + self.gridLayout = QtGui.QGridLayout(self.groupBox) + self.gridLayout.setObjectName("gridLayout") + self.prefer_album_score = QtGui.QSlider(self.groupBox) + self.prefer_album_score.setMaximum(100) + self.prefer_album_score.setProperty("value", 50) + self.prefer_album_score.setOrientation(QtCore.Qt.Horizontal) + self.prefer_album_score.setObjectName("prefer_album_score") + self.gridLayout.addWidget(self.prefer_album_score, 0, 2, 1, 1) + self.prefer_single_score = QtGui.QSlider(self.groupBox) + self.prefer_single_score.setMaximum(100) + self.prefer_single_score.setProperty("value", 50) + self.prefer_single_score.setOrientation(QtCore.Qt.Horizontal) + self.prefer_single_score.setObjectName("prefer_single_score") + self.gridLayout.addWidget(self.prefer_single_score, 1, 2, 1, 1) + self.label = QtGui.QLabel(self.groupBox) + self.label.setObjectName("label") + self.gridLayout.addWidget(self.label, 0, 0, 1, 1) + self.label_2 = QtGui.QLabel(self.groupBox) + self.label_2.setObjectName("label_2") + self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1) + self.prefer_ep_score = QtGui.QSlider(self.groupBox) + self.prefer_ep_score.setMaximum(100) + self.prefer_ep_score.setProperty("value", 50) + self.prefer_ep_score.setOrientation(QtCore.Qt.Horizontal) + self.prefer_ep_score.setObjectName("prefer_ep_score") + self.gridLayout.addWidget(self.prefer_ep_score, 2, 2, 1, 1) + self.prefer_compilation_score = QtGui.QSlider(self.groupBox) + self.prefer_compilation_score.setMaximum(100) + self.prefer_compilation_score.setProperty("value", 50) + self.prefer_compilation_score.setOrientation(QtCore.Qt.Horizontal) + self.prefer_compilation_score.setObjectName("prefer_compilation_score") + self.gridLayout.addWidget(self.prefer_compilation_score, 3, 2, 1, 1) + self.prefer_soundtrack_score = QtGui.QSlider(self.groupBox) + self.prefer_soundtrack_score.setMaximum(100) + self.prefer_soundtrack_score.setProperty("value", 50) + self.prefer_soundtrack_score.setOrientation(QtCore.Qt.Horizontal) + self.prefer_soundtrack_score.setObjectName("prefer_soundtrack_score") + self.gridLayout.addWidget(self.prefer_soundtrack_score, 4, 2, 1, 1) + self.prefer_spokenword_score = QtGui.QSlider(self.groupBox) + self.prefer_spokenword_score.setMaximum(100) + self.prefer_spokenword_score.setProperty("value", 50) + self.prefer_spokenword_score.setOrientation(QtCore.Qt.Horizontal) + self.prefer_spokenword_score.setObjectName("prefer_spokenword_score") + self.gridLayout.addWidget(self.prefer_spokenword_score, 5, 2, 1, 1) + self.label_3 = QtGui.QLabel(self.groupBox) + self.label_3.setObjectName("label_3") + self.gridLayout.addWidget(self.label_3, 2, 0, 1, 1) + self.label_7 = QtGui.QLabel(self.groupBox) + self.label_7.setObjectName("label_7") + self.gridLayout.addWidget(self.label_7, 3, 0, 1, 1) + self.label_8 = QtGui.QLabel(self.groupBox) + self.label_8.setObjectName("label_8") + self.gridLayout.addWidget(self.label_8, 4, 0, 1, 1) + self.label_9 = QtGui.QLabel(self.groupBox) + self.label_9.setObjectName("label_9") + self.gridLayout.addWidget(self.label_9, 5, 0, 1, 1) + self.prefer_interview_score = QtGui.QSlider(self.groupBox) + self.prefer_interview_score.setMaximum(100) + self.prefer_interview_score.setProperty("value", 50) + self.prefer_interview_score.setOrientation(QtCore.Qt.Horizontal) + self.prefer_interview_score.setObjectName("prefer_interview_score") + self.gridLayout.addWidget(self.prefer_interview_score, 6, 2, 1, 1) + self.prefer_audiobook_score = QtGui.QSlider(self.groupBox) + self.prefer_audiobook_score.setMaximum(100) + self.prefer_audiobook_score.setProperty("value", 50) + self.prefer_audiobook_score.setOrientation(QtCore.Qt.Horizontal) + self.prefer_audiobook_score.setObjectName("prefer_audiobook_score") + self.gridLayout.addWidget(self.prefer_audiobook_score, 7, 2, 1, 1) + self.prefer_live_score = QtGui.QSlider(self.groupBox) + self.prefer_live_score.setMaximum(100) + self.prefer_live_score.setProperty("value", 50) + self.prefer_live_score.setOrientation(QtCore.Qt.Horizontal) + self.prefer_live_score.setObjectName("prefer_live_score") + self.gridLayout.addWidget(self.prefer_live_score, 8, 2, 1, 1) + self.label_10 = QtGui.QLabel(self.groupBox) + self.label_10.setObjectName("label_10") + self.gridLayout.addWidget(self.label_10, 6, 0, 1, 1) + self.label_11 = QtGui.QLabel(self.groupBox) + self.label_11.setObjectName("label_11") + self.gridLayout.addWidget(self.label_11, 7, 0, 1, 1) + self.label_12 = QtGui.QLabel(self.groupBox) + self.label_12.setObjectName("label_12") + self.gridLayout.addWidget(self.label_12, 8, 0, 1, 1) + self.prefer_remix_score = QtGui.QSlider(self.groupBox) + self.prefer_remix_score.setMaximum(100) + self.prefer_remix_score.setProperty("value", 50) + self.prefer_remix_score.setOrientation(QtCore.Qt.Horizontal) + self.prefer_remix_score.setObjectName("prefer_remix_score") + self.gridLayout.addWidget(self.prefer_remix_score, 9, 2, 1, 1) + self.label_13 = QtGui.QLabel(self.groupBox) + self.label_13.setObjectName("label_13") + self.gridLayout.addWidget(self.label_13, 9, 0, 1, 1) + self.prefer_other_score = QtGui.QSlider(self.groupBox) + self.prefer_other_score.setMaximum(100) + self.prefer_other_score.setSliderPosition(50) + self.prefer_other_score.setOrientation(QtCore.Qt.Horizontal) + self.prefer_other_score.setObjectName("prefer_other_score") + self.gridLayout.addWidget(self.prefer_other_score, 10, 2, 1, 1) + self.label_14 = QtGui.QLabel(self.groupBox) + self.label_14.setObjectName("label_14") + self.gridLayout.addWidget(self.label_14, 10, 0, 1, 1) + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem) + self.reset_preferred_types_btn = QtGui.QPushButton(self.groupBox) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.reset_preferred_types_btn.sizePolicy().hasHeightForWidth()) + self.reset_preferred_types_btn.setSizePolicy(sizePolicy) + self.reset_preferred_types_btn.setObjectName("reset_preferred_types_btn") + self.horizontalLayout.addWidget(self.reset_preferred_types_btn) + self.gridLayout.addLayout(self.horizontalLayout, 12, 2, 1, 1) + self.vboxlayout.addWidget(self.groupBox) + spacerItem1 = QtGui.QSpacerItem(20, 41, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.vboxlayout.addItem(spacerItem1) self.label_6.setBuddy(self.file_lookup_threshold) self.label_4.setBuddy(self.file_lookup_threshold) self.label_5.setBuddy(self.file_lookup_threshold) @@ -79,4 +196,17 @@ class Ui_MatchingOptionsPage(object): self.file_lookup_threshold.setSuffix(_(" %")) self.label_4.setText(_("Minimal similarity for file lookups:")) self.label_5.setText(_("Minimal similarity for cluster lookups:")) + self.groupBox.setTitle(_("Preferred release types")) + self.label.setText(_("Album")) + self.label_2.setText(_("Single")) + self.label_3.setText(_("EP")) + self.label_7.setText(_("Compilation")) + self.label_8.setText(_("Soundtrack")) + self.label_9.setText(_("Spokenword")) + self.label_10.setText(_("Interview")) + self.label_11.setText(_("Audiobook")) + self.label_12.setText(_("Live")) + self.label_13.setText(_("Remix")) + self.label_14.setText(_("Other")) + self.reset_preferred_types_btn.setText(_("Reset all")) diff --git a/picard/util/__init__.py b/picard/util/__init__.py index 9169688cb..a5bbfef1f 100644 --- a/picard/util/__init__.py +++ b/picard/util/__init__.py @@ -352,3 +352,15 @@ def mbid_validate(string): def rot13(input): return u''.join(unichr(rot_13.encoding_map.get(ord(c), ord(c))) for c in input) + + +def load_release_type_scores(setting): + scores = {} + values = setting.split() + for i in range(0, len(values), 2): + scores[values[i]] = float(values[i+1]) if i+1 < len(values) else 0.0 + return scores + + +def save_release_type_scores(scores): + return " ".join(["%s %.2f" % v for v in scores.iteritems()]) diff --git a/test/test_utils.py b/test/test_utils.py index 55667b242..786db8d23 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -99,7 +99,7 @@ class TranslateArtistTest(unittest.TestCase): self.failIfEqual(u"Hamasaki, Ayumi & Keiko", util.translate_artist(u"浜崎あゆみ & KEIKO", u"Hamasaki, Ayumi & Keiko")) def test_cyrillic(self): - self.failUnlessEqual(u"Pyotr Ilyich Tchaikovsky", util.translate_artist(u"Пётр Ильич Чайковский", u"Tchaikovsky, Pyotr Ilyich")) + self.failUnlessEqual(U"Pyotr Ilyich Tchaikovsky", util.translate_artist(u"Пётр Ильич Чайковский", u"Tchaikovsky, Pyotr Ilyich")) self.failIfEqual(u"Tchaikovsky, Pyotr Ilyich", util.translate_artist(u"Пётр Ильич Чайковский", u"Tchaikovsky, Pyotr Ilyich")) self.failIfEqual(u"Пётр Ильич Чайковский", util.translate_artist(u"Пётр Ильич Чайковский", u"Tchaikovsky, Pyotr Ilyich")) @@ -112,3 +112,33 @@ class FormatTimeTest(unittest.TestCase): self.failUnlessEqual("3:00", util.format_time(179500)) self.failUnlessEqual("2:59", util.format_time(179499)) + +class LoadReleaseTypeScoresTest(unittest.TestCase): + + def test_valid(self): + release_type_score_config = "Album 1.0 Single 0.5 EP 0.5 Compilation 0.5 Soundtrack 0.5 Spokenword 0.5 Interview 0.2 Audiobook 0.0 Live 0.5 Remix 0.4 Other 0.0" + release_type_scores = util.load_release_type_scores(release_type_score_config) + self.assertEqual(1.0, release_type_scores["Album"]) + self.assertEqual(0.5, release_type_scores["Single"]) + self.assertEqual(0.2, release_type_scores["Interview"]) + self.assertEqual(0.0, release_type_scores["Audiobook"]) + self.assertEqual(0.4, release_type_scores["Remix"]) + + def test_invalid(self): + release_type_score_config = "Album 1.0 Other" + release_type_scores = util.load_release_type_scores(release_type_score_config) + self.assertEqual(1.0, release_type_scores["Album"]) + self.assertEqual(0.0, release_type_scores["Other"]) + + +class SaveReleaseTypeScoresTest(unittest.TestCase): + + def test(self): + expected = "Album 1.00 Single 0.50 Other 0.00" + scores = {"Album": 1.0, "Single": 0.5, "Other": 0.0} + saved_scores = util.save_release_type_scores(scores) + self.assertTrue("Album 1.00" in saved_scores) + self.assertTrue("Single 0.50" in saved_scores) + self.assertTrue("Other 0.00" in saved_scores) + self.assertEqual(6, len(saved_scores.split())) + diff --git a/ui/options_matching.ui b/ui/options_matching.ui index 695c8cd91..92cbf35b2 100644 --- a/ui/options_matching.ui +++ b/ui/options_matching.ui @@ -6,8 +6,8 @@ 0 0 - 383 - 313 + 382 + 498 @@ -110,6 +110,265 @@ + + + + Preferred release types + + + + + + 100 + + + 50 + + + Qt::Horizontal + + + + + + + 100 + + + 50 + + + Qt::Horizontal + + + + + + + Album + + + + + + + Single + + + + + + + 100 + + + 50 + + + Qt::Horizontal + + + + + + + 100 + + + 50 + + + Qt::Horizontal + + + + + + + 100 + + + 50 + + + Qt::Horizontal + + + + + + + 100 + + + 50 + + + Qt::Horizontal + + + + + + + EP + + + + + + + Compilation + + + + + + + Soundtrack + + + + + + + Spokenword + + + + + + + 100 + + + 50 + + + Qt::Horizontal + + + + + + + 100 + + + 50 + + + Qt::Horizontal + + + + + + + 100 + + + 50 + + + Qt::Horizontal + + + + + + + Interview + + + + + + + Audiobook + + + + + + + Live + + + + + + + 100 + + + 50 + + + Qt::Horizontal + + + + + + + Remix + + + + + + + 100 + + + 50 + + + Qt::Horizontal + + + + + + + Other + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + Reset all + + + + + + + +