store Acoustid data in DB & file

This is accomplished via a new event, "import_task_apply", which is called
right after metadata is applied to newly-imported items.

This change makes chroma REQUIRE a new version (0.6) of pyacoustid. Users with
older versions installed will see complaints about a missing method
"fingerprint_file".
This commit is contained in:
Adrian Sampson 2012-04-01 19:38:46 -07:00
parent 82a4bafc3e
commit 01dce53212
5 changed files with 48 additions and 16 deletions

View file

@ -557,7 +557,7 @@ def initial_lookup(config):
if task.sentinel:
continue
plugins.send('start_import_task', task=task, config=config)
plugins.send('import_task_start', task=task, config=config)
log.debug('Looking up: %s' % task.path)
try:
@ -653,6 +653,7 @@ def apply_choices(config):
autotag.apply_metadata(task.items, task.info)
else:
autotag.apply_item_metadata(task.item, task.info)
plugins.send('import_task_apply', config=config, task=task)
# Infer album-level fields.
if task.is_album:
@ -813,7 +814,7 @@ def item_lookup(config):
if task.sentinel:
continue
plugins.send('start_import_task', task=task, config=config)
plugins.send('import_task_start', task=task, config=config)
task.set_item_match(*autotag.tag_item(task.item, config.timid))

View file

@ -36,18 +36,27 @@ log = logging.getLogger('beets')
# was found.
_matches = {}
# Stores the fingerprint and Acoustid ID for each track. This is stored
# as metadata for each track for later use but is not relevant for
# autotagging.
_fingerprints = {}
_acoustids = {}
def acoustid_match(path):
"""Gets metadata for a file from Acoustid. Returns a recording ID
and a list of release IDs if a match is found; otherwise, returns
None.
"""Gets metadata for a file from Acoustid and populates the
_matches, _fingerprints, and _acoustids dictionaries accordingly.
"""
try:
res = acoustid.match(API_KEY, path, meta='recordings releases',
parse=False)
duration, fp = acoustid.fingerprint_file(path)
except acoustid.FingerprintGenerationError, exc:
log.error('fingerprinting of %s failed: %s' %
(repr(path), str(exc)))
return None
_fingerprints[path] = fp
try:
res = acoustid.lookup(API_KEY, fp, duration,
meta='recordings releases')
except acoustid.AcoustidError, exc:
log.debug('fingerprint matching %s failed: %s' %
(repr(path), str(exc)))
@ -59,8 +68,14 @@ def acoustid_match(path):
log.debug('chroma: no match found')
return None
result = res['results'][0]
if result['score'] < SCORE_THRESH or not result.get('recordings'):
log.debug('chroma: no recordings above threshold')
if result['score'] < SCORE_THRESH:
log.debug('chroma: no results above threshold')
return None
_acoustids[path] = result['id']
# Get recordings from the result.
if not result.get('recordings'):
log.debug('chroma: no recordings found')
return None
recording = result['recordings'][0]
recording_id = recording['id']
@ -70,7 +85,7 @@ def acoustid_match(path):
release_ids = []
log.debug('chroma: matched recording {}'.format(recording_id))
return recording_id, release_ids
_matches[path] = recording_id, release_ids
def _all_releases(items):
"""Given an iterable of Items, determines (according to Acoustid)
@ -126,12 +141,20 @@ class AcoustidPlugin(plugins.BeetsPlugin):
log.debug('no acoustid item candidate found')
return []
@AcoustidPlugin.listen('start_import_task')
@AcoustidPlugin.listen('import_task_start')
def fingerprint_task(config=None, task=None):
"""Fingerprint each item in the task for later use during the
autotagging candidate search.
"""
for item in task.all_items():
match = acoustid_match(item.path)
if match:
_matches[item.path] = match
acoustid_match(item.path)
@AcoustidPlugin.listen('import_task_apply')
def apply_acoustid_metadata(config=None, task=None):
"""Apply Acoustid metadata (fingerprint and ID) to the task's items.
"""
for item in task.all_items():
if item.path in _fingerprints:
item.acoustid_fingerprint = _fingerprints[item.path]
if item.path in _acoustids:
item.acoustid_id = _acoustids[item.path]

View file

@ -14,6 +14,9 @@ Changelog
fields, ``artist_sort`` and ``albumartist_sort``, that contain sortable artist
names like "Beatles, The". These fields are also used to sort albums and items
when using the ``list`` command. Thanks to Paul Provost.
* :doc:`/plugins/chroma`: The Chromaprint fingerprint and Acoustid ID are now
stored for all fingerprinted tracks. This version of beets *requires* at least
version 0.6 of `pyacoustid`_ for fingerprinting to work.
* New :doc:`/plugins/rdm`: Randomly select albums and tracks from your library.
Thanks to Philippe Mongeau.
* The :doc:`/plugins/mbcollection` by Jeffrey Aylesworth was added to the core
@ -27,6 +30,8 @@ Changelog
* Filenames are normalized with Unicode Normal Form D (NFD) on Mac OS X and NFC
on all other platforms.
.. _pyacoustid: https://github.com/sampsyo/pyacoustid
1.0b13 (March 16, 2012)
-----------------------

View file

@ -25,7 +25,7 @@ Installing Dependencies
To get fingerprinting working, you'll need to install three things: the
`Chromaprint`_ library or command-line tool, an audio decoder, and the
`pyacoustid`_ Python library.
`pyacoustid`_ Python library (version 0.6 or later).
First, you will need to install `Chromaprint`_, either as a dynamic library or
in the form of a command-line tool (``fpcalc``). The Chromaprint site has links

View file

@ -134,9 +134,12 @@ currently available are:
* *write*: called with an ``Item`` and a ``MediaFile`` object just before a
file's metadata is written to disk.
* *start_import_task*: called when before an import task begins processing.
* *import_task_start*: called when before an import task begins processing.
Parameters: ``task`` and ``config``.
* *import_task_apply*: called after metadata changes have been applied in an
import task. Parameters: ``task`` and ``config``.
The included ``mpdupdate`` plugin provides an example use case for event listeners.
Extend the Autotagger