[music] Fix find_or_create for tracks

This commit is contained in:
2025-07-09 13:39:10 -04:00
parent dd54a33159
commit b1b67528bf
2 changed files with 67 additions and 248 deletions

View File

@ -14,14 +14,14 @@ from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFit
from music.allmusic import get_allmusic_slug, scrape_data_from_allmusic
from music.bandcamp import get_bandcamp_slug
from music.musicbrainz import lookup_album_dict_from_mb, lookup_track_from_mb
from music.musicbrainz import lookup_album_dict_from_mb, lookup_album_from_mb, lookup_track_from_mb, lookup_artist_from_mb
from music.theaudiodb import lookup_album_from_tadb, lookup_artist_from_tadb
from music.utils import clean_artist_name
from scrobbles.mixins import ScrobblableConstants, ScrobblableMixin
logger = logging.getLogger(__name__)
BNULL = {"blank": True, "null": True}
class Artist(TimeStampedModel):
"""Represents a music artist.
@ -170,23 +170,20 @@ class Artist(TimeStampedModel):
return f"https://bandcamp.com/search?q={artist}&item_type=b"
@classmethod
def find_or_create(cls, name: str, musicbrainz_id: str = "") -> "Artist":
from music.musicbrainz import lookup_artist_from_mb
from music.utils import clean_artist_name
def find_or_create(cls, name: str = "", musicbrainz_id: str = "") -> "Artist":
keys = {}
if name:
name = clean_artist_name(name)
keys["name"] = name
if not name:
raise Exception("Must have name to lookup artist")
artist = None
name = clean_artist_name(name)
# Check for name/mbid combo, just mbid and then just name
if musicbrainz_id:
artist = cls.objects.filter(
name=name, musicbrainz_id=musicbrainz_id
).first()
if not artist:
artist = cls.objects.filter(musicbrainz_id=musicbrainz_id).first()
keys["musicbrainz_id"] = musicbrainz_id
if not keys:
raise Exception("Must have name, mb_id or both to lookup artist")
artist = cls.objects.filter(**keys).first()
if not artist:
artist = cls.objects.filter(
models.Q(name=name) | models.Q(alt_names__icontains=name)
@ -593,87 +590,67 @@ class Track(ScrobblableMixin):
url = self.album.cover_image_medium.url
return url
@classmethod
def find_or_create(
cls,
title: str = "",
musicbrainz_id: str = "",
album_name: str = "",
artist_name: str = "",
enrich: bool = True,
run_time_seconds: Optional[int] = None,
cls,
title: str = "",
artist_name: str = "",
musicbrainz_id: str = "",
album_name: str = "",
run_time_seconds: int = 900,
enrich: bool = False,
commit: bool = True
) -> "Track":
# TODO we can use Q to build queries here based on whether we have mbid and album name
track = None
# Full look up with MB ID
if album_name:
track = cls.objects.filter(
musicbrainz_id=musicbrainz_id,
title=title,
artist__name=artist_name,
album__name=album_name,
).first()
# Full look up without album
if not track:
track = cls.objects.filter(
musicbrainz_id=musicbrainz_id,
title=title,
artist__name=artist_name,
).first()
"""Given a name, try to find the track by the artist from Musicbrainz.
# Full look up without MB ID
if not track:
track = cls.objects.filter(
title=title,
artist__name=artist_name,
album__name=album_name,
).first()
# Base look up without MB ID or album
if not track:
track = cls.objects.filter(
title=title,
artist__name=artist_name,
).first()
As a basic conceit we trust the source for giving us the track and artist
name
Optionally, we can update any found artists with overwrite."""
created = False
if musicbrainz_id:
track = cls.objects.filter(musicbrainz_id=musicbrainz_id).first()
artist = track.artist
if not track and not (title and album_name):
raise Exception("Cannot find track with musicbrainz_id and no track title or artist name provided.")
else:
artist = Artist.find_or_create(artist_name)
track, created = cls.objects.get_or_create(title=title, artist=artist)
if not created:
logger.info("Found exact match for track by name and artist", extra={"title": title, "artist_name": artist_name, "track_id": track.id})
if track.album and album_name != track.album.name:
# TODO found track, but it's on a different album ... associations?
logger.info("Found track by artist, but album is different.")
album = Album.find_or_create()
if enrich:
album = None
if album_name:
album = Album.find_or_create(album_name)
if artist.musicbrainz_id:
track_dict = lookup_track_from_mb(title, artist.musicbrainz_id)
musicbrainz_id = musicbrainz_id or track_dict.get("id", "")
found_title: bool = track_dict.get("name", False)
mismatched_title: bool = title != track_dict.get("name", "")
if found_title and mismatched_title:
logger.warning("Source track title and found title do not match", extra={"title": title, "track_dict": track_dict})
if not track and enrich:
track_dict = lookup_track_from_mb(title, artist_name, album_name)
musicbrainz_id = musicbrainz_id or track_dict.get("id", "")
# TODO This only works some of the time
# try:
# album_name = album_name or track_dict.get("release-list")[
# 0
# ].get("title", "")
# except IndexError:
# pass
if not run_time_seconds:
run_time_seconds = int(
int(track_dict.get("length", 900000)) / 1000
)
if title != track_dict.get("name", "") and track_dict.get(
"name", False
):
title = track_dict.get("name", "")
if musicbrainz_id:
track = cls.objects.filter(
musicbrainz_id=musicbrainz_id
).first()
if not track:
artist = Artist.find_or_create(name=artist_name)
album = None
if album_name:
album = Album.find_or_create(
name=album_name, artist_name=artist_name
)
track = cls.objects.create(
title=title,
album=album,
musicbrainz_id=musicbrainz_id,
artist=artist,
run_time_seconds=run_time_seconds,
)
# TODO maybe do this in a separate process?
track.fix_metadata()
track.album = album
track.artist = artist
track.run_time_seconds = run_time_seconds
if commit:
track.save()
# TODO Also set cover art and tags
return track