[music] Add better track fetching
This commit is contained in:
@ -1,4 +1,5 @@
|
||||
import logging
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
import musicbrainzngs
|
||||
@ -6,6 +7,149 @@ from dateutil.parser import parse
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
TRACK_SUFFIXES = [
|
||||
r" - Bonus Track",
|
||||
r" - Bonus",
|
||||
r" - Remix",
|
||||
r" - Live",
|
||||
r" - Radio Edit",
|
||||
r" - Extended Mix",
|
||||
r" - Instrumental",
|
||||
r" - Acoustic",
|
||||
r" - Mix",
|
||||
r" - Edit",
|
||||
r" \[Explicit\]",
|
||||
]
|
||||
|
||||
FEATURED_PATTERNS = [
|
||||
(r"\(feat\. (.+?)\)", re.IGNORECASE),
|
||||
(r"\(ft\. (.+?)\)", re.IGNORECASE),
|
||||
(r"featuring (.+)", re.IGNORECASE),
|
||||
(r"feat\. (.+)", re.IGNORECASE),
|
||||
(r"ft\. (.+)", re.IGNORECASE),
|
||||
]
|
||||
|
||||
|
||||
def strip_track_suffixes(title: str) -> str:
|
||||
for suffix in TRACK_SUFFIXES:
|
||||
title = re.sub(suffix, "", title, flags=re.IGNORECASE)
|
||||
return title.strip()
|
||||
|
||||
|
||||
def extract_featured_artists(title: str) -> tuple[str, list[str]]:
|
||||
featured = []
|
||||
cleaned = title
|
||||
for pattern, flags in FEATURED_PATTERNS:
|
||||
m = re.search(pattern, cleaned, flags)
|
||||
if m:
|
||||
featured.append(m.group(1).strip())
|
||||
cleaned = re.sub(pattern, "", cleaned, flags=flags).strip()
|
||||
return cleaned, featured
|
||||
|
||||
|
||||
def search_recordings(title: str, artist: str = "") -> list[dict]:
|
||||
kwargs: dict = {"recording": title, "limit": 10}
|
||||
if artist:
|
||||
kwargs["artist"] = artist
|
||||
try:
|
||||
return musicbrainzngs.search_recordings(**kwargs).get("recording-list", [])
|
||||
except (musicbrainzngs.WebServiceError, musicbrainzngs.ResponseError) as e:
|
||||
logger.warning(f"MusicBrainz search error: {e}")
|
||||
return []
|
||||
|
||||
|
||||
def pick_best_recording(
|
||||
recordings: list[dict], title: str, artist: str = ""
|
||||
) -> tuple[dict | None, str]:
|
||||
query_title = title.strip().casefold()
|
||||
query_artist = artist.strip().casefold() if artist else ""
|
||||
best_score: tuple[dict | None, str] = (None, "")
|
||||
|
||||
for rec in recordings:
|
||||
rec_title = rec.get("title", "").strip()
|
||||
score = int(rec.get("ext:score", 0))
|
||||
|
||||
if rec_title.casefold() != query_title:
|
||||
if score >= 85 and not best_score[0]:
|
||||
best_score = (rec, "score")
|
||||
continue
|
||||
|
||||
if query_artist:
|
||||
artist_credit = rec.get("artist-credit", [])
|
||||
rec_artist = (
|
||||
artist_credit[0]["artist"]["name"].strip()
|
||||
if artist_credit
|
||||
else ""
|
||||
)
|
||||
if rec_artist.casefold() != query_artist:
|
||||
if score >= 85 and not best_score[0]:
|
||||
best_score = (rec, "score")
|
||||
continue
|
||||
|
||||
return (rec, "exact")
|
||||
|
||||
return best_score
|
||||
|
||||
|
||||
def resolve_track(
|
||||
title: str, artist: str = "", album: str = ""
|
||||
) -> tuple[dict | None, str]:
|
||||
if not title:
|
||||
return (None, "")
|
||||
|
||||
titles_to_try: list[tuple[str, str]] = [(title, "")]
|
||||
stripped = strip_track_suffixes(title)
|
||||
if stripped != title:
|
||||
titles_to_try.append((stripped, "stripped-"))
|
||||
|
||||
for attempt_title, prefix in titles_to_try:
|
||||
if album and not prefix:
|
||||
try:
|
||||
mbid, length = get_recording_mbid_exact(
|
||||
attempt_title, artist, album
|
||||
)
|
||||
return (
|
||||
{
|
||||
"recording_mbid": mbid,
|
||||
"length_ms": length,
|
||||
},
|
||||
"exact",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if artist:
|
||||
result = get_track_metadata_with_artist(attempt_title, artist)
|
||||
if result and result.get("recording_mbid"):
|
||||
return (result, f"{prefix}exact")
|
||||
|
||||
recordings = search_recordings(attempt_title, artist)
|
||||
result, tag = pick_best_recording(recordings, attempt_title, artist)
|
||||
if result:
|
||||
length_ms = result.get("length")
|
||||
return (
|
||||
{
|
||||
"recording_mbid": result["id"],
|
||||
"length_ms": int(length_ms) if length_ms else None,
|
||||
},
|
||||
f"{prefix}{tag}",
|
||||
)
|
||||
|
||||
recordings = search_recordings(attempt_title)
|
||||
result, tag = pick_best_recording(recordings, attempt_title)
|
||||
if result:
|
||||
tag = "title-only" if not prefix else f"{prefix}title-only"
|
||||
length_ms = result.get("length")
|
||||
return (
|
||||
{
|
||||
"recording_mbid": result["id"],
|
||||
"length_ms": int(length_ms) if length_ms else None,
|
||||
},
|
||||
tag,
|
||||
)
|
||||
|
||||
return (None, "")
|
||||
|
||||
musicbrainzngs.set_useragent("Vrobbler", "1.0", "help@unbl.ink")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user