begin moving importer/autotagger to confit

This commit is contained in:
Adrian Sampson 2012-09-09 22:30:48 -07:00
parent 405390ac3a
commit 75d43270e8
6 changed files with 126 additions and 164 deletions

View file

@ -18,7 +18,7 @@ import os
import logging
import re
from beets import library, mediafile
from beets import library, mediafile, config
from beets.util import sorted_walk, ancestry
# Parts of external interface.
@ -115,7 +115,7 @@ def apply_item_metadata(item, track_info):
# At the moment, the other metadata is left intact (including album
# and track number). Perhaps these should be emptied?
def apply_metadata(album_info, mapping, per_disc_numbering=False):
def apply_metadata(album_info, mapping):
"""Set the items' metadata to match an AlbumInfo object using a
mapping from Items to TrackInfo objects. If `per_disc_numbering`,
then the track numbers are per-disc instead of per-release.
@ -148,7 +148,7 @@ def apply_metadata(album_info, mapping, per_disc_numbering=False):
# Title.
item.title = track_info.title
if per_disc_numbering:
if config['per_disc_numbering']:
item.track = track_info.medium_index
else:
item.track = track_info.index

View file

@ -23,6 +23,7 @@ from munkres import Munkres
from unidecode import unidecode
from beets import plugins
from beets import config
from beets.util import levenshtein, plurality
from beets.autotag import hooks
@ -373,7 +374,7 @@ def _add_candidate(items, results, info):
results[info.album_id] = hooks.AlbumMatch(dist, info, mapping,
extra_items, extra_tracks)
def tag_album(items, timid=False, search_artist=None, search_album=None,
def tag_album(items, search_artist=None, search_album=None,
search_id=None):
"""Bundles together the functionality used to infer tags for a
set of items comprised by an album. Returns everything relevant:
@ -407,7 +408,7 @@ def tag_album(items, timid=False, search_artist=None, search_album=None,
_add_candidate(items, candidates, id_info)
rec = recommendation(candidates.values())
log.debug('Album ID match recommendation is ' + str(rec))
if candidates and not timid:
if candidates and not config['import']['timid']:
# If we have a very good MBID match, return immediately.
# Otherwise, this match will compete against metadata-based
# matches.
@ -446,7 +447,7 @@ def tag_album(items, timid=False, search_artist=None, search_album=None,
rec = recommendation(candidates)
return cur_artist, cur_album, candidates, rec
def tag_item(item, timid=False, search_artist=None, search_title=None,
def tag_item(item, search_artist=None, search_title=None,
search_id=None):
"""Attempts to find metadata for a single track. Returns a
`(candidates, recommendation)` pair where `candidates` is a list of
@ -469,7 +470,7 @@ def tag_item(item, timid=False, search_artist=None, search_title=None,
hooks.TrackMatch(dist, track_info)
# If this is a good match, then don't keep searching.
rec = recommendation(candidates.values())
if rec == RECOMMEND_STRONG and not timid:
if rec == RECOMMEND_STRONG and not config['import_timid'].get(bool):
log.debug('Track ID match.')
return candidates.values(), rec

View file

@ -1,14 +1,20 @@
library: ~/.beetsmusic.blb
directory: ~/Music
import_write: yes
import_copy: yes
import_move: no
import_resume: ask
import_incremental: yes
import_quiet_fallback: skip
import_timid: no
import_log:
import:
write: yes
copy: yes
move: no
resume: ask
incremental: no
quiet_fallback: skip
timid: no
log:
autotag: yes
quiet: no
# Typically only set from the command line.
singletons: no
ignore: [".*", "*~"]
replace:

View file

@ -26,6 +26,7 @@ from beets import autotag
from beets import library
from beets import plugins
from beets import util
from beets import config
from beets.util import pipeline
from beets.util import syspath, normpath, displayable_path
from beets.util.enumeration import enum
@ -451,13 +452,13 @@ class ImportTask(object):
# Full-album pipeline stages.
def read_tasks(config):
def read_tasks():
"""A generator yielding all the albums (as ImportTask objects) found
in the user-specified list of paths. In the case of a singleton
import, yields single-item tasks instead.
"""
# Look for saved progress.
progress = config.resume is not False
progress = config['import']['resume'] is not False # FIXME
if progress:
resume_dirs = {}
for path in config.paths:
@ -465,7 +466,7 @@ def read_tasks(config):
if resume_dir:
# Either accept immediately or prompt for input to decide.
if config.resume:
if config['import']['resume']:
do_resume = True
log.warn('Resuming interrupted import of %s' % path)
else:
@ -478,13 +479,14 @@ def read_tasks(config):
progress_set(path, None)
# Look for saved incremental directories.
if config.incremental:
if config['import']['incremental']:
incremental_skipped = 0
history_dirs = history_get()
for toppath in config.paths:
# Check whether the path is to a file.
if config.singletons and not os.path.isdir(syspath(toppath)):
if config['import']['singletons'] and \
not os.path.isdir(syspath(toppath)):
item = library.Item.from_path(toppath)
yield ImportTask.item_task(item)
continue
@ -503,14 +505,14 @@ def read_tasks(config):
continue
# When incremental, skip paths in the history.
if config.incremental and path in history_dirs:
if config['import']['incremental'] and path in history_dirs:
log.debug(u'Skipping previously-imported path: %s' %
displayable_path(path))
incremental_skipped += 1
continue
# Yield all the necessary tasks.
if config.singletons:
if config['import']['singletons']:
for item in items:
yield ImportTask.item_task(item)
yield ImportTask.progress_sentinel(toppath, path)
@ -521,16 +523,16 @@ def read_tasks(config):
yield ImportTask.done_sentinel(toppath)
# Show skipped directories.
if config.incremental and incremental_skipped:
if config['import']['incremental'] and incremental_skipped:
log.info(u'Incremental import: skipped %i directories.' %
incremental_skipped)
def query_tasks(config):
def query_tasks():
"""A generator that works as a drop-in-replacement for read_tasks.
Instead of finding files from the filesystem, a query is used to
match items from the library.
"""
if config.singletons:
if config['import']['singletons']:
# Search for items.
for item in config.lib.items(config.query):
yield ImportTask.item_task(item)
@ -543,7 +545,7 @@ def query_tasks(config):
items = list(album.items())
yield ImportTask(None, album.item_dir(), items)
def initial_lookup(config):
def initial_lookup():
"""A coroutine for performing the initial MusicBrainz lookup for an
album. It accepts lists of Items and yields
(items, cur_artist, cur_album, candidates, rec) tuples. If no match
@ -555,15 +557,16 @@ def initial_lookup(config):
if task.sentinel:
continue
plugins.send('import_task_start', task=task, config=config)
plugins.send('import_task_start', task=task)
log.debug('Looking up: %s' % task.path)
try:
task.set_candidates(*autotag.tag_album(task.items, config.timid))
task.set_candidates(*autotag.tag_album(task.items,
config['import']['timid']))
except autotag.AutotagError:
task.set_null_candidates()
def user_query(config):
def user_query():
"""A coroutine for interfacing with the user about the tagging
process. lib is the Library to import into and logfile may be
a file-like object for logging the import process. The coroutine
@ -580,7 +583,7 @@ def user_query(config):
choice = config.choose_match_func(task, config)
task.set_choice(choice)
log_choice(config, task)
plugins.send('import_task_choice', task=task, config=config)
plugins.send('import_task_choice', task=task)
# As-tracks: transition to singleton workflow.
if choice is action.TRACKS:
@ -594,8 +597,8 @@ def user_query(config):
while True:
item_task = yield
item_tasks.append(item_task)
ipl = pipeline.Pipeline((emitter(), item_lookup(config),
item_query(config), collector()))
ipl = pipeline.Pipeline((emitter(), item_lookup(),
item_query(), collector()))
ipl.run_sequential()
task = pipeline.multiple(item_tasks)
continue
@ -607,11 +610,11 @@ def user_query(config):
# imported albums -- those that haven't reached the database
# yet.
if ident in recent or _duplicate_check(config.lib, task):
config.resolve_duplicate_func(task, config)
config.resolve_duplicate_func(task)
log_choice(config, task, True)
recent.add(ident)
def show_progress(config):
def show_progress():
"""This stage replaces the initial_lookup and user_query stages
when the importer is run without autotagging. It displays the album
name and artist as the files are added.
@ -628,7 +631,7 @@ def show_progress(config):
task.set_null_candidates()
task.set_choice(action.ASIS)
def apply_choices(config):
def apply_choices():
"""A coroutine for applying changes to albums and singletons during
the autotag process.
"""
@ -648,12 +651,11 @@ def apply_choices(config):
if task.should_write_tags():
if task.is_album:
autotag.apply_metadata(
task.match.info, task.match.mapping,
per_disc_numbering=config.per_disc_numbering
task.match.info, task.match.mapping
)
else:
autotag.apply_item_metadata(task.item, task.match.info)
plugins.send('import_task_apply', config=config, task=task)
plugins.send('import_task_apply', task=task)
# Infer album-level fields.
if task.is_album:
@ -715,7 +717,7 @@ def apply_choices(config):
for item in items:
config.lib.add(item)
def plugin_stage(config, func):
def plugin_stage(func):
"""A coroutine (pipeline stage) that calls the given function with
each non-skipped import task. These stages occur between applying
metadata changes and moving/copying/writing files.
@ -725,9 +727,9 @@ def plugin_stage(config, func):
task = yield task
if task.should_skip():
continue
func(config, task)
func(task)
def manipulate_files(config):
def manipulate_files():
"""A coroutine (pipeline stage) that performs necessary file
manipulations *after* items have been added to the library.
"""
@ -741,12 +743,12 @@ def manipulate_files(config):
items = task.imported_items()
task.old_paths = [item.path for item in items] # For deletion.
for item in items:
if config.move:
if config['import']['move']:
# Just move the file.
old_path = item.path
config.lib.move(item, False)
task.prune(old_path)
elif config.copy:
elif config['import']['copy']:
# If it's a reimport, move in-library files and copy
# out-of-library files. Otherwise, copy and keep track
# of the old path.
@ -766,7 +768,7 @@ def manipulate_files(config):
# old paths.
config.lib.move(item, True)
if config.write and task.should_write_tags():
if config['import']['write '] and task.should_write_tags():
item.write()
# Save new paths.
@ -775,9 +777,9 @@ def manipulate_files(config):
config.lib.store(item)
# Plugin event.
plugins.send('import_task_files', config=config, task=task)
plugins.send('import_task_files', task=task)
def finalize(config):
def finalize():
"""A coroutine that finishes up importer tasks. In particular, the
coroutine sends plugin events, deletes old files, and saves
progress. This is a "terminal" coroutine (it yields None).
@ -785,9 +787,9 @@ def finalize(config):
while True:
task = yield
if task.should_skip():
if config.resume is not False:
if config['import']['resume ']is not False:
task.save_progress()
if config.incremental:
if config['import']['incremental']:
task.save_history()
continue
@ -797,14 +799,14 @@ def finalize(config):
if task.is_album:
album = config.lib.get_album(task.album_id)
plugins.send('album_imported',
lib=config.lib, album=album, config=config)
lib=config.lib, album=album)
else:
for item in items:
plugins.send('item_imported',
lib=config.lib, item=item, config=config)
lib=config.lib, item=item)
# Finally, delete old files.
if config.copy and config.delete:
if config['import']['copy'] and config['import']['delete']:
new_paths = [os.path.realpath(item.path) for item in items]
for old_path in task.old_paths:
# Only delete files that were actually copied.
@ -813,15 +815,15 @@ def finalize(config):
task.prune(old_path)
# Update progress.
if config.resume is not False:
if config['import']['resume'] is not False:
task.save_progress()
if config.incremental:
if config['import']['incremental']:
task.save_history()
# Singleton pipeline stages.
def item_lookup(config):
def item_lookup():
"""A coroutine used to perform the initial MusicBrainz lookup for
an item task.
"""
@ -831,11 +833,11 @@ def item_lookup(config):
if task.sentinel:
continue
plugins.send('import_task_start', task=task, config=config)
plugins.send('import_task_start', task=task)
task.set_item_candidates(*autotag.tag_item(task.item, config.timid))
task.set_item_candidates(*autotag.tag_item(task.item))
def item_query(config):
def item_query():
"""A coroutine that queries the user for input on single-item
lookups.
"""
@ -846,20 +848,20 @@ def item_query(config):
if task.sentinel:
continue
choice = config.choose_item_func(task, config)
choice = config.choose_item_func(task)
task.set_choice(choice)
log_choice(config, task)
plugins.send('import_task_choice', task=task, config=config)
plugins.send('import_task_choice', task=task)
# Duplicate check.
if task.choice_flag in (action.ASIS, action.APPLY):
ident = task.chosen_ident()
if ident in recent or _item_duplicate_check(config.lib, task):
config.resolve_duplicate_func(task, config)
config.resolve_duplicate_func(task)
log_choice(config, task, True)
recent.add(ident)
def item_progress(config):
def item_progress():
"""Skips the lookup and query stages in a non-autotagged singleton
import. Just shows progress.
"""
@ -877,41 +879,38 @@ def item_progress(config):
# Main driver.
def run_import(**kwargs):
"""Run an import. The keyword arguments are the same as those to
ImportConfig.
def run_import():
"""Run an import task.
"""
config = ImportConfig(**kwargs)
# Set up the pipeline.
if config.query is None:
stages = [read_tasks(config)]
stages = [read_tasks()]
else:
stages = [query_tasks(config)]
if config.singletons:
stages = [query_tasks()]
if config['import']['singletons']:
# Singleton importer.
if config.autot:
stages += [item_lookup(config), item_query(config)]
if config['import']['autotag']:
stages += [item_lookup(), item_query()]
else:
stages += [item_progress(config)]
stages += [item_progress()]
else:
# Whole-album importer.
if config.autot:
if config['import']['autotag']:
# Only look up and query the user when autotagging.
stages += [initial_lookup(config), user_query(config)]
stages += [initial_lookup(), user_query()]
else:
# When not autotagging, just display progress.
stages += [show_progress(config)]
stages += [apply_choices(config)]
stages += [show_progress()]
stages += [apply_choices()]
for stage_func in plugins.import_stages():
stages.append(plugin_stage(config, stage_func))
stages += [manipulate_files(config)]
stages += [finalize(config)]
stages.append(plugin_stage(stage_func))
stages += [manipulate_files()]
stages += [finalize()]
pl = pipeline.Pipeline(stages)
# Run the pipeline.
try:
if config.threaded:
if config['threaded']:
pl.run_parallel(QUEUE_SIZE)
else:
pl.run_sequential()

View file

@ -48,27 +48,10 @@ if sys.platform == 'win32':
# Constants.
CONFIG_PATH_VAR = 'BEETSCONFIG'
DEFAULT_CONFIG_FILENAME_UNIX = '.beetsconfig'
DEFAULT_CONFIG_FILENAME_WINDOWS = 'beetsconfig.ini'
DEFAULT_LIBRARY_FILENAME_UNIX = '.beetsmusic.blb'
DEFAULT_LIBRARY_FILENAME_WINDOWS = 'beetsmusic.blb'
DEFAULT_DIRECTORY_NAME = 'Music'
WINDOWS_BASEDIR = os.environ.get('APPDATA') or '~'
PF_KEY_QUERIES = {
'comp': 'comp:true',
'singleton': 'singleton:true',
}
DEFAULT_PATH_FORMATS = [
(library.PF_KEY_DEFAULT,
Template('$albumartist/$album%aunique{}/$track $title')),
(PF_KEY_QUERIES['singleton'],
Template('Non-Album/$artist/$title')),
(PF_KEY_QUERIES['comp'],
Template('Compilations/$album%aunique{}/$track $title')),
]
DEFAULT_ART_FILENAME = 'cover'
DEFAULT_TIMEOUT = 5.0
# UI exception. Commands should throw this in order to display
# nonrecoverable errors to the user.
@ -134,7 +117,7 @@ def input_(prompt=None):
return resp.decode(sys.stdin.encoding, 'ignore')
def input_options(options, require=False, prompt=None, fallback_prompt=None,
numrange=None, default=None, color=False, max_width=72):
numrange=None, default=None, max_width=72):
"""Prompts a user for input. The sequence of `options` defines the
choices the user has. A single-letter shortcut is inferred for each
option; the user's choice is returned as that single, lower-case
@ -192,7 +175,7 @@ def input_options(options, require=False, prompt=None, fallback_prompt=None,
is_default = False
# Possibly colorize the letter shortcut.
if color:
if config['color'].get(bool):
color = 'turquoise' if is_default else 'blue'
show_letter = colorize(color, show_letter)

View file

@ -100,35 +100,18 @@ default_commands.append(fields_cmd)
# import: Autotagger and importer.
DEFAULT_IMPORT_COPY = True
DEFAULT_IMPORT_MOVE = False
DEFAULT_IMPORT_WRITE = True
DEFAULT_IMPORT_DELETE = False
DEFAULT_IMPORT_AUTOT = True
DEFAULT_IMPORT_TIMID = False
DEFAULT_IMPORT_QUIET = False
DEFAULT_IMPORT_QUIET_FALLBACK = 'skip'
DEFAULT_IMPORT_RESUME = None # "ask"
DEFAULT_IMPORT_INCREMENTAL = False
DEFAULT_THREADED = True
DEFAULT_COLOR = True
DEFAULT_IGNORE = [
'.*', '*~',
]
DEFAULT_PER_DISC_NUMBERING = False
VARIOUS_ARTISTS = u'Various Artists'
PARTIAL_MATCH_MESSAGE = u'(partial match!)'
# Importer utilities and support.
def dist_string(dist, color):
def dist_string(dist):
"""Formats a distance (a float) as a similarity percentage string.
The string is colorized if color is True.
"""
out = '%.1f%%' % ((1 - dist) * 100)
if color:
if config['color'].get(bool):
if dist <= autotag.STRONG_REC_THRESH:
out = ui.colorize('green', out)
elif dist <= autotag.MEDIUM_REC_THRESH:
@ -137,8 +120,7 @@ def dist_string(dist, color):
out = ui.colorize('red', out)
return out
def show_change(cur_artist, cur_album, match, color=True,
per_disc_numbering=False):
def show_change(cur_artist, cur_album, match):
"""Print out a representation of the changes that will be made if an
album's tags are changed according to `match`, which must be an AlbumMatch
object.
@ -156,7 +138,7 @@ def show_change(cur_artist, cur_album, match, color=True,
warning = PARTIAL_MATCH_MESSAGE
else:
warning = None
if color and warning:
if config['color'].get(bool) and warning:
warning = ui.colorize('yellow', warning)
out = album_description
@ -168,7 +150,7 @@ def show_change(cur_artist, cur_album, match, color=True,
"""Return a string representing the track index of the given
TrackInfo object.
"""
if per_disc_numbering:
if config['per_disc_numbering'].get(bool):
if match.info.mediums > 1:
return u'{0}-{1}'.format(track_info.medium,
track_info.medium_index)
@ -187,7 +169,7 @@ def show_change(cur_artist, cur_album, match, color=True,
# Hide artists for VA releases.
artist_l, artist_r = u'', u''
if color:
if config['color'].get(bool):
artist_l, artist_r = ui.colordiff(artist_l, artist_r)
album_l, album_r = ui.colordiff(album_l, album_r)
@ -199,13 +181,13 @@ def show_change(cur_artist, cur_album, match, color=True,
message = u"Tagging: %s - %s" % (match.info.artist, match.info.album)
if match.extra_items or match.extra_tracks:
warning = PARTIAL_MATCH_MESSAGE
if color:
if config['color'].get(bool):
warning = ui.colorize('yellow', PARTIAL_MATCH_MESSAGE)
message += u' ' + warning
print_(message)
# Distance/similarity.
print_('(Similarity: %s)' % dist_string(match.distance, color))
print_('(Similarity: %s)' % dist_string(match.distance))
# Tracks.
pairs = match.mapping.items()
@ -221,12 +203,12 @@ def show_change(cur_artist, cur_album, match, color=True,
if item.length and track_info.length:
cur_length = ui.human_seconds_short(item.length)
new_length = ui.human_seconds_short(track_info.length)
if color:
if config['color'].get(bool):
cur_length = ui.colorize('red', cur_length)
new_length = ui.colorize('red', new_length)
# Possibly colorize changes.
if color:
if config['color'].get(bool):
cur_title, new_title = ui.colordiff(cur_title, new_title)
cur_track = ui.colorize('red', cur_track)
new_track = ui.colorize('red', new_track)
@ -258,16 +240,16 @@ def show_change(cur_artist, cur_album, match, color=True,
for track_info in match.extra_tracks:
line = u' * Missing track: {0} ({1})'.format(track_info.title,
format_index(track_info))
if color:
if config['color'].get(bool):
line = ui.colorize('yellow', line)
print_(line)
for item in match.extra_items:
line = u' * Unmatched track: {0} ({1})'.format(item.title, item.track)
if color:
if config['color'].get(bool):
line = ui.colorize('yellow', line)
print_(line)
def show_item_change(item, match, color):
def show_item_change(item, match):
"""Print out the change that would occur by tagging `item` with the
metadata from `match`, a TrackMatch object.
"""
@ -275,7 +257,7 @@ def show_item_change(item, match, color):
cur_title, new_title = item.title, match.info.title
if cur_artist != new_artist or cur_title != new_title:
if color:
if config['color'].get():
cur_artist, new_artist = ui.colordiff(cur_artist, new_artist)
cur_title, new_title = ui.colordiff(cur_title, new_title)
@ -287,7 +269,7 @@ def show_item_change(item, match, color):
else:
print_("Tagging track: %s - %s" % (cur_artist, cur_title))
print_('(Similarity: %s)' % dist_string(match.distance, color))
print_('(Similarity: %s)' % dist_string(match.distance))
def should_resume(config, path):
return ui.input_yn("Import of the directory:\n%s"
@ -305,9 +287,8 @@ def _quiet_fall_back(config):
assert(False)
return config.quiet_fallback
def choose_candidate(candidates, singleton, rec, color, timid,
cur_artist=None, cur_album=None, item=None,
itemcount=None, per_disc_numbering=False):
def choose_candidate(candidates, singleton, rec, cur_artist=None,
cur_album=None, item=None, itemcount=None):
"""Given a sorted list of candidates, ask the user for a selection
of which candidate to use. Applies to both full albums and
singletons (tracks). Candidates are either AlbumMatch or TrackMatch
@ -338,7 +319,7 @@ def choose_candidate(candidates, singleton, rec, color, timid,
'https://github.com/sampsyo/beets/wiki/FAQ#wiki-nomatch')
opts = ('Use as-is', 'as Tracks', 'Skip', 'Enter search',
'enter Id', 'aBort')
sel = ui.input_options(opts, color=color)
sel = ui.input_options(opts)
if sel == 'u':
return importer.action.ASIS
elif sel == 't':
@ -372,7 +353,7 @@ def choose_candidate(candidates, singleton, rec, color, timid,
for i, match in enumerate(candidates):
print_('%i. %s - %s (%s)' %
(i + 1, match.info.artist, match.info.title,
dist_string(match.distance, color)))
dist_string(match.distance)))
else:
print_('Finding tags for album "%s - %s".' %
(cur_artist, cur_album))
@ -394,12 +375,12 @@ def choose_candidate(candidates, singleton, rec, color, timid,
elif year:
line += u' [%s]' % year
line += ' (%s)' % dist_string(match.distance, color)
line += ' (%s)' % dist_string(match.distance)
# Point out the partial matches.
if match.extra_items or match.extra_tracks:
warning = PARTIAL_MATCH_MESSAGE
if color:
if config['color'].get(bool):
warning = ui.colorize('yellow', warning)
line += u' %s' % warning
@ -412,8 +393,7 @@ def choose_candidate(candidates, singleton, rec, color, timid,
else:
opts = ('Skip', 'Use as-is', 'as Tracks', 'Enter search',
'enter Id', 'aBort')
sel = ui.input_options(opts, numrange=(1, len(candidates)),
color=color)
sel = ui.input_options(opts, numrange=(1, len(candidates)))
if sel == 's':
return importer.action.SKIP
elif sel == 'u':
@ -436,13 +416,12 @@ def choose_candidate(candidates, singleton, rec, color, timid,
# Show what we're about to do.
if singleton:
show_item_change(item, match, color)
show_item_change(item, match)
else:
show_change(cur_artist, cur_album, match, color,
per_disc_numbering)
show_change(cur_artist, cur_album, match)
# Exact match => tag automatically if we're not in timid mode.
if rec == autotag.RECOMMEND_STRONG and not timid:
if rec == autotag.RECOMMEND_STRONG and not config['import']['timid']:
return match
# Ask for confirmation.
@ -452,7 +431,7 @@ def choose_candidate(candidates, singleton, rec, color, timid,
else:
opts = ('Apply', 'More candidates', 'Skip', 'Use as-is',
'as Tracks', 'Enter search', 'enter Id', 'aBort')
sel = ui.input_options(opts, color=color)
sel = ui.input_options(opts)
if sel == 'a':
return match
elif sel == 'm':
@ -508,7 +487,7 @@ def choose_match(task, config):
# No input; just make a decision.
if task.rec == autotag.RECOMMEND_STRONG:
match = task.candidates[0]
show_change(task.cur_artist, task.cur_album, match, config.color)
show_change(task.cur_artist, task.cur_album, match)
return match
else:
return _quiet_fall_back(config)
@ -517,10 +496,8 @@ def choose_match(task, config):
candidates, rec = task.candidates, task.rec
while True:
# Ask for a choice from the user.
choice = choose_candidate(candidates, False, rec, config.color,
config.timid, task.cur_artist,
task.cur_album, itemcount=len(task.items),
per_disc_numbering=config.per_disc_numbering)
choice = choose_candidate(candidates, False, rec, task.cur_artist,
task.cur_album, itemcount=len(task.items))
# Choose which tags to use.
if choice in (importer.action.SKIP, importer.action.ASIS,
@ -532,8 +509,7 @@ def choose_match(task, config):
search_artist, search_album = manual_search(False)
try:
_, _, candidates, rec = \
autotag.tag_album(task.items, config.timid, search_artist,
search_album)
autotag.tag_album(task.items, search_artist, search_album)
except autotag.AutotagError:
candidates, rec = None, None
elif choice is importer.action.MANUAL_ID:
@ -542,8 +518,7 @@ def choose_match(task, config):
if search_id:
try:
_, _, candidates, rec = \
autotag.tag_album(task.items, config.timid,
search_id=search_id)
autotag.tag_album(task.items, search_id=search_id)
except autotag.AutotagError:
candidates, rec = None, None
else:
@ -564,15 +539,14 @@ def choose_item(task, config):
# Quiet mode; make a decision.
if rec == autotag.RECOMMEND_STRONG:
match = candidates[0]
show_item_change(task.item, match, config.color)
show_item_change(task.item, match)
return match
else:
return _quiet_fall_back(config)
while True:
# Ask for a choice.
choice = choose_candidate(candidates, True, rec, config.color,
config.timid, item=task.item)
choice = choose_candidate(candidates, True, rec, item=task.item)
if choice in (importer.action.SKIP, importer.action.ASIS):
return choice
@ -581,13 +555,13 @@ def choose_item(task, config):
elif choice == importer.action.MANUAL:
# Continue in the loop with a new set of candidates.
search_artist, search_title = manual_search(True)
candidates, rec = autotag.tag_item(task.item, config.timid,
search_artist, search_title)
candidates, rec = autotag.tag_item(task.item, search_artist,
search_title)
elif choice == importer.action.MANUAL_ID:
# Ask for a track ID.
search_id = manual_id(True)
if search_id:
candidates, rec = autotag.tag_item(task.item, config.timid,
candidates, rec = autotag.tag_item(task.item,
search_id=search_id)
else:
# Chose a candidate.
@ -607,8 +581,7 @@ def resolve_duplicate(task, config):
sel = 's'
else:
sel = ui.input_options(
('Skip new', 'Keep both', 'Remove old'),
color=config.color
('Skip new', 'Keep both', 'Remove old')
)
if sel == 's':
@ -1139,7 +1112,7 @@ def modify_func(lib, config, opts, args):
if not mods:
raise ui.UserError('no modifications specified')
write = opts.write if opts.write is not None else \
config['import_write'].get(bool)
config['import']['write'].get(bool)
modify_items(lib, mods, query, write, opts.move, opts.album, not opts.yes)
modify_cmd.func = modify_func
default_commands.append(modify_cmd)