Merge pull request #1047 from phw/PICARD-1395-musicbrainz-genres

PICARD-1395: MusicBrainz genres
This commit is contained in:
Philipp Wolfer
2018-11-27 14:09:40 +01:00
committed by GitHub
18 changed files with 441 additions and 305 deletions

View File

@@ -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

View File

@@ -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']

View File

@@ -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)

View File

@@ -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

View File

@@ -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):

View File

@@ -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"]

View File

@@ -40,8 +40,8 @@ from picard.ui.options import (
cdlookup,
cover,
fingerprinting,
folksonomy,
general,
genres,
interface,
matching,
metadata,

View File

@@ -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)

View File

@@ -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)

View File

@@ -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"]:

View File

@@ -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, _(", "))

View File

@@ -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, _(", "))

View File

@@ -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"))

View File

@@ -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"

71
test/test_dataobj.py Normal file
View File

@@ -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)

View File

@@ -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):

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FolksonomyOptionsPage</class>
<widget class="QWidget" name="FolksonomyOptionsPage">
<class>GenresOptionsPage</class>
<widget class="QWidget" name="GenresOptionsPage">
<property name="geometry">
<rect>
<x>0</x>
@@ -12,44 +12,66 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="rename_files_3">
<widget class="QGroupBox" name="use_genres">
<property name="title">
<string>Folksonomy Tags</string>
<string>Use genres from MusicBrainz</string>
</property>
<property name="flat">
<bool>false</bool>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="ignore_tags_2">
<widget class="QCheckBox" name="only_my_genres">
<property name="text">
<string>Ignore tags:</string>
<string>Only use my genres</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="ignore_tags"/>
</item>
<item>
<widget class="QCheckBox" name="only_my_tags">
<widget class="QCheckBox" name="artists_genres">
<property name="text">
<string>Only use my tags</string>
<string>Fall back on album's artists genres if no genres are found for the release or release group</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="artists_tags">
<property name="enabled">
<bool>true</bool>
</property>
<widget class="QCheckBox" name="folksonomy_tags">
<property name="text">
<string>Fall back on album's artists tags if no tags are found for the release or release group</string>
<string>Use folksonomy tags as genre</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="ignore_genres_2">
<property name="text">
<string>Genres or folksonomy tags to exclude (comma-separated list):</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="ignore_genres"/>
</item>
<item>
<layout class="QHBoxLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="margin">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
@@ -61,15 +83,15 @@
</sizepolicy>
</property>
<property name="text">
<string>Minimal tag usage:</string>
<string>Minimal genre usage:</string>
</property>
<property name="buddy">
<cstring>min_tag_usage</cstring>
<cstring>min_genre_usage</cstring>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="min_tag_usage">
<widget class="QSpinBox" name="min_genre_usage">
<property name="suffix">
<string> %</string>
</property>
@@ -85,7 +107,16 @@
<property name="spacing">
<number>6</number>
</property>
<property name="margin">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
@@ -97,15 +128,15 @@
</sizepolicy>
</property>
<property name="text">
<string>Maximum number of tags:</string>
<string>Maximum number of genres:</string>
</property>
<property name="buddy">
<cstring>min_tag_usage</cstring>
<cstring>min_genre_usage</cstring>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="max_tags">
<widget class="QSpinBox" name="max_genres">
<property name="maximum">
<number>100</number>
</property>
@@ -118,11 +149,20 @@
<property name="spacing">
<number>6</number>
</property>
<property name="margin">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="ignore_tags_4">
<widget class="QLabel" name="ignore_genres_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>4</horstretch>
@@ -130,12 +170,12 @@
</sizepolicy>
</property>
<property name="text">
<string>Join multiple tags with:</string>
<string>Join multiple genres with:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="join_tags">
<widget class="QComboBox" name="join_genres">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>1</horstretch>

View File

@@ -77,13 +77,6 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="folksonomy_tags">
<property name="text">
<string>Use folksonomy tags as genre</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@@ -176,7 +169,6 @@
<tabstop>convert_punctuation</tabstop>
<tabstop>release_ars</tabstop>
<tabstop>track_ars</tabstop>
<tabstop>folksonomy_tags</tabstop>
<tabstop>va_name</tabstop>
<tabstop>va_name_default</tabstop>
<tabstop>nat_name</tabstop>