mirror of
https://github.com/beetbox/beets.git
synced 2026-02-01 21:11:25 +01:00
Merge pull request #2581 from bartkl/master
Set field values on the import command line #1881
This commit is contained in:
commit
e0ce507055
8 changed files with 177 additions and 2 deletions
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue