diff --git a/vrobbler/apps/music/utils.py b/vrobbler/apps/music/utils.py index ba2bb24..f3df9e5 100644 --- a/vrobbler/apps/music/utils.py +++ b/vrobbler/apps/music/utils.py @@ -11,15 +11,21 @@ logger = logging.getLogger(__name__) from music.models import Artist, Album, Track -def get_or_create_artist(name: str) -> Artist: - artist, artist_created = Artist.objects.get_or_create(name=name) - if artist_created: +def get_or_create_artist(name: str, mbid: str = None) -> Artist: + if mbid: + artist, artist_created = Artist.objects.get_or_create( + name=name, musicbrainz_id=mbid + ) + else: + artist, artist_created = Artist.objects.get_or_create(name=name) + + if not mbid: artist.musicbrainz_id = lookup_artist_id_from_mb(artist.name) artist.save(update_fields=["musicbrainz_id"]) return artist -def get_or_create_album(name: str, artist: Artist) -> Album: +def get_or_create_album(name: str, artist: Artist, mbid: str = None) -> Album: album = None album_created = False albums = Album.objects.filter(name__iexact=name) @@ -31,11 +37,11 @@ def get_or_create_album(name: str, artist: Artist) -> Album: album = potential_album if not album: album_created = True - album = Album.objects.create(name=name) + album = Album.objects.create(name=name, musicbrainz_id=mbid) album.save() album.artists.add(artist) - if album_created: + if album_created or not mbid: album_dict = lookup_album_dict_from_mb( album.name, artist_name=artist.name ) diff --git a/vrobbler/apps/scrobbles/lastfm.py b/vrobbler/apps/scrobbles/lastfm.py index bd93033..cc4758e 100644 --- a/vrobbler/apps/scrobbles/lastfm.py +++ b/vrobbler/apps/scrobbles/lastfm.py @@ -5,6 +5,7 @@ from datetime import datetime, timedelta import pylast import pytz from django.conf import settings +from django.utils import timezone from music.utils import ( get_or_create_album, get_or_create_artist, @@ -71,8 +72,8 @@ class LastFM: ten_seconds_eariler = timestamp - timedelta(seconds=15) ten_seconds_later = timestamp + timedelta(seconds=15) existing = Scrobble.objects.filter( - created__gte=ten_seconds_eariler, - created__lte=ten_seconds_later, + timestamp__gte=ten_seconds_eariler, + timestamp__lte=ten_seconds_later, track=track, ).first() if existing: @@ -112,32 +113,42 @@ class LastFM: def get_last_scrobbles(self, time_from=None, time_to=None): """Given a user, Last.fm api key, and secret key, grab a list of scrobbled tracks""" + lfm_params = {} scrobbles = [] if time_from: - time_from = int(time_from.timestamp()) + lfm_params["time_from"] = int(time_from.timestamp()) if time_to: - time_to = int(time_to.timestamp()) + lfm_params["time_to"] = int(time_to.timestamp()) + + # if not time_from and not time_to: + lfm_params['limit'] = None + + found_scrobbles = self.user.get_recent_tracks(**lfm_params) - if not time_from and not time_to: - found_scrobbles = self.user.get_recent_tracks(limit=None) - else: - found_scrobbles = self.user.get_recent_tracks( - time_from=time_from, time_to=time_to - ) for scrobble in found_scrobbles: - run_time_ticks = scrobble.track.get_duration() - run_time = run_time_ticks / 1000 + try: + run_time_ticks = scrobble.track.get_duration() + run_time = int(run_time_ticks / 1000) + except pylast.MalformedResponseError: + run_time_ticks = None + run_time = None + logger.warn(f"Track {scrobble.track} has no duration") + + timestamp = datetime.utcfromtimestamp( + int(scrobble.timestamp) + ).replace(tzinfo=pytz.utc) + artist = scrobble.track.get_artist().name + + logger.debug(f"{artist},{scrobble.track.title},{timestamp}") scrobbles.append( { - "artist": scrobble.track.get_artist().name, + "artist": artist, "album": scrobble.album, "title": scrobble.track.title, "mbid": scrobble.track.get_mbid(), - "run_time": int(run_time), + "run_time": run_time, "run_time_ticks": run_time_ticks, - "timestamp": datetime.utcfromtimestamp( - int(scrobble.timestamp) - ).replace(tzinfo=pytz.utc), + "timestamp": timestamp, } ) return scrobbles diff --git a/vrobbler/apps/scrobbles/musicbrainz.py b/vrobbler/apps/scrobbles/musicbrainz.py index 24af9ac..a7f15d6 100644 --- a/vrobbler/apps/scrobbles/musicbrainz.py +++ b/vrobbler/apps/scrobbles/musicbrainz.py @@ -62,9 +62,12 @@ def lookup_album_dict_from_mb(release_name: str, artist_name: str) -> dict: extra={"result": top_result}, ) return {} - print(top_result) + year = None + if top_result.get("date"): + year = parse(top_result["date"]).year + return { - "year": parse(top_result["date"]).year, + "year": year, "mb_id": top_result["id"], "mb_group_id": top_result["release-group"]["id"], } diff --git a/vrobbler/apps/scrobbles/scrobblers.py b/vrobbler/apps/scrobbles/scrobblers.py index 04d5335..3f34fc6 100644 --- a/vrobbler/apps/scrobbles/scrobblers.py +++ b/vrobbler/apps/scrobbles/scrobblers.py @@ -10,6 +10,11 @@ from scrobbles.models import Scrobble from scrobbles.utils import convert_to_seconds, parse_mopidy_uri from videos.models import Video from sports.models import SportEvent +from vrobbler.apps.music.utils import ( + get_or_create_album, + get_or_create_artist, + get_or_create_track, +) logger = logging.getLogger(__name__) @@ -57,23 +62,23 @@ def mopidy_scrobble_podcast( def mopidy_scrobble_track( data_dict: dict, user_id: Optional[int] ) -> Optional[Scrobble]: - artist_dict = { - "name": data_dict.get("artist", None), - "musicbrainz_id": data_dict.get("musicbrainz_artist_id", None), - } - - album_dict = { - "name": data_dict.get("album"), - "musicbrainz_id": data_dict.get("musicbrainz_album_id"), - } - - track_dict = { - "title": data_dict.get("name"), - "run_time_ticks": data_dict.get("run_time_ticks"), - "run_time": data_dict.get("run_time"), - } - - track = Track.find_or_create(artist_dict, album_dict, track_dict) + artist = get_or_create_artist( + data_dict.get("artist"), + mbid=data_dict.get("musicbrainz_artist_id", None), + ) + album = get_or_create_album( + data_dict.get("album"), + artist=artist, + mbid=data_dict.get("musicbrainz_album_id"), + ) + track = get_or_create_track( + title=data_dict.get("name"), + mbid=data_dict.get("musicbrainz_track_id"), + artist=artist, + album=album, + run_time_ticks=data_dict.get("run_time_ticks"), + run_time=data_dict.get("run_time"), + ) # Now we run off a scrobble mopidy_data = { @@ -136,38 +141,30 @@ def jellyfin_scrobble_track( logger.error("No playback position tick from Jellyfin, aborting") return - artist_dict = { - 'name': data_dict.get(JELLYFIN_POST_KEYS["ARTIST_NAME"], None), - 'musicbrainz_id': data_dict.get( - JELLYFIN_POST_KEYS["ARTIST_MB_ID"], None - ), - } + artist = get_or_create_artist( + data_dict.get(JELLYFIN_POST_KEYS["ARTIST_NAME"]), + mbid=data_dict.get(JELLYFIN_POST_KEYS["ARTIST_MB_ID"]), + ) + album = get_or_create_album( + data_dict.get(JELLYFIN_POST_KEYS["ALBUM_NAME"]), + artist=artist, + mbid=data_dict.get(JELLYFIN_POST_KEYS['ALBUM_MB_ID']), + ) - album_dict = { - "name": data_dict.get(JELLYFIN_POST_KEYS["ALBUM_NAME"], None), - "musicbrainz_id": data_dict.get(JELLYFIN_POST_KEYS['ALBUM_MB_ID']), - } - - # Convert ticks from Jellyfin from microseconds to nanoseconds - # Ain't nobody got time for nanoseconds - track_dict = { - "title": data_dict.get("Name", ""), - "run_time_ticks": data_dict.get( - JELLYFIN_POST_KEYS["RUN_TIME_TICKS"], None - ) - // 10000, - "run_time": convert_to_seconds( - data_dict.get(JELLYFIN_POST_KEYS["RUN_TIME"], None) - ), - } - track = Track.find_or_create(artist_dict, album_dict, track_dict) - - # Prefer Mopidy MD IDs to Jellyfin, so skip if we already have one - if not track.musicbrainz_id: - track.musicbrainz_id = data_dict.get( - JELLYFIN_POST_KEYS["TRACK_MB_ID"], None - ) - track.save() + run_time_ticks = ( + data_dict.get(JELLYFIN_POST_KEYS["RUN_TIME_TICKS"]) // 10000 + ) + run_time = convert_to_seconds( + data_dict.get(JELLYFIN_POST_KEYS["RUN_TIME"]) + ) + track = get_or_create_track( + title=data_dict.get("Name"), + mbid=data_dict.get(JELLYFIN_POST_KEYS["TRACK_MB_ID"]), + artist=artist, + album=album, + run_time_ticks=run_time_ticks, + run_time=run_time, + ) scrobble_dict = build_scrobble_dict(data_dict, user_id)