mirror of
https://github.com/rembo10/headphones.git
synced 2026-07-20 16:03:59 +01:00
update Beets
This commit is contained in:
+105
-94
@@ -16,18 +16,17 @@
|
||||
autotagger. Requires the pyacoustid library.
|
||||
"""
|
||||
|
||||
from beets import plugins
|
||||
from beets import ui
|
||||
from beets import util
|
||||
from beets import config
|
||||
from beets.autotag import hooks
|
||||
import confuse
|
||||
import acoustid
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from functools import partial
|
||||
import re
|
||||
|
||||
API_KEY = '1vOwZtEn'
|
||||
import acoustid
|
||||
import confuse
|
||||
|
||||
from beets import config, plugins, ui, util
|
||||
from beets.autotag import hooks
|
||||
|
||||
API_KEY = "1vOwZtEn"
|
||||
SCORE_THRESH = 0.5
|
||||
TRACK_ID_WEIGHT = 10.0
|
||||
COMMON_REL_THRESH = 0.6 # How many tracks must have an album in common?
|
||||
@@ -49,8 +48,7 @@ _acoustids = {}
|
||||
|
||||
|
||||
def prefix(it, count):
|
||||
"""Truncate an iterable to at most `count` items.
|
||||
"""
|
||||
"""Truncate an iterable to at most `count` items."""
|
||||
for i, v in enumerate(it):
|
||||
if i >= count:
|
||||
break
|
||||
@@ -58,13 +56,12 @@ def prefix(it, count):
|
||||
|
||||
|
||||
def releases_key(release, countries, original_year):
|
||||
"""Used as a key to sort releases by date then preferred country
|
||||
"""
|
||||
date = release.get('date')
|
||||
"""Used as a key to sort releases by date then preferred country"""
|
||||
date = release.get("date")
|
||||
if date and original_year:
|
||||
year = date.get('year', 9999)
|
||||
month = date.get('month', 99)
|
||||
day = date.get('day', 99)
|
||||
year = date.get("year", 9999)
|
||||
month = date.get("month", 99)
|
||||
day = date.get("day", 99)
|
||||
else:
|
||||
year = 9999
|
||||
month = 99
|
||||
@@ -72,9 +69,9 @@ def releases_key(release, countries, original_year):
|
||||
|
||||
# Uses index of preferred countries to sort
|
||||
country_key = 99
|
||||
if release.get('country'):
|
||||
if release.get("country"):
|
||||
for i, country in enumerate(countries):
|
||||
if country.match(release['country']):
|
||||
if country.match(release["country"]):
|
||||
country_key = i
|
||||
break
|
||||
|
||||
@@ -88,56 +85,63 @@ def acoustid_match(log, path):
|
||||
try:
|
||||
duration, fp = acoustid.fingerprint_file(util.syspath(path))
|
||||
except acoustid.FingerprintGenerationError as exc:
|
||||
log.error('fingerprinting of {0} failed: {1}',
|
||||
util.displayable_path(repr(path)), exc)
|
||||
log.error(
|
||||
"fingerprinting of {0} failed: {1}",
|
||||
util.displayable_path(repr(path)),
|
||||
exc,
|
||||
)
|
||||
return None
|
||||
fp = fp.decode()
|
||||
_fingerprints[path] = fp
|
||||
try:
|
||||
res = acoustid.lookup(API_KEY, fp, duration,
|
||||
meta='recordings releases')
|
||||
res = acoustid.lookup(API_KEY, fp, duration, meta="recordings releases")
|
||||
except acoustid.AcoustidError as exc:
|
||||
log.debug('fingerprint matching {0} failed: {1}',
|
||||
util.displayable_path(repr(path)), exc)
|
||||
log.debug(
|
||||
"fingerprint matching {0} failed: {1}",
|
||||
util.displayable_path(repr(path)),
|
||||
exc,
|
||||
)
|
||||
return None
|
||||
log.debug('chroma: fingerprinted {0}',
|
||||
util.displayable_path(repr(path)))
|
||||
log.debug("chroma: fingerprinted {0}", util.displayable_path(repr(path)))
|
||||
|
||||
# Ensure the response is usable and parse it.
|
||||
if res['status'] != 'ok' or not res.get('results'):
|
||||
log.debug('no match found')
|
||||
if res["status"] != "ok" or not res.get("results"):
|
||||
log.debug("no match found")
|
||||
return None
|
||||
result = res['results'][0] # Best match.
|
||||
if result['score'] < SCORE_THRESH:
|
||||
log.debug('no results above threshold')
|
||||
result = res["results"][0] # Best match.
|
||||
if result["score"] < SCORE_THRESH:
|
||||
log.debug("no results above threshold")
|
||||
return None
|
||||
_acoustids[path] = result['id']
|
||||
_acoustids[path] = result["id"]
|
||||
|
||||
# Get recording and releases from the result
|
||||
if not result.get('recordings'):
|
||||
log.debug('no recordings found')
|
||||
if not result.get("recordings"):
|
||||
log.debug("no recordings found")
|
||||
return None
|
||||
recording_ids = []
|
||||
releases = []
|
||||
for recording in result['recordings']:
|
||||
recording_ids.append(recording['id'])
|
||||
if 'releases' in recording:
|
||||
releases.extend(recording['releases'])
|
||||
for recording in result["recordings"]:
|
||||
recording_ids.append(recording["id"])
|
||||
if "releases" in recording:
|
||||
releases.extend(recording["releases"])
|
||||
|
||||
# The releases list is essentially in random order from the Acoustid lookup
|
||||
# so we optionally sort it using the match.preferred configuration options.
|
||||
# 'original_year' to sort the earliest first and
|
||||
# 'countries' to then sort preferred countries first.
|
||||
country_patterns = config['match']['preferred']['countries'].as_str_seq()
|
||||
country_patterns = config["match"]["preferred"]["countries"].as_str_seq()
|
||||
countries = [re.compile(pat, re.I) for pat in country_patterns]
|
||||
original_year = config['match']['preferred']['original_year']
|
||||
releases.sort(key=partial(releases_key,
|
||||
countries=countries,
|
||||
original_year=original_year))
|
||||
release_ids = [rel['id'] for rel in releases]
|
||||
original_year = config["match"]["preferred"]["original_year"]
|
||||
releases.sort(
|
||||
key=partial(
|
||||
releases_key, countries=countries, original_year=original_year
|
||||
)
|
||||
)
|
||||
release_ids = [rel["id"] for rel in releases]
|
||||
|
||||
log.debug('matched recordings {0} on releases {1}',
|
||||
recording_ids, release_ids)
|
||||
log.debug(
|
||||
"matched recordings {0} on releases {1}", recording_ids, release_ids
|
||||
)
|
||||
_matches[path] = recording_ids, release_ids
|
||||
|
||||
|
||||
@@ -167,14 +171,16 @@ class AcoustidPlugin(plugins.BeetsPlugin):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.config.add({
|
||||
'auto': True,
|
||||
})
|
||||
config['acoustid']['apikey'].redact = True
|
||||
self.config.add(
|
||||
{
|
||||
"auto": True,
|
||||
}
|
||||
)
|
||||
config["acoustid"]["apikey"].redact = True
|
||||
|
||||
if self.config['auto']:
|
||||
self.register_listener('import_task_start', self.fingerprint_task)
|
||||
self.register_listener('import_task_apply', apply_acoustid_metadata)
|
||||
if self.config["auto"]:
|
||||
self.register_listener("import_task_start", self.fingerprint_task)
|
||||
self.register_listener("import_task_apply", apply_acoustid_metadata)
|
||||
|
||||
def fingerprint_task(self, task, session):
|
||||
return fingerprint_task(self._log, task, session)
|
||||
@@ -186,7 +192,7 @@ class AcoustidPlugin(plugins.BeetsPlugin):
|
||||
return dist
|
||||
|
||||
recording_ids, _ = _matches[item.path]
|
||||
dist.add_expr('track_id', info.track_id not in recording_ids)
|
||||
dist.add_expr("track_id", info.track_id not in recording_ids)
|
||||
return dist
|
||||
|
||||
def candidates(self, items, artist, album, va_likely, extra_tags=None):
|
||||
@@ -196,7 +202,7 @@ class AcoustidPlugin(plugins.BeetsPlugin):
|
||||
if album:
|
||||
albums.append(album)
|
||||
|
||||
self._log.debug('acoustid album candidates: {0}', len(albums))
|
||||
self._log.debug("acoustid album candidates: {0}", len(albums))
|
||||
return albums
|
||||
|
||||
def item_candidates(self, item, artist, title):
|
||||
@@ -209,29 +215,31 @@ class AcoustidPlugin(plugins.BeetsPlugin):
|
||||
track = hooks.track_for_mbid(recording_id)
|
||||
if track:
|
||||
tracks.append(track)
|
||||
self._log.debug('acoustid item candidates: {0}', len(tracks))
|
||||
self._log.debug("acoustid item candidates: {0}", len(tracks))
|
||||
return tracks
|
||||
|
||||
def commands(self):
|
||||
submit_cmd = ui.Subcommand('submit',
|
||||
help='submit Acoustid fingerprints')
|
||||
submit_cmd = ui.Subcommand(
|
||||
"submit", help="submit Acoustid fingerprints"
|
||||
)
|
||||
|
||||
def submit_cmd_func(lib, opts, args):
|
||||
try:
|
||||
apikey = config['acoustid']['apikey'].as_str()
|
||||
apikey = config["acoustid"]["apikey"].as_str()
|
||||
except confuse.NotFoundError:
|
||||
raise ui.UserError('no Acoustid user API key provided')
|
||||
raise ui.UserError("no Acoustid user API key provided")
|
||||
submit_items(self._log, apikey, lib.items(ui.decargs(args)))
|
||||
|
||||
submit_cmd.func = submit_cmd_func
|
||||
|
||||
fingerprint_cmd = ui.Subcommand(
|
||||
'fingerprint',
|
||||
help='generate fingerprints for items without them'
|
||||
"fingerprint", help="generate fingerprints for items without them"
|
||||
)
|
||||
|
||||
def fingerprint_cmd_func(lib, opts, args):
|
||||
for item in lib.items(ui.decargs(args)):
|
||||
fingerprint_item(self._log, item, write=ui.should_write())
|
||||
|
||||
fingerprint_cmd.func = fingerprint_cmd_func
|
||||
|
||||
return [submit_cmd, fingerprint_cmd]
|
||||
@@ -250,8 +258,7 @@ def fingerprint_task(log, task, session):
|
||||
|
||||
|
||||
def apply_acoustid_metadata(task, session):
|
||||
"""Apply Acoustid metadata (fingerprint and ID) to the task's items.
|
||||
"""
|
||||
"""Apply Acoustid metadata (fingerprint and ID) to the task's items."""
|
||||
for item in task.imported_items():
|
||||
if item.path in _fingerprints:
|
||||
item.acoustid_fingerprint = _fingerprints[item.path]
|
||||
@@ -263,17 +270,16 @@ def apply_acoustid_metadata(task, session):
|
||||
|
||||
|
||||
def submit_items(log, userkey, items, chunksize=64):
|
||||
"""Submit fingerprints for the items to the Acoustid server.
|
||||
"""
|
||||
"""Submit fingerprints for the items to the Acoustid server."""
|
||||
data = [] # The running list of dictionaries to submit.
|
||||
|
||||
def submit_chunk():
|
||||
"""Submit the current accumulated fingerprint data."""
|
||||
log.info('submitting {0} fingerprints', len(data))
|
||||
log.info("submitting {0} fingerprints", len(data))
|
||||
try:
|
||||
acoustid.submit(API_KEY, userkey, data)
|
||||
except acoustid.AcoustidError as exc:
|
||||
log.warning('acoustid submission error: {0}', exc)
|
||||
log.warning("acoustid submission error: {0}", exc)
|
||||
del data[:]
|
||||
|
||||
for item in items:
|
||||
@@ -281,23 +287,25 @@ def submit_items(log, userkey, items, chunksize=64):
|
||||
|
||||
# Construct a submission dictionary for this item.
|
||||
item_data = {
|
||||
'duration': int(item.length),
|
||||
'fingerprint': fp,
|
||||
"duration": int(item.length),
|
||||
"fingerprint": fp,
|
||||
}
|
||||
if item.mb_trackid:
|
||||
item_data['mbid'] = item.mb_trackid
|
||||
log.debug('submitting MBID')
|
||||
item_data["mbid"] = item.mb_trackid
|
||||
log.debug("submitting MBID")
|
||||
else:
|
||||
item_data.update({
|
||||
'track': item.title,
|
||||
'artist': item.artist,
|
||||
'album': item.album,
|
||||
'albumartist': item.albumartist,
|
||||
'year': item.year,
|
||||
'trackno': item.track,
|
||||
'discno': item.disc,
|
||||
})
|
||||
log.debug('submitting textual metadata')
|
||||
item_data.update(
|
||||
{
|
||||
"track": item.title,
|
||||
"artist": item.artist,
|
||||
"album": item.album,
|
||||
"albumartist": item.albumartist,
|
||||
"year": item.year,
|
||||
"trackno": item.track,
|
||||
"discno": item.disc,
|
||||
}
|
||||
)
|
||||
log.debug("submitting textual metadata")
|
||||
data.append(item_data)
|
||||
|
||||
# If we have enough data, submit a chunk.
|
||||
@@ -318,28 +326,31 @@ def fingerprint_item(log, item, write=False):
|
||||
"""
|
||||
# Get a fingerprint and length for this track.
|
||||
if not item.length:
|
||||
log.info('{0}: no duration available',
|
||||
util.displayable_path(item.path))
|
||||
log.info("{0}: no duration available", util.displayable_path(item.path))
|
||||
elif item.acoustid_fingerprint:
|
||||
if write:
|
||||
log.info('{0}: fingerprint exists, skipping',
|
||||
util.displayable_path(item.path))
|
||||
log.info(
|
||||
"{0}: fingerprint exists, skipping",
|
||||
util.displayable_path(item.path),
|
||||
)
|
||||
else:
|
||||
log.info('{0}: using existing fingerprint',
|
||||
util.displayable_path(item.path))
|
||||
log.info(
|
||||
"{0}: using existing fingerprint",
|
||||
util.displayable_path(item.path),
|
||||
)
|
||||
return item.acoustid_fingerprint
|
||||
else:
|
||||
log.info('{0}: fingerprinting',
|
||||
util.displayable_path(item.path))
|
||||
log.info("{0}: fingerprinting", util.displayable_path(item.path))
|
||||
try:
|
||||
_, fp = acoustid.fingerprint_file(util.syspath(item.path))
|
||||
item.acoustid_fingerprint = fp.decode()
|
||||
if write:
|
||||
log.info('{0}: writing fingerprint',
|
||||
util.displayable_path(item.path))
|
||||
log.info(
|
||||
"{0}: writing fingerprint", util.displayable_path(item.path)
|
||||
)
|
||||
item.try_write()
|
||||
if item._db:
|
||||
item.store()
|
||||
return item.acoustid_fingerprint
|
||||
except acoustid.FingerprintGenerationError as exc:
|
||||
log.info('fingerprint generation failed: {0}', exc)
|
||||
log.info("fingerprint generation failed: {0}", exc)
|
||||
|
||||
Reference in New Issue
Block a user