From 685c99d023cd5c305eed1618dd510cfb599a2e05 Mon Sep 17 00:00:00 2001 From: Colin Powell Date: Thu, 12 Jan 2023 16:05:29 -0500 Subject: [PATCH] Fix proper scrobbling of podcasts --- vrobbler/apps/scrobbles/admin.py | 3 +- vrobbler/apps/scrobbles/models.py | 52 ++++++++++++++++++++------- vrobbler/apps/scrobbles/scrobblers.py | 8 +++-- vrobbler/apps/scrobbles/utils.py | 32 +++++++++++++++++ vrobbler/apps/scrobbles/views.py | 6 ++-- vrobbler/settings.py | 5 +++ 6 files changed, 88 insertions(+), 18 deletions(-) diff --git a/vrobbler/apps/scrobbles/admin.py b/vrobbler/apps/scrobbles/admin.py index b9f0285..63347ce 100644 --- a/vrobbler/apps/scrobbles/admin.py +++ b/vrobbler/apps/scrobbles/admin.py @@ -13,8 +13,9 @@ class ScrobbleAdmin(admin.ModelAdmin): "source", "playback_position", "in_progress", + "is_paused", ) - list_filter = ("in_progress", "source", "track__artist") + list_filter = ("is_paused", "in_progress", "source", "track__artist") ordering = ("-timestamp",) def media_name(self, obj): diff --git a/vrobbler/apps/scrobbles/models.py b/vrobbler/apps/scrobbles/models.py index 8022280..4ac1c4a 100644 --- a/vrobbler/apps/scrobbles/models.py +++ b/vrobbler/apps/scrobbles/models.py @@ -10,6 +10,7 @@ from django.utils import timezone from django_extensions.db.models import TimeStampedModel from music.models import Track from podcasts.models import Episode +from scrobbles.utils import check_scrobble_for_finish from videos.models import Video logger = logging.getLogger(__name__) @@ -180,6 +181,13 @@ class Scrobble(TimeStampedModel): mopidy_status = scrobble_data.pop('status', None) scrobble_is_stale = False + if scrobble: + logger.debug(f"Updating scrobble ticks") + scrobble.playback_position_ticks = scrobble_data.get( + "playback_position_ticks" + ) + scrobble.save(update_fields=['playback_position_ticks']) + if mopidy_status == "stopped": logger.info(f"Mopidy sent a message to stop {scrobble}") if not scrobble: @@ -190,7 +198,37 @@ class Scrobble(TimeStampedModel): # Mopidy finished a play, scrobble away scrobble.in_progress = False - scrobble.save(update_fields=['in_progress']) + scrobble.played_to_completion = True + scrobble.save( + update_fields=['in_progress', 'played_to_completion'] + ) + return scrobble + + if mopidy_status == "paused": + logger.info(f"Mopidy sent a message to pause {scrobble}") + if not scrobble: + logger.info("Message to pause while not started, ignoring") + return + if scrobble.is_paused: + logger.info("Message to pause while paused, ignoring") + return + + # Mopidy finished a play, scrobble away + scrobble.is_paused = True + scrobble.save(update_fields=["is_paused"]) + scrobble = check_scrobble_for_finish(scrobble) + return scrobble + + if mopidy_status == "resumed": + logger.info(f"Mopidy sent a message to resume {scrobble}") + if not scrobble: + logger.info("Message to resume while not started, ignoring") + return + if not scrobble.is_paused: + logger.info("Message to resume while not paused, resuming") + # Mopidy finished a play, scrobble away + scrobble.is_paused = False + scrobble.save(update_fields=["is_paused"]) return scrobble if scrobble and not mopidy_status: @@ -218,16 +256,6 @@ class Scrobble(TimeStampedModel): # If we hit our completion threshold, save it and get ready # to scrobble again if we re-watch this. - if scrobble.percent_played >= getattr( - settings, "PERCENT_FOR_COMPLETION", 95 - ): - scrobble.in_progress = False - scrobble.playback_position_ticks = scrobble.media_run_time_ticks - scrobble.save() - - if scrobble.percent_played % 5 == 0: - if getattr(settings, "KEEP_DETAILED_SCROBBLE_LOGS", False): - scrobble.scrobble_log += f"\n{str(scrobble.timestamp)} - {scrobble.playback_position} - {str(scrobble.playback_position_ticks)} - {str(scrobble.percent_played)}%" - scrobble.save(update_fields=['scrobble_log']) + scrobble = check_scrobble_for_finish(scrobble) return scrobble diff --git a/vrobbler/apps/scrobbles/scrobblers.py b/vrobbler/apps/scrobbles/scrobblers.py index 635f75f..cee6bc0 100644 --- a/vrobbler/apps/scrobbles/scrobblers.py +++ b/vrobbler/apps/scrobbles/scrobblers.py @@ -10,7 +10,9 @@ from scrobbles.utils import parse_mopidy_uri logger = logging.getLogger(__name__) -def scrobble_podcast(data_dict: dict, user_id: Optional[int]) -> Scrobble: +def mopidy_scrobble_podcast( + data_dict: dict, user_id: Optional[int] +) -> Scrobble: mopidy_uri = data_dict.get("mopidy_uri", "") parsed_data = parse_mopidy_uri(mopidy_uri) @@ -37,6 +39,7 @@ def scrobble_podcast(data_dict: dict, user_id: Optional[int]) -> Scrobble: mopidy_data = { "user_id": user_id, "timestamp": timezone.now(), + "playback_position_ticks": data_dict.get("playback_time_ticks"), "source": "Mopidy", "status": data_dict.get("status"), } @@ -49,7 +52,7 @@ def scrobble_podcast(data_dict: dict, user_id: Optional[int]) -> Scrobble: return scrobble -def scrobble_track( +def mopidy_scrobble_track( data_dict: dict, user_id: Optional[int] ) -> Optional[Scrobble]: artist_dict = { @@ -74,6 +77,7 @@ def scrobble_track( mopidy_data = { "user_id": user_id, "timestamp": timezone.now(), + "playback_position_ticks": data_dict.get("playback_time_ticks"), "source": "Mopidy", "status": data_dict.get("status"), } diff --git a/vrobbler/apps/scrobbles/utils.py b/vrobbler/apps/scrobbles/utils.py index 9c94f03..c5914de 100644 --- a/vrobbler/apps/scrobbles/utils.py +++ b/vrobbler/apps/scrobbles/utils.py @@ -3,6 +3,7 @@ from typing import Any from urllib.parse import unquote from dateutil.parser import ParserError, parse +from django.conf import settings logger = logging.getLogger(__name__) @@ -57,3 +58,34 @@ def parse_mopidy_uri(uri: str) -> dict: 'podcast_name': podcast_str, 'pub_date': pub_date, } + + +def check_scrobble_for_finish(scrobble: "Scrobble") -> None: + completion_percent = getattr(settings, "MUSIC_COMPLETION_PERCENT", 90) + if scrobble.podcast_episode: + completion_percent = getattr( + settings, "PODCAST_COMPLETION_PERCENT", 25 + ) + logger.debug(f"Completion set to {completion_percent}") + + if scrobble.percent_played >= completion_percent: + logger.debug( + f"{scrobble} meets completion goal of {completion_percent}, finishing" + ) + scrobble.in_progress = False + scrobble.is_paused = False + scrobble.playback_position_ticks = scrobble.media_obj.run_time_ticks + scrobble.save( + update_fields=[ + "in_progress", + "is_paused", + "playback_position_ticks", + ] + ) + + if scrobble.percent_played % 5 == 0: + if getattr(settings, "KEEP_DETAILED_SCROBBLE_LOGS", False): + scrobble.scrobble_log += f"\n{str(scrobble.timestamp)} - {scrobble.playback_position} - {str(scrobble.playback_position_ticks)} - {str(scrobble.percent_played)}%" + scrobble.save(update_fields=['scrobble_log']) + + return scrobble diff --git a/vrobbler/apps/scrobbles/views.py b/vrobbler/apps/scrobbles/views.py index 06ca3fc..b67c5e3 100644 --- a/vrobbler/apps/scrobbles/views.py +++ b/vrobbler/apps/scrobbles/views.py @@ -27,7 +27,7 @@ from vrobbler.apps.music.aggregators import ( top_tracks, week_of_scrobbles, ) -from scrobbles.scrobblers import scrobble_podcast, scrobble_track +from scrobbles.scrobblers import mopidy_scrobble_podcast, mopidy_scrobble_track logger = logging.getLogger(__name__) @@ -191,9 +191,9 @@ def mopidy_websocket(request): logger.debug(f"{json_data}") if 'podcast' in data_dict.get('mopidy_uri'): - scrobble = scrobble_podcast(data_dict, request.user.id) + scrobble = mopidy_scrobble_podcast(data_dict, request.user.id) else: - scrobble = scrobble_track(data_dict, request.user.id) + scrobble = mopidy_scrobble_track(data_dict, request.user.id) if not scrobble: return Response({}, status=status.HTTP_400_BAD_REQUEST) diff --git a/vrobbler/settings.py b/vrobbler/settings.py index 138aad6..b78ac5b 100644 --- a/vrobbler/settings.py +++ b/vrobbler/settings.py @@ -37,6 +37,11 @@ KEEP_DETAILED_SCROBBLE_LOGS = os.getenv( "VROBBLER_KEEP_DETAILED_SCROBBLE_LOGS", False ) +PODCAST_COMPLETION_PERCENT = os.getenv( + "VROBBLER_PODCAST_COMPLETION_PERCENT", 25 +) +MUSIC_COMPLETION_PERCENT = os.getenv("VROBBLER_MUSIC_COMPLETION_PERCENT", 90) + # Should we cull old in-progress scrobbles that are beyond the wait period for resuming? DELETE_STALE_SCROBBLES = os.getenv("VROBBLER_DELETE_STALE_SCROBBLES", True)