diff --git a/beets/importer.py b/beets/importer.py index cc81f6ddb..4d623e5ca 100644 --- a/beets/importer.py +++ b/beets/importer.py @@ -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)) diff --git a/beetsplug/chroma.py b/beetsplug/chroma.py index 3a71d4727..bf765001c 100644 --- a/beetsplug/chroma.py +++ b/beetsplug/chroma.py @@ -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] diff --git a/docs/changelog.rst b/docs/changelog.rst index 10df4931b..b505cfca2 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -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) ----------------------- diff --git a/docs/plugins/chroma.rst b/docs/plugins/chroma.rst index beb97edb4..347a5963f 100644 --- a/docs/plugins/chroma.rst +++ b/docs/plugins/chroma.rst @@ -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 diff --git a/docs/plugins/writing.rst b/docs/plugins/writing.rst index 327c7ce5e..ba11ed322 100644 --- a/docs/plugins/writing.rst +++ b/docs/plugins/writing.rst @@ -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