From b4b6147eb03da99592dcefbaa2daa36154e16f2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Lalinsk=C3=BD?= Date: Sat, 16 Feb 2008 21:00:56 +0100 Subject: [PATCH] Folksonomy tags/genre support. More or less a direct copy of the Last.fm plugin, but works with MB tags. --- picard/album.py | 50 ++++++-- picard/dataobj.py | 5 + picard/mbxml.py | 14 ++- picard/ui/options/dialog.py | 1 + picard/ui/options/folksonomy.py | 59 +++++++++ picard/ui/options/metadata.py | 3 + picard/ui/ui_options_folksonomy.py | 122 ++++++++++++++++++ picard/ui/ui_options_metadata.py | 45 +++---- ui/options_folksonomy.ui | 196 +++++++++++++++++++++++++++++ ui/options_metadata.ui | 51 ++++---- 10 files changed, 494 insertions(+), 52 deletions(-) create mode 100644 picard/ui/options/folksonomy.py create mode 100644 picard/ui/ui_options_folksonomy.py create mode 100644 ui/options_folksonomy.ui diff --git a/picard/album.py b/picard/album.py index bf72a1bce..6bec68cfa 100644 --- a/picard/album.py +++ b/picard/album.py @@ -33,6 +33,13 @@ from picard.mbxml import release_to_metadata, track_to_metadata VARIOUS_ARTISTS_ID = '89ad4ac3-39f7-470e-963a-56509c546377' +_TRANSLATE_TAGS = { + "hip hop": u"Hip-Hop", + "synth-pop": u"Synthpop", + "electronica": u"Electronic", +} + + class ReleaseEvent(object): ATTRS = ['date', 'releasecountry', 'label', 'barcode', 'catalognumber'] @@ -76,6 +83,32 @@ class Album(DataObject, Item): for file in self.unmatched_files.iterfiles(): yield file + def _convert_folksonomy_tags_to_genre(self, track): + # Combine release and track tags + tags = dict(self.folksonomy_tags) + for name, count in track.folksonomy_tags: + tags.setdefault(name, 0) + tags[name] += count + # Convert counts to values from 0 to 100 + maxcount = max(tags.values()) + taglist = [] + for name, count in tags.items(): + taglist.append((100 * maxcount / count, name)) + taglist.sort(reverse=True) + # And generate the genre metadata tag + maxtags = self.config.setting['max_tags'] + minusage = self.config.setting['min_tag_usage'] + genre = [] + for usage, name in taglist[:maxtags]: + if usage < minusage: + break + name = _TRANSLATE_TAGS.get(name, name.title()) + genre.append(name) + join_tags = self.config.setting['join_tags'] + if join_tags: + genre = [join_tags.join(genre)] + track.metadata['genre'] = genre + def _parse_release(self, document): self.log.debug("Loading release %r", self.id) @@ -130,7 +163,7 @@ class Album(DataObject, Item): tm = t.metadata tm.copy(m) tm['tracknumber'] = str(i + 1) - track_to_metadata(node, tm, config=self.config) + track_to_metadata(node, tm, config=self.config, track=t) artists.add(tm['musicbrainz_artistid']) m.length += tm.length @@ -142,7 +175,10 @@ class Album(DataObject, Item): if tm['musicbrainz_artistid'] == VARIOUS_ARTISTS_ID: tm['artistsort'] = tm['artist'] = self.config.setting['va_name'] - # Album metadata plugins + if self.config.setting['folksonomy_tags']: + self._convert_folksonomy_tags_to_genre(t) + + # Track metadata plugins try: run_track_metadata_processors(self, tm, release_node, node) except: @@ -215,13 +251,13 @@ class Album(DataObject, Item): self._new_metadata = Metadata() self._new_tracks = [] self._requests = 1 + inc = ['tracks', 'puids', 'artist', 'release-events'] if self.config.setting['release_ars'] or self.config.setting['track_ars']: + inc += ['artist-rels', 'url-rels'] if self.config.setting['track_ars']: - inc = ('tracks', 'puids', 'artist', 'release-events', 'labels', 'artist-rels', 'url-rels', 'track-level-rels') - else: - inc = ('tracks', 'puids', 'artist', 'release-events', 'labels', 'artist-rels', 'url-rels') - else: - inc = ('tracks', 'puids', 'artist', 'release-events') + inc += ['track-level-rels'] + if self.config.setting['folksonomy_tags']: + inc += ['tags'] self.tagger.xmlws.get_release_by_id(self.id, self._release_request_finished, inc=inc) def update(self, update_tracks=True): diff --git a/picard/dataobj.py b/picard/dataobj.py index 629701d0c..67ca51443 100644 --- a/picard/dataobj.py +++ b/picard/dataobj.py @@ -24,3 +24,8 @@ class DataObject(LockableObject): def __init__(self, id): LockableObject.__init__(self) self.id = id + self.folksonomy_tags = {} + + def add_folksonomy_tag(self, name, count): + self.folksonomy_tags.setdefault(name, 0) + self.folksonomy_tags[name] += count diff --git a/picard/mbxml.py b/picard/mbxml.py index bda3f8473..3eb03bd7d 100644 --- a/picard/mbxml.py +++ b/picard/mbxml.py @@ -106,7 +106,7 @@ def artist_to_metadata(node, m, release=False): _set_artist_item(m, release, 'albumartistsort', 'artistsort', nodes[0].text) -def track_to_metadata(node, m, config=None): +def track_to_metadata(node, m, config=None, track=None): m['musicbrainz_trackid'] = node.attribs['id'] m.length = 0 for name, nodes in node.children.iteritems(): @@ -122,6 +122,8 @@ def track_to_metadata(node, m, config=None): _relations_to_metadata(nodes, m, config) elif name == 'release_list': release_to_metadata(nodes[0].release[0], m) + elif name == 'tag_list': + add_folksonomy_tags(nodes[0], track) def release_to_metadata(node, m, config=None, album=None): @@ -169,3 +171,13 @@ def release_to_metadata(node, m, config=None, album=None): m['tracknumber'] = str(int(nodes[0].attribs['offset']) + 1) if 'count' in nodes[0].attribs: m['totaltracks'] = nodes[0].attribs['count'] + elif name == 'tag_list': + add_folksonomy_tags(nodes[0], album) + + +def add_folksonomy_tags(node, obj): + if obj and 'tag' in node.children: + for tag in node.tag: + name = tag.text + count = int(tag.attribs['count']) + obj.add_folksonomy_tag(name, count) diff --git a/picard/ui/options/dialog.py b/picard/ui/options/dialog.py index 0dea5f715..518c6d731 100644 --- a/picard/ui/options/dialog.py +++ b/picard/ui/options/dialog.py @@ -29,6 +29,7 @@ from picard.ui.options import ( cover, general, interface, + folksonomy, matching, metadata, naming, diff --git a/picard/ui/options/folksonomy.py b/picard/ui/options/folksonomy.py new file mode 100644 index 000000000..f7f1ba95e --- /dev/null +++ b/picard/ui/options/folksonomy.py @@ -0,0 +1,59 @@ +# -*- 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 PyQt4 import QtCore, QtGui +from picard.config import BoolOption, TextOption, IntOption +from picard.ui.options import OptionsPage, OptionsCheckError, 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 = [ + IntOption("setting", "max_tags", 5), + IntOption("setting", "min_tag_usage", 90), + TextOption("setting", "ignore_tags", "seen live,favorites,fixme"), + TextOption("setting", "join_tags", ""), + ] + + def __init__(self, parent=None): + super(FolksonomyOptionsPage, self).__init__(parent) + self.ui = Ui_FolksonomyOptionsPage() + self.ui.setupUi(self) + + def load(self): + self.ui.max_tags.setValue(self.config.setting["max_tags"]) + self.ui.min_tag_usage.setValue(self.config.setting["min_tag_usage"]) + self.ui.join_tags.setEditText(self.config.setting["join_tags"]) + self.ui.ignore_tags.setText(self.config.setting["ignore_tags"]) + + def save(self): + self.config.setting["max_tags"] = self.ui.max_tags.value() + self.config.setting["min_tag_usage"] = self.ui.min_tag_usage.value() + self.config.setting["join_tags"] = self.ui.join_tags.currentText() + self.config.setting["ignore_tags"] = self.ui.ignore_tags.text() + + +register_options_page(FolksonomyOptionsPage) diff --git a/picard/ui/options/metadata.py b/picard/ui/options/metadata.py index e1b2d2446..45a6105b2 100644 --- a/picard/ui/options/metadata.py +++ b/picard/ui/options/metadata.py @@ -37,6 +37,7 @@ class MetadataOptionsPage(OptionsPage): BoolOption("setting", "translate_artist_names", False), BoolOption("setting", "release_ars", True), BoolOption("setting", "track_ars", False), + BoolOption("setting", "folksonomy_tags", False), ] def __init__(self, parent=None): @@ -50,6 +51,7 @@ class MetadataOptionsPage(OptionsPage): self.ui.translate_artist_names.setChecked(self.config.setting["translate_artist_names"]) self.ui.release_ars.setChecked(self.config.setting["release_ars"]) self.ui.track_ars.setChecked(self.config.setting["track_ars"]) + self.ui.folksonomy_tags.setChecked(self.config.setting["folksonomy_tags"]) self.ui.va_name.setText(self.config.setting["va_name"]) self.ui.nat_name.setText(self.config.setting["nat_name"]) @@ -57,6 +59,7 @@ class MetadataOptionsPage(OptionsPage): self.config.setting["translate_artist_names"] = self.ui.translate_artist_names.isChecked() self.config.setting["release_ars"] = self.ui.release_ars.isChecked() self.config.setting["track_ars"] = self.ui.track_ars.isChecked() + self.config.setting["folksonomy_tags"] = self.ui.folksonomy_tags.isChecked() self.config.setting["va_name"] = self.ui.va_name.text() self.config.setting["nat_name"] = self.ui.nat_name.text() diff --git a/picard/ui/ui_options_folksonomy.py b/picard/ui/ui_options_folksonomy.py new file mode 100644 index 000000000..23b4931e2 --- /dev/null +++ b/picard/ui/ui_options_folksonomy.py @@ -0,0 +1,122 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'ui/options_folksonomy.ui' +# +# Created: Sat Feb 16 20:39:33 2008 +# by: PyQt4 UI code generator 4.3 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +class Ui_FolksonomyOptionsPage(object): + def setupUi(self, FolksonomyOptionsPage): + FolksonomyOptionsPage.setObjectName("FolksonomyOptionsPage") + FolksonomyOptionsPage.resize(QtCore.QSize(QtCore.QRect(0,0,367,267).size()).expandedTo(FolksonomyOptionsPage.minimumSizeHint())) + + self.vboxlayout = QtGui.QVBoxLayout(FolksonomyOptionsPage) + self.vboxlayout.setObjectName("vboxlayout") + + self.rename_files_3 = QtGui.QGroupBox(FolksonomyOptionsPage) + self.rename_files_3.setObjectName("rename_files_3") + + self.vboxlayout1 = QtGui.QVBoxLayout(self.rename_files_3) + self.vboxlayout1.setObjectName("vboxlayout1") + + self.ignore_tags_2 = QtGui.QLabel(self.rename_files_3) + self.ignore_tags_2.setObjectName("ignore_tags_2") + self.vboxlayout1.addWidget(self.ignore_tags_2) + + self.ignore_tags = QtGui.QLineEdit(self.rename_files_3) + self.ignore_tags.setObjectName("ignore_tags") + self.vboxlayout1.addWidget(self.ignore_tags) + + self.hboxlayout = QtGui.QHBoxLayout() + self.hboxlayout.setSpacing(6) + self.hboxlayout.setMargin(0) + self.hboxlayout.setObjectName("hboxlayout") + + self.label_5 = QtGui.QLabel(self.rename_files_3) + + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding,QtGui.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 = QtGui.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.vboxlayout1.addLayout(self.hboxlayout) + + self.hboxlayout1 = QtGui.QHBoxLayout() + self.hboxlayout1.setSpacing(6) + self.hboxlayout1.setMargin(0) + self.hboxlayout1.setObjectName("hboxlayout1") + + self.label_6 = QtGui.QLabel(self.rename_files_3) + + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding,QtGui.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 = QtGui.QSpinBox(self.rename_files_3) + self.max_tags.setMaximum(100) + self.max_tags.setObjectName("max_tags") + self.hboxlayout1.addWidget(self.max_tags) + self.vboxlayout1.addLayout(self.hboxlayout1) + + self.hboxlayout2 = QtGui.QHBoxLayout() + self.hboxlayout2.setSpacing(6) + self.hboxlayout2.setMargin(0) + self.hboxlayout2.setObjectName("hboxlayout2") + + self.ignore_tags_4 = QtGui.QLabel(self.rename_files_3) + + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred,QtGui.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 = QtGui.QComboBox(self.rename_files_3) + + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred,QtGui.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.hboxlayout2.addWidget(self.join_tags) + self.vboxlayout1.addLayout(self.hboxlayout2) + self.vboxlayout.addWidget(self.rename_files_3) + + spacerItem = QtGui.QSpacerItem(181,31,QtGui.QSizePolicy.Minimum,QtGui.QSizePolicy.Expanding) + self.vboxlayout.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): + self.rename_files_3.setTitle(_("Folksonomy Tags")) + self.ignore_tags_2.setText(_("Ignore tags:")) + self.label_5.setText(_("Minimal tag usage:")) + self.min_tag_usage.setSuffix(_(" %")) + self.label_6.setText(_("Maximal number of tags:")) + self.ignore_tags_4.setText(_("Join multiple tags with:")) + self.join_tags.addItem(_(" / ")) + self.join_tags.addItem(_(", ")) + diff --git a/picard/ui/ui_options_metadata.py b/picard/ui/ui_options_metadata.py index 6dc4ead06..dd81a6671 100644 --- a/picard/ui/ui_options_metadata.py +++ b/picard/ui/ui_options_metadata.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'ui/options_metadata.ui' # -# Created: Sun Jan 13 17:42:15 2008 +# Created: Sat Feb 16 19:53:00 2008 # by: PyQt4 UI code generator 4.3 # # WARNING! All changes made in this file will be lost! @@ -12,68 +12,68 @@ from PyQt4 import QtCore, QtGui class Ui_MetadataOptionsPage(object): def setupUi(self, MetadataOptionsPage): MetadataOptionsPage.setObjectName("MetadataOptionsPage") - MetadataOptionsPage.resize(QtCore.QSize(QtCore.QRect(0,0,344,356).size()).expandedTo(MetadataOptionsPage.minimumSizeHint())) + MetadataOptionsPage.resize(QtCore.QSize(QtCore.QRect(0,0,387,302).size()).expandedTo(MetadataOptionsPage.minimumSizeHint())) self.vboxlayout = QtGui.QVBoxLayout(MetadataOptionsPage) - self.vboxlayout.setMargin(9) - self.vboxlayout.setSpacing(6) self.vboxlayout.setObjectName("vboxlayout") self.rename_files = QtGui.QGroupBox(MetadataOptionsPage) self.rename_files.setObjectName("rename_files") - self.vboxlayout1 = QtGui.QVBoxLayout(self.rename_files) - self.vboxlayout1.setMargin(9) - self.vboxlayout1.setSpacing(2) - self.vboxlayout1.setObjectName("vboxlayout1") + self.gridlayout = QtGui.QGridLayout(self.rename_files) + self.gridlayout.setObjectName("gridlayout") self.translate_artist_names = QtGui.QCheckBox(self.rename_files) self.translate_artist_names.setObjectName("translate_artist_names") - self.vboxlayout1.addWidget(self.translate_artist_names) + self.gridlayout.addWidget(self.translate_artist_names,0,0,1,1) self.release_ars = QtGui.QCheckBox(self.rename_files) self.release_ars.setObjectName("release_ars") - self.vboxlayout1.addWidget(self.release_ars) + self.gridlayout.addWidget(self.release_ars,1,0,1,1) self.track_ars = QtGui.QCheckBox(self.rename_files) self.track_ars.setObjectName("track_ars") - self.vboxlayout1.addWidget(self.track_ars) + self.gridlayout.addWidget(self.track_ars,2,0,1,1) + + self.folksonomy_tags = QtGui.QCheckBox(self.rename_files) + self.folksonomy_tags.setObjectName("folksonomy_tags") + self.gridlayout.addWidget(self.folksonomy_tags,3,0,1,1) self.vboxlayout.addWidget(self.rename_files) self.rename_files_2 = QtGui.QGroupBox(MetadataOptionsPage) self.rename_files_2.setObjectName("rename_files_2") - self.gridlayout = QtGui.QGridLayout(self.rename_files_2) - self.gridlayout.setMargin(9) - self.gridlayout.setSpacing(2) - self.gridlayout.setObjectName("gridlayout") + self.gridlayout1 = QtGui.QGridLayout(self.rename_files_2) + self.gridlayout1.setMargin(9) + self.gridlayout1.setSpacing(2) + self.gridlayout1.setObjectName("gridlayout1") self.label_3 = QtGui.QLabel(self.rename_files_2) self.label_3.setObjectName("label_3") - self.gridlayout.addWidget(self.label_3,0,0,1,2) + self.gridlayout1.addWidget(self.label_3,0,0,1,2) self.label_4 = QtGui.QLabel(self.rename_files_2) self.label_4.setObjectName("label_4") - self.gridlayout.addWidget(self.label_4,2,0,1,2) + self.gridlayout1.addWidget(self.label_4,2,0,1,2) self.nat_name = QtGui.QLineEdit(self.rename_files_2) self.nat_name.setObjectName("nat_name") - self.gridlayout.addWidget(self.nat_name,3,0,1,1) + self.gridlayout1.addWidget(self.nat_name,3,0,1,1) self.nat_name_default = QtGui.QPushButton(self.rename_files_2) self.nat_name_default.setObjectName("nat_name_default") - self.gridlayout.addWidget(self.nat_name_default,3,1,1,1) + self.gridlayout1.addWidget(self.nat_name_default,3,1,1,1) self.va_name_default = QtGui.QPushButton(self.rename_files_2) self.va_name_default.setObjectName("va_name_default") - self.gridlayout.addWidget(self.va_name_default,1,1,1,1) + self.gridlayout1.addWidget(self.va_name_default,1,1,1,1) self.va_name = QtGui.QLineEdit(self.rename_files_2) self.va_name.setObjectName("va_name") - self.gridlayout.addWidget(self.va_name,1,0,1,1) + self.gridlayout1.addWidget(self.va_name,1,0,1,1) self.vboxlayout.addWidget(self.rename_files_2) - spacerItem = QtGui.QSpacerItem(326,51,QtGui.QSizePolicy.Minimum,QtGui.QSizePolicy.Expanding) + spacerItem = QtGui.QSpacerItem(331,41,QtGui.QSizePolicy.Minimum,QtGui.QSizePolicy.Expanding) self.vboxlayout.addItem(spacerItem) self.label_3.setBuddy(self.va_name_default) self.label_4.setBuddy(self.nat_name_default) @@ -90,6 +90,7 @@ class Ui_MetadataOptionsPage(object): self.translate_artist_names.setText(_("Translate foreign artist names to English where possible")) self.release_ars.setText(_("Use release relationships")) self.track_ars.setText(_("Use track relationships")) + self.folksonomy_tags.setText(_("Use folksonomy tags as genre")) self.rename_files_2.setTitle(_("Custom Fields")) self.label_3.setText(_("Various artists:")) self.label_4.setText(_("Non-album tracks:")) diff --git a/ui/options_folksonomy.ui b/ui/options_folksonomy.ui new file mode 100644 index 000000000..b2dd43695 --- /dev/null +++ b/ui/options_folksonomy.ui @@ -0,0 +1,196 @@ + + FolksonomyOptionsPage + + + + 0 + 0 + 367 + 267 + + + + + + + Folksonomy Tags + + + + + + Ignore tags: + + + + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Minimal tag usage: + + + min_tag_usage + + + + + + + % + + + 100 + + + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Maximal number of tags: + + + min_tag_usage + + + + + + + 100 + + + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 4 + 0 + + + + Join multiple tags with: + + + + + + + + 1 + 0 + + + + true + + + + + + + + + / + + + + + , + + + + + + + + + + + + + Qt::Vertical + + + + 181 + 31 + + + + + + + + + diff --git a/ui/options_metadata.ui b/ui/options_metadata.ui index eafe1079f..0989645b5 100644 --- a/ui/options_metadata.ui +++ b/ui/options_metadata.ui @@ -5,50 +5,45 @@ 0 0 - 344 - 356 + 387 + 302 - - 9 - - - 6 - Metadata - - - 9 - - - 2 - - + + Translate foreign artist names to English where possible - + Use release relationships - + Use track relationships + + + + Use folksonomy tags as genre + + + @@ -58,10 +53,22 @@ Custom Fields - + 9 - + + 9 + + + 9 + + + 9 + + + 2 + + 2 @@ -114,8 +121,8 @@ - 326 - 51 + 331 + 41