diff --git a/.gitignore b/.gitignore index 20756da8a..4456c8063 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ locale *.exe .DS_Store po/countries/countries.pot +po/attributes/attributes.pot diff --git a/.tx/config b/.tx/config index c767707e5..405ed3983 100644 --- a/.tx/config +++ b/.tx/config @@ -12,3 +12,9 @@ file_filter = po/countries/.po source_file = po/countries/countries.pot source_lang = en type = PO + +[musicbrainz.attributes] +file_filter = po/attributes/.po +source_file = po/attributes/attributes.pot +source_lang = en +type = PO diff --git a/NEWS.txt b/NEWS.txt index 4395ea09c..ab3ce3930 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -24,7 +24,6 @@ * Added "_artists_sort", "_albumartists", "_albumartists_sort" variables for scripts and plugins. * Made Picard use the country names also used on the MusicBrainz website (PICARD-205) * New setup.py command `get_po_files` (Retrieve po files from transifex) - * New setup.py command `update_countries` (Regenerate countries.py) * New setup.py command `regen_pot_file` (Regenerate po/picard.pot) * New Work tag (which for Classical music is often different from the track title) saved as ID3 TOAL tag. * New Composer Sort Order tag (variable %composersort%). @@ -38,6 +37,9 @@ * Show the ID3 version of the file in the Info... dialog (Ctrl-I) (PICARD-218) * Fixed a bug where Picard crashed if a MP3 file had malformed TRCK or TPOS tags (PICARD-112) * Add --files option to setup.py build_ui, used to force .ui to .py regeneration (PICARD-566) + * New setup.py command `update_constants` (Regenerate countries.py and attributes.py) + * Made Picard use release groups, medium formats and cover art types also used on the MusicBrainz website + * Use MusicBrainz Server translations for release groups, medium formats and cover art types diff --git a/picard/__init__.py b/picard/__init__.py index 1f13df46f..8c0f42ada 100644 --- a/picard/__init__.py +++ b/picard/__init__.py @@ -22,7 +22,7 @@ import re PICARD_APP_NAME = "Picard" PICARD_ORG_NAME = "MusicBrainz" -PICARD_VERSION = (1, 3, 0, 'dev', 3) +PICARD_VERSION = (1, 3, 0, 'dev', 4) class VersionError(Exception): diff --git a/picard/attributes.py b/picard/attributes.py new file mode 100644 index 000000000..6a7419c70 --- /dev/null +++ b/picard/attributes.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +# Automatically generated - don't edit. +# Use `python setup.py update_constants` to update it. + +MB_ATTRIBUTES = { + u'DB:cover_art_archive.art_type/name:001': u'Front', + u'DB:cover_art_archive.art_type/name:002': u'Back', + u'DB:cover_art_archive.art_type/name:003': u'Booklet', + u'DB:cover_art_archive.art_type/name:004': u'Medium', + u'DB:cover_art_archive.art_type/name:005': u'Obi', + u'DB:cover_art_archive.art_type/name:006': u'Spine', + u'DB:cover_art_archive.art_type/name:007': u'Track', + u'DB:cover_art_archive.art_type/name:008': u'Other', + u'DB:cover_art_archive.art_type/name:009': u'Tray', + u'DB:cover_art_archive.art_type/name:010': u'Sticker', + u'DB:cover_art_archive.art_type/name:011': u'Poster', + u'DB:cover_art_archive.art_type/name:012': u'Liner', + u'DB:cover_art_archive.art_type/name:013': u'Watermark', + u'DB:medium_format/name:001': u'CD', + u'DB:medium_format/name:002': u'DVD', + u'DB:medium_format/name:003': u'SACD', + u'DB:medium_format/name:004': u'DualDisc', + u'DB:medium_format/name:005': u'LaserDisc', + u'DB:medium_format/name:006': u'MiniDisc', + u'DB:medium_format/name:007': u'Vinyl', + u'DB:medium_format/name:008': u'Cassette', + u'DB:medium_format/name:009': u'Cartridge', + u'DB:medium_format/name:010': u'Reel-to-reel', + u'DB:medium_format/name:011': u'DAT', + u'DB:medium_format/name:012': u'Digital Media', + u'DB:medium_format/name:013': u'Other', + u'DB:medium_format/name:014': u'Wax Cylinder', + u'DB:medium_format/name:015': u'Piano Roll', + u'DB:medium_format/name:016': u'DCC', + u'DB:medium_format/name:017': u'HD-DVD', + u'DB:medium_format/name:018': u'DVD-Audio', + u'DB:medium_format/name:019': u'DVD-Video', + u'DB:medium_format/name:020': u'Blu-ray', + u'DB:medium_format/name:021': u'VHS', + u'DB:medium_format/name:022': u'VCD', + u'DB:medium_format/name:023': u'SVCD', + u'DB:medium_format/name:024': u'Betamax', + u'DB:medium_format/name:025': u'HDCD', + u'DB:medium_format/name:026': u'USB Flash Drive', + u'DB:medium_format/name:027': u'slotMusic', + u'DB:medium_format/name:028': u'UMD', + u'DB:medium_format/name:029': u'7" Vinyl', + u'DB:medium_format/name:030': u'10" Vinyl', + u'DB:medium_format/name:031': u'12" Vinyl', + u'DB:medium_format/name:032': u'Videotape', + u'DB:medium_format/name:033': u'CD-R', + u'DB:medium_format/name:034': u'8cm CD', + u'DB:medium_format/name:035': u'Blu-spec CD', + u'DB:medium_format/name:036': u'SHM-CD', + u'DB:medium_format/name:037': u'HQCD', + u'DB:medium_format/name:038': u'Hybrid SACD', + u'DB:medium_format/name:039': u'CD+G', + u'DB:medium_format/name:040': u'8cm CD+G', + u'DB:release_group_primary_type/name:001': u'Album', + u'DB:release_group_primary_type/name:002': u'Single', + u'DB:release_group_primary_type/name:003': u'EP', + u'DB:release_group_primary_type/name:011': u'Other', + u'DB:release_group_primary_type/name:012': u'Broadcast', + u'DB:release_group_secondary_type/name:001': u'Compilation', + u'DB:release_group_secondary_type/name:002': u'Soundtrack', + u'DB:release_group_secondary_type/name:003': u'Spokenword', + u'DB:release_group_secondary_type/name:004': u'Interview', + u'DB:release_group_secondary_type/name:005': u'Audiobook', + u'DB:release_group_secondary_type/name:006': u'Live', + u'DB:release_group_secondary_type/name:007': u'Remix', + u'DB:release_group_secondary_type/name:008': u'DJ-mix', + u'DB:release_group_secondary_type/name:009': u'Mixtape/Street', +} diff --git a/picard/config_upgrade.py b/picard/config_upgrade.py index 463999853..fac1f9184 100644 --- a/picard/config_upgrade.py +++ b/picard/config_upgrade.py @@ -118,10 +118,29 @@ def upgrade_to_v1_3_0_dev_3(): _s[opt] = _s.raw_value(opt).split(sep) +def upgrade_to_v1_3_0_dev_4(): + """Option "release_type_scores" is now a list of tuples + """ + def load_release_type_scores(setting): + scores = [] + values = setting.split() + for i in range(0, len(values), 2): + try: + score = float(values[i + 1]) + except IndexError: + score = 0.0 + scores.append((values[i], score)) + return scores + + opt = "release_type_scores" + _s[opt] = load_release_type_scores(_s.raw_value(opt)) + + def upgrade_config(): cfg = config._config cfg.register_upgrade_hook(upgrade_to_v1_0_0_final_0) cfg.register_upgrade_hook(upgrade_to_v1_3_0_dev_1) cfg.register_upgrade_hook(upgrade_to_v1_3_0_dev_2) cfg.register_upgrade_hook(upgrade_to_v1_3_0_dev_3) + cfg.register_upgrade_hook(upgrade_to_v1_3_0_dev_4) cfg.run_upgrade_hooks(log.debug) diff --git a/picard/const.py b/picard/const.py index efd9bc1e4..e7f20e5e0 100644 --- a/picard/const.py +++ b/picard/const.py @@ -63,42 +63,17 @@ PICARD_URLS = { VARIOUS_ARTISTS_ID = '89ad4ac3-39f7-470e-963a-56509c546377' # Release formats -RELEASE_FORMATS = { - u'CD': N_('CD'), - u'CD-R': N_('CD-R'), - u'HDCD': N_('HDCD'), - u'8cm CD': N_('8cm CD'), - u'Vinyl': N_('Vinyl'), - u'7" Vinyl': N_('7" Vinyl'), - u'10" Vinyl': N_('10" Vinyl'), - u'12" Vinyl': N_('12" Vinyl'), - u'Digital Media': N_('Digital Media'), - u'USB Flash Drive': N_('USB Flash Drive'), - u'slotMusic': N_('slotMusic'), - u'Cassette': N_('Cassette'), - u'DVD': N_('DVD'), - u'DVD-Audio': N_('DVD-Audio'), - u'DVD-Video': N_('DVD-Video'), - u'SACD': N_('SACD'), - u'DualDisc': N_('DualDisc'), - u'MiniDisc': N_('MiniDisc'), - u'Blu-ray': N_('Blu-ray'), - u'HD-DVD': N_('HD-DVD'), - u'Videotape': N_('Videotape'), - u'VHS': N_('VHS'), - u'Betamax': N_('Betamax'), - u'VCD': N_('VCD'), - u'SVCD': N_('SVCD'), - u'UMD': N_('UMD'), - u'Other': N_('Other'), - u'LaserDisc': N_('LaserDisc'), - u'Cartridge': N_('Cartridge'), - u'Reel-to-reel': N_('Reel-to-reel'), - u'DAT': N_('DAT'), - u'Wax Cylinder': N_('Wax Cylinder'), - u'Piano Roll': N_('Piano Roll'), - u'DCC': N_('DCC') -} +from picard.attributes import MB_ATTRIBUTES +RELEASE_FORMATS = {} +RELEASE_PRIMARY_GROUPS = {} +RELEASE_SECONDARY_GROUPS = {} +for k, v in MB_ATTRIBUTES.iteritems(): + if k.startswith(u'DB:medium_format/name:'): + RELEASE_FORMATS[v] = v + elif k.startswith(u'DB:release_group_primary_type/name:'): + RELEASE_PRIMARY_GROUPS[v] = v + elif k.startswith(u'DB:release_group_secondary_type/name:'): + RELEASE_SECONDARY_GROUPS[v] = v # Release countries from picard.countries import RELEASE_COUNTRIES diff --git a/picard/countries.py b/picard/countries.py index fa67a2835..64405ed41 100644 --- a/picard/countries.py +++ b/picard/countries.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Automatically generated - don't edit. -# Use `python setup.py update_countries` to update it. +# Use `python setup.py update_constants` to update it. RELEASE_COUNTRIES = { u'AD': u'Andorra', diff --git a/picard/coverartarchive.py b/picard/coverartarchive.py index 845932aa4..8ddeb3e51 100644 --- a/picard/coverartarchive.py +++ b/picard/coverartarchive.py @@ -17,19 +17,14 @@ # 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.attributes import MB_ATTRIBUTES # list of types from http://musicbrainz.org/doc/Cover_Art/Types # order of declaration is preserved in selection box -CAA_TYPES = [ - {'name': "front", 'title': N_("Front")}, - {'name': "back", 'title': N_("Back")}, - {'name': "booklet", 'title': N_("Booklet")}, - {'name': "medium", 'title': N_("Medium")}, - {'name': "tray", 'title': N_("Tray")}, - {'name': "obi", 'title': N_("Obi")}, - {'name': "spine", 'title': N_("Spine")}, - {'name': "track", 'title': N_("Track")}, - {'name': "sticker", 'title': N_("Sticker")}, - {'name': "other", 'title': N_("Other")}, - {'name': "unknown", 'title': N_("Unknown")}, # pseudo type, used for the no type case -] +CAA_TYPES = [] +for k, v in sorted(MB_ATTRIBUTES.items(), key=lambda (k,v): k): + if k.startswith(u'DB:cover_art_archive.art_type/name:'): + CAA_TYPES.append({'name': v.lower(), 'title': v}) + +# pseudo type, used for the no type case +CAA_TYPES.append({'name': "unknown", 'title': N_(u"Unknown")}) diff --git a/picard/i18n.py b/picard/i18n.py index 85161d0e5..755e440bc 100644 --- a/picard/i18n.py +++ b/picard/i18n.py @@ -72,6 +72,9 @@ def setup_gettext(localedir, ui_language=None, logger=None): logger("Loading gettext translation (picard-countries), localedir=%r", localedir) trans_countries = gettext.translation("picard-countries", localedir) _ugettext_countries = trans_countries.ugettext + logger("Loading gettext translation (picard-attributes), localedir=%r", localedir) + trans_attributes = gettext.translation("picard-attributes", localedir) + _ugettext_attributes = trans_attributes.ugettext except IOError as e: logger(e) __builtin__.__dict__['_'] = lambda a: a @@ -85,10 +88,38 @@ def setup_gettext(localedir, ui_language=None, logger=None): def _ugettext_countries(msg): return msg + def _ugettext_attributes(msg): + return msg + __builtin__.__dict__['ungettext'] = _ungettext __builtin__.__dict__['ugettext_countries'] = _ugettext_countries + __builtin__.__dict__['ugettext_attributes'] = _ugettext_attributes logger("_ = %r", _) logger("N_ = %r", N_) logger("ungettext = %r", ungettext) logger("ugettext_countries = %r", ugettext_countries) + logger("ugettext_attributes = %r", ugettext_attributes) + + +# Workaround for po files with msgctxt which isn't supported by current python +# gettext +# msgctxt are used within attributes.po, and ugettext is failing to translate +# strings due to that +# This workaround is a hack until we get proper msgctxt support +_CONTEXT_SEPARATOR = "\x04" +def ugettext_ctxt(ugettext_, message, context=None): + if context is None: + return ugettext_(message) + + msg_with_ctxt = u"%s%s%s" % (context, _CONTEXT_SEPARATOR, message) + translated = ugettext_(msg_with_ctxt) + if _CONTEXT_SEPARATOR in translated: + # no translation found, return original message + return message + return translated + + +def ugettext_attr(message, context=None): + """Translate MB attributes, depending on context""" + return ugettext_ctxt(ugettext_attributes, message, context) diff --git a/picard/metadata.py b/picard/metadata.py index e7af4d78f..c6bd757ab 100644 --- a/picard/metadata.py +++ b/picard/metadata.py @@ -32,7 +32,6 @@ from picard.plugin import ExtensionPoint from picard.similarity import similarity2 from picard.util import ( encode_filename, - load_release_type_scores, mimetype as mime, replace_non_ascii, replace_win32_incompat, @@ -288,7 +287,7 @@ class Metadata(dict): parts.append((score, weights["format"])) if "releasetype" in weights: - type_scores = load_release_type_scores(config.setting["release_type_scores"]) + type_scores = dict(config.setting["release_type_scores"]) if 'release_group' in release.children and 'type' in release.release_group[0].attribs: release_type = release.release_group[0].type score = type_scores.get(release_type, type_scores.get('Other', 0.5)) diff --git a/picard/ui/options/cover.py b/picard/ui/options/cover.py index 83c4f9aa0..090e2e392 100644 --- a/picard/ui/options/cover.py +++ b/picard/ui/options/cover.py @@ -22,6 +22,7 @@ from picard import config from picard.ui.options import OptionsPage, register_options_page from picard.ui.ui_options_cover import Ui_CoverOptionsPage from picard.coverartarchive import CAA_TYPES +from picard.i18n import ugettext_attr class CAATypesSelector(object): @@ -39,7 +40,11 @@ class CAATypesSelector(object): def _add_item(self, typ, enabled=False): item = QtGui.QListWidgetItem(self.widget) - item.setText(typ['title']) + if typ['name'] == 'unknown': + title = _(typ['title']) + else: + title = ugettext_attr(typ['title'], u"cover_art_type") + item.setText(title) tooltip = u"CAA: %(name)s" % typ item.setToolTip(tooltip) if enabled: diff --git a/picard/ui/options/releases.py b/picard/ui/options/releases.py index 0b3726dab..038242807 100644 --- a/picard/ui/options/releases.py +++ b/picard/ui/options/releases.py @@ -21,10 +21,64 @@ from operator import itemgetter from locale import strcoll from PyQt4 import QtCore, QtGui from picard import config -from picard.util import load_release_type_scores, save_release_type_scores from picard.ui.options import OptionsPage, register_options_page from picard.ui.ui_options_releases import Ui_ReleasesOptionsPage -from picard.const import RELEASE_COUNTRIES, RELEASE_FORMATS +from picard.const import (RELEASE_COUNTRIES, + RELEASE_FORMATS, + RELEASE_PRIMARY_GROUPS, + RELEASE_SECONDARY_GROUPS) +from picard.i18n import ugettext_attr + + +_DEFAULT_SCORE = 0.5 +_release_type_scores = [(g, _DEFAULT_SCORE) for g in RELEASE_PRIMARY_GROUPS.keys() + RELEASE_SECONDARY_GROUPS.keys()] + + +class ReleaseTypeScore: + + def __init__(self, group, layout, label, cell): + row, column = cell # it uses 2 cells (r,c and r,c+1) + self.group = group + self.layout = layout + self.label = QtGui.QLabel(self.group) + self.label.setText(label) + self.layout.addWidget(self.label, row, column, 1, 1) + self.slider = QtGui.QSlider(self.group) + self.slider.setMaximum(100) + self.slider.setOrientation(QtCore.Qt.Horizontal) + self.layout.addWidget(self.slider, row, column + 1, 1, 1) + self.reset() + + def setValue(self, value): + self.slider.setValue(int(value * 100)) + + def value(self): + return float(self.slider.value()) / 100.0 + + def reset(self): + self.setValue(_DEFAULT_SCORE) + + +class RowColIter: + + def __init__(self, max_cells, max_cols=6, step=2): + assert(max_cols % step == 0) + self.step = step + self.cols = max_cols + self.rows = int((max_cells - 1) / (self.cols / step)) + 1 + self.current = (-1, 0) + + def __iter__(self): + return self + + def next(self): + row, col = self.current + row += 1 + if row == self.rows: + col += self.step + row = 0 + self.current = (row, col) + return self.current class ReleasesOptionsPage(OptionsPage): @@ -36,29 +90,43 @@ class ReleasesOptionsPage(OptionsPage): ACTIVE = True options = [ - config.TextOption("setting", "release_type_scores", "Album 0.5 Single 0.5 EP 0.5 Compilation 0.5 Soundtrack 0.5 Spokenword 0.5 Interview 0.5 Audiobook 0.5 Live 0.5 Remix 0.5 Other 0.5"), + config.ListOption("setting", "release_type_scores", _release_type_scores), config.ListOption("setting", "preferred_release_countries", []), config.ListOption("setting", "preferred_release_formats", []), ] - _release_type_sliders = {} - def __init__(self, parent=None): super(ReleasesOptionsPage, self).__init__(parent) self.ui = Ui_ReleasesOptionsPage() self.ui.setupUi(self) - self.ui.reset_preferred_types_btn.clicked.connect(self.reset_preferred_types) - self._release_type_sliders["Album"] = self.ui.prefer_album_score - self._release_type_sliders["Single"] = self.ui.prefer_single_score - self._release_type_sliders["EP"] = self.ui.prefer_ep_score - self._release_type_sliders["Compilation"] = self.ui.prefer_compilation_score - self._release_type_sliders["Soundtrack"] = self.ui.prefer_soundtrack_score - self._release_type_sliders["Spokenword"] = self.ui.prefer_spokenword_score - self._release_type_sliders["Interview"] = self.ui.prefer_interview_score - self._release_type_sliders["Audiobook"] = self.ui.prefer_audiobook_score - self._release_type_sliders["Live"] = self.ui.prefer_live_score - self._release_type_sliders["Remix"] = self.ui.prefer_remix_score - self._release_type_sliders["Other"] = self.ui.prefer_other_score + + self._release_type_sliders = {} + + def add_slider(name, griditer, context): + label = ugettext_attr(name, context) + self._release_type_sliders[name] = \ + ReleaseTypeScore(self.ui.type_group, + self.ui.gridLayout, + label, + griditer.next()) + + griditer = RowColIter(len(RELEASE_PRIMARY_GROUPS) + + len(RELEASE_SECONDARY_GROUPS) + 1) # +1 for Reset button + for name in RELEASE_PRIMARY_GROUPS: + add_slider(name, griditer, context=u'release_group_primary_type') + for name in RELEASE_SECONDARY_GROUPS: + add_slider(name, griditer, context=u'release_group_secondary_type') + + self.reset_preferred_types_btn = QtGui.QPushButton(self.ui.type_group) + self.reset_preferred_types_btn.setText(_("Reset all")) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.reset_preferred_types_btn.sizePolicy().hasHeightForWidth()) + self.reset_preferred_types_btn.setSizePolicy(sizePolicy) + r, c = griditer.next() + self.ui.gridLayout.addWidget(self.reset_preferred_types_btn, r, c, 1, 2) + self.reset_preferred_types_btn.clicked.connect(self.reset_preferred_types) self.ui.add_countries.clicked.connect(self.add_preferred_countries) self.ui.remove_countries.clicked.connect(self.remove_preferred_countries) @@ -70,9 +138,10 @@ class ReleasesOptionsPage(OptionsPage): self.ui.preferred_format_list.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) def load(self): - scores = load_release_type_scores(config.setting["release_type_scores"]) + scores = dict(config.setting["release_type_scores"]) for (release_type, release_type_slider) in self._release_type_sliders.iteritems(): - release_type_slider.setValue(int(scores.get(release_type, 0.5) * 100)) + release_type_slider.setValue(scores.get(release_type, + _DEFAULT_SCORE)) self._load_list_items("preferred_release_countries", RELEASE_COUNTRIES, self.ui.country_list, self.ui.preferred_country_list) @@ -80,17 +149,17 @@ class ReleasesOptionsPage(OptionsPage): self.ui.format_list, self.ui.preferred_format_list) def save(self): - scores = {} + scores = [] for (release_type, release_type_slider) in self._release_type_sliders.iteritems(): - scores[release_type] = float(release_type_slider.value()) / 100.0 - config.setting["release_type_scores"] = save_release_type_scores(scores) + scores.append((release_type, release_type_slider.value())) + config.setting["release_type_scores"] = scores self._save_list_items("preferred_release_countries", self.ui.preferred_country_list) self._save_list_items("preferred_release_formats", self.ui.preferred_format_list) def reset_preferred_types(self): for release_type_slider in self._release_type_sliders.values(): - release_type_slider.setValue(50) + release_type_slider.reset() def add_preferred_countries(self): self._move_selected_items(self.ui.country_list, self.ui.preferred_country_list) @@ -114,7 +183,11 @@ class ReleasesOptionsPage(OptionsPage): def _load_list_items(self, setting, source, list1, list2): if setting == "preferred_release_countries": - source_list = [(c[0], ugettext_countries(c[1])) for c in source.items()] + source_list = [(c[0], ugettext_countries(c[1])) for c in + source.items()] + elif setting == "preferred_release_formats": + source_list = [(c[0], ugettext_attr(c[1], u"medium_format")) for c + in source.items()] else: source_list = [(c[0], _(c[1])) for c in source.items()] source_list.sort(key=itemgetter(1), cmp=strcoll) diff --git a/picard/ui/ui_options_cover.py b/picard/ui/ui_options_cover.py index 1da7c9db2..b82005bd0 100644 --- a/picard/ui/ui_options_cover.py +++ b/picard/ui/ui_options_cover.py @@ -83,12 +83,11 @@ class Ui_CoverOptionsPage(object): self.label_2.setObjectName(_fromUtf8("label_2")) self.verticalLayout_3.addWidget(self.label_2) self.caa_types_selector_1 = QtGui.QListWidget(self.gb_caa) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Preferred) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.caa_types_selector_1.sizePolicy().hasHeightForWidth()) self.caa_types_selector_1.setSizePolicy(sizePolicy) - self.caa_types_selector_1.setMaximumSize(QtCore.QSize(16777215, 80)) self.caa_types_selector_1.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) self.caa_types_selector_1.setTabKeyNavigation(True) self.caa_types_selector_1.setProperty("showDropIndicator", False) diff --git a/picard/ui/ui_options_releases.py b/picard/ui/ui_options_releases.py index caa8f85c4..650631cc3 100644 --- a/picard/ui/ui_options_releases.py +++ b/picard/ui/ui_options_releases.py @@ -21,113 +21,6 @@ class Ui_ReleasesOptionsPage(object): self.gridLayout = QtGui.QGridLayout(self.type_group) self.gridLayout.setVerticalSpacing(6) self.gridLayout.setObjectName(_fromUtf8("gridLayout")) - self.label = QtGui.QLabel(self.type_group) - self.label.setObjectName(_fromUtf8("label")) - self.gridLayout.addWidget(self.label, 0, 0, 1, 1) - self.prefer_album_score = QtGui.QSlider(self.type_group) - self.prefer_album_score.setMaximum(100) - self.prefer_album_score.setProperty("value", 50) - self.prefer_album_score.setOrientation(QtCore.Qt.Horizontal) - self.prefer_album_score.setObjectName(_fromUtf8("prefer_album_score")) - self.gridLayout.addWidget(self.prefer_album_score, 0, 1, 1, 1) - self.label_2 = QtGui.QLabel(self.type_group) - self.label_2.setObjectName(_fromUtf8("label_2")) - self.gridLayout.addWidget(self.label_2, 0, 2, 1, 1) - self.prefer_single_score = QtGui.QSlider(self.type_group) - self.prefer_single_score.setMaximum(100) - self.prefer_single_score.setProperty("value", 50) - self.prefer_single_score.setOrientation(QtCore.Qt.Horizontal) - self.prefer_single_score.setObjectName(_fromUtf8("prefer_single_score")) - self.gridLayout.addWidget(self.prefer_single_score, 0, 3, 1, 1) - self.label_3 = QtGui.QLabel(self.type_group) - self.label_3.setObjectName(_fromUtf8("label_3")) - self.gridLayout.addWidget(self.label_3, 0, 4, 1, 1) - self.prefer_ep_score = QtGui.QSlider(self.type_group) - self.prefer_ep_score.setMaximum(100) - self.prefer_ep_score.setProperty("value", 50) - self.prefer_ep_score.setOrientation(QtCore.Qt.Horizontal) - self.prefer_ep_score.setObjectName(_fromUtf8("prefer_ep_score")) - self.gridLayout.addWidget(self.prefer_ep_score, 0, 5, 1, 1) - self.label_7 = QtGui.QLabel(self.type_group) - self.label_7.setObjectName(_fromUtf8("label_7")) - self.gridLayout.addWidget(self.label_7, 1, 0, 1, 1) - self.prefer_compilation_score = QtGui.QSlider(self.type_group) - self.prefer_compilation_score.setMaximum(100) - self.prefer_compilation_score.setProperty("value", 50) - self.prefer_compilation_score.setOrientation(QtCore.Qt.Horizontal) - self.prefer_compilation_score.setObjectName(_fromUtf8("prefer_compilation_score")) - self.gridLayout.addWidget(self.prefer_compilation_score, 1, 1, 1, 1) - self.label_8 = QtGui.QLabel(self.type_group) - self.label_8.setObjectName(_fromUtf8("label_8")) - self.gridLayout.addWidget(self.label_8, 1, 2, 1, 1) - self.prefer_soundtrack_score = QtGui.QSlider(self.type_group) - self.prefer_soundtrack_score.setMaximum(100) - self.prefer_soundtrack_score.setProperty("value", 50) - self.prefer_soundtrack_score.setOrientation(QtCore.Qt.Horizontal) - self.prefer_soundtrack_score.setObjectName(_fromUtf8("prefer_soundtrack_score")) - self.gridLayout.addWidget(self.prefer_soundtrack_score, 1, 3, 1, 1) - self.label_9 = QtGui.QLabel(self.type_group) - self.label_9.setObjectName(_fromUtf8("label_9")) - self.gridLayout.addWidget(self.label_9, 1, 4, 1, 1) - self.prefer_spokenword_score = QtGui.QSlider(self.type_group) - self.prefer_spokenword_score.setMaximum(100) - self.prefer_spokenword_score.setProperty("value", 50) - self.prefer_spokenword_score.setOrientation(QtCore.Qt.Horizontal) - self.prefer_spokenword_score.setObjectName(_fromUtf8("prefer_spokenword_score")) - self.gridLayout.addWidget(self.prefer_spokenword_score, 1, 5, 1, 1) - self.label_10 = QtGui.QLabel(self.type_group) - self.label_10.setObjectName(_fromUtf8("label_10")) - self.gridLayout.addWidget(self.label_10, 2, 0, 1, 1) - self.prefer_interview_score = QtGui.QSlider(self.type_group) - self.prefer_interview_score.setMaximum(100) - self.prefer_interview_score.setProperty("value", 50) - self.prefer_interview_score.setOrientation(QtCore.Qt.Horizontal) - self.prefer_interview_score.setObjectName(_fromUtf8("prefer_interview_score")) - self.gridLayout.addWidget(self.prefer_interview_score, 2, 1, 1, 1) - self.label_11 = QtGui.QLabel(self.type_group) - self.label_11.setObjectName(_fromUtf8("label_11")) - self.gridLayout.addWidget(self.label_11, 2, 2, 1, 1) - self.prefer_audiobook_score = QtGui.QSlider(self.type_group) - self.prefer_audiobook_score.setMaximum(100) - self.prefer_audiobook_score.setProperty("value", 50) - self.prefer_audiobook_score.setOrientation(QtCore.Qt.Horizontal) - self.prefer_audiobook_score.setObjectName(_fromUtf8("prefer_audiobook_score")) - self.gridLayout.addWidget(self.prefer_audiobook_score, 2, 3, 1, 1) - self.label_12 = QtGui.QLabel(self.type_group) - self.label_12.setObjectName(_fromUtf8("label_12")) - self.gridLayout.addWidget(self.label_12, 2, 4, 1, 1) - self.prefer_live_score = QtGui.QSlider(self.type_group) - self.prefer_live_score.setMaximum(100) - self.prefer_live_score.setProperty("value", 50) - self.prefer_live_score.setOrientation(QtCore.Qt.Horizontal) - self.prefer_live_score.setObjectName(_fromUtf8("prefer_live_score")) - self.gridLayout.addWidget(self.prefer_live_score, 2, 5, 1, 1) - self.label_13 = QtGui.QLabel(self.type_group) - self.label_13.setObjectName(_fromUtf8("label_13")) - self.gridLayout.addWidget(self.label_13, 3, 0, 1, 1) - self.prefer_remix_score = QtGui.QSlider(self.type_group) - self.prefer_remix_score.setMaximum(100) - self.prefer_remix_score.setProperty("value", 50) - self.prefer_remix_score.setOrientation(QtCore.Qt.Horizontal) - self.prefer_remix_score.setObjectName(_fromUtf8("prefer_remix_score")) - self.gridLayout.addWidget(self.prefer_remix_score, 3, 1, 1, 1) - self.label_14 = QtGui.QLabel(self.type_group) - self.label_14.setObjectName(_fromUtf8("label_14")) - self.gridLayout.addWidget(self.label_14, 3, 2, 1, 1) - self.prefer_other_score = QtGui.QSlider(self.type_group) - self.prefer_other_score.setMaximum(100) - self.prefer_other_score.setSliderPosition(50) - self.prefer_other_score.setOrientation(QtCore.Qt.Horizontal) - self.prefer_other_score.setObjectName(_fromUtf8("prefer_other_score")) - self.gridLayout.addWidget(self.prefer_other_score, 3, 3, 1, 1) - self.reset_preferred_types_btn = QtGui.QPushButton(self.type_group) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.reset_preferred_types_btn.sizePolicy().hasHeightForWidth()) - self.reset_preferred_types_btn.setSizePolicy(sizePolicy) - self.reset_preferred_types_btn.setObjectName(_fromUtf8("reset_preferred_types_btn")) - self.gridLayout.addWidget(self.reset_preferred_types_btn, 4, 5, 1, 1) self.verticalLayout_3.addWidget(self.type_group) self.country_group = QtGui.QGroupBox(ReleasesOptionsPage) self.country_group.setObjectName(_fromUtf8("country_group")) @@ -209,18 +102,6 @@ class Ui_ReleasesOptionsPage(object): def retranslateUi(self, ReleasesOptionsPage): self.type_group.setTitle(_("Preferred release types")) - self.label.setText(_("Album")) - self.label_2.setText(_("Single")) - self.label_3.setText(_("EP")) - self.label_7.setText(_("Compilation")) - self.label_8.setText(_("Soundtrack")) - self.label_9.setText(_("Spokenword")) - self.label_10.setText(_("Interview")) - self.label_11.setText(_("Audiobook")) - self.label_12.setText(_("Live")) - self.label_13.setText(_("Remix")) - self.label_14.setText(_("Other")) - self.reset_preferred_types_btn.setText(_("Reset all")) self.country_group.setTitle(_("Preferred release countries")) self.add_countries.setText(_(">")) self.remove_countries.setText(_("<")) diff --git a/picard/util/__init__.py b/picard/util/__init__.py index 5213f7a4a..f9545be73 100644 --- a/picard/util/__init__.py +++ b/picard/util/__init__.py @@ -267,22 +267,6 @@ def rot13(input): return u''.join(unichr(rot_13.encoding_map.get(ord(c), ord(c))) for c in input) -def load_release_type_scores(setting): - scores = {} - values = setting.split() - for i in range(0, len(values), 2): - try: - score = float(values[i + 1]) - except IndexError: - score = 0.0 - scores[values[i]] = score - return scores - - -def save_release_type_scores(scores): - return " ".join(["%s %.2f" % v for v in scores.iteritems()]) - - def parse_amazon_url(url): """Extract host and asin from an amazon url. It returns a dict with host and asin keys on success, None else diff --git a/po/attributes/.gitignore b/po/attributes/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/setup.py b/setup.py index 1236dc4ef..f6751f389 100755 --- a/setup.py +++ b/setup.py @@ -424,8 +424,8 @@ def _get_option_name(obj): raise Exception("No such command class") -class picard_update_countries(Command): - description = "Regenerate countries.py" +class picard_update_constants(Command): + description = "Regenerate attributes.py and countries.py" user_options = [ ('skip-pull', None, "skip the tx pull steps"), ] @@ -443,22 +443,22 @@ class picard_update_countries(Command): from babel.messages import pofile - countries = dict() if not self.skip_pull: txpull_cmd = [ tx_executable, 'pull', '--force', - '--resource=musicbrainz.countries', + '--resource=musicbrainz.attributes,musicbrainz.countries', '--source', '--language=none', ] self.spawn(txpull_cmd) - potfile = os.path.join('po', 'countries', 'countries.pot') + countries = dict() + countries_potfile = os.path.join('po', 'countries', 'countries.pot') isocode_comment = u'iso.code:' - with open(potfile, 'rb') as f: - log.info('Parsing %s' % potfile) + with open(countries_potfile, 'rb') as f: + log.info('Parsing %s' % countries_potfile) po = pofile.read_po(f) for message in po: if not message.id or not isinstance(message.id, unicode): @@ -472,6 +472,28 @@ class picard_update_countries(Command): else: sys.exit('Failed to extract any country code/name !') + attributes = dict() + attributes_potfile = os.path.join('po', 'attributes', 'attributes.pot') + extract_attributes = ( + u'DB:cover_art_archive.art_type/name', + u'DB:medium_format/name', + u'DB:release_group_primary_type/name', + u'DB:release_group_secondary_type/name', + ) + with open(attributes_potfile, 'rb') as f: + log.info('Parsing %s' % attributes_potfile) + po = pofile.read_po(f) + for message in po: + if not message.id or not isinstance(message.id, unicode): + continue + for loc, pos in message.locations: + if loc in extract_attributes: + attributes[u"%s:%03d" % (loc, pos)] = message.id + if attributes: + self.attributes_py_file(attributes) + else: + sys.exit('Failed to extract any attribute !') + def countries_py_file(self, countries): header = (u"# -*- coding: utf-8 -*-\n" u"# Automatically generated - don't edit.\n" @@ -492,6 +514,26 @@ class picard_update_countries(Command): log.info("%s was rewritten (%d countries)" % (filename, len(countries))) + def attributes_py_file(self, attributes): + header = (u"# -*- coding: utf-8 -*-\n" + u"# Automatically generated - don't edit.\n" + u"# Use `python setup.py {option}` to update it.\n" + u"\n" + u"MB_ATTRIBUTES = {{\n") + line = u" u'{key}': u'{value}',\n" + footer = u"}}\n" + filename = os.path.join('picard', 'attributes.py') + with open(filename, 'w') as attributes_py: + def write_utf8(s, **kwargs): + attributes_py.write(s.format(**kwargs).encode('utf-8')) + + write_utf8(header, option=_get_option_name(self)) + for key, value in sorted(attributes.items(), key=lambda (k,v): k): + write_utf8(line, key=key, value=value.replace("'", "\\'")) + write_utf8(footer) + log.info("%s was rewritten (%d attributes)" % (filename, + len(attributes))) + def cflags_to_include_dirs(cflags): cflags = cflags.split() @@ -507,6 +549,7 @@ def _picard_get_locale_files(): path_domain = { 'po': 'picard', os.path.join('po', 'countries'): 'picard-countries', + os.path.join('po', 'attributes'): 'picard-attributes', } for path, domain in path_domain.iteritems(): for filepath in glob.glob(os.path.join(path, '*.po')): @@ -537,7 +580,7 @@ args2 = { 'clean_ui': picard_clean_ui, 'install': picard_install, 'install_locales': picard_install_locales, - 'update_countries': picard_update_countries, + 'update_constants': picard_update_constants, 'get_po_files': picard_get_po_files, 'regen_pot_file': picard_regen_pot_file, }, diff --git a/test/test_utils.py b/test/test_utils.py index 13462a7a0..2b788f67c 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -92,36 +92,6 @@ class FormatTimeTest(unittest.TestCase): self.assertEqual("2:59", util.format_time(179499)) -class LoadReleaseTypeScoresTest(unittest.TestCase): - - def test_valid(self): - release_type_score_config = "Album 1.0 Single 0.5 EP 0.5 Compilation 0.5 Soundtrack 0.5 Spokenword 0.5 Interview 0.2 Audiobook 0.0 Live 0.5 Remix 0.4 Other 0.0" - release_type_scores = util.load_release_type_scores(release_type_score_config) - self.assertEqual(1.0, release_type_scores["Album"]) - self.assertEqual(0.5, release_type_scores["Single"]) - self.assertEqual(0.2, release_type_scores["Interview"]) - self.assertEqual(0.0, release_type_scores["Audiobook"]) - self.assertEqual(0.4, release_type_scores["Remix"]) - - def test_invalid(self): - release_type_score_config = "Album 1.0 Other" - release_type_scores = util.load_release_type_scores(release_type_score_config) - self.assertEqual(1.0, release_type_scores["Album"]) - self.assertEqual(0.0, release_type_scores["Other"]) - - -class SaveReleaseTypeScoresTest(unittest.TestCase): - - def test(self): - expected = "Album 1.00 Single 0.50 Other 0.00" - scores = {"Album": 1.0, "Single": 0.5, "Other": 0.0} - saved_scores = util.save_release_type_scores(scores) - self.assertTrue("Album 1.00" in saved_scores) - self.assertTrue("Single 0.50" in saved_scores) - self.assertTrue("Other 0.00" in saved_scores) - self.assertEqual(6, len(saved_scores.split())) - - class HiddenPathTest(unittest.TestCase): def test(self): diff --git a/ui/options_cover.ui b/ui/options_cover.ui index 457469153..5a0567326 100644 --- a/ui/options_cover.ui +++ b/ui/options_cover.ui @@ -162,17 +162,11 @@ - + 0 0 - - - 16777215 - 80 - - Qt::ScrollBarAsNeeded diff --git a/ui/options_releases.ui b/ui/options_releases.ui index f4a1de70f..2366b883e 100644 --- a/ui/options_releases.ui +++ b/ui/options_releases.ui @@ -20,239 +20,6 @@ 6 - - - - Album - - - - - - - 100 - - - 50 - - - Qt::Horizontal - - - - - - - Single - - - - - - - 100 - - - 50 - - - Qt::Horizontal - - - - - - - EP - - - - - - - 100 - - - 50 - - - Qt::Horizontal - - - - - - - Compilation - - - - - - - 100 - - - 50 - - - Qt::Horizontal - - - - - - - Soundtrack - - - - - - - 100 - - - 50 - - - Qt::Horizontal - - - - - - - Spokenword - - - - - - - 100 - - - 50 - - - Qt::Horizontal - - - - - - - Interview - - - - - - - 100 - - - 50 - - - Qt::Horizontal - - - - - - - Audiobook - - - - - - - 100 - - - 50 - - - Qt::Horizontal - - - - - - - Live - - - - - - - 100 - - - 50 - - - Qt::Horizontal - - - - - - - Remix - - - - - - - 100 - - - 50 - - - Qt::Horizontal - - - - - - - Other - - - - - - - 100 - - - 50 - - - Qt::Horizontal - - - - - - - - 0 - 0 - - - - Reset all - - -