mirror of
https://github.com/rembo10/headphones.git
synced 2026-07-21 00:14:02 +01:00
InRough update of the beets lib to 1.0b15
This commit is contained in:
+184
-144
@@ -8,14 +8,15 @@
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
|
||||
"""Provides the basic, interface-agnostic workflow for importing and
|
||||
autotagging music files.
|
||||
"""
|
||||
from __future__ import with_statement # Python 2.5
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import logging
|
||||
import pickle
|
||||
@@ -23,7 +24,6 @@ from collections import defaultdict
|
||||
|
||||
from lib.beets import autotag
|
||||
from lib.beets import library
|
||||
import lib.beets.autotag.art
|
||||
from lib.beets import plugins
|
||||
from lib.beets import util
|
||||
from lib.beets.util import pipeline
|
||||
@@ -56,7 +56,7 @@ def tag_log(logfile, status, path):
|
||||
reflect the reason the album couldn't be tagged.
|
||||
"""
|
||||
if logfile:
|
||||
print >>logfile, '%s %s' % (status, path)
|
||||
print('{0} {1}'.format(status, path), file=logfile)
|
||||
logfile.flush()
|
||||
|
||||
def log_choice(config, task, duplicate=False):
|
||||
@@ -80,23 +80,6 @@ def log_choice(config, task, duplicate=False):
|
||||
elif task.choice_flag is action.SKIP:
|
||||
tag_log(config.logfile, 'skip', path)
|
||||
|
||||
def _reopen_lib(lib):
|
||||
"""Because of limitations in SQLite, a given Library is bound to
|
||||
the thread in which it was created. This function reopens Library
|
||||
objects so that they can be used from separate threads.
|
||||
"""
|
||||
if isinstance(lib, library.Library):
|
||||
return library.Library(
|
||||
lib.path,
|
||||
lib.directory,
|
||||
lib.path_formats,
|
||||
lib.art_filename,
|
||||
lib.timeout,
|
||||
lib.replacements,
|
||||
)
|
||||
else:
|
||||
return lib
|
||||
|
||||
def _duplicate_check(lib, task):
|
||||
"""Check whether an album already exists in the library. Returns a
|
||||
list of Album objects (empty if no duplicates are found).
|
||||
@@ -193,7 +176,7 @@ def _save_state(state):
|
||||
try:
|
||||
with open(STATE_FILE, 'w') as f:
|
||||
pickle.dump(state, f)
|
||||
except IOError, exc:
|
||||
except IOError as exc:
|
||||
log.error(u'state file could not be written: %s' % unicode(exc))
|
||||
|
||||
|
||||
@@ -259,11 +242,11 @@ class ImportConfig(object):
|
||||
then never touched again.
|
||||
"""
|
||||
_fields = ['lib', 'paths', 'resume', 'logfile', 'color', 'quiet',
|
||||
'quiet_fallback', 'copy', 'write', 'art', 'delete',
|
||||
'quiet_fallback', 'copy', 'move', 'write', 'delete',
|
||||
'choose_match_func', 'should_resume_func', 'threaded',
|
||||
'autot', 'singletons', 'timid', 'choose_item_func',
|
||||
'query', 'incremental', 'ignore',
|
||||
'resolve_duplicate_func']
|
||||
'resolve_duplicate_func', 'per_disc_numbering']
|
||||
def __init__(self, **kwargs):
|
||||
for slot in self._fields:
|
||||
setattr(self, slot, kwargs[slot])
|
||||
@@ -283,6 +266,14 @@ class ImportConfig(object):
|
||||
self.resume = False
|
||||
self.incremental = False
|
||||
|
||||
# Copy and move are mutually exclusive.
|
||||
if self.move:
|
||||
self.copy = False
|
||||
|
||||
# Only delete when copying.
|
||||
if not self.copy:
|
||||
self.delete = False
|
||||
|
||||
|
||||
# The importer task class.
|
||||
|
||||
@@ -296,6 +287,7 @@ class ImportTask(object):
|
||||
self.items = items
|
||||
self.sentinel = False
|
||||
self.remove_duplicates = False
|
||||
self.is_album = True
|
||||
|
||||
@classmethod
|
||||
def done_sentinel(cls, toppath):
|
||||
@@ -324,56 +316,50 @@ class ImportTask(object):
|
||||
obj.is_album = False
|
||||
return obj
|
||||
|
||||
def set_match(self, cur_artist, cur_album, candidates, rec):
|
||||
def set_candidates(self, cur_artist, cur_album, candidates, rec):
|
||||
"""Sets the candidates for this album matched by the
|
||||
`autotag.tag_album` method.
|
||||
"""
|
||||
assert self.is_album
|
||||
assert not self.sentinel
|
||||
self.cur_artist = cur_artist
|
||||
self.cur_album = cur_album
|
||||
self.candidates = candidates
|
||||
self.rec = rec
|
||||
self.is_album = True
|
||||
|
||||
def set_null_match(self):
|
||||
def set_null_candidates(self):
|
||||
"""Set the candidates to indicate no album match was found.
|
||||
"""
|
||||
self.set_match(None, None, None, None)
|
||||
self.cur_artist = None
|
||||
self.cur_album = None
|
||||
self.candidates = None
|
||||
self.rec = None
|
||||
|
||||
def set_item_match(self, candidates, rec):
|
||||
def set_item_candidates(self, candidates, rec):
|
||||
"""Set the match for a single-item task."""
|
||||
assert not self.is_album
|
||||
assert self.item is not None
|
||||
self.item_match = (candidates, rec)
|
||||
|
||||
def set_null_item_match(self):
|
||||
"""For single-item tasks, mark the item as having no matches.
|
||||
"""
|
||||
assert not self.is_album
|
||||
assert self.item is not None
|
||||
self.item_match = None
|
||||
self.candidates = candidates
|
||||
self.rec = rec
|
||||
|
||||
def set_choice(self, choice):
|
||||
"""Given either an (info, items) tuple or an action constant,
|
||||
indicates that an action has been selected by the user (or
|
||||
automatically).
|
||||
"""Given an AlbumMatch or TrackMatch object or an action constant,
|
||||
indicates that an action has been selected for this task.
|
||||
"""
|
||||
assert not self.sentinel
|
||||
# Not part of the task structure:
|
||||
assert choice not in (action.MANUAL, action.MANUAL_ID)
|
||||
assert choice != action.APPLY # Only used internally.
|
||||
assert choice != action.APPLY # Only used internally.
|
||||
if choice in (action.SKIP, action.ASIS, action.TRACKS):
|
||||
self.choice_flag = choice
|
||||
self.info = None
|
||||
self.match = None
|
||||
else:
|
||||
assert not isinstance(choice, action)
|
||||
if self.is_album:
|
||||
info, items = choice
|
||||
self.items = items # Reordered items list.
|
||||
assert isinstance(choice, autotag.AlbumMatch)
|
||||
else:
|
||||
info = choice
|
||||
self.info = info
|
||||
self.choice_flag = action.APPLY # Implicit choice.
|
||||
assert isinstance(choice, autotag.TrackMatch)
|
||||
self.choice_flag = action.APPLY # Implicit choice.
|
||||
self.match = choice
|
||||
|
||||
def save_progress(self):
|
||||
"""Updates the progress state to indicate that this album has
|
||||
@@ -393,7 +379,9 @@ class ImportTask(object):
|
||||
if self.sentinel or self.is_album:
|
||||
history_add(self.path)
|
||||
|
||||
|
||||
# Logical decisions.
|
||||
|
||||
def should_write_tags(self):
|
||||
"""Should new info be written to the files' metadata?"""
|
||||
if self.choice_flag == action.APPLY:
|
||||
@@ -402,16 +390,16 @@ class ImportTask(object):
|
||||
return False
|
||||
else:
|
||||
assert False
|
||||
def should_fetch_art(self):
|
||||
"""Should album art be downloaded for this album?"""
|
||||
return self.should_write_tags() and self.is_album
|
||||
|
||||
def should_skip(self):
|
||||
"""After a choice has been made, returns True if this is a
|
||||
sentinel or it has been marked for skipping.
|
||||
"""
|
||||
return self.sentinel or self.choice_flag == action.SKIP
|
||||
|
||||
# Useful data.
|
||||
|
||||
# Convenient data.
|
||||
|
||||
def chosen_ident(self):
|
||||
"""Returns identifying metadata about the current choice. For
|
||||
albums, this is an (artist, album) pair. For items, this is
|
||||
@@ -424,12 +412,41 @@ class ImportTask(object):
|
||||
if self.choice_flag is action.ASIS:
|
||||
return (self.cur_artist, self.cur_album)
|
||||
elif self.choice_flag is action.APPLY:
|
||||
return (self.info.artist, self.info.album)
|
||||
return (self.match.info.artist, self.match.info.album)
|
||||
else:
|
||||
if self.choice_flag is action.ASIS:
|
||||
return (self.item.artist, self.item.title)
|
||||
elif self.choice_flag is action.APPLY:
|
||||
return (self.info.artist, self.info.title)
|
||||
return (self.match.info.artist, self.match.info.title)
|
||||
|
||||
def imported_items(self):
|
||||
"""Return a list of Items that should be added to the library.
|
||||
If this is an album task, return the list of items in the
|
||||
selected match or everything if the choice is ASIS. If this is a
|
||||
singleton task, return a list containing the item.
|
||||
"""
|
||||
if self.is_album:
|
||||
if self.choice_flag == action.ASIS:
|
||||
return list(self.items)
|
||||
elif self.choice_flag == action.APPLY:
|
||||
return self.match.mapping.keys()
|
||||
else:
|
||||
assert False
|
||||
else:
|
||||
return [self.item]
|
||||
|
||||
|
||||
# Utilities.
|
||||
|
||||
def prune(self, filename):
|
||||
"""Prune any empty directories above the given file. If this
|
||||
task has no `toppath` or the file path provided is not within
|
||||
the `toppath`, then this function has no effect. Similarly, if
|
||||
the file still exists, no pruning is performed, so it's safe to
|
||||
call when the file in question may not have been removed.
|
||||
"""
|
||||
if self.toppath and not os.path.exists(filename):
|
||||
util.prune_dirs(os.path.dirname(filename), self.toppath)
|
||||
|
||||
|
||||
# Full-album pipeline stages.
|
||||
@@ -464,14 +481,14 @@ def read_tasks(config):
|
||||
if config.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)):
|
||||
item = library.Item.from_path(toppath)
|
||||
yield ImportTask.item_task(item)
|
||||
continue
|
||||
|
||||
|
||||
# Produce paths under this directory.
|
||||
if progress:
|
||||
resume_dir = resume_dirs.get(toppath)
|
||||
@@ -513,16 +530,14 @@ def query_tasks(config):
|
||||
Instead of finding files from the filesystem, a query is used to
|
||||
match items from the library.
|
||||
"""
|
||||
lib = _reopen_lib(config.lib)
|
||||
|
||||
if config.singletons:
|
||||
# Search for items.
|
||||
for item in lib.items(config.query):
|
||||
for item in config.lib.items(config.query):
|
||||
yield ImportTask.item_task(item)
|
||||
|
||||
else:
|
||||
# Search for albums.
|
||||
for album in lib.albums(config.query):
|
||||
for album in config.lib.albums(config.query):
|
||||
log.debug('yielding album %i: %s - %s' %
|
||||
(album.id, album.albumartist, album.album))
|
||||
items = list(album.items())
|
||||
@@ -540,11 +555,13 @@ def initial_lookup(config):
|
||||
if task.sentinel:
|
||||
continue
|
||||
|
||||
plugins.send('import_task_start', task=task, config=config)
|
||||
|
||||
log.debug('Looking up: %s' % task.path)
|
||||
try:
|
||||
task.set_match(*autotag.tag_album(task.items, config.timid))
|
||||
task.set_candidates(*autotag.tag_album(task.items, config.timid))
|
||||
except autotag.AutotagError:
|
||||
task.set_null_match()
|
||||
task.set_null_candidates()
|
||||
|
||||
def user_query(config):
|
||||
"""A coroutine for interfacing with the user about the tagging
|
||||
@@ -552,18 +569,18 @@ def user_query(config):
|
||||
a file-like object for logging the import process. The coroutine
|
||||
accepts and yields ImportTask objects.
|
||||
"""
|
||||
lib = _reopen_lib(config.lib)
|
||||
recent = set()
|
||||
task = None
|
||||
while True:
|
||||
task = yield task
|
||||
if task.sentinel:
|
||||
continue
|
||||
|
||||
|
||||
# Ask the user for a choice.
|
||||
choice = config.choose_match_func(task, config)
|
||||
task.set_choice(choice)
|
||||
log_choice(config, task)
|
||||
plugins.send('import_task_choice', task=task, config=config)
|
||||
|
||||
# As-tracks: transition to singleton workflow.
|
||||
if choice is action.TRACKS:
|
||||
@@ -577,7 +594,7 @@ def user_query(config):
|
||||
while True:
|
||||
item_task = yield
|
||||
item_tasks.append(item_task)
|
||||
ipl = pipeline.Pipeline((emitter(), item_lookup(config),
|
||||
ipl = pipeline.Pipeline((emitter(), item_lookup(config),
|
||||
item_query(config), collector()))
|
||||
ipl.run_sequential()
|
||||
task = pipeline.multiple(item_tasks)
|
||||
@@ -589,7 +606,7 @@ def user_query(config):
|
||||
# The "recent" set keeps track of identifiers for recently
|
||||
# imported albums -- those that haven't reached the database
|
||||
# yet.
|
||||
if ident in recent or _duplicate_check(lib, task):
|
||||
if ident in recent or _duplicate_check(config.lib, task):
|
||||
config.resolve_duplicate_func(task, config)
|
||||
log_choice(config, task, True)
|
||||
recent.add(ident)
|
||||
@@ -608,21 +625,20 @@ def show_progress(config):
|
||||
log.info(task.path)
|
||||
|
||||
# Behave as if ASIS were selected.
|
||||
task.set_null_match()
|
||||
task.set_null_candidates()
|
||||
task.set_choice(action.ASIS)
|
||||
|
||||
|
||||
def apply_choices(config):
|
||||
"""A coroutine for applying changes to albums during the autotag
|
||||
process.
|
||||
"""A coroutine for applying changes to albums and singletons during
|
||||
the autotag process.
|
||||
"""
|
||||
lib = _reopen_lib(config.lib)
|
||||
task = None
|
||||
while True:
|
||||
while True:
|
||||
task = yield task
|
||||
if task.should_skip():
|
||||
continue
|
||||
|
||||
items = [i for i in task.items if i] if task.is_album else [task.item]
|
||||
items = task.imported_items()
|
||||
# Clear IDs in case the items are being re-tagged.
|
||||
for item in items:
|
||||
item.id = None
|
||||
@@ -631,9 +647,13 @@ def apply_choices(config):
|
||||
# Change metadata.
|
||||
if task.should_write_tags():
|
||||
if task.is_album:
|
||||
autotag.apply_metadata(task.items, task.info)
|
||||
autotag.apply_metadata(
|
||||
task.match.info, task.match.mapping,
|
||||
per_disc_numbering=config.per_disc_numbering
|
||||
)
|
||||
else:
|
||||
autotag.apply_item_metadata(task.item, task.info)
|
||||
autotag.apply_item_metadata(task.item, task.match.info)
|
||||
plugins.send('import_task_apply', config=config, task=task)
|
||||
|
||||
# Infer album-level fields.
|
||||
if task.is_album:
|
||||
@@ -642,14 +662,14 @@ def apply_choices(config):
|
||||
# Find existing item entries that these are replacing (for
|
||||
# re-imports). Old album structures are automatically cleaned up
|
||||
# when the last item is removed.
|
||||
replaced_items = defaultdict(list)
|
||||
task.replaced_items = defaultdict(list)
|
||||
for item in items:
|
||||
dup_items = lib.items(library.MatchQuery('path', item.path))
|
||||
dup_items = config.lib.items(library.MatchQuery('path', item.path))
|
||||
for dup_item in dup_items:
|
||||
replaced_items[item].append(dup_item)
|
||||
task.replaced_items[item].append(dup_item)
|
||||
log.debug('replacing item %i: %s' %
|
||||
(dup_item.id, displayable_path(item.path)))
|
||||
log.debug('%i of %i items replaced' % (len(replaced_items),
|
||||
log.debug('%i of %i items replaced' % (len(task.replaced_items),
|
||||
len(items)))
|
||||
|
||||
# Find old items that should be replaced as part of a duplicate
|
||||
@@ -657,93 +677,111 @@ def apply_choices(config):
|
||||
duplicate_items = []
|
||||
if task.remove_duplicates:
|
||||
if task.is_album:
|
||||
for album in _duplicate_check(lib, task):
|
||||
for album in _duplicate_check(config.lib, task):
|
||||
duplicate_items += album.items()
|
||||
else:
|
||||
duplicate_items = _item_duplicate_check(lib, task)
|
||||
duplicate_items = _item_duplicate_check(config.lib, task)
|
||||
log.debug('removing %i old duplicated items' %
|
||||
len(duplicate_items))
|
||||
|
||||
# Delete duplicate files that are located inside the library
|
||||
# directory.
|
||||
for duplicate_path in [i.path for i in duplicate_items]:
|
||||
if lib.directory in util.ancestry(duplicate_path):
|
||||
if config.lib.directory in util.ancestry(duplicate_path):
|
||||
log.debug(u'deleting replaced duplicate %s' %
|
||||
util.displayable_path(duplicate_path))
|
||||
util.soft_remove(duplicate_path)
|
||||
util.remove(duplicate_path)
|
||||
util.prune_dirs(os.path.dirname(duplicate_path),
|
||||
lib.directory)
|
||||
config.lib.directory)
|
||||
|
||||
# Move/copy files.
|
||||
task.old_paths = [item.path for item in items]
|
||||
for item in items:
|
||||
if config.copy:
|
||||
# If we're replacing an item, then move rather than
|
||||
# copying.
|
||||
old_path = item.path
|
||||
do_copy = not bool(replaced_items[item])
|
||||
lib.move(item, do_copy, task.is_album)
|
||||
if not do_copy:
|
||||
# If we moved the item, remove the now-nonexistent
|
||||
# file from old_paths.
|
||||
task.old_paths.remove(old_path)
|
||||
if config.write and task.should_write_tags():
|
||||
item.write()
|
||||
|
||||
# Add items to library. We consolidate this at the end to avoid
|
||||
# locking while we do the copying and tag updates.
|
||||
try:
|
||||
# Add items -- before path changes -- to the library. We add the
|
||||
# items now (rather than at the end) so that album structures
|
||||
# are in place before calls to destination().
|
||||
with config.lib.transaction():
|
||||
# Remove old items.
|
||||
for replaced in replaced_items.itervalues():
|
||||
for replaced in task.replaced_items.itervalues():
|
||||
for item in replaced:
|
||||
lib.remove(item)
|
||||
config.lib.remove(item)
|
||||
for item in duplicate_items:
|
||||
lib.remove(item)
|
||||
config.lib.remove(item)
|
||||
|
||||
# Add new ones.
|
||||
if task.is_album:
|
||||
# Add an album.
|
||||
album = lib.add_album(items)
|
||||
album = config.lib.add_album(items)
|
||||
task.album_id = album.id
|
||||
else:
|
||||
# Add tracks.
|
||||
for item in items:
|
||||
lib.add(item)
|
||||
finally:
|
||||
lib.save()
|
||||
config.lib.add(item)
|
||||
|
||||
def fetch_art(config):
|
||||
"""A coroutine that fetches and applies album art for albums where
|
||||
appropriate.
|
||||
def plugin_stage(config, 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.
|
||||
"""
|
||||
task = None
|
||||
while True:
|
||||
task = yield task
|
||||
if task.should_skip():
|
||||
continue
|
||||
func(config, task)
|
||||
|
||||
def manipulate_files(config):
|
||||
"""A coroutine (pipeline stage) that performs necessary file
|
||||
manipulations *after* items have been added to the library.
|
||||
"""
|
||||
lib = _reopen_lib(config.lib)
|
||||
task = None
|
||||
while True:
|
||||
task = yield task
|
||||
if task.should_skip():
|
||||
continue
|
||||
|
||||
if task.should_fetch_art():
|
||||
artpath = lib.beets.autotag.art.art_for_album(task.info, task.path)
|
||||
# Move/copy files.
|
||||
items = task.imported_items()
|
||||
task.old_paths = [item.path for item in items] # For deletion.
|
||||
for item in items:
|
||||
if config.move:
|
||||
# Just move the file.
|
||||
old_path = item.path
|
||||
config.lib.move(item, False)
|
||||
task.prune(old_path)
|
||||
elif config.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.
|
||||
old_path = item.path
|
||||
if task.replaced_items[item]:
|
||||
# This is a reimport. Move in-library files and copy
|
||||
# out-of-library files.
|
||||
if config.lib.directory in util.ancestry(old_path):
|
||||
config.lib.move(item, False)
|
||||
# We moved the item, so remove the
|
||||
# now-nonexistent file from old_paths.
|
||||
task.old_paths.remove(old_path)
|
||||
else:
|
||||
config.lib.move(item, True)
|
||||
else:
|
||||
# A normal import. Just copy files and keep track of
|
||||
# old paths.
|
||||
config.lib.move(item, True)
|
||||
|
||||
# Save the art if any was found.
|
||||
if artpath:
|
||||
try:
|
||||
album = lib.get_album(task.album_id)
|
||||
album.set_art(artpath)
|
||||
if config.delete and not util.samefile(artpath,
|
||||
album.artpath):
|
||||
# Delete the original file after it's imported.
|
||||
os.remove(artpath)
|
||||
finally:
|
||||
lib.save(False)
|
||||
if config.write and task.should_write_tags():
|
||||
item.write()
|
||||
|
||||
# Save new paths.
|
||||
with config.lib.transaction():
|
||||
for item in items:
|
||||
config.lib.store(item)
|
||||
|
||||
# Plugin event.
|
||||
plugins.send('import_task_files', config=config, task=task)
|
||||
|
||||
def finalize(config):
|
||||
"""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).
|
||||
"""
|
||||
lib = _reopen_lib(config.lib)
|
||||
while True:
|
||||
task = yield
|
||||
if task.should_skip():
|
||||
@@ -753,15 +791,17 @@ def finalize(config):
|
||||
task.save_history()
|
||||
continue
|
||||
|
||||
items = [i for i in task.items if i] if task.is_album else [task.item]
|
||||
items = task.imported_items()
|
||||
|
||||
# Announce that we've added an album.
|
||||
if task.is_album:
|
||||
album = lib.get_album(task.album_id)
|
||||
plugins.send('album_imported', lib=lib, album=album, config=config)
|
||||
album = config.lib.get_album(task.album_id)
|
||||
plugins.send('album_imported',
|
||||
lib=config.lib, album=album, config=config)
|
||||
else:
|
||||
for item in items:
|
||||
plugins.send('item_imported', lib=lib, item=item, config=config)
|
||||
plugins.send('item_imported',
|
||||
lib=config.lib, item=item, config=config)
|
||||
|
||||
# Finally, delete old files.
|
||||
if config.copy and config.delete:
|
||||
@@ -769,11 +809,8 @@ def finalize(config):
|
||||
for old_path in task.old_paths:
|
||||
# Only delete files that were actually copied.
|
||||
if old_path not in new_paths:
|
||||
os.remove(syspath(old_path))
|
||||
# Clean up directory if it is emptied.
|
||||
if task.toppath:
|
||||
util.prune_dirs(os.path.dirname(old_path),
|
||||
task.toppath)
|
||||
util.remove(syspath(old_path), False)
|
||||
task.prune(old_path)
|
||||
|
||||
# Update progress.
|
||||
if config.resume is not False:
|
||||
@@ -794,13 +831,14 @@ def item_lookup(config):
|
||||
if task.sentinel:
|
||||
continue
|
||||
|
||||
task.set_item_match(*autotag.tag_item(task.item, config.timid))
|
||||
plugins.send('import_task_start', task=task, config=config)
|
||||
|
||||
task.set_item_candidates(*autotag.tag_item(task.item, config.timid))
|
||||
|
||||
def item_query(config):
|
||||
"""A coroutine that queries the user for input on single-item
|
||||
lookups.
|
||||
"""
|
||||
lib = _reopen_lib(config.lib)
|
||||
task = None
|
||||
recent = set()
|
||||
while True:
|
||||
@@ -811,11 +849,12 @@ def item_query(config):
|
||||
choice = config.choose_item_func(task, config)
|
||||
task.set_choice(choice)
|
||||
log_choice(config, task)
|
||||
plugins.send('import_task_choice', task=task, config=config)
|
||||
|
||||
# Duplicate check.
|
||||
if task.choice_flag in (action.ASIS, action.APPLY):
|
||||
ident = task.chosen_ident()
|
||||
if ident in recent or _item_duplicate_check(lib, task):
|
||||
if ident in recent or _item_duplicate_check(config.lib, task):
|
||||
config.resolve_duplicate_func(task, config)
|
||||
log_choice(config, task, True)
|
||||
recent.add(ident)
|
||||
@@ -832,7 +871,7 @@ def item_progress(config):
|
||||
continue
|
||||
|
||||
log.info(displayable_path(task.item.path))
|
||||
task.set_null_item_match()
|
||||
task.set_null_candidates()
|
||||
task.set_choice(action.ASIS)
|
||||
|
||||
|
||||
@@ -843,7 +882,7 @@ def run_import(**kwargs):
|
||||
ImportConfig.
|
||||
"""
|
||||
config = ImportConfig(**kwargs)
|
||||
|
||||
|
||||
# Set up the pipeline.
|
||||
if config.query is None:
|
||||
stages = [read_tasks(config)]
|
||||
@@ -864,8 +903,9 @@ def run_import(**kwargs):
|
||||
# When not autotagging, just display progress.
|
||||
stages += [show_progress(config)]
|
||||
stages += [apply_choices(config)]
|
||||
if config.art:
|
||||
stages += [fetch_art(config)]
|
||||
for stage_func in plugins.import_stages():
|
||||
stages.append(plugin_stage(config, stage_func))
|
||||
stages += [manipulate_files(config)]
|
||||
stages += [finalize(config)]
|
||||
pl = pipeline.Pipeline(stages)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user