Merge pull request #2581 from bartkl/master

Set field values on the import command line #1881
This commit is contained in:
Adrian Sampson 2017-06-13 09:18:48 -04:00
commit e0ce507055
8 changed files with 177 additions and 2 deletions

View file

@ -25,7 +25,8 @@ import:
pretend: false
search_ids: []
duplicate_action: ask
bell: no
bell: no
set_fields: {}
clutter: ["Thumbs.DB", ".DS_Store"]
ignore: [".*", "*~", "System Volume Information", "lost+found"]

View file

@ -419,7 +419,7 @@ class ImportTask(BaseImportTask):
from the `candidates` list.
* `find_duplicates()` Returns a list of albums from `lib` with the
same artist and album name as the task.
same artist and album name as the task.
* `apply_metadata()` Sets the attributes of the items from the
task's `match` attribute.
@ -429,6 +429,9 @@ class ImportTask(BaseImportTask):
* `manipulate_files()` Copy, move, and write files depending on the
session configuration.
* `set_fields()` Sets the fields given at CLI or configuration to
the specified values.
* `finalize()` Update the import progress and cleanup the file
system.
"""
@ -530,6 +533,19 @@ class ImportTask(BaseImportTask):
util.prune_dirs(os.path.dirname(item.path),
lib.directory)
def set_fields(self):
"""Sets the fields given at CLI or configuration to the specified
values.
"""
for field, view in config['import']['set_fields'].items():
value = view.get()
log.debug(u'Set field {1}={2} for {0}',
displayable_path(self.paths),
field,
value)
self.album[field] = value
self.album.store()
def finalize(self, session):
"""Save progress, clean up files, and emit plugin event.
"""
@ -877,6 +893,19 @@ class SingletonImportTask(ImportTask):
def reload(self):
self.item.load()
def set_fields(self):
"""Sets the fields given at CLI or configuration to the specified
values.
"""
for field, view in config['import']['set_fields'].items():
value = view.get()
log.debug(u'Set field {1}={2} for {0}',
displayable_path(self.paths),
field,
value)
self.item[field] = value
self.item.store()
# FIXME The inheritance relationships are inverted. This is why there
# are so many methods which pass. More responsibility should be delegated to
@ -1385,6 +1414,14 @@ def apply_choice(session, task):
task.add(session.lib)
# If ``set_fields`` is set, set those fields to the
# configured values.
# NOTE: This cannot be done before the ``task.add()`` call above,
# because then the ``ImportTask`` won't have an `album` for which
# it can set the fields.
if config['import']['set_fields']:
task.set_fields()
@pipeline.mutator_stage
def plugin_stage(session, func, task):

View file

@ -769,6 +769,34 @@ def show_path_changes(path_changes):
pad = max_width - len(source)
log.info(u'{0} {1} -> {2}', source, ' ' * pad, dest)
# Helper functions for option parsing.
def _store_dict(option, opt_str, value, parser):
"""Custom action callback to parse options which have ``key=value``
pairs as values. All such pairs passed for this option are
aggregated into a dictionary.
"""
dest = option.dest
option_values = getattr(parser.values, dest, None)
if option_values is None:
# This is the first supplied ``key=value`` pair of option.
# Initialize empty dictionary and get a reference to it.
setattr(parser.values, dest, dict())
option_values = getattr(parser.values, dest)
try:
key, value = map(lambda s: util.text_string(s), value.split('='))
if not (key and value):
raise ValueError
except ValueError:
raise UserError(
"supplied argument `{0}' is not of the form `key=value'"
.format(value))
option_values[key] = value
class CommonOptionsParser(optparse.OptionParser, object):
"""Offers a simple way to add common formatting options.

View file

@ -40,6 +40,7 @@ from beets import config
from beets import logging
from beets.util.confit import _package_path
import six
from . import _store_dict
VARIOUS_ARTISTS = u'Various Artists'
PromptChoice = namedtuple('PromptChoice', ['short', 'long', 'callback'])
@ -1017,6 +1018,12 @@ import_cmd.parser.add_option(
metavar='ID',
help=u'restrict matching to a specific metadata backend ID'
)
import_cmd.parser.add_option(
u'--set', dest='set_fields', action='callback',
callback=_store_dict,
metavar='FIELD=VALUE',
help=u'set the given fields to the supplied values'
)
import_cmd.func = import_func
default_commands.append(import_cmd)

View file

@ -88,6 +88,14 @@ Here's a full list of new features:
Thanks to :user:`jansol`.
:bug:`2488`
:bug:`2524`
* A new field, ``composer_sort``, is now supported and fetched from
MusicBrainz.
Thanks to :user:`dosoe`.
:bug:`2519` :bug:`2529`
* It is now possible to set fields to certain values during import, using
either the `importer.set_fields` dictionary in the config file, or by
passing one or more `--set field=value` options on the command-line.
:bug: `1881`
There are also quite a few fixes:

View file

@ -138,6 +138,16 @@ Optional command flags:
searching for other candidates by using the ``--search-id SEARCH_ID`` option.
Multiple IDs can be specified by simply repeating the option several times.
* You can supply ``--set`` options with ``field=value`` pairs to assign to
those fields the specified values on import, in addition to such field/value
pairs defined in the ``importer.set_fields`` dictionary in the configuration
file. Make sure to use an option per field/value pair, like so::
beet import --set genre="Alternative Rock" --set mood="emotional"
Note that values for the fields specified on the command-line override the
ones defined for those fields in the configuration file.
.. _rarfile: https://pypi.python.org/pypi/rarfile/2.2
.. only:: html

View file

@ -586,6 +586,26 @@ Ring the terminal bell to get your attention when the importer needs your input.
Default: ``no``.
.. _set_fields:
set_fields
~~~~~~~~~~
A dictionary of field/value pairs, each one used to set a field to the
corresponding value during import.
Example: ::
set_fields:
genre: 'To Listen'
collection: 'Unordered'
Note that field/value pairs supplied via ``--set`` options on the
command-line are processed in addition to those specified here. Those values
override the ones defined here in the case of fields with the same name.
Default: ``{}`` (empty).
.. _musicbrainz-config:
MusicBrainz Options

View file

@ -543,6 +543,38 @@ class ImportSingletonTest(_common.TestCase, ImportHelper):
self.assertEqual(len(self.lib.items()), 2)
self.assertEqual(len(self.lib.albums()), 2)
def test_set_fields(self):
genre = u"\U0001F3B7 Jazz"
collection = u"To Listen"
config['import']['set_fields'] = {
u'collection': collection,
u'genre': genre
}
# As-is item import.
self.assertEqual(self.lib.albums().get(), None)
self.importer.add_choice(importer.action.ASIS)
self.importer.run()
for item in self.lib.items():
item.load() # TODO: Not sure this is necessary.
self.assertEqual(item.genre, genre)
self.assertEqual(item.collection, collection)
# Remove item from library to test again with APPLY choice.
item.remove()
# Autotagged.
self.assertEqual(self.lib.albums().get(), None)
self.importer.clear_choices()
self.importer.add_choice(importer.action.APPLY)
self.importer.run()
for item in self.lib.items():
item.load()
self.assertEqual(item.genre, genre)
self.assertEqual(item.collection, collection)
class ImportTest(_common.TestCase, ImportHelper):
"""Test APPLY, ASIS and SKIP choices.
@ -672,6 +704,38 @@ class ImportTest(_common.TestCase, ImportHelper):
with self.assertRaises(AttributeError):
self.lib.items().get().data_source
def test_set_fields(self):
genre = u"\U0001F3B7 Jazz"
collection = u"To Listen"
config['import']['set_fields'] = {
u'collection': collection,
u'genre': genre
}
# As-is album import.
self.assertEqual(self.lib.albums().get(), None)
self.importer.add_choice(importer.action.ASIS)
self.importer.run()
for album in self.lib.albums():
album.load() # TODO: Not sure this is necessary.
self.assertEqual(album.genre, genre)
self.assertEqual(album.collection, collection)
# Remove album from library to test again with APPLY choice.
album.remove()
# Autotagged.
self.assertEqual(self.lib.albums().get(), None)
self.importer.clear_choices()
self.importer.add_choice(importer.action.APPLY)
self.importer.run()
for album in self.lib.albums():
album.load()
self.assertEqual(album.genre, genre)
self.assertEqual(album.collection, collection)
class ImportTracksTest(_common.TestCase, ImportHelper):
"""Test TRACKS and APPLY choice.