mirror of
https://github.com/beetbox/beets.git
synced 2025-12-25 10:05:13 +01:00
Introduce a new Proposal type for tag results
tag_album and tag_item now return a Proposal. The idea is that plugin actions should also be able to return Proposal values, just like the built-in actions.
This commit is contained in:
parent
9b0a867c73
commit
3578f0d429
3 changed files with 44 additions and 25 deletions
|
|
@ -22,6 +22,7 @@ from __future__ import division, absolute_import, print_function
|
|||
import datetime
|
||||
import re
|
||||
from munkres import Munkres
|
||||
from collections import namedtuple
|
||||
|
||||
from beets import logging
|
||||
from beets import plugins
|
||||
|
|
@ -52,6 +53,13 @@ class Recommendation(OrderedEnum):
|
|||
strong = 3
|
||||
|
||||
|
||||
# A structure for holding a set of possible matches to choose between. This
|
||||
# consists of a list of possible candidates (i.e., AlbumInfo or TrackInfo
|
||||
# objects) and a recommendation value.
|
||||
|
||||
Proposal = namedtuple('Proposal', ('candidates', 'recommendation'))
|
||||
|
||||
|
||||
# Primary matching functionality.
|
||||
|
||||
def current_metadata(items):
|
||||
|
|
@ -379,9 +387,8 @@ def _add_candidate(items, results, info):
|
|||
|
||||
def tag_album(items, search_artist=None, search_album=None,
|
||||
search_ids=[]):
|
||||
"""Return a tuple of a artist name, an album name, a list of
|
||||
`AlbumMatch` candidates from the metadata backend, and a
|
||||
`Recommendation`.
|
||||
"""Return a tuple of the current artist name, the current album
|
||||
name, and a `Proposal` containing `AlbumMatch` candidates.
|
||||
|
||||
The artist and album are the most common values of these fields
|
||||
among `items`.
|
||||
|
|
@ -429,7 +436,7 @@ def tag_album(items, search_artist=None, search_album=None,
|
|||
if rec == Recommendation.strong:
|
||||
log.debug(u'ID match.')
|
||||
return cur_artist, cur_album, \
|
||||
list(candidates.values()), rec
|
||||
Proposal(list(candidates.values()), rec)
|
||||
|
||||
# Search terms.
|
||||
if not (search_artist and search_album):
|
||||
|
|
@ -454,14 +461,15 @@ def tag_album(items, search_artist=None, search_album=None,
|
|||
# Sort and get the recommendation.
|
||||
candidates = _sort_candidates(candidates.values())
|
||||
rec = _recommendation(candidates)
|
||||
return cur_artist, cur_album, candidates, rec
|
||||
return cur_artist, cur_album, Proposal(candidates, rec)
|
||||
|
||||
|
||||
def tag_item(item, search_artist=None, search_title=None,
|
||||
search_ids=[]):
|
||||
"""Attempts to find metadata for a single track. Returns a
|
||||
`(candidates, recommendation)` pair where `candidates` is a list of
|
||||
TrackMatch objects. `search_artist` and `search_title` may be used
|
||||
"""Find metadata for a single track. Return a `Proposal` consisting
|
||||
of `TrackMatch` objects.
|
||||
|
||||
`search_artist` and `search_title` may be used
|
||||
to override the current metadata for the purposes of the MusicBrainz
|
||||
title. `search_ids` may be used for restricting the search to a list
|
||||
of metadata backend IDs.
|
||||
|
|
@ -484,14 +492,14 @@ def tag_item(item, search_artist=None, search_title=None,
|
|||
if rec == Recommendation.strong and \
|
||||
not config['import']['timid']:
|
||||
log.debug(u'Track ID match.')
|
||||
return _sort_candidates(candidates.values()), rec
|
||||
return Proposal(_sort_candidates(candidates.values()), rec)
|
||||
|
||||
# If we're searching by ID, don't proceed.
|
||||
if search_ids:
|
||||
if candidates:
|
||||
return _sort_candidates(candidates.values()), rec
|
||||
return Proposal(_sort_candidates(candidates.values()), rec)
|
||||
else:
|
||||
return [], Recommendation.none
|
||||
return Proposal([], Recommendation.none)
|
||||
|
||||
# Search terms.
|
||||
if not (search_artist and search_title):
|
||||
|
|
@ -507,4 +515,4 @@ def tag_item(item, search_artist=None, search_title=None,
|
|||
log.debug(u'Found {0} candidates.', len(candidates))
|
||||
candidates = _sort_candidates(candidates.values())
|
||||
rec = _recommendation(candidates)
|
||||
return candidates, rec
|
||||
return Proposal(candidates, rec)
|
||||
|
|
|
|||
|
|
@ -587,12 +587,12 @@ class ImportTask(BaseImportTask):
|
|||
candidate IDs are stored in self.search_ids: if present, the
|
||||
initial lookup is restricted to only those IDs.
|
||||
"""
|
||||
artist, album, candidates, recommendation = \
|
||||
artist, album, prop = \
|
||||
autotag.tag_album(self.items, search_ids=self.search_ids)
|
||||
self.cur_artist = artist
|
||||
self.cur_album = album
|
||||
self.candidates = candidates
|
||||
self.rec = recommendation
|
||||
self.candidates = prop.candidates
|
||||
self.rec = prop.recommendation
|
||||
|
||||
def find_duplicates(self, lib):
|
||||
"""Return a list of albums from `lib` with the same artist and
|
||||
|
|
@ -830,10 +830,9 @@ class SingletonImportTask(ImportTask):
|
|||
plugins.send('item_imported', lib=lib, item=item)
|
||||
|
||||
def lookup_candidates(self):
|
||||
candidates, recommendation = autotag.tag_item(
|
||||
self.item, search_ids=self.search_ids)
|
||||
self.candidates = candidates
|
||||
self.rec = recommendation
|
||||
prop = autotag.tag_item(self.item, search_ids=self.search_ids)
|
||||
self.candidates = prop.candidates
|
||||
self.rec = prop.recommendation
|
||||
|
||||
def find_duplicates(self, lib):
|
||||
"""Return a list of items from `lib` that have the same artist
|
||||
|
|
|
|||
|
|
@ -752,18 +752,22 @@ class TerminalImportSession(importer.ImportSession):
|
|||
elif choice is importer.action.MANUAL:
|
||||
# Try again with manual search terms.
|
||||
search_artist, search_album = manual_search(False)
|
||||
_, _, candidates, rec = autotag.tag_album(
|
||||
_, _, prop = autotag.tag_album(
|
||||
task.items, search_artist, search_album
|
||||
)
|
||||
candidates = prop.candidates
|
||||
rec = prop.recommendation
|
||||
|
||||
# Manual ID. We prompt for the ID and run the loop again.
|
||||
elif choice is importer.action.MANUAL_ID:
|
||||
# Try a manually-entered ID.
|
||||
search_id = manual_id(False)
|
||||
if search_id:
|
||||
_, _, candidates, rec = autotag.tag_album(
|
||||
_, _, prop = autotag.tag_album(
|
||||
task.items, search_ids=search_id.split()
|
||||
)
|
||||
candidates = prop.candidates
|
||||
rec = prop.recommendation
|
||||
|
||||
# Plugin-provided choices. We invoke the associated callback
|
||||
# function.
|
||||
|
|
@ -807,25 +811,33 @@ class TerminalImportSession(importer.ImportSession):
|
|||
|
||||
if choice in (importer.action.SKIP, importer.action.ASIS):
|
||||
return choice
|
||||
|
||||
elif choice == importer.action.TRACKS:
|
||||
assert False # TRACKS is only legal for albums.
|
||||
|
||||
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, search_artist,
|
||||
search_title)
|
||||
prop = autotag.tag_item(task.item, search_artist, search_title)
|
||||
candidates = prop.candidates
|
||||
rec = prop.recommendation
|
||||
|
||||
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, search_ids=search_id.split())
|
||||
prop = autotag.tag_item(task.item,
|
||||
search_ids=search_id.split())
|
||||
candidates = prop.candidates
|
||||
rec = prop.recommendation
|
||||
|
||||
elif choice in list(extra_ops.keys()):
|
||||
# Allow extra ops to automatically set the post-choice.
|
||||
post_choice = extra_ops[choice](self, task)
|
||||
if isinstance(post_choice, importer.action):
|
||||
# MANUAL and MANUAL_ID have no effect, even if returned.
|
||||
return post_choice
|
||||
|
||||
else:
|
||||
# Chose a candidate.
|
||||
assert isinstance(choice, autotag.TrackMatch)
|
||||
|
|
|
|||
Loading…
Reference in a new issue