feat: auto-discover similar tracks during autoplay via Last.fm/MusicBrainz
Every Nth autoplay pick (configurable via discover_ratio), query Last.fm for similar tracks. When Last.fm has no key or returns nothing, fall back to MusicBrainz tag-based recording search (no API key needed). Discovered tracks are resolved via yt-dlp and deduplicated within the session. If discovery fails, the kept-deck shuffle continues as before. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -58,6 +58,8 @@ def _ps(bot):
|
||||
"history": [],
|
||||
"autoplay": cfg.get("autoplay", True),
|
||||
"autoplay_cooldown": cfg.get("autoplay_cooldown", 30),
|
||||
"discover": cfg.get("discover", True),
|
||||
"discover_ratio": cfg.get("discover_ratio", 3),
|
||||
"announce": cfg.get("announce", False),
|
||||
"paused": None,
|
||||
"_watcher_task": None,
|
||||
@@ -575,17 +577,64 @@ async def _play_loop(bot, *, seek: float = 0.0, fade_in: float | bool = True) ->
|
||||
seek_req = [None]
|
||||
ps["seek_req"] = seek_req
|
||||
_autoplay_pool: list[_Track] = [] # shuffled deck, refilled each cycle
|
||||
_discover_seen: set[str] = set() # "artist:title" dedup within session
|
||||
_autoplay_count: int = 0 # autoplay picks since loop start
|
||||
try:
|
||||
while ps["queue"] or ps.get("autoplay"):
|
||||
# Autoplay: cooldown + silence wait, then pick next from shuffled deck
|
||||
if not ps["queue"]:
|
||||
if not _autoplay_pool:
|
||||
kept = _load_kept_tracks(bot)
|
||||
if not kept:
|
||||
break
|
||||
random.shuffle(kept)
|
||||
_autoplay_pool = kept
|
||||
log.info("music: autoplay shuffled %d kept tracks", len(kept))
|
||||
_autoplay_count += 1
|
||||
|
||||
# -- Discovery attempt on every Nth autoplay pick --
|
||||
discovered = False
|
||||
ratio = ps.get("discover_ratio", 3)
|
||||
if (ps.get("discover") and ratio > 0
|
||||
and _autoplay_count % ratio == 0
|
||||
and ps["history"]):
|
||||
last = ps["history"][-1]
|
||||
try:
|
||||
lfm = bot.registry._modules.get("lastfm")
|
||||
if lfm and hasattr(lfm, "discover_similar"):
|
||||
pair = await lfm.discover_similar(bot, last.title)
|
||||
if pair:
|
||||
a, t = pair
|
||||
key = f"{a.lower()}:{t.lower()}"
|
||||
if key not in _discover_seen:
|
||||
_discover_seen.add(key)
|
||||
loop = asyncio.get_running_loop()
|
||||
res = await loop.run_in_executor(
|
||||
None, _resolve_tracks,
|
||||
f"{a} {t}", 1,
|
||||
)
|
||||
if res:
|
||||
discovered = True
|
||||
pick = _Track(
|
||||
url=res[0][0], title=res[0][1],
|
||||
requester="discover",
|
||||
)
|
||||
log.info(
|
||||
"music: discovered '%s' "
|
||||
"similar to '%s'",
|
||||
pick.title, last.title,
|
||||
)
|
||||
except Exception:
|
||||
log.warning(
|
||||
"music: discovery failed, using kept deck",
|
||||
exc_info=True,
|
||||
)
|
||||
|
||||
# -- Kept-deck fallback --
|
||||
if not discovered:
|
||||
if not _autoplay_pool:
|
||||
kept = _load_kept_tracks(bot)
|
||||
if not kept:
|
||||
break
|
||||
random.shuffle(kept)
|
||||
_autoplay_pool = kept
|
||||
log.info("music: autoplay shuffled %d kept tracks",
|
||||
len(kept))
|
||||
pick = _autoplay_pool.pop(0)
|
||||
|
||||
cooldown = ps.get("autoplay_cooldown", 30)
|
||||
log.info("music: autoplay cooldown %ds before next track",
|
||||
cooldown)
|
||||
@@ -602,9 +651,8 @@ async def _play_loop(bot, *, seek: float = 0.0, fade_in: float | bool = True) ->
|
||||
# Re-check: someone may have queued something or stopped
|
||||
if ps["queue"]:
|
||||
continue
|
||||
pick = _autoplay_pool.pop(0)
|
||||
ps["queue"].append(pick)
|
||||
log.info("music: autoplay picked '%s' (%d remaining)",
|
||||
log.info("music: autoplay queued '%s' (%d pool remaining)",
|
||||
pick.title, len(_autoplay_pool))
|
||||
track = ps["queue"].pop(0)
|
||||
ps["current"] = track
|
||||
|
||||
Reference in New Issue
Block a user