[music] Tracks can have multiple artists
All checks were successful
build & deploy / test (push) Successful in 1m54s
build & deploy / build-and-deploy (push) Successful in 33s

This commit is contained in:
2026-05-28 23:32:09 -04:00
parent 17aed1191d
commit 22956c7c7f
15 changed files with 231 additions and 84 deletions

View File

@ -124,7 +124,7 @@ class Artist(TimeStampedModel):
def charts(self):
from scrobbles.models import ChartRecord
return ChartRecord.objects.filter(track__artist=self).order_by("-year")
return ChartRecord.objects.filter(track__artists=self).order_by("-year")
def scrape_allmusic(self, force=False) -> None:
if not self.allmusic_id or force:
@ -422,7 +422,8 @@ class Album(TimeStampedModel):
self.artists.add(new_artist)
if not new_artist:
for t in self.track_set.all():
self.artists.add(t.artist)
for a in t.artists.all():
self.artists.add(a)
if not self.cover_image or self.cover_image == "default-image-replace-me":
self.fetch_artwork()
self.fix_album_artist()
@ -573,7 +574,10 @@ class Album(TimeStampedModel):
class Track(ScrobblableMixin):
COMPLETION_PERCENT = getattr(settings, "MUSIC_COMPLETION_PERCENT", 100)
artist = models.ForeignKey(Artist, on_delete=models.DO_NOTHING)
artist_fk = models.ForeignKey(
Artist, on_delete=models.DO_NOTHING, **BNULL, related_name="+"
)
artists = models.ManyToManyField(Artist)
albums = models.ManyToManyField(Album, related_name="tracks")
album = models.ForeignKey(Album, on_delete=models.DO_NOTHING, **BNULL)
musicbrainz_id = models.CharField(max_length=255, **BNULL)
@ -581,6 +585,10 @@ class Track(ScrobblableMixin):
class Meta:
unique_together = [["album", "musicbrainz_id"]]
@property
def artist(self):
return self.artists.first() or self.artist_fk
def __str__(self):
return f"{self.title} by {self.artist}"
@ -615,8 +623,9 @@ class Track(ScrobblableMixin):
@property
def primary_image_url(self) -> str:
url = ""
if self.artist.thumbnail:
url = self.artist.thumbnail_medium.url
primary_artist = self.artist
if primary_artist and primary_artist.thumbnail:
url = primary_artist.thumbnail_medium.url
if self.primary_album and self.primary_album.cover_image:
url = self.primary_album.cover_image_medium.url
return url
@ -625,6 +634,7 @@ class Track(ScrobblableMixin):
def find_or_create(
cls,
title: str = "",
artist_names: list[str] | None = None,
artist_name: str = "",
album_name: str = "",
run_time_seconds: int | None = None,
@ -638,25 +648,40 @@ class Track(ScrobblableMixin):
name
Optionally, we can update any found artists with overwrite."""
from music.utils import parse_artist_names
if artist_names is None and artist_name:
artist_names = parse_artist_names(artist_name)
if not artist_names:
artist_names = []
album = None
if album_name:
logger.info(f"Looking up album for: {album_name}")
album = Album.find_or_create(name=album_name, artist_name=artist_name)
artist = album.album_artist
else:
artist = Artist.find_or_create(artist_name, track_name=title)
if not artist:
artist = Artist.find_or_create(artist_name)
first_name = artist_names[0] if artist_names else ""
album = Album.find_or_create(name=album_name, artist_name=first_name)
artist_objs = []
for name in artist_names:
artist = Artist.find_or_create(name, track_name=title)
if artist:
artist_objs.append(artist)
track = None
if artist_objs:
track = cls.objects.filter(title=title, artists__in=artist_objs).first()
lookup_keys = {"title": title, "artist": artist}
if run_time_seconds:
lookup_keys["base_run_time_seconds"] = run_time_seconds
logger.info(f"Looking up track using: {lookup_keys}")
track = cls.objects.filter(**lookup_keys).first()
if not track:
track = cls.objects.filter(title=title, artist=artist).first()
if not track:
track, _ = cls.objects.get_or_create(title=title, artist=artist)
track = cls.objects.filter(title=title).first()
if not track:
track = cls(title=title)
track.save()
track.refresh_from_db()
if artist_objs:
track.artists.add(*artist_objs)
if album:
track.albums.add(album)
@ -666,17 +691,18 @@ class Track(ScrobblableMixin):
track.base_run_time_seconds = run_time_seconds
track.musicbrainz_id = mbid
else:
artist_name_str = " & ".join(artist_names) if artist_names else ""
logger.info(
f"Enriching track {track}",
extra={
"title": title,
"artist_name": artist_name,
"artist_name": artist_name_str,
"track_id": track.id,
},
)
try:
mbid, length = get_recording_mbid_exact(
title, artist_name, album_name
title, artist_name_str, album_name
)
except Exception:
print("No musicbrainz result found, cannot enrich")
@ -688,6 +714,4 @@ class Track(ScrobblableMixin):
return track
def fix_metadata(self, force_update=False):
...
def fix_metadata(self, force_update=False): ...