diff --git a/picard/__init__.py b/picard/__init__.py index cebc496cf..679600a5d 100644 --- a/picard/__init__.py +++ b/picard/__init__.py @@ -21,7 +21,7 @@ import re PICARD_ORG_NAME = "MusicBrainz" PICARD_APP_NAME = "Picard" -PICARD_VERSION = (2, 0, 5, 'dev', 1) +PICARD_VERSION = (2, 1, 0, 'dev', 1) # optional build version diff --git a/picard/album.py b/picard/album.py index 7921b8583..8c82a6270 100644 --- a/picard/album.py +++ b/picard/album.py @@ -364,9 +364,9 @@ class Album(DataObject, Item): self.status = _("[loading album information]") if self.release_group: self.release_group.loaded = False - self.release_group.folksonomy_tags.clear() + self.release_group.genres.clear() self.metadata.clear() - self.folksonomy_tags.clear() + self.genres.clear() self.update() self._new_metadata = Metadata() self._new_tracks = [] @@ -382,12 +382,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/config_upgrade.py b/picard/config_upgrade.py index 63b7f789a..b5ee09460 100644 --- a/picard/config_upgrade.py +++ b/picard/config_upgrade.py @@ -95,9 +95,7 @@ def upgrade_to_v1_3_0_dev_1(): _s = config.setting old_opt = "windows_compatible_filenames" new_opt = "windows_compatibility" - if old_opt in _s: - _s[new_opt] = _s.value(old_opt, config.BoolOption, True) - _s.remove(old_opt) + rename_option(old_opt, new_opt, config.BoolOption, True) def upgrade_to_v1_3_0_dev_2(): @@ -219,12 +217,9 @@ def upgrade_to_v1_4_0_dev_6(): def upgrade_to_v1_4_0_dev_7(): """Option "save_only_front_images_to_tags" was renamed to "embed_only_one_front_image".""" - _s = config.setting old_opt = "save_only_front_images_to_tags" new_opt = "embed_only_one_front_image" - if old_opt in _s: - _s[new_opt] = _s.value(old_opt, config.BoolOption, True) - _s.remove(old_opt) + rename_option(old_opt, new_opt, config.BoolOption, True) def upgrade_to_v2_0_0_dev_3(): @@ -246,6 +241,26 @@ def upgrade_to_v2_0_0_dev_3(): _s[opt] = _CAA_SIZE_COMPAT[value] +def upgrade_to_v2_1_0_dev_1(): + """Upgrade genre related options""" + _s = config.setting + if _s.get("folksonomy_tags"): + _s["use_genres"] = True + rename_option("max_tags", "max_genres", config.IntOption, 5) + rename_option("min_tag_usage", "min_genre_usage", config.IntOption, 90) + rename_option("ignore_tags", "ignore_genres", config.TextOption, "") + rename_option("join_tags", "join_genres", config.TextOption, "") + rename_option("only_my_tags", "only_my_genres", config.BoolOption, False) + rename_option("artists_tags", "artists_genres", config.BoolOption, False) + + +def rename_option(old_opt, new_opt, option_type, default): + _s = config.setting + if old_opt in _s: + _s[new_opt] = _s.value(old_opt, option_type, default) + _s.remove(old_opt) + + def upgrade_config(): cfg = config.config cfg.register_upgrade_hook(upgrade_to_v1_0_0_final_0) @@ -260,4 +275,5 @@ def upgrade_config(): cfg.register_upgrade_hook(upgrade_to_v1_4_0_dev_6) cfg.register_upgrade_hook(upgrade_to_v1_4_0_dev_7) cfg.register_upgrade_hook(upgrade_to_v2_0_0_dev_3) + cfg.register_upgrade_hook(upgrade_to_v2_1_0_dev_1) cfg.run_upgrade_hooks(log.debug) diff --git a/picard/dataobj.py b/picard/dataobj.py index 31d4e9602..35482b8e7 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 @@ -25,13 +26,25 @@ class DataObject(LockableObject): def __init__(self, obj_id): super().__init__() self.id = obj_id - self.folksonomy_tags = {} + self.genres = {} self.item = None - def add_folksonomy_tag(self, name, count): - self.folksonomy_tags[name] = self.folksonomy_tags.get(name, 0) + count + def add_genre(self, name, count): + self.genres[name] = self.genres.get(name, 0) + count @staticmethod - def merge_folksonomy_tags(this, that): + def set_genre_inc_params(inc): + require_authentication = False + if config.setting['use_genres']: + use_folksonomy = config.setting['folksonomy_tags'] + if config.setting['only_my_genres']: + 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_genres(this, that): for name, count in that.items(): this[name] = this.get(name, 0) + count diff --git a/picard/mbjson.py b/picard/mbjson.py index f8ee39f4b..486d41a4a 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,21 +490,21 @@ 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'] count = tag['count'] if key: - obj.add_folksonomy_tag(key, count) + obj.add_genre(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'] if key: - obj.add_folksonomy_tag(key, 1) + obj.add_genre(key, 1) def add_isrcs_to_metadata(node, metadata): diff --git a/picard/track.py b/picard/track.py index 0868dd5e6..f81b996e9 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 @@ -199,18 +199,18 @@ class Track(DataObject, Item): def _convert_folksonomy_tags_to_genre(self): # Combine release and track tags - tags = dict(self.folksonomy_tags) - self.merge_folksonomy_tags(tags, self.album.folksonomy_tags) + tags = dict(self.genres) + self.merge_genres(tags, self.album.genres) if self.album.release_group: - self.merge_folksonomy_tags(tags, self.album.release_group.folksonomy_tags) - if not tags and config.setting['artists_tags']: + self.merge_genres(tags, self.album.release_group.genres) + if not tags and config.setting['artists_genres']: # For compilations use each track's artists to look up tags if self.metadata['musicbrainz_albumartistid'] == VARIOUS_ARTISTS_ID: for artist in self._track_artists: - self.merge_folksonomy_tags(tags, artist.folksonomy_tags) + self.merge_genres(tags, artist.genres) else: for artist in self.album.get_album_artists(): - self.merge_folksonomy_tags(tags, artist.folksonomy_tags) + self.merge_genres(tags, artist.genres) # Ignore tags with zero or lower score tags = dict((name, count) for name, count in tags.items() if count > 0) if not tags: @@ -222,27 +222,27 @@ class Track(DataObject, Item): taglist.append((100 * count // maxcount, name)) taglist.sort(reverse=True) # And generate the genre metadata tag - maxtags = config.setting['max_tags'] - minusage = config.setting['min_tag_usage'] - ignore_tags = self._get_ignored_folksonomy_tags() + maxtags = config.setting['max_genres'] + minusage = config.setting['min_genre_usage'] + ignore_genres = self._get_ignored_folksonomy_tags() genre = [] for usage, name in taglist[:maxtags]: - if name.lower() in ignore_tags: + if name.lower() in ignore_genres: continue if usage < minusage: break name = _TRANSLATE_TAGS.get(name, name.title()) genre.append(name) - join_tags = config.setting['join_tags'] - if join_tags: - genre = [join_tags.join(genre)] + join_genres = config.setting['join_genres'] + if join_genres: + genre = [join_genres.join(genre)] self.metadata['genre'] = genre def _get_ignored_folksonomy_tags(self): tags = [] - ignore_tags = config.setting['ignore_tags'] - if ignore_tags: - tags = [s.strip().lower() for s in ignore_tags.split(',')] + ignore_genres = config.setting['ignore_genres'] + if ignore_genres: + tags = [s.strip().lower() for s in ignore_genres.split(',')] return tags def update_orig_metadata_images(self): @@ -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/dialog.py b/picard/ui/options/dialog.py index a08958d27..c34582085 100644 --- a/picard/ui/options/dialog.py +++ b/picard/ui/options/dialog.py @@ -40,8 +40,8 @@ from picard.ui.options import ( cdlookup, cover, fingerprinting, - folksonomy, general, + genres, interface, matching, metadata, diff --git a/picard/ui/options/folksonomy.py b/picard/ui/options/folksonomy.py deleted file mode 100644 index 2630f1700..000000000 --- a/picard/ui/options/folksonomy.py +++ /dev/null @@ -1,68 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Picard, the next-generation MusicBrainz tagger -# Copyright (C) 2008 Lukáš Lalinský -# -# 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. - -from picard import config - -from picard.ui.options import ( - OptionsPage, - register_options_page, -) -from picard.ui.ui_options_folksonomy import Ui_FolksonomyOptionsPage - - -class FolksonomyOptionsPage(OptionsPage): - - NAME = "folsonomy" - TITLE = N_("Folksonomy Tags") - PARENT = "metadata" - SORT_ORDER = 20 - ACTIVE = True - - options = [ - config.IntOption("setting", "max_tags", 5), - config.IntOption("setting", "min_tag_usage", 90), - config.TextOption("setting", "ignore_tags", "seen live,favorites,fixme,owned"), - config.TextOption("setting", "join_tags", ""), - config.BoolOption("setting", "only_my_tags", False), - config.BoolOption("setting", "artists_tags", False), - ] - - def __init__(self, parent=None): - super().__init__(parent) - self.ui = Ui_FolksonomyOptionsPage() - self.ui.setupUi(self) - - def load(self): - 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"]) - self.ui.ignore_tags.setText(config.setting["ignore_tags"]) - self.ui.only_my_tags.setChecked(config.setting["only_my_tags"]) - self.ui.artists_tags.setChecked(config.setting["artists_tags"]) - - def save(self): - 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() - config.setting["ignore_tags"] = self.ui.ignore_tags.text() - config.setting["only_my_tags"] = self.ui.only_my_tags.isChecked() - config.setting["artists_tags"] = self.ui.artists_tags.isChecked() - - -register_options_page(FolksonomyOptionsPage) diff --git a/picard/ui/options/genres.py b/picard/ui/options/genres.py new file mode 100644 index 000000000..2eb601f78 --- /dev/null +++ b/picard/ui/options/genres.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +# +# Picard, the next-generation MusicBrainz tagger +# Copyright (C) 2008 Lukáš Lalinský +# +# 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. + +from picard import config + +from picard.ui.options import ( + OptionsPage, + register_options_page, +) +from picard.ui.ui_options_genres import Ui_GenresOptionsPage + + +class GenresOptionsPage(OptionsPage): + + NAME = "genres" + TITLE = N_("Genres") + PARENT = "metadata" + SORT_ORDER = 20 + ACTIVE = True + + options = [ + config.BoolOption("setting", "use_genres", False), + config.IntOption("setting", "max_genres", 5), + config.IntOption("setting", "min_genre_usage", 90), + config.TextOption("setting", "ignore_genres", "seen live, favorites, fixme, owned"), + config.TextOption("setting", "join_genres", ""), + config.BoolOption("setting", "only_my_genres", False), + config.BoolOption("setting", "artists_genres", False), + config.BoolOption("setting", "folksonomy_tags", False), + ] + + def __init__(self, parent=None): + super().__init__(parent) + self.ui = Ui_GenresOptionsPage() + self.ui.setupUi(self) + + def load(self): + self.ui.use_genres.setChecked(config.setting["use_genres"]) + self.ui.max_genres.setValue(config.setting["max_genres"]) + self.ui.min_genre_usage.setValue(config.setting["min_genre_usage"]) + self.ui.join_genres.setEditText(config.setting["join_genres"]) + self.ui.ignore_genres.setText(config.setting["ignore_genres"]) + self.ui.only_my_genres.setChecked(config.setting["only_my_genres"]) + self.ui.artists_genres.setChecked(config.setting["artists_genres"]) + self.ui.folksonomy_tags.setChecked(config.setting["folksonomy_tags"]) + + def save(self): + config.setting["use_genres"] = self.ui.use_genres.isChecked() + config.setting["max_genres"] = self.ui.max_genres.value() + config.setting["min_genre_usage"] = self.ui.min_genre_usage.value() + config.setting["join_genres"] = self.ui.join_genres.currentText() + config.setting["ignore_genres"] = self.ui.ignore_genres.text() + config.setting["only_my_genres"] = self.ui.only_my_genres.isChecked() + config.setting["artists_genres"] = self.ui.artists_genres.isChecked() + config.setting["folksonomy_tags"] = self.ui.folksonomy_tags.isChecked() + + +register_options_page(GenresOptionsPage) diff --git a/picard/ui/options/metadata.py b/picard/ui/options/metadata.py index 37512a4fe..b8f8a0fb5 100644 --- a/picard/ui/options/metadata.py +++ b/picard/ui/options/metadata.py @@ -42,7 +42,6 @@ class MetadataOptionsPage(OptionsPage): config.BoolOption("setting", "translate_artist_names", False), config.BoolOption("setting", "release_ars", True), config.BoolOption("setting", "track_ars", False), - config.BoolOption("setting", "folksonomy_tags", False), config.BoolOption("setting", "convert_punctuation", True), config.BoolOption("setting", "standardize_artists", False), config.BoolOption("setting", "standardize_instruments", True), @@ -71,7 +70,6 @@ class MetadataOptionsPage(OptionsPage): self.ui.convert_punctuation.setChecked(config.setting["convert_punctuation"]) self.ui.release_ars.setChecked(config.setting["release_ars"]) self.ui.track_ars.setChecked(config.setting["track_ars"]) - self.ui.folksonomy_tags.setChecked(config.setting["folksonomy_tags"]) self.ui.va_name.setText(config.setting["va_name"]) self.ui.nat_name.setText(config.setting["nat_name"]) self.ui.standardize_artists.setChecked(config.setting["standardize_artists"]) @@ -83,7 +81,6 @@ class MetadataOptionsPage(OptionsPage): config.setting["convert_punctuation"] = self.ui.convert_punctuation.isChecked() config.setting["release_ars"] = self.ui.release_ars.isChecked() config.setting["track_ars"] = self.ui.track_ars.isChecked() - config.setting["folksonomy_tags"] = self.ui.folksonomy_tags.isChecked() config.setting["va_name"] = self.ui.va_name.text() nat_name = self.ui.nat_name.text() if nat_name != config.setting["nat_name"]: diff --git a/picard/ui/ui_options_folksonomy.py b/picard/ui/ui_options_folksonomy.py deleted file mode 100644 index d01b500ce..000000000 --- a/picard/ui/ui_options_folksonomy.py +++ /dev/null @@ -1,112 +0,0 @@ -# -*- coding: utf-8 -*- - -# Automatically generated - don't edit. -# Use `python setup.py build_ui` to update it. - -from PyQt5 import QtCore, QtGui, QtWidgets - -class Ui_FolksonomyOptionsPage(object): - def setupUi(self, FolksonomyOptionsPage): - FolksonomyOptionsPage.setObjectName("FolksonomyOptionsPage") - FolksonomyOptionsPage.resize(590, 304) - self.verticalLayout_2 = QtWidgets.QVBoxLayout(FolksonomyOptionsPage) - self.verticalLayout_2.setObjectName("verticalLayout_2") - self.rename_files_3 = QtWidgets.QGroupBox(FolksonomyOptionsPage) - self.rename_files_3.setObjectName("rename_files_3") - self.verticalLayout = QtWidgets.QVBoxLayout(self.rename_files_3) - 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.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.setObjectName("artists_tags") - self.verticalLayout.addWidget(self.artists_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) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.label_5.sizePolicy().hasHeightForWidth()) - 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.setMaximum(100) - self.min_tag_usage.setObjectName("min_tag_usage") - self.hboxlayout.addWidget(self.min_tag_usage) - self.verticalLayout.addLayout(self.hboxlayout) - self.hboxlayout1 = QtWidgets.QHBoxLayout() - self.hboxlayout1.setContentsMargins(0, 0, 0, 0) - self.hboxlayout1.setSpacing(6) - self.hboxlayout1.setObjectName("hboxlayout1") - self.label_6 = QtWidgets.QLabel(self.rename_files_3) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.label_6.sizePolicy().hasHeightForWidth()) - 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.setMaximum(100) - self.max_tags.setObjectName("max_tags") - self.hboxlayout1.addWidget(self.max_tags) - self.verticalLayout.addLayout(self.hboxlayout1) - self.hboxlayout2 = QtWidgets.QHBoxLayout() - 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) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(4) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.ignore_tags_4.sizePolicy().hasHeightForWidth()) - 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) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(1) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.join_tags.sizePolicy().hasHeightForWidth()) - self.join_tags.setSizePolicy(sizePolicy) - self.join_tags.setEditable(True) - self.join_tags.setObjectName("join_tags") - self.join_tags.addItem("") - self.join_tags.setItemText(0, "") - self.join_tags.addItem("") - self.join_tags.addItem("") - self.hboxlayout2.addWidget(self.join_tags) - self.verticalLayout.addLayout(self.hboxlayout2) - self.verticalLayout_2.addWidget(self.rename_files_3) - spacerItem = QtWidgets.QSpacerItem(181, 31, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) - self.verticalLayout_2.addItem(spacerItem) - self.label_5.setBuddy(self.min_tag_usage) - self.label_6.setBuddy(self.min_tag_usage) - - self.retranslateUi(FolksonomyOptionsPage) - QtCore.QMetaObject.connectSlotsByName(FolksonomyOptionsPage) - - def retranslateUi(self, FolksonomyOptionsPage): - _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.label_5.setText(_("Minimal tag usage:")) - self.min_tag_usage.setSuffix(_(" %")) - self.label_6.setText(_("Maximum number of tags:")) - self.ignore_tags_4.setText(_("Join multiple tags with:")) - self.join_tags.setItemText(1, _(" / ")) - self.join_tags.setItemText(2, _(", ")) - diff --git a/picard/ui/ui_options_genres.py b/picard/ui/ui_options_genres.py new file mode 100644 index 000000000..179e14c62 --- /dev/null +++ b/picard/ui/ui_options_genres.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- + +# Automatically generated - don't edit. +# Use `python setup.py build_ui` to update it. + +from PyQt5 import QtCore, QtGui, QtWidgets + +class Ui_GenresOptionsPage(object): + def setupUi(self, GenresOptionsPage): + GenresOptionsPage.setObjectName("GenresOptionsPage") + GenresOptionsPage.resize(590, 304) + self.verticalLayout_2 = QtWidgets.QVBoxLayout(GenresOptionsPage) + self.verticalLayout_2.setObjectName("verticalLayout_2") + 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.only_my_genres = QtWidgets.QCheckBox(self.use_genres) + self.only_my_genres.setObjectName("only_my_genres") + self.verticalLayout.addWidget(self.only_my_genres) + self.artists_genres = QtWidgets.QCheckBox(self.use_genres) + self.artists_genres.setObjectName("artists_genres") + self.verticalLayout.addWidget(self.artists_genres) + self.folksonomy_tags = QtWidgets.QCheckBox(self.use_genres) + self.folksonomy_tags.setObjectName("folksonomy_tags") + self.verticalLayout.addWidget(self.folksonomy_tags) + self.ignore_genres_2 = QtWidgets.QLabel(self.use_genres) + self.ignore_genres_2.setObjectName("ignore_genres_2") + self.verticalLayout.addWidget(self.ignore_genres_2) + self.ignore_genres = QtWidgets.QLineEdit(self.use_genres) + self.ignore_genres.setObjectName("ignore_genres") + self.verticalLayout.addWidget(self.ignore_genres) + 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.use_genres) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_5.sizePolicy().hasHeightForWidth()) + self.label_5.setSizePolicy(sizePolicy) + self.label_5.setObjectName("label_5") + self.hboxlayout.addWidget(self.label_5) + self.min_genre_usage = QtWidgets.QSpinBox(self.use_genres) + self.min_genre_usage.setMaximum(100) + self.min_genre_usage.setObjectName("min_genre_usage") + self.hboxlayout.addWidget(self.min_genre_usage) + self.verticalLayout.addLayout(self.hboxlayout) + self.hboxlayout1 = QtWidgets.QHBoxLayout() + self.hboxlayout1.setContentsMargins(0, 0, 0, 0) + self.hboxlayout1.setSpacing(6) + self.hboxlayout1.setObjectName("hboxlayout1") + self.label_6 = QtWidgets.QLabel(self.use_genres) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_6.sizePolicy().hasHeightForWidth()) + self.label_6.setSizePolicy(sizePolicy) + self.label_6.setObjectName("label_6") + self.hboxlayout1.addWidget(self.label_6) + self.max_genres = QtWidgets.QSpinBox(self.use_genres) + self.max_genres.setMaximum(100) + self.max_genres.setObjectName("max_genres") + self.hboxlayout1.addWidget(self.max_genres) + self.verticalLayout.addLayout(self.hboxlayout1) + self.hboxlayout2 = QtWidgets.QHBoxLayout() + self.hboxlayout2.setContentsMargins(0, 0, 0, 0) + self.hboxlayout2.setSpacing(6) + self.hboxlayout2.setObjectName("hboxlayout2") + self.ignore_genres_4 = QtWidgets.QLabel(self.use_genres) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(4) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.ignore_genres_4.sizePolicy().hasHeightForWidth()) + self.ignore_genres_4.setSizePolicy(sizePolicy) + self.ignore_genres_4.setObjectName("ignore_genres_4") + self.hboxlayout2.addWidget(self.ignore_genres_4) + self.join_genres = QtWidgets.QComboBox(self.use_genres) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(1) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.join_genres.sizePolicy().hasHeightForWidth()) + self.join_genres.setSizePolicy(sizePolicy) + self.join_genres.setEditable(True) + self.join_genres.setObjectName("join_genres") + self.join_genres.addItem("") + self.join_genres.setItemText(0, "") + self.join_genres.addItem("") + self.join_genres.addItem("") + self.hboxlayout2.addWidget(self.join_genres) + self.verticalLayout.addLayout(self.hboxlayout2) + 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_genre_usage) + self.label_6.setBuddy(self.min_genre_usage) + + self.retranslateUi(GenresOptionsPage) + QtCore.QMetaObject.connectSlotsByName(GenresOptionsPage) + + def retranslateUi(self, GenresOptionsPage): + _translate = QtCore.QCoreApplication.translate + self.use_genres.setTitle(_("Use genres from MusicBrainz")) + self.only_my_genres.setText(_("Only use my genres")) + self.artists_genres.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.ignore_genres_2.setText(_("Genres or folksonomy tags to exclude (comma-separated list):")) + self.label_5.setText(_("Minimal genre usage:")) + self.min_genre_usage.setSuffix(_(" %")) + self.label_6.setText(_("Maximum number of genres:")) + self.ignore_genres_4.setText(_("Join multiple genres with:")) + self.join_genres.setItemText(1, _(" / ")) + self.join_genres.setItemText(2, _(", ")) + diff --git a/picard/ui/ui_options_metadata.py b/picard/ui/ui_options_metadata.py index c3abce3dc..1b98d547b 100644 --- a/picard/ui/ui_options_metadata.py +++ b/picard/ui/ui_options_metadata.py @@ -43,9 +43,6 @@ class Ui_MetadataOptionsPage(object): self.track_ars = QtWidgets.QCheckBox(self.metadata_groupbox) self.track_ars.setObjectName("track_ars") self.verticalLayout_3.addWidget(self.track_ars) - self.folksonomy_tags = QtWidgets.QCheckBox(self.metadata_groupbox) - self.folksonomy_tags.setObjectName("folksonomy_tags") - self.verticalLayout_3.addWidget(self.folksonomy_tags) self.verticalLayout.addWidget(self.metadata_groupbox) self.custom_fields_groupbox = QtWidgets.QGroupBox(MetadataOptionsPage) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) @@ -89,8 +86,7 @@ class Ui_MetadataOptionsPage(object): MetadataOptionsPage.setTabOrder(self.standardize_artists, self.convert_punctuation) MetadataOptionsPage.setTabOrder(self.convert_punctuation, self.release_ars) MetadataOptionsPage.setTabOrder(self.release_ars, self.track_ars) - MetadataOptionsPage.setTabOrder(self.track_ars, self.folksonomy_tags) - MetadataOptionsPage.setTabOrder(self.folksonomy_tags, self.va_name) + MetadataOptionsPage.setTabOrder(self.track_ars, self.va_name) MetadataOptionsPage.setTabOrder(self.va_name, self.va_name_default) MetadataOptionsPage.setTabOrder(self.va_name_default, self.nat_name) MetadataOptionsPage.setTabOrder(self.nat_name, self.nat_name_default) @@ -104,9 +100,9 @@ class Ui_MetadataOptionsPage(object): self.convert_punctuation.setText(_("Convert Unicode punctuation characters to ASCII")) self.release_ars.setText(_("Use release relationships")) self.track_ars.setText(_("Use track relationships")) - self.folksonomy_tags.setText(_("Use folksonomy tags as genre")) self.custom_fields_groupbox.setTitle(_("Custom Fields")) self.label_6.setText(_("Various artists:")) self.label_7.setText(_("Non-album tracks:")) self.nat_name_default.setText(_("Default")) self.va_name_default.setText(_("Default")) + 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..945b7738c --- /dev/null +++ b/test/test_dataobj.py @@ -0,0 +1,71 @@ +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_genres'] = 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_genres'] = 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_genres'] = 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_genres'] = True + require_auth = self.obj.set_genre_inc_params(inc) + self.assertIn('user-tags', inc) + self.assertTrue(require_auth) + + def test_add_genres(self): + self.obj.add_genre('genre1', 2) + self.assertEqual(self.obj.genres['genre1'], 2) + self.obj.add_genre('genre1', 5) + self.assertEqual(self.obj.genres['genre1'], 7) + + def test_merge_genres(self): + genres1 = {'a': 2, 'b': 7} + genres2 = {'b': 4, 'c': 3} + DataObject.merge_genres(genres1, genres2) + self.assertEqual(genres1['a'], 2) + self.assertEqual(genres1['b'], 11) + self.assertEqual(genres1['c'], 3) + self.assertNotIn('a', genres2) + self.assertEqual(genres2['b'], 4) + self.assertEqual(genres2['c'], 3) diff --git a/test/test_mbjson.py b/test/test_mbjson.py index eeafaf849..c56f2a187 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.genres, { + 'genre1': 6, 'genre2': 3, + 'tag1': 6, 'tag2': 3 }) def test_media_formats_from_node(self): formats = media_formats_from_node(self.json_doc['media']) @@ -246,7 +248,7 @@ class ReleaseGroupTest(MBJSONTest): self.assertEqual(m['releasetype'], 'album') self.assertEqual(m['~primaryreleasetype'], 'album') self.assertEqual(m['~releasegroup'], 'The Dark Side of the Moon') - self.assertEqual(r.folksonomy_tags, {'test2': 3, 'test': 6}) + self.assertEqual(r.genres, {'test2': 3, 'test': 6}) class NullReleaseGroupTest(MBJSONTest): diff --git a/ui/options_folksonomy.ui b/ui/options_genres.ui similarity index 62% rename from ui/options_folksonomy.ui rename to ui/options_genres.ui index a6c29b68f..4c555dfbf 100644 --- a/ui/options_folksonomy.ui +++ b/ui/options_genres.ui @@ -1,7 +1,7 @@ - FolksonomyOptionsPage - + GenresOptionsPage + 0 @@ -12,44 +12,66 @@ - + - Folksonomy Tags + Use genres from MusicBrainz + + + false + + + true + + + false - + - Ignore tags: + Only use my genres - - - - + - Only use my tags + Fall back on album's artists genres if no genres are found for the release or release group - - - true - + - Fall back on album's artists tags if no tags are found for the release or release group + Use folksonomy tags as genre + + + + Genres or folksonomy tags to exclude (comma-separated list): + + + + + + 6 - + + 0 + + + 0 + + + 0 + + 0 @@ -61,15 +83,15 @@ - Minimal tag usage: + Minimal genre usage: - min_tag_usage + min_genre_usage - + % @@ -85,7 +107,16 @@ 6 - + + 0 + + + 0 + + + 0 + + 0 @@ -97,15 +128,15 @@ - Maximum number of tags: + Maximum number of genres: - min_tag_usage + min_genre_usage - + 100 @@ -118,11 +149,20 @@ 6 - + + 0 + + + 0 + + + 0 + + 0 - + 4 @@ -130,12 +170,12 @@ - Join multiple tags with: + Join multiple genres with: - + 1 diff --git a/ui/options_metadata.ui b/ui/options_metadata.ui index 15cf61317..d78660740 100644 --- a/ui/options_metadata.ui +++ b/ui/options_metadata.ui @@ -77,13 +77,6 @@ - - - - Use folksonomy tags as genre - - - @@ -176,7 +169,6 @@ convert_punctuation release_ars track_ars - folksonomy_tags va_name va_name_default nat_name