import logging import re from typing import Optional from music.musicbrainz import ( lookup_album_dict_from_mb, lookup_artist_from_mb, lookup_track_from_mb, ) from music.constants import VARIOUS_ARTIST_DICT from scrobbles.utils import convert_to_seconds logger = logging.getLogger(__name__) from music.models import Album, Artist, Track def clean_artist_name(name: str) -> str: """Remove featured names from artist string.""" if "feat." in name.lower(): name = re.split("feat.", name, flags=re.IGNORECASE)[0].strip() if "featuring" in name.lower(): name = re.split("featuring", name, flags=re.IGNORECASE)[0].strip() if "&" in name.lower(): name = re.split("&", name, flags=re.IGNORECASE)[0].strip() return name # TODO These are depreacted, remove them eventually def get_or_create_artist(name: str, mbid: str = "") -> Artist: """Get an Artist object from the database. Check if an artist with this name or Musicbrainz ID already exists. Otherwise, go lookup artist data from Musicbrainz and create one. """ artist = None name = clean_artist_name(name) # Check for name/mbid combo, just mbid and then just name artist = Artist.objects.filter(name=name, mbid=mbid).first() if not artist: artist = Artist.objects.filter(musicbrainz_id=mbid).first() if not artist: artist = Artist.objects.filter(name=name).first() # Does not exist, look it up from Musicbrainz if not artist: artist_dict = lookup_artist_from_mb(name) mbid = mbid or artist_dict.get("id", "") if mbid: artist = Artist.objects.filter(musicbrainz_id=mbid).first() if not artist: artist = Artist.objects.create(name=name, musicbrainz_id=mbid) # TODO maybe this should be spun off into an async task? artist.fix_metadata() return artist # TODO These are depreacted, remove them eventually def get_or_create_album( name: str, artist: Artist, mbid: str = None ) -> Optional[Album]: album = None album_dict = lookup_album_dict_from_mb(name, artist_name=artist.name) name = name or album_dict.get("title", None) if not name: logger.debug( f"Cannot get or create album by {artist} with no name ({name})" ) return album = Album.objects.filter( musicbrainz_id=mbid, name=name, artists__in=[artist] ).first() if not album: mbid_group = album_dict.get("mb_group_id") album = Album.objects.filter( musicbrainz_releasegroup_id=mbid_group ).first() if not album and name: mbid = mbid or album_dict["mb_id"] album, album_created = Album.objects.get_or_create(musicbrainz_id=mbid) if album_created: album.name = name album.year = album_dict["year"] album.musicbrainz_releasegroup_id = album_dict["mb_group_id"] album.musicbrainz_albumartist_id = artist.musicbrainz_id album.save( update_fields=[ "name", "musicbrainz_id", "year", "musicbrainz_releasegroup_id", "musicbrainz_albumartist_id", ] ) album.artists.add(artist) album.fix_album_artist() album.fetch_artwork() album.scrape_allmusic() if not album: logger.warn(f"No album found for {name} and {mbid}") album.fix_album_artist() return album # TODO These are depreacted, remove them eventually def get_or_create_track(post_data: dict, post_keys: dict) -> Track: try: track_run_time_seconds = int( post_data.get(post_keys.get("RUN_TIME"), 0) ) except ValueError: # Sometimes we get run time as a string like "01:35" track_run_time_seconds = convert_to_seconds( post_data.get(post_keys.get("RUN_TIME"), 0) ) artist_name = post_data.get(post_keys.get("ARTIST_NAME"), "") artist_mb_id = post_data.get(post_keys.get("ARTIST_MB_ID"), "") album_title = post_data.get(post_keys.get("ALBUM_NAME"), "") album_mb_id = post_data.get(post_keys.get("ALBUM_MB_ID"), "") track_title = post_data.get(post_keys.get("TRACK_TITLE"), "") track_mb_id = post_data.get(post_keys.get("TRACK_MB_ID"), "") artist = Artist.find_or_create(artist_name, artist_mb_id) album = None # We may get no album ID or title, in which case, skip if album_mb_id or album_title: album = Album.find_or_create( album_title, str(artist.name), album_mb_id ) track = None if not track_mb_id and album: try: track_mb_id = lookup_track_from_mb( track_title, artist.musicbrainz_id, album.musicbrainz_id, ).get("id", 0) except TypeError: pass if not track_title and not track_mb_id: logger.info( "Cannot find track without either title or MB ID", extra={"post_data": post_data}, ) return if track_mb_id: track = Track.objects.filter(musicbrainz_id=track_mb_id).first() if not track and track_title: track = Track.objects.filter(title=track_title, artist=artist).first() if not track: track = Track.objects.create( title=track_title, artist=artist, album=album, musicbrainz_id=track_mb_id, run_time_seconds=track_run_time_seconds, ) return track def get_or_create_various_artists() -> Artist: artist = Artist.objects.filter(name="Various Artists").first() if not artist: artist = Artist.objects.create(**VARIOUS_ARTIST_DICT) logger.info("Created Various Artists placeholder") return artist