We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Allow on-demand only acoustid fingerprinting
It would be great to be able to have the chroma plugin activated, but only for the explicit `submit` use case, so that you don't have to keep enabling and disabling it to avoid the dramatic slowdown when doing imports.

Some kind of an option like:

``` yaml
acoustid:
  auto: no
```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `beetsplug/chroma.py`
Content:
```
1 # This file is part of beets.
2 # Copyright 2013, Adrian Sampson.
3 #
4 # Permission is hereby granted, free of charge, to any person obtaining
5 # a copy of this software and associated documentation files (the
6 # "Software"), to deal in the Software without restriction, including
7 # without limitation the rights to use, copy, modify, merge, publish,
8 # distribute, sublicense, and/or sell copies of the Software, and to
9 # permit persons to whom the Software is furnished to do so, subject to
10 # the following conditions:
11 #
12 # The above copyright notice and this permission notice shall be
13 # included in all copies or substantial portions of the Software.
14 
15 """Adds Chromaprint/Acoustid acoustic fingerprinting support to the
16 autotagger. Requires the pyacoustid library.
17 """
18 from beets import plugins
19 from beets import ui
20 from beets import util
21 from beets import config
22 from beets.util import confit
23 from beets.autotag import hooks
24 import acoustid
25 import logging
26 from collections import defaultdict
27 
28 API_KEY = '1vOwZtEn'
29 SCORE_THRESH = 0.5
30 TRACK_ID_WEIGHT = 10.0
31 COMMON_REL_THRESH = 0.6  # How many tracks must have an album in common?
32 
33 log = logging.getLogger('beets')
34 
35 # Stores the Acoustid match information for each track. This is
36 # populated when an import task begins and then used when searching for
37 # candidates. It maps audio file paths to (recording_ids, release_ids)
38 # pairs. If a given path is not present in the mapping, then no match
39 # was found.
40 _matches = {}
41 
42 # Stores the fingerprint and Acoustid ID for each track. This is stored
43 # as metadata for each track for later use but is not relevant for
44 # autotagging.
45 _fingerprints = {}
46 _acoustids = {}
47 
48 
49 def acoustid_match(path):
50     """Gets metadata for a file from Acoustid and populates the
51     _matches, _fingerprints, and _acoustids dictionaries accordingly.
52     """
53     try:
54         duration, fp = acoustid.fingerprint_file(util.syspath(path))
55     except acoustid.FingerprintGenerationError as exc:
56         log.error('fingerprinting of %s failed: %s' %
57                   (repr(path), str(exc)))
58         return None
59     _fingerprints[path] = fp
60     try:
61         res = acoustid.lookup(API_KEY, fp, duration,
62                               meta='recordings releases')
63     except acoustid.AcoustidError as exc:
64         log.debug('fingerprint matching %s failed: %s' %
65                   (repr(path), str(exc)))
66         return None
67     log.debug('chroma: fingerprinted %s' % repr(path))
68 
69     # Ensure the response is usable and parse it.
70     if res['status'] != 'ok' or not res.get('results'):
71         log.debug('chroma: no match found')
72         return None
73     result = res['results'][0]  # Best match.
74     if result['score'] < SCORE_THRESH:
75         log.debug('chroma: no results above threshold')
76         return None
77     _acoustids[path] = result['id']
78 
79     # Get recording and releases from the result.
80     if not result.get('recordings'):
81         log.debug('chroma: no recordings found')
82         return None
83     recording_ids = []
84     release_ids = []
85     for recording in result['recordings']:
86         recording_ids.append(recording['id'])
87         if 'releases' in recording:
88             release_ids += [rel['id'] for rel in recording['releases']]
89 
90     log.debug('chroma: matched recordings {0}'.format(recording_ids))
91     _matches[path] = recording_ids, release_ids
92 
93 
94 # Plugin structure and autotagging logic.
95 
96 
97 def _all_releases(items):
98     """Given an iterable of Items, determines (according to Acoustid)
99     which releases the items have in common. Generates release IDs.
100     """
101     # Count the number of "hits" for each release.
102     relcounts = defaultdict(int)
103     for item in items:
104         if item.path not in _matches:
105             continue
106 
107         _, release_ids = _matches[item.path]
108         for release_id in release_ids:
109             relcounts[release_id] += 1
110 
111     for release_id, count in relcounts.iteritems():
112         if float(count) / len(items) > COMMON_REL_THRESH:
113             yield release_id
114 
115 
116 class AcoustidPlugin(plugins.BeetsPlugin):
117     def track_distance(self, item, info):
118         dist = hooks.Distance()
119         if item.path not in _matches or not info.track_id:
120             # Match failed or no track ID.
121             return dist
122 
123         recording_ids, _ = _matches[item.path]
124         dist.add_expr('track_id', info.track_id not in recording_ids)
125         return dist
126 
127     def candidates(self, items, artist, album, va_likely):
128         albums = []
129         for relid in _all_releases(items):
130             album = hooks.album_for_mbid(relid)
131             if album:
132                 albums.append(album)
133 
134         log.debug('acoustid album candidates: %i' % len(albums))
135         return albums
136 
137     def item_candidates(self, item, artist, title):
138         if item.path not in _matches:
139             return []
140 
141         recording_ids, _ = _matches[item.path]
142         tracks = []
143         for recording_id in recording_ids:
144             track = hooks.track_for_mbid(recording_id)
145             if track:
146                 tracks.append(track)
147         log.debug('acoustid item candidates: {0}'.format(len(tracks)))
148         return tracks
149 
150     def commands(self):
151         submit_cmd = ui.Subcommand('submit',
152                                    help='submit Acoustid fingerprints')
153 
154         def submit_cmd_func(lib, opts, args):
155             try:
156                 apikey = config['acoustid']['apikey'].get(unicode)
157             except confit.NotFoundError:
158                 raise ui.UserError('no Acoustid user API key provided')
159             submit_items(apikey, lib.items(ui.decargs(args)))
160         submit_cmd.func = submit_cmd_func
161 
162         fingerprint_cmd = ui.Subcommand(
163             'fingerprint',
164             help='generate fingerprints for items without them'
165         )
166 
167         def fingerprint_cmd_func(lib, opts, args):
168             for item in lib.items(ui.decargs(args)):
169                 fingerprint_item(item,
170                                  write=config['import']['write'].get(bool))
171         fingerprint_cmd.func = fingerprint_cmd_func
172 
173         return [submit_cmd, fingerprint_cmd]
174 
175 
176 # Hooks into import process.
177 
178 
179 @AcoustidPlugin.listen('import_task_start')
180 def fingerprint_task(task, session):
181     """Fingerprint each item in the task for later use during the
182     autotagging candidate search.
183     """
184     items = task.items if task.is_album else [task.item]
185     for item in items:
186         acoustid_match(item.path)
187 
188 
189 @AcoustidPlugin.listen('import_task_apply')
190 def apply_acoustid_metadata(task, session):
191     """Apply Acoustid metadata (fingerprint and ID) to the task's items.
192     """
193     for item in task.imported_items():
194         if item.path in _fingerprints:
195             item.acoustid_fingerprint = _fingerprints[item.path]
196         if item.path in _acoustids:
197             item.acoustid_id = _acoustids[item.path]
198 
199 
200 # UI commands.
201 
202 
203 def submit_items(userkey, items, chunksize=64):
204     """Submit fingerprints for the items to the Acoustid server.
205     """
206     data = []  # The running list of dictionaries to submit.
207 
208     def submit_chunk():
209         """Submit the current accumulated fingerprint data."""
210         log.info('submitting {0} fingerprints'.format(len(data)))
211         try:
212             acoustid.submit(API_KEY, userkey, data)
213         except acoustid.AcoustidError as exc:
214             log.warn(u'acoustid submission error: {0}'.format(exc))
215         del data[:]
216 
217     for item in items:
218         fp = fingerprint_item(item)
219 
220         # Construct a submission dictionary for this item.
221         item_data = {
222             'duration': int(item.length),
223             'fingerprint': fp,
224         }
225         if item.mb_trackid:
226             item_data['mbid'] = item.mb_trackid
227             log.debug('submitting MBID')
228         else:
229             item_data.update({
230                 'track': item.title,
231                 'artist': item.artist,
232                 'album': item.album,
233                 'albumartist': item.albumartist,
234                 'year': item.year,
235                 'trackno': item.track,
236                 'discno': item.disc,
237             })
238             log.debug('submitting textual metadata')
239         data.append(item_data)
240 
241         # If we have enough data, submit a chunk.
242         if len(data) >= chunksize:
243             submit_chunk()
244 
245     # Submit remaining data in a final chunk.
246     if data:
247         submit_chunk()
248 
249 
250 def fingerprint_item(item, write=False):
251     """Get the fingerprint for an Item. If the item already has a
252     fingerprint, it is not regenerated. If fingerprint generation fails,
253     return None. If the items are associated with a library, they are
254     saved to the database. If `write` is set, then the new fingerprints
255     are also written to files' metadata.
256     """
257     # Get a fingerprint and length for this track.
258     if not item.length:
259         log.info(u'{0}: no duration available'.format(
260             util.displayable_path(item.path)
261         ))
262     elif item.acoustid_fingerprint:
263         if write:
264             log.info(u'{0}: fingerprint exists, skipping'.format(
265                 util.displayable_path(item.path)
266             ))
267         else:
268             log.info(u'{0}: using existing fingerprint'.format(
269                 util.displayable_path(item.path)
270             ))
271             return item.acoustid_fingerprint
272     else:
273         log.info(u'{0}: fingerprinting'.format(
274             util.displayable_path(item.path)
275         ))
276         try:
277             _, fp = acoustid.fingerprint_file(item.path)
278             item.acoustid_fingerprint = fp
279             if write:
280                 log.info(u'{0}: writing fingerprint'.format(
281                     util.displayable_path(item.path)
282                 ))
283                 item.try_write()
284             if item._db:
285                 item.store()
286             return item.acoustid_fingerprint
287         except acoustid.FingerprintGenerationError as exc:
288             log.info(
289                 'fingerprint generation failed: {0}'.format(exc)
290             )
291
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/beetsplug/chroma.py b/beetsplug/chroma.py
--- a/beetsplug/chroma.py
+++ b/beetsplug/chroma.py
@@ -181,9 +181,11 @@
     """Fingerprint each item in the task for later use during the
     autotagging candidate search.
     """
-    items = task.items if task.is_album else [task.item]
-    for item in items:
-        acoustid_match(item.path)
+    auto = config['acoustid']['auto']
+    if auto:
+        items = task.items if task.is_album else [task.item]
+        for item in items:
+            acoustid_match(item.path)
 
 
 @AcoustidPlugin.listen('import_task_apply')

{"golden_diff": "diff --git a/beetsplug/chroma.py b/beetsplug/chroma.py\n--- a/beetsplug/chroma.py\n+++ b/beetsplug/chroma.py\n@@ -181,9 +181,11 @@\n     \"\"\"Fingerprint each item in the task for later use during the\n     autotagging candidate search.\n     \"\"\"\n-    items = task.items if task.is_album else [task.item]\n-    for item in items:\n-        acoustid_match(item.path)\n+    auto = config['acoustid']['auto']\n+    if auto:\n+        items = task.items if task.is_album else [task.item]\n+        for item in items:\n+            acoustid_match(item.path)\n \n \n @AcoustidPlugin.listen('import_task_apply')\n", "issue": "Allow on-demand only acoustid fingerprinting\nIt would be great to be able to have the chroma plugin activated, but only for the explicit `submit` use case, so that you don't have to keep enabling and disabling it to avoid the dramatic slowdown when doing imports.\n\nSome kind of an option like:\n\n``` yaml\nacoustid:\n  auto: no\n```\n\n", "before_files": [{"content": "# This file is part of beets.\n# Copyright 2013, Adrian Sampson.\n#\n# Permission is hereby granted, free of charge, to any person obtaining\n# a copy of this software and associated documentation files (the\n# \"Software\"), to deal in the Software without restriction, including\n# without limitation the rights to use, copy, modify, merge, publish,\n# distribute, sublicense, and/or sell copies of the Software, and to\n# permit persons to whom the Software is furnished to do so, subject to\n# the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n\n\"\"\"Adds Chromaprint/Acoustid acoustic fingerprinting support to the\nautotagger. Requires the pyacoustid library.\n\"\"\"\nfrom beets import plugins\nfrom beets import ui\nfrom beets import util\nfrom beets import config\nfrom beets.util import confit\nfrom beets.autotag import hooks\nimport acoustid\nimport logging\nfrom collections import defaultdict\n\nAPI_KEY = '1vOwZtEn'\nSCORE_THRESH = 0.5\nTRACK_ID_WEIGHT = 10.0\nCOMMON_REL_THRESH = 0.6  # How many tracks must have an album in common?\n\nlog = logging.getLogger('beets')\n\n# Stores the Acoustid match information for each track. This is\n# populated when an import task begins and then used when searching for\n# candidates. It maps audio file paths to (recording_ids, release_ids)\n# pairs. If a given path is not present in the mapping, then no match\n# was found.\n_matches = {}\n\n# Stores the fingerprint and Acoustid ID for each track. This is stored\n# as metadata for each track for later use but is not relevant for\n# autotagging.\n_fingerprints = {}\n_acoustids = {}\n\n\ndef acoustid_match(path):\n    \"\"\"Gets metadata for a file from Acoustid and populates the\n    _matches, _fingerprints, and _acoustids dictionaries accordingly.\n    \"\"\"\n    try:\n        duration, fp = acoustid.fingerprint_file(util.syspath(path))\n    except acoustid.FingerprintGenerationError as exc:\n        log.error('fingerprinting of %s failed: %s' %\n                  (repr(path), str(exc)))\n        return None\n    _fingerprints[path] = fp\n    try:\n        res = acoustid.lookup(API_KEY, fp, duration,\n                              meta='recordings releases')\n    except acoustid.AcoustidError as exc:\n        log.debug('fingerprint matching %s failed: %s' %\n                  (repr(path), str(exc)))\n        return None\n    log.debug('chroma: fingerprinted %s' % repr(path))\n\n    # Ensure the response is usable and parse it.\n    if res['status'] != 'ok' or not res.get('results'):\n        log.debug('chroma: no match found')\n        return None\n    result = res['results'][0]  # Best match.\n    if result['score'] < SCORE_THRESH:\n        log.debug('chroma: no results above threshold')\n        return None\n    _acoustids[path] = result['id']\n\n    # Get recording and releases from the result.\n    if not result.get('recordings'):\n        log.debug('chroma: no recordings found')\n        return None\n    recording_ids = []\n    release_ids = []\n    for recording in result['recordings']:\n        recording_ids.append(recording['id'])\n        if 'releases' in recording:\n            release_ids += [rel['id'] for rel in recording['releases']]\n\n    log.debug('chroma: matched recordings {0}'.format(recording_ids))\n    _matches[path] = recording_ids, release_ids\n\n\n# Plugin structure and autotagging logic.\n\n\ndef _all_releases(items):\n    \"\"\"Given an iterable of Items, determines (according to Acoustid)\n    which releases the items have in common. Generates release IDs.\n    \"\"\"\n    # Count the number of \"hits\" for each release.\n    relcounts = defaultdict(int)\n    for item in items:\n        if item.path not in _matches:\n            continue\n\n        _, release_ids = _matches[item.path]\n        for release_id in release_ids:\n            relcounts[release_id] += 1\n\n    for release_id, count in relcounts.iteritems():\n        if float(count) / len(items) > COMMON_REL_THRESH:\n            yield release_id\n\n\nclass AcoustidPlugin(plugins.BeetsPlugin):\n    def track_distance(self, item, info):\n        dist = hooks.Distance()\n        if item.path not in _matches or not info.track_id:\n            # Match failed or no track ID.\n            return dist\n\n        recording_ids, _ = _matches[item.path]\n        dist.add_expr('track_id', info.track_id not in recording_ids)\n        return dist\n\n    def candidates(self, items, artist, album, va_likely):\n        albums = []\n        for relid in _all_releases(items):\n            album = hooks.album_for_mbid(relid)\n            if album:\n                albums.append(album)\n\n        log.debug('acoustid album candidates: %i' % len(albums))\n        return albums\n\n    def item_candidates(self, item, artist, title):\n        if item.path not in _matches:\n            return []\n\n        recording_ids, _ = _matches[item.path]\n        tracks = []\n        for recording_id in recording_ids:\n            track = hooks.track_for_mbid(recording_id)\n            if track:\n                tracks.append(track)\n        log.debug('acoustid item candidates: {0}'.format(len(tracks)))\n        return tracks\n\n    def commands(self):\n        submit_cmd = ui.Subcommand('submit',\n                                   help='submit Acoustid fingerprints')\n\n        def submit_cmd_func(lib, opts, args):\n            try:\n                apikey = config['acoustid']['apikey'].get(unicode)\n            except confit.NotFoundError:\n                raise ui.UserError('no Acoustid user API key provided')\n            submit_items(apikey, lib.items(ui.decargs(args)))\n        submit_cmd.func = submit_cmd_func\n\n        fingerprint_cmd = ui.Subcommand(\n            'fingerprint',\n            help='generate fingerprints for items without them'\n        )\n\n        def fingerprint_cmd_func(lib, opts, args):\n            for item in lib.items(ui.decargs(args)):\n                fingerprint_item(item,\n                                 write=config['import']['write'].get(bool))\n        fingerprint_cmd.func = fingerprint_cmd_func\n\n        return [submit_cmd, fingerprint_cmd]\n\n\n# Hooks into import process.\n\n\n@AcoustidPlugin.listen('import_task_start')\ndef fingerprint_task(task, session):\n    \"\"\"Fingerprint each item in the task for later use during the\n    autotagging candidate search.\n    \"\"\"\n    items = task.items if task.is_album else [task.item]\n    for item in items:\n        acoustid_match(item.path)\n\n\n@AcoustidPlugin.listen('import_task_apply')\ndef apply_acoustid_metadata(task, session):\n    \"\"\"Apply Acoustid metadata (fingerprint and ID) to the task's items.\n    \"\"\"\n    for item in task.imported_items():\n        if item.path in _fingerprints:\n            item.acoustid_fingerprint = _fingerprints[item.path]\n        if item.path in _acoustids:\n            item.acoustid_id = _acoustids[item.path]\n\n\n# UI commands.\n\n\ndef submit_items(userkey, items, chunksize=64):\n    \"\"\"Submit fingerprints for the items to the Acoustid server.\n    \"\"\"\n    data = []  # The running list of dictionaries to submit.\n\n    def submit_chunk():\n        \"\"\"Submit the current accumulated fingerprint data.\"\"\"\n        log.info('submitting {0} fingerprints'.format(len(data)))\n        try:\n            acoustid.submit(API_KEY, userkey, data)\n        except acoustid.AcoustidError as exc:\n            log.warn(u'acoustid submission error: {0}'.format(exc))\n        del data[:]\n\n    for item in items:\n        fp = fingerprint_item(item)\n\n        # Construct a submission dictionary for this item.\n        item_data = {\n            'duration': int(item.length),\n            'fingerprint': fp,\n        }\n        if item.mb_trackid:\n            item_data['mbid'] = item.mb_trackid\n            log.debug('submitting MBID')\n        else:\n            item_data.update({\n                'track': item.title,\n                'artist': item.artist,\n                'album': item.album,\n                'albumartist': item.albumartist,\n                'year': item.year,\n                'trackno': item.track,\n                'discno': item.disc,\n            })\n            log.debug('submitting textual metadata')\n        data.append(item_data)\n\n        # If we have enough data, submit a chunk.\n        if len(data) >= chunksize:\n            submit_chunk()\n\n    # Submit remaining data in a final chunk.\n    if data:\n        submit_chunk()\n\n\ndef fingerprint_item(item, write=False):\n    \"\"\"Get the fingerprint for an Item. If the item already has a\n    fingerprint, it is not regenerated. If fingerprint generation fails,\n    return None. If the items are associated with a library, they are\n    saved to the database. If `write` is set, then the new fingerprints\n    are also written to files' metadata.\n    \"\"\"\n    # Get a fingerprint and length for this track.\n    if not item.length:\n        log.info(u'{0}: no duration available'.format(\n            util.displayable_path(item.path)\n        ))\n    elif item.acoustid_fingerprint:\n        if write:\n            log.info(u'{0}: fingerprint exists, skipping'.format(\n                util.displayable_path(item.path)\n            ))\n        else:\n            log.info(u'{0}: using existing fingerprint'.format(\n                util.displayable_path(item.path)\n            ))\n            return item.acoustid_fingerprint\n    else:\n        log.info(u'{0}: fingerprinting'.format(\n            util.displayable_path(item.path)\n        ))\n        try:\n            _, fp = acoustid.fingerprint_file(item.path)\n            item.acoustid_fingerprint = fp\n            if write:\n                log.info(u'{0}: writing fingerprint'.format(\n                    util.displayable_path(item.path)\n                ))\n                item.try_write()\n            if item._db:\n                item.store()\n            return item.acoustid_fingerprint\n        except acoustid.FingerprintGenerationError as exc:\n            log.info(\n                'fingerprint generation failed: {0}'.format(exc)\n            )\n", "path": "beetsplug/chroma.py"}], "after_files": [{"content": "# This file is part of beets.\n# Copyright 2013, Adrian Sampson.\n#\n# Permission is hereby granted, free of charge, to any person obtaining\n# a copy of this software and associated documentation files (the\n# \"Software\"), to deal in the Software without restriction, including\n# without limitation the rights to use, copy, modify, merge, publish,\n# distribute, sublicense, and/or sell copies of the Software, and to\n# permit persons to whom the Software is furnished to do so, subject to\n# the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n\n\"\"\"Adds Chromaprint/Acoustid acoustic fingerprinting support to the\nautotagger. Requires the pyacoustid library.\n\"\"\"\nfrom beets import plugins\nfrom beets import ui\nfrom beets import util\nfrom beets import config\nfrom beets.util import confit\nfrom beets.autotag import hooks\nimport acoustid\nimport logging\nfrom collections import defaultdict\n\nAPI_KEY = '1vOwZtEn'\nSCORE_THRESH = 0.5\nTRACK_ID_WEIGHT = 10.0\nCOMMON_REL_THRESH = 0.6  # How many tracks must have an album in common?\n\nlog = logging.getLogger('beets')\n\n# Stores the Acoustid match information for each track. This is\n# populated when an import task begins and then used when searching for\n# candidates. It maps audio file paths to (recording_ids, release_ids)\n# pairs. If a given path is not present in the mapping, then no match\n# was found.\n_matches = {}\n\n# Stores the fingerprint and Acoustid ID for each track. This is stored\n# as metadata for each track for later use but is not relevant for\n# autotagging.\n_fingerprints = {}\n_acoustids = {}\n\n\ndef acoustid_match(path):\n    \"\"\"Gets metadata for a file from Acoustid and populates the\n    _matches, _fingerprints, and _acoustids dictionaries accordingly.\n    \"\"\"\n    try:\n        duration, fp = acoustid.fingerprint_file(util.syspath(path))\n    except acoustid.FingerprintGenerationError as exc:\n        log.error('fingerprinting of %s failed: %s' %\n                  (repr(path), str(exc)))\n        return None\n    _fingerprints[path] = fp\n    try:\n        res = acoustid.lookup(API_KEY, fp, duration,\n                              meta='recordings releases')\n    except acoustid.AcoustidError as exc:\n        log.debug('fingerprint matching %s failed: %s' %\n                  (repr(path), str(exc)))\n        return None\n    log.debug('chroma: fingerprinted %s' % repr(path))\n\n    # Ensure the response is usable and parse it.\n    if res['status'] != 'ok' or not res.get('results'):\n        log.debug('chroma: no match found')\n        return None\n    result = res['results'][0]  # Best match.\n    if result['score'] < SCORE_THRESH:\n        log.debug('chroma: no results above threshold')\n        return None\n    _acoustids[path] = result['id']\n\n    # Get recording and releases from the result.\n    if not result.get('recordings'):\n        log.debug('chroma: no recordings found')\n        return None\n    recording_ids = []\n    release_ids = []\n    for recording in result['recordings']:\n        recording_ids.append(recording['id'])\n        if 'releases' in recording:\n            release_ids += [rel['id'] for rel in recording['releases']]\n\n    log.debug('chroma: matched recordings {0}'.format(recording_ids))\n    _matches[path] = recording_ids, release_ids\n\n\n# Plugin structure and autotagging logic.\n\n\ndef _all_releases(items):\n    \"\"\"Given an iterable of Items, determines (according to Acoustid)\n    which releases the items have in common. Generates release IDs.\n    \"\"\"\n    # Count the number of \"hits\" for each release.\n    relcounts = defaultdict(int)\n    for item in items:\n        if item.path not in _matches:\n            continue\n\n        _, release_ids = _matches[item.path]\n        for release_id in release_ids:\n            relcounts[release_id] += 1\n\n    for release_id, count in relcounts.iteritems():\n        if float(count) / len(items) > COMMON_REL_THRESH:\n            yield release_id\n\n\nclass AcoustidPlugin(plugins.BeetsPlugin):\n    def track_distance(self, item, info):\n        dist = hooks.Distance()\n        if item.path not in _matches or not info.track_id:\n            # Match failed or no track ID.\n            return dist\n\n        recording_ids, _ = _matches[item.path]\n        dist.add_expr('track_id', info.track_id not in recording_ids)\n        return dist\n\n    def candidates(self, items, artist, album, va_likely):\n        albums = []\n        for relid in _all_releases(items):\n            album = hooks.album_for_mbid(relid)\n            if album:\n                albums.append(album)\n\n        log.debug('acoustid album candidates: %i' % len(albums))\n        return albums\n\n    def item_candidates(self, item, artist, title):\n        if item.path not in _matches:\n            return []\n\n        recording_ids, _ = _matches[item.path]\n        tracks = []\n        for recording_id in recording_ids:\n            track = hooks.track_for_mbid(recording_id)\n            if track:\n                tracks.append(track)\n        log.debug('acoustid item candidates: {0}'.format(len(tracks)))\n        return tracks\n\n    def commands(self):\n        submit_cmd = ui.Subcommand('submit',\n                                   help='submit Acoustid fingerprints')\n\n        def submit_cmd_func(lib, opts, args):\n            try:\n                apikey = config['acoustid']['apikey'].get(unicode)\n            except confit.NotFoundError:\n                raise ui.UserError('no Acoustid user API key provided')\n            submit_items(apikey, lib.items(ui.decargs(args)))\n        submit_cmd.func = submit_cmd_func\n\n        fingerprint_cmd = ui.Subcommand(\n            'fingerprint',\n            help='generate fingerprints for items without them'\n        )\n\n        def fingerprint_cmd_func(lib, opts, args):\n            for item in lib.items(ui.decargs(args)):\n                fingerprint_item(item,\n                                 write=config['import']['write'].get(bool))\n        fingerprint_cmd.func = fingerprint_cmd_func\n\n        return [submit_cmd, fingerprint_cmd]\n\n\n# Hooks into import process.\n\n\n@AcoustidPlugin.listen('import_task_start')\ndef fingerprint_task(task, session):\n    \"\"\"Fingerprint each item in the task for later use during the\n    autotagging candidate search.\n    \"\"\"\n    auto = config['acoustid']['auto']\n    if auto:\n        items = task.items if task.is_album else [task.item]\n        for item in items:\n            acoustid_match(item.path)\n\n\n@AcoustidPlugin.listen('import_task_apply')\ndef apply_acoustid_metadata(task, session):\n    \"\"\"Apply Acoustid metadata (fingerprint and ID) to the task's items.\n    \"\"\"\n    for item in task.imported_items():\n        if item.path in _fingerprints:\n            item.acoustid_fingerprint = _fingerprints[item.path]\n        if item.path in _acoustids:\n            item.acoustid_id = _acoustids[item.path]\n\n\n# UI commands.\n\n\ndef submit_items(userkey, items, chunksize=64):\n    \"\"\"Submit fingerprints for the items to the Acoustid server.\n    \"\"\"\n    data = []  # The running list of dictionaries to submit.\n\n    def submit_chunk():\n        \"\"\"Submit the current accumulated fingerprint data.\"\"\"\n        log.info('submitting {0} fingerprints'.format(len(data)))\n        try:\n            acoustid.submit(API_KEY, userkey, data)\n        except acoustid.AcoustidError as exc:\n            log.warn(u'acoustid submission error: {0}'.format(exc))\n        del data[:]\n\n    for item in items:\n        fp = fingerprint_item(item)\n\n        # Construct a submission dictionary for this item.\n        item_data = {\n            'duration': int(item.length),\n            'fingerprint': fp,\n        }\n        if item.mb_trackid:\n            item_data['mbid'] = item.mb_trackid\n            log.debug('submitting MBID')\n        else:\n            item_data.update({\n                'track': item.title,\n                'artist': item.artist,\n                'album': item.album,\n                'albumartist': item.albumartist,\n                'year': item.year,\n                'trackno': item.track,\n                'discno': item.disc,\n            })\n            log.debug('submitting textual metadata')\n        data.append(item_data)\n\n        # If we have enough data, submit a chunk.\n        if len(data) >= chunksize:\n            submit_chunk()\n\n    # Submit remaining data in a final chunk.\n    if data:\n        submit_chunk()\n\n\ndef fingerprint_item(item, write=False):\n    \"\"\"Get the fingerprint for an Item. If the item already has a\n    fingerprint, it is not regenerated. If fingerprint generation fails,\n    return None. If the items are associated with a library, they are\n    saved to the database. If `write` is set, then the new fingerprints\n    are also written to files' metadata.\n    \"\"\"\n    # Get a fingerprint and length for this track.\n    if not item.length:\n        log.info(u'{0}: no duration available'.format(\n            util.displayable_path(item.path)\n        ))\n    elif item.acoustid_fingerprint:\n        if write:\n            log.info(u'{0}: fingerprint exists, skipping'.format(\n                util.displayable_path(item.path)\n            ))\n        else:\n            log.info(u'{0}: using existing fingerprint'.format(\n                util.displayable_path(item.path)\n            ))\n            return item.acoustid_fingerprint\n    else:\n        log.info(u'{0}: fingerprinting'.format(\n            util.displayable_path(item.path)\n        ))\n        try:\n            _, fp = acoustid.fingerprint_file(item.path)\n            item.acoustid_fingerprint = fp\n            if write:\n                log.info(u'{0}: writing fingerprint'.format(\n                    util.displayable_path(item.path)\n                ))\n                item.try_write()\n            if item._db:\n                item.store()\n            return item.acoustid_fingerprint\n        except acoustid.FingerprintGenerationError as exc:\n            log.info(\n                'fingerprint generation failed: {0}'.format(exc)\n            )\n", "path": "beetsplug/chroma.py"}]}