update Beets

This commit is contained in:
AdeHub
2024-08-24 16:44:41 +12:00
parent a63098a919
commit 046d4d82b4
116 changed files with 17353 additions and 9964 deletions
+105 -94
View File
@@ -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)