diff --git a/picard/album.py b/picard/album.py index 9825de6c9..e5441935f 100644 --- a/picard/album.py +++ b/picard/album.py @@ -379,12 +379,7 @@ class Album(DataObject, Item): inc += ['artist-rels', 'release-rels', 'url-rels', 'recording-rels', 'work-rels'] if config.setting['track_ars']: inc += ['recording-level-rels', 'work-level-rels'] - if config.setting['folksonomy_tags']: - if config.setting['only_my_tags']: - require_authentication = True - inc += ['user-tags'] - else: - inc += ['tags'] + require_authentication = self.set_genre_inc_params(inc) if config.setting['enable_ratings']: require_authentication = True inc += ['user-ratings'] diff --git a/picard/dataobj.py b/picard/dataobj.py index 31d4e9602..64db4b4ee 100644 --- a/picard/dataobj.py +++ b/picard/dataobj.py @@ -17,6 +17,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +from picard import config from picard.util import LockableObject @@ -31,6 +32,17 @@ class DataObject(LockableObject): def add_folksonomy_tag(self, name, count): self.folksonomy_tags[name] = self.folksonomy_tags.get(name, 0) + count + def set_genre_inc_params(self, inc): + require_authentication = False + if config.setting['use_genres']: + use_folksonomy = config.setting['folksonomy_tags'] + if config.setting['only_my_tags']: + require_authentication = True + inc += ['user-tags'] if use_folksonomy else ['user-genres'] + else: + inc += ['tags'] if use_folksonomy else ['genres'] + return require_authentication + @staticmethod def merge_folksonomy_tags(this, that): for name, count in that.items(): diff --git a/picard/mbjson.py b/picard/mbjson.py index f8ee39f4b..581b31e4c 100644 --- a/picard/mbjson.py +++ b/picard/mbjson.py @@ -369,10 +369,10 @@ def recording_to_metadata(node, m, track=None): track.append_track_artist(artist['artist']['id']) elif key == 'relations': _relations_to_metadata(value, m) - elif key == 'tags' and track: - add_folksonomy_tags(value, track) - elif key == 'user-tags' and track: - add_user_folksonomy_tags(value, track) + elif key in ('genres', 'tags') and track: + add_genres(value, track) + elif key in ('user-genres', 'user-tags') and track: + add_user_genres(value, track) elif key == 'isrcs': add_isrcs_to_metadata(value, m) elif key == 'video' and value: @@ -458,10 +458,10 @@ def release_to_metadata(node, m, album=None): m['~releaselanguage'] = value['language'] if 'script' in value: m['script'] = value['script'] - elif key == 'tags': - add_folksonomy_tags(value, album) - elif key == 'user-tags': - add_user_folksonomy_tags(value, album) + elif key in ('genres', 'tags'): + add_genres(value, album) + elif key in ('user-genres', 'user-tags'): + add_user_genres(value, album) def release_group_to_metadata(node, m, release_group=None): @@ -472,10 +472,10 @@ def release_group_to_metadata(node, m, release_group=None): continue if key in _RELEASE_GROUP_TO_METADATA: m[_RELEASE_GROUP_TO_METADATA[key]] = value - elif key == 'tags': - add_folksonomy_tags(value, release_group) - elif key == 'user-tags': - add_user_folksonomy_tags(value, release_group) + elif key in ('genres', 'tags'): + add_genres(value, release_group) + elif key in ('user-genres', 'user-tags'): + add_user_genres(value, release_group) elif key == 'primary-type': m['~primaryreleasetype'] = value.lower() elif key == 'secondary-types': @@ -490,7 +490,7 @@ def add_secondary_release_types(node, m): m.add_unique('~secondaryreleasetype', secondary_type.lower()) -def add_folksonomy_tags(node, obj): +def add_genres(node, obj): if obj is not None: for tag in node: key = tag['name'] @@ -499,7 +499,7 @@ def add_folksonomy_tags(node, obj): obj.add_folksonomy_tag(key, count) -def add_user_folksonomy_tags(node, obj): +def add_user_genres(node, obj): if obj is not None: for tag in node: key = tag['name'] diff --git a/picard/track.py b/picard/track.py index 0868dd5e6..c930a69de 100644 --- a/picard/track.py +++ b/picard/track.py @@ -190,7 +190,7 @@ class Track(DataObject, Item): if tm['title'] == SILENCE_TRACK_TITLE: tm['~silence'] = '1' - if config.setting['folksonomy_tags']: + if config.setting['use_genres']: self._convert_folksonomy_tags_to_genre() # Convert Unicode punctuation @@ -284,12 +284,7 @@ class NonAlbumTrack(Track): if config.setting["track_ars"]: inc += ["artist-rels", "url-rels", "recording-rels", "work-rels", "work-level-rels"] - if config.setting["folksonomy_tags"]: - if config.setting["only_my_tags"]: - mblogin = True - inc += ["user-tags"] - else: - inc += ["tags"] + mblogin = self.set_genre_inc_params(inc) if config.setting["enable_ratings"]: mblogin = True inc += ["user-ratings"] diff --git a/picard/ui/options/genres.py b/picard/ui/options/genres.py index 7da9a7e4f..8585eb256 100644 --- a/picard/ui/options/genres.py +++ b/picard/ui/options/genres.py @@ -35,6 +35,7 @@ class GenresOptionsPage(OptionsPage): ACTIVE = True options = [ + config.BoolOption("setting", "use_genres", False), config.IntOption("setting", "max_tags", 5), config.IntOption("setting", "min_tag_usage", 90), config.TextOption("setting", "ignore_tags", "seen live,favorites,fixme,owned"), @@ -50,6 +51,7 @@ class GenresOptionsPage(OptionsPage): self.ui.setupUi(self) def load(self): + self.ui.use_genres.setChecked(config.setting["use_genres"]) self.ui.max_tags.setValue(config.setting["max_tags"]) self.ui.min_tag_usage.setValue(config.setting["min_tag_usage"]) self.ui.join_tags.setEditText(config.setting["join_tags"]) @@ -59,6 +61,7 @@ class GenresOptionsPage(OptionsPage): self.ui.folksonomy_tags.setChecked(config.setting["folksonomy_tags"]) def save(self): + config.setting["use_genres"] = self.ui.use_genres.isChecked() config.setting["max_tags"] = self.ui.max_tags.value() config.setting["min_tag_usage"] = self.ui.min_tag_usage.value() config.setting["join_tags"] = self.ui.join_tags.currentText() diff --git a/picard/ui/ui_options_genres.py b/picard/ui/ui_options_genres.py index d2d856f9a..7285d722f 100644 --- a/picard/ui/ui_options_genres.py +++ b/picard/ui/ui_options_genres.py @@ -11,31 +11,33 @@ class Ui_GenresOptionsPage(object): GenresOptionsPage.resize(590, 304) self.verticalLayout_2 = QtWidgets.QVBoxLayout(GenresOptionsPage) self.verticalLayout_2.setObjectName("verticalLayout_2") - self.rename_files_3 = QtWidgets.QGroupBox(GenresOptionsPage) - self.rename_files_3.setObjectName("rename_files_3") - self.verticalLayout = QtWidgets.QVBoxLayout(self.rename_files_3) + self.use_genres = QtWidgets.QGroupBox(GenresOptionsPage) + self.use_genres.setFlat(False) + self.use_genres.setCheckable(True) + self.use_genres.setChecked(False) + self.use_genres.setObjectName("use_genres") + self.verticalLayout = QtWidgets.QVBoxLayout(self.use_genres) self.verticalLayout.setObjectName("verticalLayout") - self.ignore_tags_2 = QtWidgets.QLabel(self.rename_files_3) - self.ignore_tags_2.setObjectName("ignore_tags_2") - self.verticalLayout.addWidget(self.ignore_tags_2) - self.ignore_tags = QtWidgets.QLineEdit(self.rename_files_3) - self.ignore_tags.setObjectName("ignore_tags") - self.verticalLayout.addWidget(self.ignore_tags) - self.only_my_tags = QtWidgets.QCheckBox(self.rename_files_3) + self.only_my_tags = QtWidgets.QCheckBox(self.use_genres) self.only_my_tags.setObjectName("only_my_tags") self.verticalLayout.addWidget(self.only_my_tags) - self.artists_tags = QtWidgets.QCheckBox(self.rename_files_3) - self.artists_tags.setEnabled(True) + self.artists_tags = QtWidgets.QCheckBox(self.use_genres) self.artists_tags.setObjectName("artists_tags") self.verticalLayout.addWidget(self.artists_tags) - self.folksonomy_tags = QtWidgets.QCheckBox(self.rename_files_3) + self.folksonomy_tags = QtWidgets.QCheckBox(self.use_genres) self.folksonomy_tags.setObjectName("folksonomy_tags") self.verticalLayout.addWidget(self.folksonomy_tags) + self.ignore_tags_2 = QtWidgets.QLabel(self.use_genres) + self.ignore_tags_2.setObjectName("ignore_tags_2") + self.verticalLayout.addWidget(self.ignore_tags_2) + self.ignore_tags = QtWidgets.QLineEdit(self.use_genres) + self.ignore_tags.setObjectName("ignore_tags") + self.verticalLayout.addWidget(self.ignore_tags) self.hboxlayout = QtWidgets.QHBoxLayout() self.hboxlayout.setContentsMargins(0, 0, 0, 0) self.hboxlayout.setSpacing(6) self.hboxlayout.setObjectName("hboxlayout") - self.label_5 = QtWidgets.QLabel(self.rename_files_3) + self.label_5 = QtWidgets.QLabel(self.use_genres) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -43,7 +45,7 @@ class Ui_GenresOptionsPage(object): self.label_5.setSizePolicy(sizePolicy) self.label_5.setObjectName("label_5") self.hboxlayout.addWidget(self.label_5) - self.min_tag_usage = QtWidgets.QSpinBox(self.rename_files_3) + self.min_tag_usage = QtWidgets.QSpinBox(self.use_genres) self.min_tag_usage.setMaximum(100) self.min_tag_usage.setObjectName("min_tag_usage") self.hboxlayout.addWidget(self.min_tag_usage) @@ -52,7 +54,7 @@ class Ui_GenresOptionsPage(object): self.hboxlayout1.setContentsMargins(0, 0, 0, 0) self.hboxlayout1.setSpacing(6) self.hboxlayout1.setObjectName("hboxlayout1") - self.label_6 = QtWidgets.QLabel(self.rename_files_3) + self.label_6 = QtWidgets.QLabel(self.use_genres) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -60,7 +62,7 @@ class Ui_GenresOptionsPage(object): self.label_6.setSizePolicy(sizePolicy) self.label_6.setObjectName("label_6") self.hboxlayout1.addWidget(self.label_6) - self.max_tags = QtWidgets.QSpinBox(self.rename_files_3) + self.max_tags = QtWidgets.QSpinBox(self.use_genres) self.max_tags.setMaximum(100) self.max_tags.setObjectName("max_tags") self.hboxlayout1.addWidget(self.max_tags) @@ -69,7 +71,7 @@ class Ui_GenresOptionsPage(object): self.hboxlayout2.setContentsMargins(0, 0, 0, 0) self.hboxlayout2.setSpacing(6) self.hboxlayout2.setObjectName("hboxlayout2") - self.ignore_tags_4 = QtWidgets.QLabel(self.rename_files_3) + self.ignore_tags_4 = QtWidgets.QLabel(self.use_genres) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(4) sizePolicy.setVerticalStretch(0) @@ -77,7 +79,7 @@ class Ui_GenresOptionsPage(object): self.ignore_tags_4.setSizePolicy(sizePolicy) self.ignore_tags_4.setObjectName("ignore_tags_4") self.hboxlayout2.addWidget(self.ignore_tags_4) - self.join_tags = QtWidgets.QComboBox(self.rename_files_3) + self.join_tags = QtWidgets.QComboBox(self.use_genres) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(1) sizePolicy.setVerticalStretch(0) @@ -91,7 +93,7 @@ class Ui_GenresOptionsPage(object): self.join_tags.addItem("") self.hboxlayout2.addWidget(self.join_tags) self.verticalLayout.addLayout(self.hboxlayout2) - self.verticalLayout_2.addWidget(self.rename_files_3) + self.verticalLayout_2.addWidget(self.use_genres) spacerItem = QtWidgets.QSpacerItem(181, 31, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.verticalLayout_2.addItem(spacerItem) self.label_5.setBuddy(self.min_tag_usage) @@ -102,15 +104,15 @@ class Ui_GenresOptionsPage(object): def retranslateUi(self, GenresOptionsPage): _translate = QtCore.QCoreApplication.translate - self.rename_files_3.setTitle(_("Folksonomy Tags")) - self.ignore_tags_2.setText(_("Ignore tags:")) - self.only_my_tags.setText(_("Only use my tags")) - self.artists_tags.setText(_("Fall back on album\'s artists tags if no tags are found for the release or release group")) + self.use_genres.setTitle(_("Use genres from MusicBrainz")) + self.only_my_tags.setText(_("Only use my genres")) + self.artists_tags.setText(_("Fall back on album\'s artists genres if no genres are found for the release or release group")) self.folksonomy_tags.setText(_("Use folksonomy tags as genre")) - self.label_5.setText(_("Minimal tag usage:")) + self.ignore_tags_2.setText(_("Ignore genres:")) + self.label_5.setText(_("Minimal genre usage:")) self.min_tag_usage.setSuffix(_(" %")) - self.label_6.setText(_("Maximum number of tags:")) - self.ignore_tags_4.setText(_("Join multiple tags with:")) + self.label_6.setText(_("Maximum number of genres:")) + self.ignore_tags_4.setText(_("Join multiple genres with:")) self.join_tags.setItemText(1, _(" / ")) self.join_tags.setItemText(2, _(", ")) diff --git a/test/data/ws_data/release.json b/test/data/ws_data/release.json index 1d88cf591..60a223c42 100644 --- a/test/data/ws_data/release.json +++ b/test/data/ws_data/release.json @@ -1,10 +1,17 @@ { + "genres": [ + {"name": "genre1", "count": 5}, + {"name": "genre2", "count": 3} + ], + "user-genres": [ + {"name": "genre1"} + ], "tags": [ - {"name": "test", "count": 5}, - {"name": "test2", "count": 3} + {"name": "tag1", "count": 5}, + {"name": "tag2", "count": 3} ], "user-tags": [ - {"name": "test"} + {"name": "tag1"} ], "release-group": { "first-release-date": "1973-03-24", @@ -25,18 +32,18 @@ "title": "The Dark Side of the Moon", "disambiguation": "", "secondary-type-ids": [ - + ], "primary-type-id": "f529b476-6e62-324f-b0aa-1f3e33d313fc", "secondary-types": [ - + ], "aliases": [ - + ] }, "aliases": [ - + ], "cover-art-archive": { "front": true, @@ -163,14 +170,14 @@ "relations": [ { "attributes": [ - + ], "target-credit": "George Hardie N.T.A.", "source-credit": "", "target-type": "artist", "type": "design/illustration", "attribute-values": { - + }, "begin": null, "type-id": "307e95dd-88b5-419b-8223-b146d4a0d439", @@ -179,7 +186,7 @@ "artist": { "id": "89931942-3182-4448-8e63-0c2ce90f1f81", "aliases": [ - + ], "sort-name": "Hardie, George", "disambiguation": "", @@ -189,14 +196,14 @@ }, { "attributes": [ - + ], "target-credit": "", "source-credit": "", "type": "design/illustration", "target-type": "artist", "attribute-values": { - + }, "begin": null, "type-id": "307e95dd-88b5-419b-8223-b146d4a0d439", @@ -207,7 +214,7 @@ "name": "Hipgnosis", "id": "fd1a4572-59ca-40f2-8f55-b82be28bb0ff", "aliases": [ - + ], "sort-name": "Hipgnosis", "disambiguation": "UK art design group" @@ -227,16 +234,16 @@ "target-type": "url", "type": "discogs", "attributes": [ - + ], "attribute-values": { - + }, "begin": null }, { "attribute-values": { - + }, "begin": null, "type": "photography", @@ -244,14 +251,14 @@ "target-credit": "", "source-credit": "", "attributes": [ - + ], "ended": false, "artist": { "id": "fd1a4572-59ca-40f2-8f55-b82be28bb0ff", "sort-name": "Hipgnosis", "aliases": [ - + ], "disambiguation": "UK art design group", "name": "Hipgnosis" diff --git a/test/test_dataobj.py b/test/test_dataobj.py new file mode 100644 index 000000000..2e3dd5c99 --- /dev/null +++ b/test/test_dataobj.py @@ -0,0 +1,54 @@ +from test.picardtestcase import PicardTestCase + +from picard import config +from picard.dataobj import DataObject + + +class DataObjectTest(PicardTestCase): + + def setUp(self): + super().setUp() + self.obj = DataObject('id') + + def test_set_genre_inc_params_no_genres(self): + inc = [] + config.setting['use_genres'] = False + require_auth = self.obj.set_genre_inc_params(inc) + self.assertEqual([], inc) + self.assertFalse(require_auth) + + def test_set_genre_inc_params_with_genres(self): + inc = [] + config.setting['use_genres'] = True + config.setting['folksonomy_tags'] = False + config.setting['only_my_tags'] = False + require_auth = self.obj.set_genre_inc_params(inc) + self.assertIn('genres', inc) + self.assertFalse(require_auth) + + def test_set_genre_inc_params_with_user_genres(self): + inc = [] + config.setting['use_genres'] = True + config.setting['folksonomy_tags'] = False + config.setting['only_my_tags'] = True + require_auth = self.obj.set_genre_inc_params(inc) + self.assertIn('user-genres', inc) + self.assertTrue(require_auth) + + def test_set_genre_inc_params_with_tags(self): + inc = [] + config.setting['use_genres'] = True + config.setting['folksonomy_tags'] = True + config.setting['only_my_tags'] = False + require_auth = self.obj.set_genre_inc_params(inc) + self.assertIn('tags', inc) + self.assertFalse(require_auth) + + def test_set_genre_inc_params_with_user_tags(self): + inc = [] + config.setting['use_genres'] = True + config.setting['folksonomy_tags'] = True + config.setting['only_my_tags'] = True + require_auth = self.obj.set_genre_inc_params(inc) + self.assertIn('user-tags', inc) + self.assertTrue(require_auth) diff --git a/test/test_mbjson.py b/test/test_mbjson.py index eeafaf849..ddcb041dc 100644 --- a/test/test_mbjson.py +++ b/test/test_mbjson.py @@ -65,7 +65,9 @@ class ReleaseTest(MBJSONTest): self.assertEqual(m['~albumartists'], 'Pink Floyd') self.assertEqual(m['~albumartists_sort'], 'Pink Floyd') self.assertEqual(m['~releaselanguage'], 'eng') - self.assertEqual(a.folksonomy_tags, {'test2': 3, 'test': 6}) + self.assertEqual(a.folksonomy_tags, { + 'genre1': 6, 'genre2': 3, + 'tag1': 6, 'tag2': 3 }) def test_media_formats_from_node(self): formats = media_formats_from_node(self.json_doc['media']) diff --git a/ui/options_genres.ui b/ui/options_genres.ui index 841bf5e61..c3dd7cee5 100644 --- a/ui/options_genres.ui +++ b/ui/options_genres.ui @@ -12,35 +12,31 @@ - + - Folksonomy Tags + Use genres from MusicBrainz + + + false + + + true + + + false - - - - Ignore tags: - - - - - - - Only use my tags + Only use my genres - - true - - Fall back on album's artists tags if no tags are found for the release or release group + Fall back on album's artists genres if no genres are found for the release or release group @@ -51,6 +47,16 @@ + + + + Ignore genres: + + + + + + @@ -77,7 +83,7 @@ - Minimal tag usage: + Minimal genre usage: min_tag_usage @@ -122,7 +128,7 @@ - Maximum number of tags: + Maximum number of genres: min_tag_usage @@ -164,7 +170,7 @@ - Join multiple tags with: + Join multiple genres with: