|
@@ -14,7 +14,12 @@ 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_album_from_mb, lookup_track_from_mb, lookup_artist_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
|
|
@@ -22,6 +27,7 @@ from scrobbles.mixins import ScrobblableConstants, ScrobblableMixin
|
|
|
logger = logging.getLogger(__name__)
|
|
|
BNULL = {"blank": True, "null": True}
|
|
|
|
|
|
+
|
|
|
class Artist(TimeStampedModel):
|
|
|
"""Represents a music artist.
|
|
|
|
|
@@ -170,7 +176,9 @@ 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":
|
|
|
+ def find_or_create(
|
|
|
+ cls, name: str = "", musicbrainz_id: str = ""
|
|
|
+ ) -> "Artist":
|
|
|
keys = {}
|
|
|
if name:
|
|
|
name = clean_artist_name(name)
|
|
@@ -547,12 +555,8 @@ class Album(TimeStampedModel):
|
|
|
class Track(ScrobblableMixin):
|
|
|
COMPLETION_PERCENT = getattr(settings, "MUSIC_COMPLETION_PERCENT", 100)
|
|
|
|
|
|
- class Opinion(models.IntegerChoices):
|
|
|
- DOWN = -1, "Thumbs down"
|
|
|
- NEUTRAL = 0, "No opinion"
|
|
|
- UP = 1, "Thumbs up"
|
|
|
-
|
|
|
artist = models.ForeignKey(Artist, on_delete=models.DO_NOTHING)
|
|
|
+ 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)
|
|
|
|
|
@@ -562,6 +566,10 @@ class Track(ScrobblableMixin):
|
|
|
def __str__(self):
|
|
|
return f"{self.title} by {self.artist}"
|
|
|
|
|
|
+ @property
|
|
|
+ def primary_album(self):
|
|
|
+ return self.albums.order_by("year").first()
|
|
|
+
|
|
|
def get_absolute_url(self):
|
|
|
return reverse("music:track_detail", kwargs={"slug": self.uuid})
|
|
|
|
|
@@ -590,17 +598,16 @@ class Track(ScrobblableMixin):
|
|
|
url = self.album.cover_image_medium.url
|
|
|
return url
|
|
|
|
|
|
-
|
|
|
@classmethod
|
|
|
def find_or_create(
|
|
|
- cls,
|
|
|
- title: str = "",
|
|
|
- artist_name: str = "",
|
|
|
- musicbrainz_id: str = "",
|
|
|
- album_name: str = "",
|
|
|
- run_time_seconds: int = 900,
|
|
|
- enrich: bool = False,
|
|
|
- commit: bool = True
|
|
|
+ cls,
|
|
|
+ title: str = "",
|
|
|
+ artist_name: str = "",
|
|
|
+ musicbrainz_id: str = "",
|
|
|
+ album_name: str = "",
|
|
|
+ run_time_seconds: int = 900,
|
|
|
+ enrich: bool = False,
|
|
|
+ commit: bool = True,
|
|
|
) -> "Track":
|
|
|
"""Given a name, try to find the track by the artist from Musicbrainz.
|
|
|
|
|
@@ -613,13 +620,24 @@ class Track(ScrobblableMixin):
|
|
|
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.")
|
|
|
+ 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)
|
|
|
+ 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})
|
|
|
+ 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?
|
|
@@ -638,14 +656,16 @@ class Track(ScrobblableMixin):
|
|
|
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})
|
|
|
+ logger.warning(
|
|
|
+ "Source track title and found title do not match",
|
|
|
+ extra={"title": title, "track_dict": track_dict},
|
|
|
+ )
|
|
|
|
|
|
if not run_time_seconds:
|
|
|
run_time_seconds = int(
|
|
|
int(track_dict.get("length", 900000)) / 1000
|
|
|
)
|
|
|
|
|
|
-
|
|
|
track.album = album
|
|
|
track.artist = artist
|
|
|
track.run_time_seconds = run_time_seconds
|