|
@@ -14,14 +14,14 @@ from imagekit.models import ImageSpecField
|
|
from imagekit.processors import ResizeToFit
|
|
from imagekit.processors import ResizeToFit
|
|
from music.allmusic import get_allmusic_slug, scrape_data_from_allmusic
|
|
from music.allmusic import get_allmusic_slug, scrape_data_from_allmusic
|
|
from music.bandcamp import get_bandcamp_slug
|
|
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.theaudiodb import lookup_album_from_tadb, lookup_artist_from_tadb
|
|
|
|
+from music.utils import clean_artist_name
|
|
from scrobbles.mixins import ScrobblableConstants, ScrobblableMixin
|
|
from scrobbles.mixins import ScrobblableConstants, ScrobblableMixin
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
logger = logging.getLogger(__name__)
|
|
BNULL = {"blank": True, "null": True}
|
|
BNULL = {"blank": True, "null": True}
|
|
|
|
|
|
-
|
|
|
|
class Artist(TimeStampedModel):
|
|
class Artist(TimeStampedModel):
|
|
"""Represents a music artist.
|
|
"""Represents a music artist.
|
|
|
|
|
|
@@ -170,23 +170,20 @@ class Artist(TimeStampedModel):
|
|
return f"https://bandcamp.com/search?q={artist}&item_type=b"
|
|
return f"https://bandcamp.com/search?q={artist}&item_type=b"
|
|
|
|
|
|
@classmethod
|
|
@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")
|
|
|
|
|
|
+ if musicbrainz_id:
|
|
|
|
+ keys["musicbrainz_id"] = musicbrainz_id
|
|
|
|
|
|
- artist = None
|
|
|
|
- name = clean_artist_name(name)
|
|
|
|
|
|
+ if not keys:
|
|
|
|
+ raise Exception("Must have name, mb_id or both to lookup artist")
|
|
|
|
+
|
|
|
|
+ artist = cls.objects.filter(**keys).first()
|
|
|
|
|
|
- # 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()
|
|
|
|
if not artist:
|
|
if not artist:
|
|
artist = cls.objects.filter(
|
|
artist = cls.objects.filter(
|
|
models.Q(name=name) | models.Q(alt_names__icontains=name)
|
|
models.Q(name=name) | models.Q(alt_names__icontains=name)
|
|
@@ -593,87 +590,67 @@ class Track(ScrobblableMixin):
|
|
url = self.album.cover_image_medium.url
|
|
url = self.album.cover_image_medium.url
|
|
return url
|
|
return url
|
|
|
|
|
|
|
|
+
|
|
@classmethod
|
|
@classmethod
|
|
def find_or_create(
|
|
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":
|
|
) -> "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:
|
|
if not run_time_seconds:
|
|
run_time_seconds = int(
|
|
run_time_seconds = int(
|
|
int(track_dict.get("length", 900000)) / 1000
|
|
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
|
|
return track
|